FabledFunks

0x17fe7bc0781864eb5772e1f464d621a9b2ac9ac6

Verification
Verified
v0.8.24+commit.e11b9ed9
Type
Contract
16,412 bytes
ABI entries
46
17 read · 10 write
License
none

Contract information

Address
0x17fe7bc0781864eb5772e1f464d621a9b2ac9ac6
Chain
Robinhood Chain (4663)
Compiler
v0.8.24+commit.e11b9ed9
Optimization
Enabled
Creation tx
0xaa2416cb4f…4c29a24451

Token

Name
Fabled Funks
Symbol
FUNK
Decimals
Holders
192

Read contract (17)

MAX_PER_WALLET()uint256
MAX_SUPPLY()uint256
RESERVE()uint256
balanceOf(address)uint256
getApproved(uint256)address
isApprovedForAll(address, address)bool
mintedBy(address)uint256
name()string
owner()address
ownerOf(uint256)address
publicMinted()uint256
reserveMinted()uint256
royaltyInfo(uint256, uint256)address, uint256
supportsInterface(bytes4)bool
symbol()string
tokenURI(uint256)string
totalMinted()uint256

Events (4)

ApprovalApprovalForAllOwnershipTransferredTransfer

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "royaltyReceiver",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "numerator",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "denominator",
        "type": "uint256"
      }
    ],
    "name": "ERC2981InvalidDefaultRoyalty",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "receiver",
        "type": "address"
      }
    ],
    "name": "ERC2981InvalidDefaultRoyaltyReceiver",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "numerator",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "denominator",
        "type": "uint256"
      }
    ],
    "name": "ERC2981InvalidTokenRoyalty",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "receiver",
        "type": "address"
      }
    ],
    "name": "ERC2981InvalidTokenRoyaltyReceiver",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "sender",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "owner",
        "type": "address"
      }
    ],
    "name": "ERC721IncorrectOwner",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "operator",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      }
    ],
    "name": "ERC721InsufficientApproval",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "approver",
        "type": "address"
      }
    ],
    "name": "ERC721InvalidApprover",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "operator",
        "type": "address"
      }
    ],
    "name": "ERC721InvalidOperator",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "owner",
        "type": "address"
      }
    ],
    "name": "ERC721InvalidOwner",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "receiver",
        "type": "address"
      }
    ],
    "name": "ERC721InvalidReceiver",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "sender",
        "type": "address"
      }
    ],
    "name": "ERC721InvalidSender",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      }
    ],
    "name": "ERC721NonexistentToken",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "owner",
        "type": "address"
      }
    ],
    "name": "OwnableInvalidOwner",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "OwnableUnauthorizedAccount",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "owner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "approved",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      }
    ],
    "name": "Approval",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "owner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "operator",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "bool",
        "name": "approved",
        "type": "bool"
      }
    ],
    "name": "ApprovalForAll",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "previousOwner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "OwnershipTransferred",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "from",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      }
    ],
    "name": "Transfer",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "MAX_PER_WALLET",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MAX_SUPPLY",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "RESERVE",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      }
    ],
    "name": "approve",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "owner",
        "type": "address"
      }
    ],
    "name": "balanceOf",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      }
    ],
    "name": "getApproved",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "owner",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "operator",
        "type": "address"
      }
    ],
    "name": "isApprovedForAll",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "qty",
        "type": "uint256"
      }
    ],
    "name": "mint",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "name": "mintedBy",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "name",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "owner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "qty",
        "type": "uint256"
      }
    ],
    "name": "ownerMint",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      }
    ],
    "name": "ownerOf",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "publicMinted",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "renounceOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "reserveMinted",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "salePrice",
        "type": "uint256"
      }
    ],
    "name": "royaltyInfo",
    "outputs": [
      {
        "internalType": "address",
        "name": "receiver",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "from",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      }
    ],
    "name": "safeTransferFrom",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "from",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      },
      {
        "internalType": "bytes",
        "name": "data",
        "type": "bytes"
      }
    ],
    "name": "safeTransferFrom",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "operator",
        "type": "address"
      },
      {
        "internalType": "bool",
        "name": "approved",
        "type": "bool"
      }
    ],
    "name": "setApprovalForAll",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "receiver",
        "type": "address"
      },
      {
        "internalType": "uint96",
        "name": "bps",
        "type": "uint96"
      }
    ],
    "name": "setDefaultRoyalty",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes4",
        "name": "iid",
        "type": "bytes4"
      }
    ],
    "name": "supportsInterface",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "symbol",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "id",
        "type": "uint256"
      }
    ],
    "name": "tokenURI",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "totalMinted",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "from",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      }
    ],
    "name": "transferFrom",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "transferOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  }
]

Source code

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import {ERC2981} from "@openzeppelin/contracts/token/common/ERC2981.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Base64} from "@openzeppelin/contracts/utils/Base64.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";

/// @title Fabled Funks
/// @notice 1,000 fully on-chain glitched pixel fox punks on Robinhood Chain.
///         900 free public mint + 100 den reserve. Art is a 24x24 sprite stored
///         as a 288-byte constant; per-token seed drives palettes, GAN-style
///         corruption (row melts, dead pixels, scanlines, FRIED hue-rotate),
///         a ~7% party hat drop, and a procedurally generated on-chain FABLE.
///         Exactly ONE purple party hat exists: token #1. Homage to BASTARD
///         GAN PUNKS. No IPFS. No server. Oo-de-lally.
contract FabledFunks is ERC721, ERC2981, Ownable {
    using Strings for uint256;

    uint256 public constant MAX_SUPPLY = 1000;
    uint256 public constant RESERVE = 100;
    uint256 public constant MAX_PER_WALLET = 5;

    uint256 public totalMinted;
    uint256 public publicMinted;
    uint256 public reserveMinted;
    mapping(uint256 => uint256) private _seed;
    mapping(address => uint256) public mintedBy;

    // 24x24 sprite, 2 cells/byte. 0=bg 1=black 2=fur 4=white 5=hood 6=hoodDark 7=eye 8=nose 9=innerEar
    bytes private constant SPRITE =
        hex"000000000000000000000000000110000000000000011000001221000000000000122100001292100000000001292100012992100000000001299210012992211111111112299210012255555555555555552210001555555555555555555100015562222222222226551000015622222222222222651000015622222222222222651000015627712222221772651000015627712222221772651000015622222222222222651000015644222222222244651000015644422444422444651000015644444488444444651000015564444488444446551000015556444444444465551000015555664444446655551000015555556611665555551000155555555555555555555510155555555555555555555510156655555555555555566510";

    constructor(address royaltyReceiver) ERC721("Fabled Funks", "FUNK") Ownable(msg.sender) {
        _setDefaultRoyalty(royaltyReceiver, 500);
    }

    // ---- mint ----------------------------------------------------------------

    function mint(uint256 qty) external {
        require(qty > 0 && qty <= MAX_PER_WALLET, "bad qty");
        require(publicMinted + qty <= MAX_SUPPLY - RESERVE, "public sold out");
        require(mintedBy[msg.sender] + qty <= MAX_PER_WALLET, "wallet cap");
        mintedBy[msg.sender] += qty;
        publicMinted += qty;
        _mintN(msg.sender, qty);
    }

    function ownerMint(address to, uint256 qty) external onlyOwner {
        require(reserveMinted + qty <= RESERVE, "reserve gone");
        reserveMinted += qty;
        _mintN(to, qty);
    }

    function _mintN(address to, uint256 qty) internal {
        for (uint256 i = 0; i < qty; i++) {
            uint256 id = ++totalMinted;
            _seed[id] = uint256(keccak256(abi.encodePacked(blockhash(block.number - 1), to, id)));
            _safeMint(to, id);
        }
    }

    // ---- trait tables --------------------------------------------------------

    function _furName(uint256 i) internal pure returns (string memory) {
        return ["EMBER", "RUST", "GOLDEN", "ARCTIC", "SHADOW", "TOXIC"][i];
    }

    function _furCol(uint256 i) internal pure returns (string memory) {
        return ["#e8622a", "#b0431a", "#f0913b", "#e9edf2", "#3a3d44", "#7ee23b"][i];
    }

    function _hoodName(uint256 i) internal pure returns (string memory) {
        return ["LINCOLN GREEN", "OXBLOOD", "CHARCOAL", "ROYAL", "ACID", "HOT PINK"][i];
    }

    function _hoodCol(uint256 i) internal pure returns (string memory) {
        return ["#2f6b34", "#6e1f22", "#2b2b2b", "#33417a", "#b8d432", "#e0369b"][i];
    }

    function _hoodDark(uint256 i) internal pure returns (string memory) {
        return ["#224d26", "#4d1315", "#1a1a1a", "#232d59", "#8fa71f", "#a82273"][i];
    }

    function _eyeName(uint256 i) internal pure returns (string memory) {
        return ["AMBER", "EMERALD", "ICE", "BLOOD"][i];
    }

    function _eyeCol(uint256 i) internal pure returns (string memory) {
        return ["#ffb02e", "#39d353", "#67e8f9", "#ff3b3b"][i];
    }

    function _calm(uint256 i) internal pure returns (string memory) {
        return ["#5b7a8c", "#7a6a5b", "#6a7a5b", "#8c7a9a", "#9a8a6a", "#708090"][i];
    }

    function _loud(uint256 i) internal pure returns (string memory) {
        return ["#ff2d95", "#39ff14", "#00e5ff", "#ffe600", "#ff5e00", "#b967ff"][i];
    }

    function _glitchCol(uint256 i) internal pure returns (string memory) {
        return ["#ff2d95", "#39ff14", "#00e5ff", "#ffe600", "#ff0044", "#b967ff", "#ffffff", "#0b0b0e"][i];
    }

    function _glitchName(uint256 g) internal pure returns (string memory) {
        return ["CLEAN", "GLITCHED", "CORRUPTED", "FRIED AF"][g];
    }

    // RuneScape-style party hats. Purple (index 5) is a 1/1: token #1 only.
    function _phatName(uint256 i) internal pure returns (string memory) {
        return ["BLUE", "RED", "GREEN", "YELLOW", "WHITE", "PURPLE 1/1"][i];
    }

    function _phatCol(uint256 i) internal pure returns (string memory) {
        return ["#4a5cff", "#e23b3b", "#3ddc55", "#ffe148", "#f2f2f2", "#b04ddb"][i];
    }

    function _phatDark(uint256 i) internal pure returns (string memory) {
        return ["#3242c9", "#a82424", "#27a03b", "#d4b52a", "#c9c9c9", "#7e2fa8"][i];
    }

    // ---- fable ---------------------------------------------------------------

    function _opener(uint256 i) internal pure returns (string memory) {
        return [
            "THE FOX", "A BASTARD FOX", "THE HOODED ONE", "A FERAL DEGEN",
            "THE OUTLAW", "A GLITCHED FOX", "THE TRICKSTER", "AN ONCHAIN FOX"
        ][i];
    }

    function _deed(uint256 i) internal pure returns (string memory) {
        return [
            "ROBBED THE SHERIFF'S LIQUIDITY POOL",
            "SOLD THE KING'S ARROWS FOR GAS",
            "APED THE ROYAL TREASURY INTO A MEMECOIN",
            "STOLE THE PRINCE'S PRIVATE KEYS",
            "FARMED THE FOREST AND DUMPED ON NOBLES",
            "MINTED 900 FREE AND KEPT 100",
            "FRONTRAN THE ARCHERY TOURNAMENT",
            "BRIDGED OUT OF NOTTINGHAM AT 3AM"
        ][i];
    }

    function _moral(uint256 i) internal pure returns (string memory) {
        return [
            "NEVER TRUST A CLEAN WALLET",
            "THE HOOD STAYS ON",
            "EVERY FOX IS A FABLE",
            "FORTUNE FAVORS THE FERAL",
            "OO-DE-LALLY MEANS OO-DE-LALLY",
            "RICH STAY RICH UNTIL THEY MEET A FOX",
            "GAS IS TEMPORARY, LORE IS FOREVER",
            "MORALS ARE FOR THE UNHOODED"
        ][i];
    }

    // ---- derivation ----------------------------------------------------------

    struct T {
        uint256 fur;
        uint256 hood;
        uint256 eye;
        uint256 bgKind; // 0 calm, 1 loud, 2 trippy, 3 void
        uint256 bgA;
        uint256 bgB;
        uint256 glitch; // 0..3
        bool phat;
        uint256 phatIdx;
    }

    function _traits(uint256 id) internal view returns (T memory t) {
        uint256 s = _seed[id];
        t.fur = s % 6; s /= 6;
        t.hood = s % 6; s /= 6;
        t.eye = s % 4; s /= 4;
        uint256 b = s % 100; s /= 100;
        if (b < 30) { t.bgKind = 0; t.bgA = s % 6; }
        else if (b < 70) { t.bgKind = 1; t.bgA = s % 6; }
        else if (b < 95) { t.bgKind = 2; t.bgA = s % 6; t.bgB = (t.bgA + 1 + (s / 6) % 5) % 6; }
        else { t.bgKind = 3; }
        s /= 36;
        uint256 g = s % 100;
        t.glitch = g < 20 ? 0 : g < 65 ? 1 : g < 90 ? 2 : 3;
        // party hat: token #1 is THE purple phat; ~7% of the rest get one of 5 colors
        if (id == 1) {
            // curated 1/1 grail — the "#013" look + the only purple party hat
            t.fur = 5;      // TOXIC
            t.hood = 3;     // ROYAL
            t.eye = 3;      // BLOOD
            t.bgKind = 0;   // CALM AF
            t.bgA = 5;     // navy slate (matched to #013)
            t.glitch = 2;   // CORRUPTED
            t.phat = true;
            t.phatIdx = 5;
        } else {
            uint256 p = uint256(keccak256(abi.encodePacked(_seed[id], "phat")));
            if (p % 100 < 7) {
                t.phat = true;
                t.phatIdx = (p / 100) % 5;
            }
        }
    }

    function _cell(uint256 x, uint256 y) internal pure returns (uint256) {
        uint256 i = y * 24 + x;
        uint8 b = uint8(SPRITE[i >> 1]);
        return (i & 1 == 0) ? (b >> 4) : (b & 0x0f);
    }

    function _symCol(uint256 sym, T memory t) internal pure returns (string memory) {
        if (sym == 1) return "#0b0b0e";
        if (sym == 2) return _furCol(t.fur);
        if (sym == 4) return "#f4f1ea";
        if (sym == 5) return _hoodCol(t.hood);
        if (sym == 6) return _hoodDark(t.hood);
        if (sym == 7) return _eyeCol(t.eye);
        if (sym == 8) return "#141518";
        return "#1c1f24"; // 9 inner ear
    }

    // ---- svg -----------------------------------------------------------------

    function _svg(uint256 id, T memory t) internal view returns (string memory) {
        uint256 s = _seed[id];
        uint256[24] memory shift;
        uint256 nShift = [uint256(0), 2, 4, 5][t.glitch];
        for (uint256 j = 0; j < nShift; j++) {
            uint256 h = uint256(keccak256(abi.encodePacked(s, "shift", j)));
            shift[4 + (h % 18)] = 1 + (h / 18) % 4;
        }
        bytes memory corr = new bytes(576);
        uint256 nCorr = [uint256(0), 10, 26, 40][t.glitch];
        for (uint256 j = 0; j < nCorr; j++) {
            uint256 h = uint256(keccak256(abi.encodePacked(s, "corr", j)));
            corr[h % 576] = bytes1(uint8(1 + (h / 576) % 8));
        }

        string memory body = "";
        for (uint256 y = 0; y < 24; y++) {
            string memory rowStr = "";
            uint256 x = 0;
            while (x < 24) {
                uint256 code = _code(x, y, shift[y], corr);
                if (code == 0) { x++; continue; }
                uint256 x2 = x;
                while (x2 + 1 < 24 && _code(x2 + 1, y, shift[y], corr) == code) x2++;
                rowStr = string.concat(
                    rowStr,
                    '<rect x="', (x * 20).toString(),
                    '" y="', (y * 20).toString(),
                    '" width="', ((x2 - x + 1) * 20).toString(),
                    '" height="20" fill="',
                    code >= 256 ? _glitchCol(code - 256) : _symCol(code, t),
                    '"/>'
                );
                x = x2 + 1;
            }
            body = string.concat(body, rowStr);
        }
        if (t.phat) body = string.concat(body, _phatSvg(t.phatIdx));
        return string.concat(_svgOpen(s, t), body, _svgClose(t));
    }

    /// RuneScape-style zigzag party hat, drawn over the hood crown (cells 8-16, rows 1-4)
    function _phatSvg(uint256 i) internal pure returns (string memory) {
        string memory c = _phatCol(i);
        return string.concat(
            '<rect x="180" y="20" width="20" height="20" fill="', c, '"/>',
            '<rect x="240" y="20" width="20" height="20" fill="', c, '"/>',
            '<rect x="300" y="20" width="20" height="20" fill="', c, '"/>',
            '<rect x="160" y="40" width="60" height="20" fill="', c, '"/>',
            '<rect x="220" y="40" width="60" height="20" fill="', c, '"/>',
            '<rect x="280" y="40" width="60" height="20" fill="', c, '"/>',
            '<rect x="160" y="60" width="180" height="20" fill="', c, '"/>',
            '<rect x="160" y="80" width="180" height="20" fill="', _phatDark(i), '"/>'
        );
    }

    function _code(uint256 x, uint256 y, uint256 dx, bytes memory corr) internal pure returns (uint256) {
        uint8 c = uint8(corr[y * 24 + x]);
        if (c > 0) return 256 + (c - 1);
        return _cell((x + 24 - dx) % 24, y);
    }

    function _svgOpen(uint256 s, T memory t) internal pure returns (string memory) {
        string memory open =
            '<svg xmlns="http://www.w3.org/2000/svg" width="480" height="480" viewBox="0 0 480 480">';
        if (t.glitch == 3) {
            open = string.concat(
                open,
                '<filter id="f"><feColorMatrix type="hueRotate" values="',
                (40 + (s % 280)).toString(),
                '"/></filter><g filter="url(#f)">'
            );
        }
        if (t.bgKind == 2) {
            return string.concat(
                open,
                '<defs><linearGradient id="g" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="',
                _loud(t.bgA), '"/><stop offset="1" stop-color="', _loud(t.bgB),
                '"/></linearGradient></defs><rect width="480" height="480" fill="url(#g)"/>'
            );
        }
        string memory bg = t.bgKind == 0 ? _calm(t.bgA) : t.bgKind == 1 ? _loud(t.bgA) : "#000000";
        return string.concat(open, '<rect width="480" height="480" fill="', bg, '"/>');
    }

    function _svgClose(T memory t) internal pure returns (string memory) {
        string memory close = "";
        if (t.glitch >= 2) {
            close =
                '<defs><pattern id="s" width="20" height="20" patternUnits="userSpaceOnUse"><rect width="20" height="10" fill="#000"/></pattern></defs><rect width="480" height="480" fill="url(#s)" opacity="0.14"/>';
        }
        if (t.glitch == 3) close = string.concat(close, "</g>");
        return string.concat(close, "</svg>");
    }

    // ---- metadata ------------------------------------------------------------

    function _bgName(T memory t) internal pure returns (string memory) {
        return t.bgKind == 0 ? "CALM AF" : t.bgKind == 1 ? "LOUD" : t.bgKind == 2 ? "TRIPPY" : "VOID";
    }

    function _fable(uint256 id) internal view returns (string memory) {
        uint256 h = uint256(keccak256(abi.encodePacked(_seed[id], "fable")));
        return string.concat(
            "FABLE #", id.toString(), ": ", _opener(h % 8), " ", _deed((h / 8) % 8),
            ". MORAL: ", _moral((h / 64) % 8), "."
        );
    }

    function tokenURI(uint256 id) public view override returns (string memory) {
        _requireOwned(id);
        T memory t = _traits(id);
        string memory json = string.concat(
            '{"name":"Fabled Funk #', id.toString(),
            '","description":"', _fable(id),
            ' || 1000 FULLY ONCHAIN GLITCHED FOX PUNKS ON ROBINHOOD CHAIN. 900 FREE, 100 KEPT BY THE DEN. ONE PURPLE PARTY HAT EXISTS.",',
            '"image":"data:image/svg+xml;base64,', Base64.encode(bytes(_svg(id, t))),
            '","attributes":[{"trait_type":"Fur","value":"', _furName(t.fur),
            '"},{"trait_type":"Hood","value":"', _hoodName(t.hood),
            '"},{"trait_type":"Eyes","value":"', _eyeName(t.eye),
            '"},{"trait_type":"Background","value":"', _bgName(t),
            '"},{"trait_type":"Glitch","value":"', _glitchName(t.glitch),
            '"},{"trait_type":"Party Hat","value":"', t.phat ? _phatName(t.phatIdx) : "NONE", '"}]}'
        );
        return string.concat("data:application/json;base64,", Base64.encode(bytes(json)));
    }

    // ---- admin / overrides ---------------------------------------------------

    function setDefaultRoyalty(address receiver, uint96 bps) external onlyOwner {
        _setDefaultRoyalty(receiver, bps);
    }

    function supportsInterface(bytes4 iid) public view override(ERC721, ERC2981) returns (bool) {
        return super.supportsInterface(iid);
    }
}
Chain explorer4224msChain node85ms