// File: contracts/Metadatea.sol



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

/*
                                                                                        @@@@@@@@@@@@
                                                                                        @@@@@@@@@@@@
                                                                                        @@@@@@@@@@@@
                                                                                        @@@@@@@@@@@@
                                                                                        @@@@@@@@@@@@
                                                                                        @@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                                @@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                                @@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                                @@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                                @@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                                @@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                                @@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@           @@@@@@@@@@@@          @@@@@@@@@@@@          @@@@@@@@@@@@          @@@@@@@@@@@@
@@@@@@@@@@@           @@@@@@@@@@@@          @@@@@@@@@@@@          @@@@@@@@@@@@          @@@@@@@@@@@@
@@@@@@@@@@@           @@@@@@@@@@@@          @@@@@@@@@@@@          @@@@@@@@@@@@          @@@@@@@@@@@@
@@@@@@@@@@@           @@@@@@@@@@@@          @@@@@@@@@@@@          @@@@@@@@@@@@          @@@@@@@@@@@@
@@@@@@@@@@@           @@@@@@@@@@@@          @@@@@@@@@@@@          @@@@@@@@@@@@          @@@@@@@@@@@@
@@@@@@@@@@@           @@@@@@@@@@@@          @@@@@@@@@@@@          @@@@@@@@@@@@          @@@@@@@@@@@@
@@@@@@@@@@@           @@@@@@@@@@@@          @@@@@@@@@@@@          @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@           @@@@@@@@@@@@          @@@@@@@@@@@@          @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@           @@@@@@@@@@@@          @@@@@@@@@@@@          @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@           @@@@@@@@@@@@          @@@@@@@@@@@@          @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@           @@@@@@@@@@@@          @@@@@@@@@@@@          @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@           @@@@@@@@@@@@          @@@@@@@@@@@@          @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
*/

import "erc721a/contracts/ERC721A.sol";
import "erc721a/contracts/extensions/ERC721AQueryable.sol";
import "erc721a/contracts/extensions/ERC721ABurnable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "./IERC4906.sol";
import "./Structs.sol";
import "./Art.sol";
import "./Utils.sol";

/// @title an ERC721A contract named Metadate/a
/// @author parker thompson
/// @notice NFTs with fully on-chain art generation
/// @dev implements ERC4906 to notify marketplaces of metadata updates
contract Metadatea is
    ERC721A,
    ERC721AQueryable,
    ERC721ABurnable,
    Ownable,
    IERC4906
{
    /// @notice variable to store an instance of the Art contract
    Art private _art;

    /// @notice deploys the Metadate/a contract and an instance of the Art contract
    constructor() ERC721A("Metadate/a", "MD") Ownable(msg.sender) {
        _art = new Art();

        // mint the first token to the deployer address
        _mint(msg.sender, 1);
        _mintArt(1, 0);
    }

    /// @notice get the start timestamp for collector claiming
    /// @dev 1705932000 == 9AM EST 1/22/24
    uint256 private constant _collectorsStart = 1705932000;

    /// @notice get the price to mint a token in wei
    /// @dev 5000000000000000 wei is 0.005 eth, ~$12.50 USD at time of deployment
    uint256 private constant _mintPrice = 5000000000000000;

    /// @notice get the status of super rare distribution
    /// @dev can only be updated by calling the distributeSuperRares function
    bool private _superRaresDistributed = false;

    /// @notice get the timestamp when minting closes
    /// @return mintCloseTime timestamp
    /// @dev will be updated when totalSupply is greater than _blanksToMint
    uint256 public mintCloseTime = 55555555555;

    /// @notice get the number of blanks that can be minted
    uint256 private constant _blanksToMint = 1000;

    /// @notice get the number of seconds to add to mintCloseTime after all blanks are minted
    /// @dev 259200 seconds == 72 hours
    uint256 private constant _mintSecondsTimerAfterBlanks = 259200;

    /// @notice get the count of token edits that have been made
    /// @return totalEdits count
    /// @dev stored as a public variable for accuracy and convenience
    uint256 public totalEdits = 0;

    /// @notice initialize an instance of the Abstract Glitch contract
    IERC721 private constant _abstractGlitch =
        IERC721(0xF9f27AA7718dCcF4b4306e0321c2Ce85215fA902);

    /// @notice map token IDs to a token struct to store metadata
    mapping(uint256 => Structs.Token) public tokens;

    /// @notice map wallet addresses that have claimed free tokens
    mapping(uint256 => bool) public claimedAbstractGlitchTokens;

    /// @notice map wallet addresses to the allow list
    mapping(address => bool) public allowListWallets;

    /// @notice check the active mint progression
    /// @dev use to qualify other contract functions
    function checkMintProgression() public view returns (uint256) {
        if (block.timestamp < _collectorsStart) {
            return 0; // minting hasn't started
        } else if (block.timestamp < _collectorsStart + 3 hours) {
            return 1; // abstract glitch holders claim
        } else if (block.timestamp < _collectorsStart + 7 hours) {
            return 2; // allow list mint
        } else if (block.timestamp < mintCloseTime) {
            return 3; // public mint
        } else {
            return 4; // mint ended
        }
    }

    /// @notice mint Metadate/a (MD) tokens
    /// @param quantity of tokens to mint
    /// @dev when _blanksToMint is reached, the minting window is updated
    function mint(uint256 quantity) public payable {
        uint256 mintProgression = checkMintProgression();

        if (msg.value < quantity * _mintPrice) {
            revert Utils.NotEnoughEth();
        }

        if (mintProgression == 2) {
            if (
                allowListWallets[msg.sender] ||
                _abstractGlitch.balanceOf(msg.sender) > 0
            ) {
                _handleMint(quantity);
            } else {
                revert Utils.NotAllowed();
            }
        } else if (mintProgression == 3) {
            _handleMint(quantity);
        } else {
            revert Utils.NotAllowed();
        }
    }

    /// @notice claim free Metadate/a (MD) tokens
    /// @param abstractGlitchId owned by message sender
    function claim(uint256 abstractGlitchId) public {
        if (
            _abstractGlitch.ownerOf(abstractGlitchId) == msg.sender &&
            !claimedAbstractGlitchTokens[abstractGlitchId] &&
            checkMintProgression() > 0 &&
            checkMintProgression() < 4
        ) {
            _handleMint(1);
            claimedAbstractGlitchTokens[abstractGlitchId] = true;
        } else {
            revert Utils.ClaimNotAvailable();
        }
    }

    /// @notice internal function to mint Metadate/a (MD) tokens
    /// @param quantity of tokens to mint
    function _handleMint(uint256 quantity) internal {
        for (uint256 i = 0; i < quantity; i++) {
            uint256 index = _nextTokenId() + i;
            if (index > _blanksToMint) {
                _mintArt(
                    index,
                    uint8(Utils.random(index + block.timestamp, 1, 6))
                );
            } else {
                if (index == _blanksToMint) {
                    mintCloseTime =
                        block.timestamp +
                        _mintSecondsTimerAfterBlanks;
                }
                _mintArt(index, 0);
            }
        }
        if (quantity <= 10) {
            _mint(msg.sender, quantity);
        } else {
            for (uint256 i = 0; i < quantity / 10; i++) {
                _mint(msg.sender, 10);
            }
            if (quantity % 10 > 0) {
                _mint(msg.sender, quantity % 10);
            }
        }
    }

    /// @notice saves the initial art of the token minted
    /// @dev uses the index of the _defaults mapping in the Art contract
    /// @dev only time the originalArtIndex is updated
    function _mintArt(uint256 id, uint256 originalArtIndex) internal {
        tokens[id].originalArtIndex = originalArtIndex;
        tokens[id].currentArtIndex = originalArtIndex;
    }

    /// @notice get the base64 encoded token URI
    /// @param tokenId of the token
    /// @return base64 encoded token URI
    function tokenURI(uint256 tokenId)
        public
        view
        virtual
        override(ERC721A, IERC721A)
        returns (string memory)
    {
        return _art.getTokenURI(tokenId, tokens[tokenId]);
    }

    /// @notice get the SVG art for a token
    /// @param tokenId of the token
    /// @return SVG art
    function getSvg(uint256 tokenId) public view returns (string memory) {
        return _art.renderSvg(tokens[tokenId]);
    }

    /// @notice edit a Metadate/a (MD) token
    /// @notice blank tokens can be edited once without burning any tokens
    /// @notice super rare tokens can be edited infinitely without burning any tokens
    /// @notice common tokens can be edited once by burning 4 other common tokens
    /// @notice only text, metadata, and text size can be edited for super rare tokens
    /// @param tokenIds array of token IDs to burn, including the token to keep as the FIRST token in the array
    /// @param artRules struct of art to update
    /// @param meta string of metadata to update
    function edit(
        uint256[] calldata tokenIds,
        Structs.ArtRules memory artRules,
        string memory meta
    ) public {
        uint256 tokenIdToKeep = tokenIds[0];
        if (!_ownsAllTokens(tokenIds, msg.sender)) {
            revert Utils.NotAllTokensOwned();
        }
        // checks to see if the token needs to be a blank or a super rare
        if (tokenIds.length == 1) {
            // checks to see if the token is a super rare
            if (
                tokens[tokenIdToKeep].currentArtIndex > 7 &&
                tokens[tokenIdToKeep].currentArtIndex < 11
            ) {
                if (
                    !Utils.validateTextSize(
                        artRules.labelText,
                        artRules.textSize
                    )
                ) {
                    revert Utils.InvalidText();
                }
                if (Utils.countCharacters(meta) > 100) {
                    revert Utils.MetaTooLong();
                }
                tokens[tokenIdToKeep].artRules.labelText = artRules.labelText;
                tokens[tokenIdToKeep].artRules.textSize = artRules.textSize;
                tokens[tokenIdToKeep].meta = meta;
                if (!tokens[tokenIdToKeep].edited) {
                    tokens[tokenIdToKeep].edited = true;
                }
            } else {
                if (
                    tokenIdToKeep > _blanksToMint ||
                    tokens[tokenIdToKeep].edited
                ) {
                    revert Utils.InvalidToken();
                }
                _updateArt(tokenIdToKeep, artRules, meta);
            }
            emit MetadataUpdate(tokenIdToKeep);
        } else if (tokenIds.length == 5) {
            if (tokens[tokenIdToKeep].edited) {
                revert Utils.InvalidToken();
            }
            for (uint256 i = 0; i < 5; i++) {
                if (
                    Utils.isSuperRareOrBlank(
                        tokens[tokenIds[i]].currentArtIndex
                    )
                ) {
                    revert Utils.InvalidToken();
                }
                if (tokenIds[i] == tokenIdToKeep) {
                    _updateArt(tokenIdToKeep, artRules, meta);
                    emit MetadataUpdate(tokenIdToKeep);
                } else {
                    burn(tokenIds[i]);
                    emit MetadataUpdate(tokenIds[i]);
                }
            }
        } else {
            revert Utils.InvalidQuantity();
        }
        totalEdits++;
    }

    /// @notice updates the metadata of a token
    /// @dev checks for art validity, but not if a token should be edited
    /// @dev the edit function checks if a token should be edited
    /// @dev this function is skipped for super rare tokens
    function _updateArt(
        uint256 tokenId,
        Structs.ArtRules memory artRules,
        string memory meta
    ) internal {
        if (artRules.spectrumIndex > 15) {
            revert Utils.InvalidSpectrum();
        }
        if (artRules.labelIndex > 7) {
            revert Utils.InvalidLabel();
        }
        if (!Utils.validateTextSize(artRules.labelText, artRules.textSize)) {
            revert Utils.InvalidText();
        }
        if (Utils.countCharacters(meta) > 100) {
            revert Utils.MetaTooLong();
        }
        tokens[tokenId].meta = meta;
        tokens[tokenId].artRules = Structs.ArtRules({
            stroke: artRules.stroke,
            vertical: artRules.vertical,
            spectrumIndex: artRules.spectrumIndex,
            labelIndex: artRules.labelIndex,
            labelText: artRules.labelText,
            textSize: artRules.textSize,
            whiteText: artRules.whiteText
        });
        // only update these if token has not been edited yet
        if (!tokens[tokenId].edited) {
            tokens[tokenId].edited = true;
            tokens[tokenId].currentArtIndex = 11;
        }
    }

    /// @notice check if all tokens in an array are owned by the same address
    function _ownsAllTokens(uint256[] calldata ids, address addy)
        internal
        view
        returns (bool)
    {
        for (uint256 i = 0; i < ids.length; i++) {
            if (ownerOf(ids[i]) != addy) {
                return false;
            }
        }
        return true;
    }

    /// @notice get the total number of tokens burned
    /// @return totalBurned count
    /// @dev will not include tokens manually sent to a 'burn' or 0x address
    function totalBurned() public view returns (uint256) {
        return _totalBurned();
    }

    /// @notice starts the token ID at 1 instead of 0
    function _startTokenId() internal view virtual override returns (uint256) {
        return 1;
    }

    /// @notice updates artwork for tokens that are burned
    function _beforeTokenTransfers(
        address from,
        address to,
        uint256 startTokenId,
        uint256 quantity
    ) internal virtual override {
        super._beforeTokenTransfers(from, to, startTokenId, quantity);
        if (to == address(0)) {
            tokens[startTokenId].currentArtIndex = 7;
        }
    }

    /// @notice add wallets to the allow list
    /// @param wallets to add
    function addAllowlistWallets(address[] calldata wallets) public onlyOwner {
        if (checkMintProgression() > 0) {
            revert Utils.NotAllowed();
        }
        for (uint256 i = 0; i < wallets.length; i++) {
            allowListWallets[wallets[i]] = true;
        }
    }

    /*
                                @@@@@@@@@@@@@@@              @@@@@@@@@@@@@@@@@@@@@@@@@@@@@              
                                @@@@@@@@@@@@@@@              @@@@@@@@@@@@@@@@@@@@@@@@@@@@@              
                                @@@@@@@@@@@@@@@              @@@@@@@@@@@@@@@@@@@@@@@@@@@@@              
                                @@@@@@@@@@@@@@@              @@@@@@@@@@@@@@@@@@@@@@@@@@@@@              
                                @@@@@@@@@@@@@@@              @@@@@@@@@@@@@@@@@@@@@@@@@@@@@              
                                @@@@@@@@@@@@@@@              @@@@@@@@@@@@@@@@@@@@@@@@@@@@@              
                                @@@@@@@@@@@@@@@              @@@@@@@@@@@@@@@@@@@@@@@@@@@@@              
                                @@@@@@@@@@@@@@@              @@@@@@@@@@@@@@@@@@@@@@@@@@@@@              
                  @@@@@@@@@@@@@@@                                                        @@@@@@@@@@@@@@@
                  @@@@@@@@@@@@@@@                                                        @@@@@@@@@@@@@@@
                  @@@@@@@@@@@@@@@                                                        @@@@@@@@@@@@@@@
                  @@@@@@@@@@@@@@@                                                        @@@@@@@@@@@@@@@
                  @@@@@@@@@@@@@@@                                                        @@@@@@@@@@@@@@@
                  @@@@@@@@@@@@@@@                                                        @@@@@@@@@@@@@@@
                  @@@@@@@@@@@@@@@                                                        @@@@@@@@@@@@@@@
                  @@@@@@@@@@@@@@@                            @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
                  @@@@@@@@@@@@@@@                            @@@@@@@@@@@@@@@@@@@@@@@@@@@@@              
                  @@@@@@@@@@@@@@@                            @@@@@@@@@@@@@@@@@@@@@@@@@@@@@              
                  @@@@@@@@@@@@@@@                            @@@@@@@@@@@@@@@@@@@@@@@@@@@@@              
                  @@@@@@@@@@@@@@@                            @@@@@@@@@@@@@@@@@@@@@@@@@@@@@              
                  @@@@@@@@@@@@@@@                            @@@@@@@@@@@@@@@@@@@@@@@@@@@@@              
                  @@@@@@@@@@@@@@@                            @@@@@@@@@@@@@@@@@@@@@@@@@@@@@              
                  @@@@@@@@@@@@@@@                            @@@@@@@@@@@@@@@@@@@@@@@@@@@@@              
    @@@@@@@@@@@@@@@@@@@@@@@@@@@@@                            @@@@@@@@@@@@@@@             @@@@@@@@@@@@@@@
    @@@@@@@@@@@@@@@@@@@@@@@@@@@@@                            @@@@@@@@@@@@@@@             @@@@@@@@@@@@@@@
    @@@@@@@@@@@@@@@@@@@@@@@@@@@@@                            @@@@@@@@@@@@@@@             @@@@@@@@@@@@@@@
    @@@@@@@@@@@@@@@@@@@@@@@@@@@@@                            @@@@@@@@@@@@@@@             @@@@@@@@@@@@@@@
    @@@@@@@@@@@@@@@@@@@@@@@@@@@@@                            @@@@@@@@@@@@@@@             @@@@@@@@@@@@@@@
    @@@@@@@@@@@@@@@@@@@@@@@@@@@@@                            @@@@@@@@@@@@@@@             @@@@@@@@@@@@@@@
    @@@@@@@@@@@@@@@@@@@@@@@@@@@@@                            @@@@@@@@@@@@@@@             @@@@@@@@@@@@@@@
    @@@@@@@@@@@@@@@@@@@@@@@@@@@@@                            @@@@@@@@@@@@@@@             @@@@@@@@@@@@@@@
    */

    /// @notice distributes super rare tokens
    /// @dev can only be called once after mint is closed
    function distributeSuperRares() public onlyOwner {
        if (checkMintProgression() < 4 || _superRaresDistributed) {
            revert Utils.NotAllowed();
        }
        for (uint256 i = 0; i < 200; i++) {
            // checks 200 common tokens for upgradability
            // edited and burned tokens will be skipped
            // it is likely that less than 200 super rares will exist
            uint256 random = Utils.random(i + 2024, _blanksToMint + 1, _totalMinted());
            if (!tokens[random].edited && tokens[random].currentArtIndex != 7) {
                tokens[random].currentArtIndex = Utils.random(i, 8, 10);
            }
        }
        _superRaresDistributed = true;
        emit BatchMetadataUpdate(_blanksToMint, _totalMinted());
    }

    /// @notice withdraw 50% of contract funds to each of two addresses
    function withdraw() external onlyOwner {
        uint256 bal = address(this).balance;
        if (bal == 0) {
            revert Utils.ZeroBalance();
        }

        uint256 amt1 = bal / 2;
        uint256 amt2 = bal - amt1;

        require(
            payable(0x9390333197446eC19B1B4Dc7dC7fc5ae4957ebBa).send(amt1) &&
                payable(0x38f0dBbA41258639ba0b2cdDC753E46157CE14d0).send(amt2)
        );
    }
}

// File: contracts/Utils.sol



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

library Utils {
    /// @notice get a pseudo random number
    /// @param input to use as a randomness seed
    /// @param min value that can be returned
    /// @param max value that can be returned
    /// @return number that is randomized based on the params
    /// @dev the same input will always return the same output
    /// @dev min and max are inclusive values and can be returned
    function random(
        uint256 input,
        uint256 min,
        uint256 max
    ) internal pure returns (uint256) {
        uint256 randRange = max - min + 1;
        return min + (uint256(keccak256(abi.encodePacked(input))) % randRange);
    }

    /// @notice convert a number to a string
    /// @param number number to convert
    /// @return result string from the converted number
    function uint2string(
        uint256 number
    ) internal pure returns (string memory result) {
        if (number == 0) {
            return "0";
        }
        uint256 x = number;
        uint256 length;
        while (x != 0) {
            length++;
            x /= 10;
        }
        bytes memory conversion = new bytes(length);
        uint256 y = length;
        while (number != 0) {
            y = y - 1;
            uint8 temp = (48 + uint8(number - (number / 10) * 10));
            bytes1 b1 = bytes1(temp);
            conversion[y] = b1;
            number /= 10;
        }
        return string(conversion);
    }

    /// @notice convert a number to a hex formatted color
    /// @param color number to convert
    /// @return hex formatted color string
    function uint2hex(uint24 color) internal pure returns (string memory) {
        uint8 red = uint8(color >> 16);
        uint8 green = uint8(color >> 8);
        uint8 blue = uint8(color);
        return
            string(
                abi.encodePacked(
                    "#",
                    uint2string(red),
                    uint2string(green),
                    uint2string(blue)
                )
            );
    }

    /// @notice count the characters in a string
    /// @param input string to count
    /// @return number of characters in the string
    /// @dev counts the number of characters in a string
    /// @dev some complex characters (like emojis) may count as more than one
    function countCharacters(
        string memory input
    ) internal pure returns (uint256) {
        uint256 length = 0;
        uint256 i = 0;

        while (i < bytes(input).length) {
            if ((uint8(bytes(input)[i]) & 0xC0) != 0x80) {
                length++;
            }
            i++;
        }

        return length;
    }

    /// @notice validate text size for artwork
    /// @param textString string value of the text
    /// @param textSize number size of the text
    /// @return whether or not the text and size combo are valid
    function validateTextSize(
        string memory textString,
        uint256 textSize
    ) internal pure returns (bool) {
        if (textSize < 6 || textSize > 29) {
            return false;
        } else {
            uint256 length = countCharacters(textString);
            uint256 max;

            if (length <= 7) {
                max = 29;
            } else if (length <= 16) {
                max = 11;
            } else if (length <= 22) {
                max = 9;
            } else {
                revert("invalid length");
            }

            return (textSize <= max);
        }
    }

    /// @notice formats characters in a string for SVG code
    /// @param content string to format
    /// @return formatted string
    /// @dev checks for known characters that need to be converted for SVG (<, >, &)
    function formatCharacters(
        string memory content
    ) internal pure returns (string memory) {
        bytes memory contentBytes = bytes(content);
        bytes memory result = new bytes(contentBytes.length * 5);

        uint256 j = 0;
        for (uint256 i = 0; i < contentBytes.length; i++) {
            if (contentBytes[i] == "&") {
                result[j++] = bytes("&")[0];
                result[j++] = bytes("a")[0];
                result[j++] = bytes("m")[0];
                result[j++] = bytes("p")[0];
                result[j++] = bytes(";")[0];
            } else if (contentBytes[i] == "<") {
                result[j++] = bytes("&")[0];
                result[j++] = bytes("l")[0];
                result[j++] = bytes("t")[0];
                result[j++] = bytes(";")[0];
            } else if (contentBytes[i] == ">") {
                result[j++] = bytes("&")[0];
                result[j++] = bytes("g")[0];
                result[j++] = bytes("t")[0];
                result[j++] = bytes(";")[0];
            } else {
                result[j++] = contentBytes[i];
            }
        }

        bytes memory trimmedResult = new bytes(j);
        for (uint256 k = 0; k < j; k++) {
            trimmedResult[k] = result[k];
        }

        return string(trimmedResult);
    }

    /// @notice check if a token is a super rare or blank
    /// @param index of the current token art
    /// @return whether or not the token is a super rare or blank
    function isSuperRareOrBlank(uint256 index) internal pure returns (bool) {
        if (index == 0) {
            return true;
        } else if (index > 7 && index < 11) {
            return true;
        } else {
            return false;
        }
    }

    error NotEnoughEth();
    error MintEnded();
    error NotTime();
    error ClaimNotAvailable();
    error NotAllTokensOwned();
    error TokenIdMismatch();
    error InvalidToken();
    error InvalidText();
    error InvalidSpectrum();
    error InvalidLabel();
    error MetaTooLong();
    error InvalidQuantity();
    error NotAllowed();
    error ZeroBalance();
    error AlreadyCalled();
}

// File: contracts/Art.sol



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

/*
---::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::---
-:.                      .                                            .               .          .:-
:.  .....:..............::::.:: .    .     .      .                  .                .           .-
:.  .+=-+*-=-:.-+:--:=+.###%-%*                               .                  .              . .-
:.  .+=-+*-=-:.-+:--:=+.###%-%*                 . .              .                 .   .          .-
:.  .+=-+*-=-:.-+:--:=+.###%-%*   .                                                               .-
:.  .+=-+*-=-:.-+:--:=+.###%-%*     .                       .          .                          .-
:.  .....:..............::::.::         .                             .        .        ..        .-
:.                          .         .          .                                              . .-
:.  .     .                              .                        .    .  .              .        .-
:.       .   .                           . .        .  .                                 .        .-
:.               .           .      .                            .                         .   .  .-
:.                .     .             .                                                           .-
:.                                        .             .    .                  .                 .-
:..     .                   .                                                .  ..                .-
:.                   .                                     .                     .       .       ..-
:.                                            .     .   .                    .                    .-
:.         .                                         .   .            .                     .     .-
:.     .                           .         .                                                    .-
:.        .                       .       .                                  .      .             .-
:.                               ..                              .                        .       .-
:.   .         ..:-==============================================================-:.              .-
:..    .     .+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%=       .    .-
:.         .+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%=          .-
:.   .    .#%%%%%%%%%%%%%%%%+-*%%%#--%#------+%--#%%%%%--%%%%%%%+-::-*%%%%%%%%%%%%%%%%%%+.        .-
:.       .+%%%%%%%%%%%%%%%%%= +%%%# .%* .****#%  #%%%%%. %%%%%*..=#*:..#%%%%%%%%%%%%%%%%%-        .-
:....    .%%%%%%%%%%%%%%%%%%= -++++ .%* .****%%  #%%%%%. %%%%%: *%%%%-.=%%%%%%%%%%%%%%%%%+.       .-
:.       .%%%%%%%%%%%%%%%%%%= :===- .%* .====%%  #%%%%%. %%%%#. #%%%%=.-%%%%%%%%%%%%%%%%%+        .-
:.        +%%%%%%%%%%%%%%%%%= +%%%# .%* .%%%%%%  #%%%%%..%%%%%= .*%%=. *%%%%%%%%%%%%%%%%%-        .-
:.        .#%%%%%%%%%%%%%%%%=.+%%%#.:%#......=%......-%......=%#-...:=%%%%%%%%%%%%%%%%%%+.        .-
:.        ..*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%=         ..-
:.       ..  .*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%=            .-
:.             ..:-=++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++=-:.              .-
:.          ..     .                 .           .             .                                  .-
:.  .      .          .       .           .                 .              .      .               .-
:.             .             .      .        .         .   .            .   .  .    .         .   .-
:.   .         .         .         .                                   .                .         .-
:. .  .         .                                             .              .                    .-
:. .                                                               .               .              .-
:.                           .             .                              .           .           .-
:.                     .                                                                          .-
:.  .          .                                                                                  .-
:..                 .                     .                  .             .                      .-
:.       .            .         .  . .                              .                             .-
:.             .                     .                              .                   .     .   .-
:.                         .                       .                                    .    .    .-
:.                               .             .                       .                 .        .-
:.         .                          .             .                                   .         .-
:.                         .                                       ...  ...  ..  .... ...  ...    .-
:.     .      .                                           .    . ................::::.:::..::::.  .-
:.. .         .  .           .                         .         ................::::::::::::::.  .-
:.                 .                   .                 .        .............. ..............   .-
-:.    ..                  .         .           .                                               .::
---::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::---
*/

import "@openzeppelin/contracts/utils/Base64.sol";
import "./Structs.sol";
import "./Utils.sol";

contract Art {
    /// @notice index predefined label options
    mapping(uint256 => Structs.Label) private _labels;

    /// @notice index predefined color spectrums
    mapping(uint256 => Structs.Spectrum) private _spectrums;

    /// @notice index predefined art rules
    mapping(uint256 => Structs.ArtRules) private _defaults;

    /// @notice horizontal barcode SVG
    string constant horizontalBarcode =
        '<path d="M9 26.32H10.12V9H9V26.32Z"/><path d="M10.84 26.32H11.32V9H10.84V26.32Z"/><path d="M12.77 26.32H13.25V9H12.77V26.32Z"/><path d="M14.05 26.32H14.53V9H14.05V26.32Z"/><path d="M15.9 26.32H18.3V9H15.9V26.32Z"/><path d="M19.75 26.32H20.23V9H19.75V26.32Z"/><path d="M21.03 26.32H22.07V9H21.03V26.32Z"/><path d="M23.52 26.32H24V9H23.52V26.32Z"/><path d="M26.65 26.32H27.13V9H26.65V26.32Z"/><path d="M29.78 26.32H30.26V9H29.78V26.32Z"/><path d="M31.71 26.32H32.83V9H31.71V26.32Z"/><path d="M33.63 26.32H34.11V9H33.63V26.32Z"/><path d="M36.76 26.32H37.88V9H36.76V26.32Z"/><path d="M38.69 26.32H39.17V9H38.69V26.32Z"/><path d="M40.57 26.32H41.01V9H40.57V26.32Z"/><path d="M42.46 26.32H42.94V9H42.46V26.32Z"/><path d="M43.74 26.32H44.86V9H43.74V26.32Z"/><path d="M47.51 26.32H47.99V9H47.51V26.32Z"/><path d="M48.8 26.32H51.12V9H48.8V26.32Z"/><path d="M51.93 26.32H53.69V9H51.93V26.32Z"/><path d="M54.49 26.32H55.61V9H54.49V26.32Z"/><path d="M57.62 26.32H59.38V9H57.62V26.32Z"/><path d="M60.11 26.32H60.59V9H60.11V26.32Z"/><path d="M61.4 26.32H62.52V9H61.4V26.32Z"/>';

    /// @notice vertical barcode SVG
    string constant verticalBarcode =
        '<path d="M9 9L9 10.12L26.32 10.12V9L9 9Z"/><path d="M9 10.84L9 11.32L26.32 11.32V10.84L9 10.84Z"/><path d="M9 12.77L9 13.25L26.32 13.25V12.77L9 12.77Z"/><path d="M9 14.05L9 14.53L26.32 14.53V14.05L9 14.05Z"/><path d="M9 15.9L9 18.3L26.32 18.3V15.9L9 15.9Z"/><path d="M9 19.75L9 20.23L26.32 20.23V19.75L9 19.75Z"/><path d="M9 21.03L9 22.07L26.32 22.07V21.03L9 21.03Z"/><path d="M9 23.52L9 24L26.32 24V23.52L9 23.52Z"/><path d="M9 26.65L9 27.13H26.32V26.65H9Z"/><path d="M9 29.78L9 30.26H26.32V29.78H9Z"/><path d="M9 31.71L9 32.83H26.32V31.71H9Z"/><path d="M9 33.63L9 34.11H26.32V33.63H9Z"/><path d="M9 36.76L9 37.88H26.32V36.76H9Z"/><path d="M9 38.69L9 39.17H26.32V38.69H9Z"/><path d="M9 40.57L9 41.01H26.32L26.32 40.57H9Z"/><path d="M9 42.46L9 42.94H26.32V42.46H9Z"/><path d="M9 43.74L9 44.86H26.32V43.74H9Z"/><path d="M9 47.51L9 47.99H26.32V47.51H9Z"/><path d="M9 48.8L9 51.12H26.32V48.8H9Z"/><path d="M9 51.93L9 53.69H26.32V51.93H9Z"/><path d="M9 54.49L9 55.61H26.32V54.49H9Z"/><path d="M9 57.62L9 59.38H26.32V57.62H9Z"/><path d="M9 60.11L9 60.59H26.32V60.11H9Z"/><path d="M9 61.4L9 62.52H26.32V61.4H9Z"/>';

    constructor() {
        /// @notice populates all of the predefined art mappings
        _populateData();
    }

    /// @notice generate art rules for a token
    /// @param token for which to generate art rules
    /// @return artRules for a given token
    function _generateArtRules(Structs.Token memory token)
        internal
        view
        returns (Structs.ArtRules memory artRules)
    {
        uint256 artIndex = token.currentArtIndex;
        if (artIndex == 7) {
            // token is burned
            artRules = _defaults[7];
        } else if (artIndex > 7 && artIndex < 11) {
            // token is a super rare
            if (token.edited) {
                artRules = _defaults[artIndex];
                artRules.labelText = token.artRules.labelText;
                artRules.textSize = token.artRules.textSize;
            } else {
                artRules = _defaults[artIndex];
            }
        } else {
            // token is a blank or a common
            if (token.edited) {
                artRules = token.artRules;
            } else {
                artRules = _defaults[artIndex];
            }
        }
    }

    /// @notice render SVG code for a given token
    /// @param token for which to render SVG code
    /// @return svg code
    function renderSvg(Structs.Token memory token)
        public
        view
        returns (string memory svg)
    {
        string memory bg = "#FFF";
        string memory barcode;
        Structs.ArtRules memory artRules = _generateArtRules(token);
        uint256 artIndex = token.currentArtIndex;

        if (artIndex > 7 && artIndex < 11) {
            // token is a super rare
            // update the background color
            if (artIndex == 8) {
                bg = "#F6871F";
            } else if (artIndex == 9) {
                bg = "#00A651";
            } else if (artIndex == 10) {
                bg = "#004E7C";
            }
        }

        if (artIndex != 7) {
            barcode = artRules.vertical ? verticalBarcode : horizontalBarcode;
        }

        svg = string(
            abi.encodePacked(
                '<svg width="800" height="800" viewBox="0 0 202 202" fill="none" xmlns="http://www.w3.org/2000/svg"><rect id="bg" x="1" y="1" rx="6" width="200" height="200" fill="',
                bg,
                '"/><rect id="label" ',
                _labels[artRules.labelIndex].rounded ? 'rx="21"' : "",
                ' x="19" y="80" width="164" height="42" fill="#212121"/><path id="textpath" d="M19 101L183 101"/><text fill="#231F20" font-family="system-ui" font-size="',
                Utils.uint2string(artRules.textSize),
                '" font-weight="bold" text-anchor="middle" letter-spacing="0em" dy=".36em"><textPath href="#textpath" startOffset="50%">',
                Utils.formatCharacters(artRules.labelText),
                '</textPath></text><circle id="c6" cx="189" cy="189" r="5"/><circle id="c5" cx="179" cy="189" r="5"/><circle id="c4" cx="169" cy="189" r="5"/><circle id="c3" cx="159" cy="189" r="5"/><circle id="c2" cx="149" cy="189" r="5"/><circle id="c1" cx="139" cy="189" r="5"/><g id="barcode">',
                barcode,
                "</g><defs><style>#barcode path{fill:#010101}#label{fill:",
                _labels[artRules.labelIndex].color,
                "}text{fill:",
                artRules.whiteText ? "#FFF" : "#000",
                "}#bg{",
                artRules.stroke ? "stroke:#231F20" : "",
                "}",
                _spectrums[artRules.spectrumIndex].styles,
                "</style></defs></svg>"
            )
        );
    }

    /// @notice generate metadata for a given token
    /// @param tokenId of the token for which to generate metadata
    /// @param token for which to generate metadata
    /// @return metadata as a json string
    function _generateMetadata(uint256 tokenId, Structs.Token memory token)
        internal
        view
        returns (string memory)
    {
        return
            string(
                abi.encodePacked(
                    '{"name": "Metadate/a ',
                    Utils.uint2string(tokenId),
                    '", "description": "An immersive art installation recording the convergence of data.", "attributes": [',
                    _generateAttributes(token),
                    '], "image": "data:image/svg+xml;base64,',
                    Base64.encode(bytes(renderSvg(token))),
                    '"}'
                )
            );
    }

    /// @notice generate attributes for a given token
    /// @param token for which to generate attributes
    /// @return attributes for a given token
    function _generateAttributes(Structs.Token memory token)
        internal
        view
        returns (bytes memory)
    {
        if (token.currentArtIndex != 7) {
            Structs.ArtRules memory artRules = _generateArtRules(token);
            return
                abi.encodePacked(
                    _attribute("Burned", '"false"', true),
                    _attribute(
                        "Edited",
                        token.edited ? '"true"' : '"false"',
                        true
                    ),
                    _attribute(
                        "Original",
                        string(abi.encodePacked('"',_defaults[token.originalArtIndex].labelText,'"')),
                        true
                    ),
                    _attribute(
                        "Super Rare",
                        token.currentArtIndex > 7 && token.currentArtIndex < 11
                            ? '"true"'
                            : '"false"',
                        true
                    ),
                    _attribute(
                        "Stroke",
                        artRules.stroke ? '"true"' : '"false"',
                        true
                    ),
                    _attribute(
                        "Barcode",
                        artRules.vertical ? '"Vertical"' : '"Horizontal"',
                        true
                    ),
                    _attribute(
                        "Label Style",
                        _labels[artRules.labelIndex].rounded
                            ? '"Round"'
                            : '"Square"',
                        true
                    ),
                    _attribute(
                        "Color Spectrum",
                        string(abi.encodePacked('"',_spectrums[artRules.spectrumIndex].name,'"')),
                        true
                    ),
                    _attribute(
                        "Label Color",
                        string(abi.encodePacked('"',_labels[artRules.labelIndex].name,'"')),
                        true
                    ),
                    _attribute(
                        "Text Color",
                        artRules.whiteText ? '"White"' : '"Black"',
                        true
                    ),
                    _attribute(
                        "Text Size",
                        Utils.uint2string(artRules.textSize),
                        true
                    ),
                    _attribute("Metadata", string(abi.encodePacked('"',token.meta,'"')), false)
                );
        } else {
            return abi.encodePacked(_attribute("Burned", '"true"', false));
        }
    }

    /// @notice generate a formatted attribute
    /// @param attType type of attribute
    /// @param attValue value of attribute
    /// @param trailingComma whether or not to include a trailing comma
    function _attribute(
        string memory attType,
        string memory attValue,
        bool trailingComma
    ) internal pure returns (string memory) {
        return
            string(
                abi.encodePacked(
                    "{",
                    '"trait_type": "',
                    attType,
                    '",'
                    '"value": ',
                    attValue,
                    "}",
                    trailingComma ? "," : ""
                )
            );
    }

    /// @notice get the base64 encoded token URI
    /// @param tokenId of the token for which to get the base64 encoded token URI
    /// @param token for which to get the base64 encoded token URI
    /// @return base64 encoded token URI
    function getTokenURI(uint256 tokenId, Structs.Token memory token)
        public
        view
        returns (string memory)
    {
        return
            string(
                abi.encodePacked(
                    "data:application/json;base64,",
                    Base64.encode(bytes(_generateMetadata(tokenId, token)))
                )
            );
    }

    /// @notice initialize the art labels, spectrums, and defaults
    /// @dev only called once during deployment
    function _populateData() internal {
        _labels[0] = Structs.Label({
            name: "White",
            color: "#FFFEF7",
            rounded: true
        });
        _labels[1] = Structs.Label({
            name: "White",
            color: "#FFFEF7",
            rounded: false
        });
        _labels[2] = Structs.Label({
            name: "Red",
            color: "#E60020",
            rounded: true
        });
        _labels[3] = Structs.Label({
            name: "Red",
            color: "#E60020",
            rounded: false
        });
        _labels[4] = Structs.Label({
            name: "Yellow",
            color: "#FFAE00",
            rounded: true
        });
        _labels[5] = Structs.Label({
            name: "Yellow",
            color: "#FFAE00",
            rounded: false
        });
        _labels[6] = Structs.Label({
            name: "Gray",
            color: "#252425",
            rounded: true
        });
        _labels[7] = Structs.Label({
            name: "Gray",
            color: "#252425",
            rounded: false
        });
        _spectrums[0] = Structs.Spectrum({
            name: "Lagos",
            styles: "#c1{fill:#F16690;opacity:80%;}#c2{fill:#F16690;opacity:100%;}#c3{fill:#EFC3A3;opacity:80%;}#c4{fill:#EFC3A3;opacity:100%;}#c5{fill:#FFCF01;opacity:80%;}#c6{fill:#FFCF01;opacity:100%;}"
        });
        _spectrums[1] = Structs.Spectrum({
            name: "San Paulo",
            styles: "#c1{fill:#F15D2A;opacity:80%;}#c2{fill:#F15D2A;opacity:100%;}#c3{fill:#B2A9A7;opacity:80%;}#c4{fill:#B2A9A7;opacity:100%;}#c5{fill:#2D6CB5;opacity:80%;}#c6{fill:#2D6CB5;opacity:100%;}"
        });
        _spectrums[2] = Structs.Spectrum({
            name: "Bogota",
            styles: "#c1{fill:#F26D61;opacity:80%;}#c2{fill:#F26D61;opacity:100%;}#c3{fill:#A3C7E9;opacity:80%;}#c4{fill:#A3C7E9;opacity:100%;}#c5{fill:#669060;opacity:80%;}#c6{fill:#669060;opacity:100%;}"
        });
        _spectrums[3] = Structs.Spectrum({
            name: "Johannesburg",
            styles: "#c1{fill:#EE202E;opacity:80%;}#c2{fill:#EE202E;opacity:100%;}#c3{fill:#F5E1D3;opacity:80%;}#c4{fill:#F5E1D3;opacity:100%;}#c5{fill:#333132;opacity:80%;}#c6{fill:#333132;opacity:100%;}"
        });
        _spectrums[4] = Structs.Spectrum({
            name: "Vancouver",
            styles: "#c1{fill:#231F20;opacity:15%;}#c2{fill:#231F20;opacity:30%;}#c3{fill:#231F20;opacity:45%;}#c4{fill:#231F20;opacity:60%;}#c5{fill:#231F20;opacity:75%;}#c6{fill:#231F20;opacity:90%;}"
        });
        _spectrums[5] = Structs.Spectrum({
            name: "Arizona",
            styles: "#c1{fill:#FFCF01;opacity:15%;}#c2{fill:#FFCF01;opacity:30%;}#c3{fill:#FFCF01;opacity:45%;}#c4{fill:#FFCF01;opacity:60%;}#c5{fill:#FFCF01;opacity:75%;}#c6{fill:#FFCF01;opacity:90%;}"
        });
        _spectrums[6] = Structs.Spectrum({
            name: "Miami",
            styles: "#c1{fill:#EF4277;opacity:80%;}#c2{fill:#EF4277;opacity:100%;}#c3{fill:#26A191;opacity:80%;}#c4{fill:#26A191;opacity:100%;}#c5{fill:#FFCF01;opacity:80%;}#c6{fill:#FFCF01;opacity:100%;}"
        });
        _spectrums[7] = Structs.Spectrum({
            name: "San Antonio",
            styles: "#c1{fill:#FB8C05;opacity:100%;}#c2{fill:#990037;opacity:100%;}#c3{fill:#00315E;opacity:100%;}#c4{fill:#00315E;opacity:100%;}#c5{fill:#990037;opacity:100%;}#c6{fill:#FB8C05;opacity:100%;}"
        });
        _spectrums[8] = Structs.Spectrum({
            name: "New York",
            styles: "#c1{fill:#002850;opacity:15%;}#c2{fill:#002850;opacity:30%;}#c3{fill:#002850;opacity:45%;}#c4{fill:#002850;opacity:60%;}#c5{fill:#002850;opacity:75%;}#c6{fill:#002850;opacity:90%;}"
        });
        _spectrums[9] = Structs.Spectrum({
            name: "Maryland",
            styles: "#c1{fill:#FA8A24;opacity:100%;}#c2{fill:#1A1818;opacity:100%;}#c3{fill:#E60020;opacity:100%;}#c4{fill:#E60020;opacity:100%;}#c5{fill:#1A1818;opacity:100%;}#c6{fill:#FA8A24;opacity:100%;}"
        });
        _spectrums[10] = Structs.Spectrum({
            name: "Singapore",
            styles: "#c1{fill:#B58B7F;opacity:100%;}#c2{fill:#1A1818;opacity:100%;}#c3{fill:#00A4A5;opacity:100%;}#c4{fill:#00A4A5;opacity:100%;}#c5{fill:#1A1818;opacity:100%;}#c6{fill:#B58B7F;opacity:100%;}"
        });
        _spectrums[11] = Structs.Spectrum({
            name: "Wyoming",
            styles: "#c1{fill:#B17E54;opacity:15%;}#c2{fill:#A26C41;opacity:30%;}#c3{fill:#955D31;opacity:45%;}#c4{fill:#874D1F;opacity:60%;}#c5{fill:#783E0C;opacity:75%;}#c6{fill:#6A3100;opacity:90%;}"
        });
        _spectrums[12] = Structs.Spectrum({
            name: "Cluj",
            styles: "#c1{fill:#FFF032;opacity:100%;}#c2{fill:#003E6A;opacity:100%;}#c3{fill:#4E4E49;opacity:100%;}#c4{fill:#4E4E49;opacity:100%;}#c5{fill:#003E6A;opacity:100%;}#c6{fill:#FFF032;opacity:100%;}"
        });
        _spectrums[13] = Structs.Spectrum({
            name: "Chicago",
            styles: "#c1{fill:#E60020;opacity:15%;}#c2{fill:#E60020;opacity:30%;}#c3{fill:#E60020;opacity:45%;}#c4{fill:#E60020;opacity:60%;}#c5{fill:#E60020;opacity:75%;}#c6{fill:#E60020;opacity:90%;}"
        });
        _spectrums[14] = Structs.Spectrum({
            name: "Portland",
            styles: "#c1{fill:#568861;opacity:100%;}#c2{fill:#457B53;opacity:100%;}#c3{fill:#326E43;opacity:100%;}#c4{fill:#206134;opacity:100%;}#c5{fill:#005526;opacity:100%;}#c6{fill:#004B1C;opacity:100%;}"
        });
        _spectrums[15] = Structs.Spectrum({name: "None", styles: ""});
        // blank
        _defaults[0] = Structs.ArtRules({
            stroke: false,
            vertical: false,
            spectrumIndex: 0,
            labelIndex: 0,
            labelText: "",
            textSize: 9,
            whiteText: false
        });
        _defaults[1] = Structs.ArtRules({
            stroke: false,
            vertical: false,
            spectrumIndex: 5,
            labelIndex: 0,
            labelText: "July 11th, 2022",
            textSize: 11,
            whiteText: false
        });
        _defaults[2] = Structs.ArtRules({
            stroke: false,
            vertical: true,
            spectrumIndex: 4,
            labelIndex: 0,
            labelText: "PAIN BEFORE PROGRESS",
            textSize: 9,
            whiteText: false
        });
        _defaults[3] = Structs.ArtRules({
            stroke: false,
            vertical: false,
            spectrumIndex: 9,
            labelIndex: 0,
            labelText: "November 6th, 1990",
            textSize: 9,
            whiteText: false
        });
        _defaults[4] = Structs.ArtRules({
            stroke: false,
            vertical: false,
            spectrumIndex: 14,
            labelIndex: 0,
            labelText: "October 31st, 2008",
            textSize: 9,
            whiteText: false
        });
        _defaults[5] = Structs.ArtRules({
            stroke: false,
            vertical: true,
            spectrumIndex: 8,
            labelIndex: 0,
            labelText: "THE MARATHON CONTINUES",
            textSize: 9,
            whiteText: false
        });
        _defaults[6] = Structs.ArtRules({
            stroke: true,
            vertical: true,
            spectrumIndex: 3,
            labelIndex: 0,
            labelText: "MAMBA MENTALITY",
            textSize: 9,
            whiteText: false
        });
        // burned
        _defaults[7] = Structs.ArtRules({
            stroke: false,
            vertical: false,
            spectrumIndex: 15,
            labelIndex: 2,
            labelText: "*NOT FOR RESALE",
            textSize: 14,
            whiteText: true
        });
        // super rare: orange
        _defaults[8] = Structs.ArtRules({
            stroke: false,
            vertical: true,
            spectrumIndex: 8,
            labelIndex: 0,
            labelText: "",
            textSize: 9,
            whiteText: false
        });
        // super rare: green
        _defaults[9] = Structs.ArtRules({
            stroke: false,
            vertical: false,
            spectrumIndex: 1,
            labelIndex: 0,
            labelText: "",
            textSize: 9,
            whiteText: false
        });
        // super rare: blue
        _defaults[10] = Structs.ArtRules({
            stroke: false,
            vertical: false,
            spectrumIndex: 13,
            labelIndex: 2,
            labelText: "",
            textSize: 9,
            whiteText: false
        });
    }
}

// File: contracts/Structs.sol



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

library Structs {
    struct Label {
        string name;
        string color;
        bool rounded;
    }

    struct Spectrum {
        string name;
        string styles;
    }

    struct ArtRules {
        bool stroke;
        bool vertical;
        bool whiteText;
        string labelText;
        uint256 spectrumIndex;
        uint256 labelIndex;
        uint256 textSize;
    }

    struct Token {
        bool edited;
        uint256 originalArtIndex;
        uint256 currentArtIndex;
        string meta;
        ArtRules artRules;
    }
}

// File: contracts/IERC4906.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.23;

/// @title EIP-721 Metadata Update Extension
interface IERC4906 {
    /// @dev This event emits when the metadata of a token is changed.
    /// So that the third-party platforms such as NFT market could
    /// timely update the images and related attributes of the NFT.
    event MetadataUpdate(uint256 _tokenId);

    /// @dev This event emits when the metadata of a range of tokens is changed.
    /// So that the third-party platforms such as NFT market could
    /// timely update the images and related attributes of the NFTs.    
    event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId);
}
// File: @openzeppelin/contracts/token/ERC721/IERC721.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721.sol)

pragma solidity ^0.8.20;

import {IERC165} from "../../utils/introspection/IERC165.sol";

/**
 * @dev Required interface of an ERC721 compliant contract.
 */
interface IERC721 is IERC165 {
    /**
     * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
     */
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
     */
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
     */
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    /**
     * @dev Returns the number of tokens in ``owner``'s account.
     */
    function balanceOf(address owner) external view returns (uint256 balance);

    /**
     * @dev Returns the owner of the `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function ownerOf(uint256 tokenId) external view returns (address owner);

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
     *   a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
     * are aware of the ERC721 protocol to prevent tokens from being forever locked.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or
     *   {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
     *   a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Transfers `tokenId` token from `from` to `to`.
     *
     * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
     * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
     * understand this adds an external call which potentially creates a reentrancy vulnerability.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Gives permission to `to` to transfer `tokenId` token to another account.
     * The approval is cleared when the token is transferred.
     *
     * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
     *
     * Requirements:
     *
     * - The caller must own the token or be an approved operator.
     * - `tokenId` must exist.
     *
     * Emits an {Approval} event.
     */
    function approve(address to, uint256 tokenId) external;

    /**
     * @dev Approve or remove `operator` as an operator for the caller.
     * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
     *
     * Requirements:
     *
     * - The `operator` cannot be the address zero.
     *
     * Emits an {ApprovalForAll} event.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Returns the account approved for `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function getApproved(uint256 tokenId) external view returns (address operator);

    /**
     * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
     *
     * See {setApprovalForAll}
     */
    function isApprovedForAll(address owner, address operator) external view returns (bool);
}

// File: @openzeppelin/contracts/access/Ownable.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;

import {Context} from "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * The initial owner is set to the address provided by the deployer. This can
 * later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    constructor(address initialOwner) {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

// File: erc721a/contracts/extensions/ERC721ABurnable.sol



// SPDX-License-Identifier: MIT
// ERC721A Contracts v4.2.3
// Creator: Chiru Labs

pragma solidity ^0.8.4;

import './IERC721ABurnable.sol';
import '../ERC721A.sol';

/**
 * @title ERC721ABurnable.
 *
 * @dev ERC721A token that can be irreversibly burned (destroyed).
 */
abstract contract ERC721ABurnable is ERC721A, IERC721ABurnable {
    /**
     * @dev Burns `tokenId`. See {ERC721A-_burn}.
     *
     * Requirements:
     *
     * - The caller must own `tokenId` or be an approved operator.
     */
    function burn(uint256 tokenId) public virtual override {
        _burn(tokenId, true);
    }
}

// File: erc721a/contracts/extensions/ERC721AQueryable.sol



// SPDX-License-Identifier: MIT
// ERC721A Contracts v4.2.3
// Creator: Chiru Labs

pragma solidity ^0.8.4;

import './IERC721AQueryable.sol';
import '../ERC721A.sol';

/**
 * @title ERC721AQueryable.
 *
 * @dev ERC721A subclass with convenience query functions.
 */
abstract contract ERC721AQueryable is ERC721A, IERC721AQueryable {
    /**
     * @dev Returns the `TokenOwnership` struct at `tokenId` without reverting.
     *
     * If the `tokenId` is out of bounds:
     *
     * - `addr = address(0)`
     * - `startTimestamp = 0`
     * - `burned = false`
     * - `extraData = 0`
     *
     * If the `tokenId` is burned:
     *
     * - `addr = <Address of owner before token was burned>`
     * - `startTimestamp = <Timestamp when token was burned>`
     * - `burned = true`
     * - `extraData = <Extra data when token was burned>`
     *
     * Otherwise:
     *
     * - `addr = <Address of owner>`
     * - `startTimestamp = <Timestamp of start of ownership>`
     * - `burned = false`
     * - `extraData = <Extra data at start of ownership>`
     */
    function explicitOwnershipOf(uint256 tokenId) public view virtual override returns (TokenOwnership memory) {
        TokenOwnership memory ownership;
        if (tokenId < _startTokenId() || tokenId >= _nextTokenId()) {
            return ownership;
        }
        ownership = _ownershipAt(tokenId);
        if (ownership.burned) {
            return ownership;
        }
        return _ownershipOf(tokenId);
    }

    /**
     * @dev Returns an array of `TokenOwnership` structs at `tokenIds` in order.
     * See {ERC721AQueryable-explicitOwnershipOf}
     */
    function explicitOwnershipsOf(uint256[] calldata tokenIds)
        external
        view
        virtual
        override
        returns (TokenOwnership[] memory)
    {
        unchecked {
            uint256 tokenIdsLength = tokenIds.length;
            TokenOwnership[] memory ownerships = new TokenOwnership[](tokenIdsLength);
            for (uint256 i; i != tokenIdsLength; ++i) {
                ownerships[i] = explicitOwnershipOf(tokenIds[i]);
            }
            return ownerships;
        }
    }

    /**
     * @dev Returns an array of token IDs owned by `owner`,
     * in the range [`start`, `stop`)
     * (i.e. `start <= tokenId < stop`).
     *
     * This function allows for tokens to be queried if the collection
     * grows too big for a single call of {ERC721AQueryable-tokensOfOwner}.
     *
     * Requirements:
     *
     * - `start < stop`
     */
    function tokensOfOwnerIn(
        address owner,
        uint256 start,
        uint256 stop
    ) external view virtual override returns (uint256[] memory) {
        unchecked {
            if (start >= stop) revert InvalidQueryRange();
            uint256 tokenIdsIdx;
            uint256 stopLimit = _nextTokenId();
            // Set `start = max(start, _startTokenId())`.
            if (start < _startTokenId()) {
                start = _startTokenId();
            }
            // Set `stop = min(stop, stopLimit)`.
            if (stop > stopLimit) {
                stop = stopLimit;
            }
            uint256 tokenIdsMaxLength = balanceOf(owner);
            // Set `tokenIdsMaxLength = min(balanceOf(owner), stop - start)`,
            // to cater for cases where `balanceOf(owner)` is too big.
            if (start < stop) {
                uint256 rangeLength = stop - start;
                if (rangeLength < tokenIdsMaxLength) {
                    tokenIdsMaxLength = rangeLength;
                }
            } else {
                tokenIdsMaxLength = 0;
            }
            uint256[] memory tokenIds = new uint256[](tokenIdsMaxLength);
            if (tokenIdsMaxLength == 0) {
                return tokenIds;
            }
            // We need to call `explicitOwnershipOf(start)`,
            // because the slot at `start` may not be initialized.
            TokenOwnership memory ownership = explicitOwnershipOf(start);
            address currOwnershipAddr;
            // If the starting slot exists (i.e. not burned), initialize `currOwnershipAddr`.
            // `ownership.address` will not be zero, as `start` is clamped to the valid token ID range.
            if (!ownership.burned) {
                currOwnershipAddr = ownership.addr;
            }
            for (uint256 i = start; i != stop && tokenIdsIdx != tokenIdsMaxLength; ++i) {
                ownership = _ownershipAt(i);
                if (ownership.burned) {
                    continue;
                }
                if (ownership.addr != address(0)) {
                    currOwnershipAddr = ownership.addr;
                }
                if (currOwnershipAddr == owner) {
                    tokenIds[tokenIdsIdx++] = i;
                }
            }
            // Downsize the array to fit.
            assembly {
                mstore(tokenIds, tokenIdsIdx)
            }
            return tokenIds;
        }
    }

    /**
     * @dev Returns an array of token IDs owned by `owner`.
     *
     * This function scans the ownership mapping and is O(`totalSupply`) in complexity.
     * It is meant to be called off-chain.
     *
     * See {ERC721AQueryable-tokensOfOwnerIn} for splitting the scan into
     * multiple smaller scans if the collection is large enough to cause
     * an out-of-gas error (10K collections should be fine).
     */
    function tokensOfOwner(address owner) external view virtual override returns (uint256[] memory) {
        unchecked {
            uint256 tokenIdsIdx;
            address currOwnershipAddr;
            uint256 tokenIdsLength = balanceOf(owner);
            uint256[] memory tokenIds = new uint256[](tokenIdsLength);
            TokenOwnership memory ownership;
            for (uint256 i = _startTokenId(); tokenIdsIdx != tokenIdsLength; ++i) {
                ownership = _ownershipAt(i);
                if (ownership.burned) {
                    continue;
                }
                if (ownership.addr != address(0)) {
                    currOwnershipAddr = ownership.addr;
                }
                if (currOwnershipAddr == owner) {
                    tokenIds[tokenIdsIdx++] = i;
                }
            }
            return tokenIds;
        }
    }
}

// File: erc721a/contracts/ERC721A.sol



// SPDX-License-Identifier: MIT
// ERC721A Contracts v4.2.3
// Creator: Chiru Labs

pragma solidity ^0.8.4;

import './IERC721A.sol';

/**
 * @dev Interface of ERC721 token receiver.
 */
interface ERC721A__IERC721Receiver {
    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    ) external returns (bytes4);
}

/**
 * @title ERC721A
 *
 * @dev Implementation of the [ERC721](https://eips.ethereum.org/EIPS/eip-721)
 * Non-Fungible Token Standard, including the Metadata extension.
 * Optimized for lower gas during batch mints.
 *
 * Token IDs are minted in sequential order (e.g. 0, 1, 2, 3, ...)
 * starting from `_startTokenId()`.
 *
 * Assumptions:
 *
 * - An owner cannot have more than 2**64 - 1 (max value of uint64) of supply.
 * - The maximum token ID cannot exceed 2**256 - 1 (max value of uint256).
 */
contract ERC721A is IERC721A {
    // Bypass for a `--via-ir` bug (https://github.com/chiru-labs/ERC721A/pull/364).
    struct TokenApprovalRef {
        address value;
    }

    // =============================================================
    //                           CONSTANTS
    // =============================================================

    // Mask of an entry in packed address data.
    uint256 private constant _BITMASK_ADDRESS_DATA_ENTRY = (1 << 64) - 1;

    // The bit position of `numberMinted` in packed address data.
    uint256 private constant _BITPOS_NUMBER_MINTED = 64;

    // The bit position of `numberBurned` in packed address data.
    uint256 private constant _BITPOS_NUMBER_BURNED = 128;

    // The bit position of `aux` in packed address data.
    uint256 private constant _BITPOS_AUX = 192;

    // Mask of all 256 bits in packed address data except the 64 bits for `aux`.
    uint256 private constant _BITMASK_AUX_COMPLEMENT = (1 << 192) - 1;

    // The bit position of `startTimestamp` in packed ownership.
    uint256 private constant _BITPOS_START_TIMESTAMP = 160;

    // The bit mask of the `burned` bit in packed ownership.
    uint256 private constant _BITMASK_BURNED = 1 << 224;

    // The bit position of the `nextInitialized` bit in packed ownership.
    uint256 private constant _BITPOS_NEXT_INITIALIZED = 225;

    // The bit mask of the `nextInitialized` bit in packed ownership.
    uint256 private constant _BITMASK_NEXT_INITIALIZED = 1 << 225;

    // The bit position of `extraData` in packed ownership.
    uint256 private constant _BITPOS_EXTRA_DATA = 232;

    // Mask of all 256 bits in a packed ownership except the 24 bits for `extraData`.
    uint256 private constant _BITMASK_EXTRA_DATA_COMPLEMENT = (1 << 232) - 1;

    // The mask of the lower 160 bits for addresses.
    uint256 private constant _BITMASK_ADDRESS = (1 << 160) - 1;

    // The maximum `quantity` that can be minted with {_mintERC2309}.
    // This limit is to prevent overflows on the address data entries.
    // For a limit of 5000, a total of 3.689e15 calls to {_mintERC2309}
    // is required to cause an overflow, which is unrealistic.
    uint256 private constant _MAX_MINT_ERC2309_QUANTITY_LIMIT = 5000;

    // The `Transfer` event signature is given by:
    // `keccak256(bytes("Transfer(address,address,uint256)"))`.
    bytes32 private constant _TRANSFER_EVENT_SIGNATURE =
        0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;

    // =============================================================
    //                            STORAGE
    // =============================================================

    // The next token ID to be minted.
    uint256 private _currentIndex;

    // The number of tokens burned.
    uint256 private _burnCounter;

    // Token name
    string private _name;

    // Token symbol
    string private _symbol;

    // Mapping from token ID to ownership details
    // An empty struct value does not necessarily mean the token is unowned.
    // See {_packedOwnershipOf} implementation for details.
    //
    // Bits Layout:
    // - [0..159]   `addr`
    // - [160..223] `startTimestamp`
    // - [224]      `burned`
    // - [225]      `nextInitialized`
    // - [232..255] `extraData`
    mapping(uint256 => uint256) private _packedOwnerships;

    // Mapping owner address to address data.
    //
    // Bits Layout:
    // - [0..63]    `balance`
    // - [64..127]  `numberMinted`
    // - [128..191] `numberBurned`
    // - [192..255] `aux`
    mapping(address => uint256) private _packedAddressData;

    // Mapping from token ID to approved address.
    mapping(uint256 => TokenApprovalRef) private _tokenApprovals;

    // Mapping from owner to operator approvals
    mapping(address => mapping(address => bool)) private _operatorApprovals;

    // =============================================================
    //                          CONSTRUCTOR
    // =============================================================

    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
        _currentIndex = _startTokenId();
    }

    // =============================================================
    //                   TOKEN COUNTING OPERATIONS
    // =============================================================

    /**
     * @dev Returns the starting token ID.
     * To change the starting token ID, please override this function.
     */
    function _startTokenId() internal view virtual returns (uint256) {
        return 0;
    }

    /**
     * @dev Returns the next token ID to be minted.
     */
    function _nextTokenId() internal view virtual returns (uint256) {
        return _currentIndex;
    }

    /**
     * @dev Returns the total number of tokens in existence.
     * Burned tokens will reduce the count.
     * To get the total number of tokens minted, please see {_totalMinted}.
     */
    function totalSupply() public view virtual override returns (uint256) {
        // Counter underflow is impossible as _burnCounter cannot be incremented
        // more than `_currentIndex - _startTokenId()` times.
        unchecked {
            return _currentIndex - _burnCounter - _startTokenId();
        }
    }

    /**
     * @dev Returns the total amount of tokens minted in the contract.
     */
    function _totalMinted() internal view virtual returns (uint256) {
        // Counter underflow is impossible as `_currentIndex` does not decrement,
        // and it is initialized to `_startTokenId()`.
        unchecked {
            return _currentIndex - _startTokenId();
        }
    }

    /**
     * @dev Returns the total number of tokens burned.
     */
    function _totalBurned() internal view virtual returns (uint256) {
        return _burnCounter;
    }

    // =============================================================
    //                    ADDRESS DATA OPERATIONS
    // =============================================================

    /**
     * @dev Returns the number of tokens in `owner`'s account.
     */
    function balanceOf(address owner) public view virtual override returns (uint256) {
        if (owner == address(0)) revert BalanceQueryForZeroAddress();
        return _packedAddressData[owner] & _BITMASK_ADDRESS_DATA_ENTRY;
    }

    /**
     * Returns the number of tokens minted by `owner`.
     */
    function _numberMinted(address owner) internal view returns (uint256) {
        return (_packedAddressData[owner] >> _BITPOS_NUMBER_MINTED) & _BITMASK_ADDRESS_DATA_ENTRY;
    }

    /**
     * Returns the number of tokens burned by or on behalf of `owner`.
     */
    function _numberBurned(address owner) internal view returns (uint256) {
        return (_packedAddressData[owner] >> _BITPOS_NUMBER_BURNED) & _BITMASK_ADDRESS_DATA_ENTRY;
    }

    /**
     * Returns the auxiliary data for `owner`. (e.g. number of whitelist mint slots used).
     */
    function _getAux(address owner) internal view returns (uint64) {
        return uint64(_packedAddressData[owner] >> _BITPOS_AUX);
    }

    /**
     * Sets the auxiliary data for `owner`. (e.g. number of whitelist mint slots used).
     * If there are multiple variables, please pack them into a uint64.
     */
    function _setAux(address owner, uint64 aux) internal virtual {
        uint256 packed = _packedAddressData[owner];
        uint256 auxCasted;
        // Cast `aux` with assembly to avoid redundant masking.
        assembly {
            auxCasted := aux
        }
        packed = (packed & _BITMASK_AUX_COMPLEMENT) | (auxCasted << _BITPOS_AUX);
        _packedAddressData[owner] = packed;
    }

    // =============================================================
    //                            IERC165
    // =============================================================

    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * [EIP section](https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified)
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30000 gas.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        // The interface IDs are constants representing the first 4 bytes
        // of the XOR of all function selectors in the interface.
        // See: [ERC165](https://eips.ethereum.org/EIPS/eip-165)
        // (e.g. `bytes4(i.functionA.selector ^ i.functionB.selector ^ ...)`)
        return
            interfaceId == 0x01ffc9a7 || // ERC165 interface ID for ERC165.
            interfaceId == 0x80ac58cd || // ERC165 interface ID for ERC721.
            interfaceId == 0x5b5e139f; // ERC165 interface ID for ERC721Metadata.
    }

    // =============================================================
    //                        IERC721Metadata
    // =============================================================

    /**
     * @dev Returns the token collection name.
     */
    function name() public view virtual override returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the token collection symbol.
     */
    function symbol() public view virtual override returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
     */
    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        if (!_exists(tokenId)) revert URIQueryForNonexistentToken();

        string memory baseURI = _baseURI();
        return bytes(baseURI).length != 0 ? string(abi.encodePacked(baseURI, _toString(tokenId))) : '';
    }

    /**
     * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each
     * token will be the concatenation of the `baseURI` and the `tokenId`. Empty
     * by default, it can be overridden in child contracts.
     */
    function _baseURI() internal view virtual returns (string memory) {
        return '';
    }

    // =============================================================
    //                     OWNERSHIPS OPERATIONS
    // =============================================================

    /**
     * @dev Returns the owner of the `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function ownerOf(uint256 tokenId) public view virtual override returns (address) {
        return address(uint160(_packedOwnershipOf(tokenId)));
    }

    /**
     * @dev Gas spent here starts off proportional to the maximum mint batch size.
     * It gradually moves to O(1) as tokens get transferred around over time.
     */
    function _ownershipOf(uint256 tokenId) internal view virtual returns (TokenOwnership memory) {
        return _unpackedOwnership(_packedOwnershipOf(tokenId));
    }

    /**
     * @dev Returns the unpacked `TokenOwnership` struct at `index`.
     */
    function _ownershipAt(uint256 index) internal view virtual returns (TokenOwnership memory) {
        return _unpackedOwnership(_packedOwnerships[index]);
    }

    /**
     * @dev Initializes the ownership slot minted at `index` for efficiency purposes.
     */
    function _initializeOwnershipAt(uint256 index) internal virtual {
        if (_packedOwnerships[index] == 0) {
            _packedOwnerships[index] = _packedOwnershipOf(index);
        }
    }

    /**
     * Returns the packed ownership data of `tokenId`.
     */
    function _packedOwnershipOf(uint256 tokenId) private view returns (uint256) {
        uint256 curr = tokenId;

        unchecked {
            if (_startTokenId() <= curr)
                if (curr < _currentIndex) {
                    uint256 packed = _packedOwnerships[curr];
                    // If not burned.
                    if (packed & _BITMASK_BURNED == 0) {
                        // Invariant:
                        // There will always be an initialized ownership slot
                        // (i.e. `ownership.addr != address(0) && ownership.burned == false`)
                        // before an unintialized ownership slot
                        // (i.e. `ownership.addr == address(0) && ownership.burned == false`)
                        // Hence, `curr` will not underflow.
                        //
                        // We can directly compare the packed value.
                        // If the address is zero, packed will be zero.
                        while (packed == 0) {
                            packed = _packedOwnerships[--curr];
                        }
                        return packed;
                    }
                }
        }
        revert OwnerQueryForNonexistentToken();
    }

    /**
     * @dev Returns the unpacked `TokenOwnership` struct from `packed`.
     */
    function _unpackedOwnership(uint256 packed) private pure returns (TokenOwnership memory ownership) {
        ownership.addr = address(uint160(packed));
        ownership.startTimestamp = uint64(packed >> _BITPOS_START_TIMESTAMP);
        ownership.burned = packed & _BITMASK_BURNED != 0;
        ownership.extraData = uint24(packed >> _BITPOS_EXTRA_DATA);
    }

    /**
     * @dev Packs ownership data into a single uint256.
     */
    function _packOwnershipData(address owner, uint256 flags) private view returns (uint256 result) {
        assembly {
            // Mask `owner` to the lower 160 bits, in case the upper bits somehow aren't clean.
            owner := and(owner, _BITMASK_ADDRESS)
            // `owner | (block.timestamp << _BITPOS_START_TIMESTAMP) | flags`.
            result := or(owner, or(shl(_BITPOS_START_TIMESTAMP, timestamp()), flags))
        }
    }

    /**
     * @dev Returns the `nextInitialized` flag set if `quantity` equals 1.
     */
    function _nextInitializedFlag(uint256 quantity) private pure returns (uint256 result) {
        // For branchless setting of the `nextInitialized` flag.
        assembly {
            // `(quantity == 1) << _BITPOS_NEXT_INITIALIZED`.
            result := shl(_BITPOS_NEXT_INITIALIZED, eq(quantity, 1))
        }
    }

    // =============================================================
    //                      APPROVAL OPERATIONS
    // =============================================================

    /**
     * @dev Gives permission to `to` to transfer `tokenId` token to another account.
     * The approval is cleared when the token is transferred.
     *
     * Only a single account can be approved at a time, so approving the
     * zero address clears previous approvals.
     *
     * Requirements:
     *
     * - The caller must own the token or be an approved operator.
     * - `tokenId` must exist.
     *
     * Emits an {Approval} event.
     */
    function approve(address to, uint256 tokenId) public payable virtual override {
        address owner = ownerOf(tokenId);

        if (_msgSenderERC721A() != owner)
            if (!isApprovedForAll(owner, _msgSenderERC721A())) {
                revert ApprovalCallerNotOwnerNorApproved();
            }

        _tokenApprovals[tokenId].value = to;
        emit Approval(owner, to, tokenId);
    }

    /**
     * @dev Returns the account approved for `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function getApproved(uint256 tokenId) public view virtual override returns (address) {
        if (!_exists(tokenId)) revert ApprovalQueryForNonexistentToken();

        return _tokenApprovals[tokenId].value;
    }

    /**
     * @dev Approve or remove `operator` as an operator for the caller.
     * Operators can call {transferFrom} or {safeTransferFrom}
     * for any token owned by the caller.
     *
     * Requirements:
     *
     * - The `operator` cannot be the caller.
     *
     * Emits an {ApprovalForAll} event.
     */
    function setApprovalForAll(address operator, bool approved) public virtual override {
        _operatorApprovals[_msgSenderERC721A()][operator] = approved;
        emit ApprovalForAll(_msgSenderERC721A(), operator, approved);
    }

    /**
     * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
     *
     * See {setApprovalForAll}.
     */
    function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {
        return _operatorApprovals[owner][operator];
    }

    /**
     * @dev Returns whether `tokenId` exists.
     *
     * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}.
     *
     * Tokens start existing when they are minted. See {_mint}.
     */
    function _exists(uint256 tokenId) internal view virtual returns (bool) {
        return
            _startTokenId() <= tokenId &&
            tokenId < _currentIndex && // If within bounds,
            _packedOwnerships[tokenId] & _BITMASK_BURNED == 0; // and not burned.
    }

    /**
     * @dev Returns whether `msgSender` is equal to `approvedAddress` or `owner`.
     */
    function _isSenderApprovedOrOwner(
        address approvedAddress,
        address owner,
        address msgSender
    ) private pure returns (bool result) {
        assembly {
            // Mask `owner` to the lower 160 bits, in case the upper bits somehow aren't clean.
            owner := and(owner, _BITMASK_ADDRESS)
            // Mask `msgSender` to the lower 160 bits, in case the upper bits somehow aren't clean.
            msgSender := and(msgSender, _BITMASK_ADDRESS)
            // `msgSender == owner || msgSender == approvedAddress`.
            result := or(eq(msgSender, owner), eq(msgSender, approvedAddress))
        }
    }

    /**
     * @dev Returns the storage slot and value for the approved address of `tokenId`.
     */
    function _getApprovedSlotAndAddress(uint256 tokenId)
        private
        view
        returns (uint256 approvedAddressSlot, address approvedAddress)
    {
        TokenApprovalRef storage tokenApproval = _tokenApprovals[tokenId];
        // The following is equivalent to `approvedAddress = _tokenApprovals[tokenId].value`.
        assembly {
            approvedAddressSlot := tokenApproval.slot
            approvedAddress := sload(approvedAddressSlot)
        }
    }

    // =============================================================
    //                      TRANSFER OPERATIONS
    // =============================================================

    /**
     * @dev Transfers `tokenId` from `from` to `to`.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token
     * by either {approve} or {setApprovalForAll}.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public payable virtual override {
        uint256 prevOwnershipPacked = _packedOwnershipOf(tokenId);

        if (address(uint160(prevOwnershipPacked)) != from) revert TransferFromIncorrectOwner();

        (uint256 approvedAddressSlot, address approvedAddress) = _getApprovedSlotAndAddress(tokenId);

        // The nested ifs save around 20+ gas over a compound boolean condition.
        if (!_isSenderApprovedOrOwner(approvedAddress, from, _msgSenderERC721A()))
            if (!isApprovedForAll(from, _msgSenderERC721A())) revert TransferCallerNotOwnerNorApproved();

        if (to == address(0)) revert TransferToZeroAddress();

        _beforeTokenTransfers(from, to, tokenId, 1);

        // Clear approvals from the previous owner.
        assembly {
            if approvedAddress {
                // This is equivalent to `delete _tokenApprovals[tokenId]`.
                sstore(approvedAddressSlot, 0)
            }
        }

        // Underflow of the sender's balance is impossible because we check for
        // ownership above and the recipient's balance can't realistically overflow.
        // Counter overflow is incredibly unrealistic as `tokenId` would have to be 2**256.
        unchecked {
            // We can directly increment and decrement the balances.
            --_packedAddressData[from]; // Updates: `balance -= 1`.
            ++_packedAddressData[to]; // Updates: `balance += 1`.

            // Updates:
            // - `address` to the next owner.
            // - `startTimestamp` to the timestamp of transfering.
            // - `burned` to `false`.
            // - `nextInitialized` to `true`.
            _packedOwnerships[tokenId] = _packOwnershipData(
                to,
                _BITMASK_NEXT_INITIALIZED | _nextExtraData(from, to, prevOwnershipPacked)
            );

            // If the next slot may not have been initialized (i.e. `nextInitialized == false`) .
            if (prevOwnershipPacked & _BITMASK_NEXT_INITIALIZED == 0) {
                uint256 nextTokenId = tokenId + 1;
                // If the next slot's address is zero and not burned (i.e. packed value is zero).
                if (_packedOwnerships[nextTokenId] == 0) {
                    // If the next slot is within bounds.
                    if (nextTokenId != _currentIndex) {
                        // Initialize the next slot to maintain correctness for `ownerOf(tokenId + 1)`.
                        _packedOwnerships[nextTokenId] = prevOwnershipPacked;
                    }
                }
            }
        }

        emit Transfer(from, to, tokenId);
        _afterTokenTransfers(from, to, tokenId, 1);
    }

    /**
     * @dev Equivalent to `safeTransferFrom(from, to, tokenId, '')`.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public payable virtual override {
        safeTransferFrom(from, to, tokenId, '');
    }

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token
     * by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement
     * {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        bytes memory _data
    ) public payable virtual override {
        transferFrom(from, to, tokenId);
        if (to.code.length != 0)
            if (!_checkContractOnERC721Received(from, to, tokenId, _data)) {
                revert TransferToNonERC721ReceiverImplementer();
            }
    }

    /**
     * @dev Hook that is called before a set of serially-ordered token IDs
     * are about to be transferred. This includes minting.
     * And also called before burning one token.
     *
     * `startTokenId` - the first token ID to be transferred.
     * `quantity` - the amount to be transferred.
     *
     * Calling conditions:
     *
     * - When `from` and `to` are both non-zero, `from`'s `tokenId` will be
     * transferred to `to`.
     * - When `from` is zero, `tokenId` will be minted for `to`.
     * - When `to` is zero, `tokenId` will be burned by `from`.
     * - `from` and `to` are never both zero.
     */
    function _beforeTokenTransfers(
        address from,
        address to,
        uint256 startTokenId,
        uint256 quantity
    ) internal virtual {}

    /**
     * @dev Hook that is called after a set of serially-ordered token IDs
     * have been transferred. This includes minting.
     * And also called after one token has been burned.
     *
     * `startTokenId` - the first token ID to be transferred.
     * `quantity` - the amount to be transferred.
     *
     * Calling conditions:
     *
     * - When `from` and `to` are both non-zero, `from`'s `tokenId` has been
     * transferred to `to`.
     * - When `from` is zero, `tokenId` has been minted for `to`.
     * - When `to` is zero, `tokenId` has been burned by `from`.
     * - `from` and `to` are never both zero.
     */
    function _afterTokenTransfers(
        address from,
        address to,
        uint256 startTokenId,
        uint256 quantity
    ) internal virtual {}

    /**
     * @dev Private function to invoke {IERC721Receiver-onERC721Received} on a target contract.
     *
     * `from` - Previous owner of the given token ID.
     * `to` - Target address that will receive the token.
     * `tokenId` - Token ID to be transferred.
     * `_data` - Optional data to send along with the call.
     *
     * Returns whether the call correctly returned the expected magic value.
     */
    function _checkContractOnERC721Received(
        address from,
        address to,
        uint256 tokenId,
        bytes memory _data
    ) private returns (bool) {
        try ERC721A__IERC721Receiver(to).onERC721Received(_msgSenderERC721A(), from, tokenId, _data) returns (
            bytes4 retval
        ) {
            return retval == ERC721A__IERC721Receiver(to).onERC721Received.selector;
        } catch (bytes memory reason) {
            if (reason.length == 0) {
                revert TransferToNonERC721ReceiverImplementer();
            } else {
                assembly {
                    revert(add(32, reason), mload(reason))
                }
            }
        }
    }

    // =============================================================
    //                        MINT OPERATIONS
    // =============================================================

    /**
     * @dev Mints `quantity` tokens and transfers them to `to`.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - `quantity` must be greater than 0.
     *
     * Emits a {Transfer} event for each mint.
     */
    function _mint(address to, uint256 quantity) internal virtual {
        uint256 startTokenId = _currentIndex;
        if (quantity == 0) revert MintZeroQuantity();

        _beforeTokenTransfers(address(0), to, startTokenId, quantity);

        // Overflows are incredibly unrealistic.
        // `balance` and `numberMinted` have a maximum limit of 2**64.
        // `tokenId` has a maximum limit of 2**256.
        unchecked {
            // Updates:
            // - `balance += quantity`.
            // - `numberMinted += quantity`.
            //
            // We can directly add to the `balance` and `numberMinted`.
            _packedAddressData[to] += quantity * ((1 << _BITPOS_NUMBER_MINTED) | 1);

            // Updates:
            // - `address` to the owner.
            // - `startTimestamp` to the timestamp of minting.
            // - `burned` to `false`.
            // - `nextInitialized` to `quantity == 1`.
            _packedOwnerships[startTokenId] = _packOwnershipData(
                to,
                _nextInitializedFlag(quantity) | _nextExtraData(address(0), to, 0)
            );

            uint256 toMasked;
            uint256 end = startTokenId + quantity;

            // Use assembly to loop and emit the `Transfer` event for gas savings.
            // The duplicated `log4` removes an extra check and reduces stack juggling.
            // The assembly, together with the surrounding Solidity code, have been
            // delicately arranged to nudge the compiler into producing optimized opcodes.
            assembly {
                // Mask `to` to the lower 160 bits, in case the upper bits somehow aren't clean.
                toMasked := and(to, _BITMASK_ADDRESS)
                // Emit the `Transfer` event.
                log4(
                    0, // Start of data (0, since no data).
                    0, // End of data (0, since no data).
                    _TRANSFER_EVENT_SIGNATURE, // Signature.
                    0, // `address(0)`.
                    toMasked, // `to`.
                    startTokenId // `tokenId`.
                )

                // The `iszero(eq(,))` check ensures that large values of `quantity`
                // that overflows uint256 will make the loop run out of gas.
                // The compiler will optimize the `iszero` away for performance.
                for {
                    let tokenId := add(startTokenId, 1)
                } iszero(eq(tokenId, end)) {
                    tokenId := add(tokenId, 1)
                } {
                    // Emit the `Transfer` event. Similar to above.
                    log4(0, 0, _TRANSFER_EVENT_SIGNATURE, 0, toMasked, tokenId)
                }
            }
            if (toMasked == 0) revert MintToZeroAddress();

            _currentIndex = end;
        }
        _afterTokenTransfers(address(0), to, startTokenId, quantity);
    }

    /**
     * @dev Mints `quantity` tokens and transfers them to `to`.
     *
     * This function is intended for efficient minting only during contract creation.
     *
     * It emits only one {ConsecutiveTransfer} as defined in
     * [ERC2309](https://eips.ethereum.org/EIPS/eip-2309),
     * instead of a sequence of {Transfer} event(s).
     *
     * Calling this function outside of contract creation WILL make your contract
     * non-compliant with the ERC721 standard.
     * For full ERC721 compliance, substituting ERC721 {Transfer} event(s) with the ERC2309
     * {ConsecutiveTransfer} event is only permissible during contract creation.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - `quantity` must be greater than 0.
     *
     * Emits a {ConsecutiveTransfer} event.
     */
    function _mintERC2309(address to, uint256 quantity) internal virtual {
        uint256 startTokenId = _currentIndex;
        if (to == address(0)) revert MintToZeroAddress();
        if (quantity == 0) revert MintZeroQuantity();
        if (quantity > _MAX_MINT_ERC2309_QUANTITY_LIMIT) revert MintERC2309QuantityExceedsLimit();

        _beforeTokenTransfers(address(0), to, startTokenId, quantity);

        // Overflows are unrealistic due to the above check for `quantity` to be below the limit.
        unchecked {
            // Updates:
            // - `balance += quantity`.
            // - `numberMinted += quantity`.
            //
            // We can directly add to the `balance` and `numberMinted`.
            _packedAddressData[to] += quantity * ((1 << _BITPOS_NUMBER_MINTED) | 1);

            // Updates:
            // - `address` to the owner.
            // - `startTimestamp` to the timestamp of minting.
            // - `burned` to `false`.
            // - `nextInitialized` to `quantity == 1`.
            _packedOwnerships[startTokenId] = _packOwnershipData(
                to,
                _nextInitializedFlag(quantity) | _nextExtraData(address(0), to, 0)
            );

            emit ConsecutiveTransfer(startTokenId, startTokenId + quantity - 1, address(0), to);

            _currentIndex = startTokenId + quantity;
        }
        _afterTokenTransfers(address(0), to, startTokenId, quantity);
    }

    /**
     * @dev Safely mints `quantity` tokens and transfers them to `to`.
     *
     * Requirements:
     *
     * - If `to` refers to a smart contract, it must implement
     * {IERC721Receiver-onERC721Received}, which is called for each safe transfer.
     * - `quantity` must be greater than 0.
     *
     * See {_mint}.
     *
     * Emits a {Transfer} event for each mint.
     */
    function _safeMint(
        address to,
        uint256 quantity,
        bytes memory _data
    ) internal virtual {
        _mint(to, quantity);

        unchecked {
            if (to.code.length != 0) {
                uint256 end = _currentIndex;
                uint256 index = end - quantity;
                do {
                    if (!_checkContractOnERC721Received(address(0), to, index++, _data)) {
                        revert TransferToNonERC721ReceiverImplementer();
                    }
                } while (index < end);
                // Reentrancy protection.
                if (_currentIndex != end) revert();
            }
        }
    }

    /**
     * @dev Equivalent to `_safeMint(to, quantity, '')`.
     */
    function _safeMint(address to, uint256 quantity) internal virtual {
        _safeMint(to, quantity, '');
    }

    // =============================================================
    //                        BURN OPERATIONS
    // =============================================================

    /**
     * @dev Equivalent to `_burn(tokenId, false)`.
     */
    function _burn(uint256 tokenId) internal virtual {
        _burn(tokenId, false);
    }

    /**
     * @dev Destroys `tokenId`.
     * The approval is cleared when the token is burned.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     *
     * Emits a {Transfer} event.
     */
    function _burn(uint256 tokenId, bool approvalCheck) internal virtual {
        uint256 prevOwnershipPacked = _packedOwnershipOf(tokenId);

        address from = address(uint160(prevOwnershipPacked));

        (uint256 approvedAddressSlot, address approvedAddress) = _getApprovedSlotAndAddress(tokenId);

        if (approvalCheck) {
            // The nested ifs save around 20+ gas over a compound boolean condition.
            if (!_isSenderApprovedOrOwner(approvedAddress, from, _msgSenderERC721A()))
                if (!isApprovedForAll(from, _msgSenderERC721A())) revert TransferCallerNotOwnerNorApproved();
        }

        _beforeTokenTransfers(from, address(0), tokenId, 1);

        // Clear approvals from the previous owner.
        assembly {
            if approvedAddress {
                // This is equivalent to `delete _tokenApprovals[tokenId]`.
                sstore(approvedAddressSlot, 0)
            }
        }

        // Underflow of the sender's balance is impossible because we check for
        // ownership above and the recipient's balance can't realistically overflow.
        // Counter overflow is incredibly unrealistic as `tokenId` would have to be 2**256.
        unchecked {
            // Updates:
            // - `balance -= 1`.
            // - `numberBurned += 1`.
            //
            // We can directly decrement the balance, and increment the number burned.
            // This is equivalent to `packed -= 1; packed += 1 << _BITPOS_NUMBER_BURNED;`.
            _packedAddressData[from] += (1 << _BITPOS_NUMBER_BURNED) - 1;

            // Updates:
            // - `address` to the last owner.
            // - `startTimestamp` to the timestamp of burning.
            // - `burned` to `true`.
            // - `nextInitialized` to `true`.
            _packedOwnerships[tokenId] = _packOwnershipData(
                from,
                (_BITMASK_BURNED | _BITMASK_NEXT_INITIALIZED) | _nextExtraData(from, address(0), prevOwnershipPacked)
            );

            // If the next slot may not have been initialized (i.e. `nextInitialized == false`) .
            if (prevOwnershipPacked & _BITMASK_NEXT_INITIALIZED == 0) {
                uint256 nextTokenId = tokenId + 1;
                // If the next slot's address is zero and not burned (i.e. packed value is zero).
                if (_packedOwnerships[nextTokenId] == 0) {
                    // If the next slot is within bounds.
                    if (nextTokenId != _currentIndex) {
                        // Initialize the next slot to maintain correctness for `ownerOf(tokenId + 1)`.
                        _packedOwnerships[nextTokenId] = prevOwnershipPacked;
                    }
                }
            }
        }

        emit Transfer(from, address(0), tokenId);
        _afterTokenTransfers(from, address(0), tokenId, 1);

        // Overflow not possible, as _burnCounter cannot be exceed _currentIndex times.
        unchecked {
            _burnCounter++;
        }
    }

    // =============================================================
    //                     EXTRA DATA OPERATIONS
    // =============================================================

    /**
     * @dev Directly sets the extra data for the ownership data `index`.
     */
    function _setExtraDataAt(uint256 index, uint24 extraData) internal virtual {
        uint256 packed = _packedOwnerships[index];
        if (packed == 0) revert OwnershipNotInitializedForExtraData();
        uint256 extraDataCasted;
        // Cast `extraData` with assembly to avoid redundant masking.
        assembly {
            extraDataCasted := extraData
        }
        packed = (packed & _BITMASK_EXTRA_DATA_COMPLEMENT) | (extraDataCasted << _BITPOS_EXTRA_DATA);
        _packedOwnerships[index] = packed;
    }

    /**
     * @dev Called during each token transfer to set the 24bit `extraData` field.
     * Intended to be overridden by the cosumer contract.
     *
     * `previousExtraData` - the value of `extraData` before transfer.
     *
     * Calling conditions:
     *
     * - When `from` and `to` are both non-zero, `from`'s `tokenId` will be
     * transferred to `to`.
     * - When `from` is zero, `tokenId` will be minted for `to`.
     * - When `to` is zero, `tokenId` will be burned by `from`.
     * - `from` and `to` are never both zero.
     */
    function _extraData(
        address from,
        address to,
        uint24 previousExtraData
    ) internal view virtual returns (uint24) {}

    /**
     * @dev Returns the next extra data for the packed ownership data.
     * The returned result is shifted into position.
     */
    function _nextExtraData(
        address from,
        address to,
        uint256 prevOwnershipPacked
    ) private view returns (uint256) {
        uint24 extraData = uint24(prevOwnershipPacked >> _BITPOS_EXTRA_DATA);
        return uint256(_extraData(from, to, extraData)) << _BITPOS_EXTRA_DATA;
    }

    // =============================================================
    //                       OTHER OPERATIONS
    // =============================================================

    /**
     * @dev Returns the message sender (defaults to `msg.sender`).
     *
     * If you are writing GSN compatible contracts, you need to override this function.
     */
    function _msgSenderERC721A() internal view virtual returns (address) {
        return msg.sender;
    }

    /**
     * @dev Converts a uint256 to its ASCII string decimal representation.
     */
    function _toString(uint256 value) internal pure virtual returns (string memory str) {
        assembly {
            // The maximum value of a uint256 contains 78 digits (1 byte per digit), but
            // we allocate 0xa0 bytes to keep the free memory pointer 32-byte word aligned.
            // We will need 1 word for the trailing zeros padding, 1 word for the length,
            // and 3 words for a maximum of 78 digits. Total: 5 * 0x20 = 0xa0.
            let m := add(mload(0x40), 0xa0)
            // Update the free memory pointer to allocate.
            mstore(0x40, m)
            // Assign the `str` to the end.
            str := sub(m, 0x20)
            // Zeroize the slot after the string.
            mstore(str, 0)

            // Cache the end of the memory to calculate the length later.
            let end := str

            // We write the string from rightmost digit to leftmost digit.
            // The following is essentially a do-while loop that also handles the zero case.
            // prettier-ignore
            for { let temp := value } 1 {} {
                str := sub(str, 1)
                // Write the character to the pointer.
                // The ASCII index of the '0' character is 48.
                mstore8(str, add(48, mod(temp, 10)))
                // Keep dividing `temp` until zero.
                temp := div(temp, 10)
                // prettier-ignore
                if iszero(temp) { break }
            }

            let length := sub(end, str)
            // Move the pointer 32 bytes leftwards to make room for the length.
            str := sub(str, 0x20)
            // Store the length.
            mstore(str, length)
        }
    }
}

// File: erc721a/contracts/extensions/IERC721AQueryable.sol



// SPDX-License-Identifier: MIT
// ERC721A Contracts v4.2.3
// Creator: Chiru Labs

pragma solidity ^0.8.4;

import '../IERC721A.sol';

/**
 * @dev Interface of ERC721AQueryable.
 */
interface IERC721AQueryable is IERC721A {
    /**
     * Invalid query range (`start` >= `stop`).
     */
    error InvalidQueryRange();

    /**
     * @dev Returns the `TokenOwnership` struct at `tokenId` without reverting.
     *
     * If the `tokenId` is out of bounds:
     *
     * - `addr = address(0)`
     * - `startTimestamp = 0`
     * - `burned = false`
     * - `extraData = 0`
     *
     * If the `tokenId` is burned:
     *
     * - `addr = <Address of owner before token was burned>`
     * - `startTimestamp = <Timestamp when token was burned>`
     * - `burned = true`
     * - `extraData = <Extra data when token was burned>`
     *
     * Otherwise:
     *
     * - `addr = <Address of owner>`
     * - `startTimestamp = <Timestamp of start of ownership>`
     * - `burned = false`
     * - `extraData = <Extra data at start of ownership>`
     */
    function explicitOwnershipOf(uint256 tokenId) external view returns (TokenOwnership memory);

    /**
     * @dev Returns an array of `TokenOwnership` structs at `tokenIds` in order.
     * See {ERC721AQueryable-explicitOwnershipOf}
     */
    function explicitOwnershipsOf(uint256[] memory tokenIds) external view returns (TokenOwnership[] memory);

    /**
     * @dev Returns an array of token IDs owned by `owner`,
     * in the range [`start`, `stop`)
     * (i.e. `start <= tokenId < stop`).
     *
     * This function allows for tokens to be queried if the collection
     * grows too big for a single call of {ERC721AQueryable-tokensOfOwner}.
     *
     * Requirements:
     *
     * - `start < stop`
     */
    function tokensOfOwnerIn(
        address owner,
        uint256 start,
        uint256 stop
    ) external view returns (uint256[] memory);

    /**
     * @dev Returns an array of token IDs owned by `owner`.
     *
     * This function scans the ownership mapping and is O(`totalSupply`) in complexity.
     * It is meant to be called off-chain.
     *
     * See {ERC721AQueryable-tokensOfOwnerIn} for splitting the scan into
     * multiple smaller scans if the collection is large enough to cause
     * an out-of-gas error (10K collections should be fine).
     */
    function tokensOfOwner(address owner) external view returns (uint256[] memory);
}

// File: erc721a/contracts/extensions/IERC721ABurnable.sol



// SPDX-License-Identifier: MIT
// ERC721A Contracts v4.2.3
// Creator: Chiru Labs

pragma solidity ^0.8.4;

import '../IERC721A.sol';

/**
 * @dev Interface of ERC721ABurnable.
 */
interface IERC721ABurnable is IERC721A {
    /**
     * @dev Burns `tokenId`. See {ERC721A-_burn}.
     *
     * Requirements:
     *
     * - The caller must own `tokenId` or be an approved operator.
     */
    function burn(uint256 tokenId) external;
}

// File: erc721a/contracts/IERC721A.sol



// SPDX-License-Identifier: MIT
// ERC721A Contracts v4.2.3
// Creator: Chiru Labs

pragma solidity ^0.8.4;

/**
 * @dev Interface of ERC721A.
 */
interface IERC721A {
    /**
     * The caller must own the token or be an approved operator.
     */
    error ApprovalCallerNotOwnerNorApproved();

    /**
     * The token does not exist.
     */
    error ApprovalQueryForNonexistentToken();

    /**
     * Cannot query the balance for the zero address.
     */
    error BalanceQueryForZeroAddress();

    /**
     * Cannot mint to the zero address.
     */
    error MintToZeroAddress();

    /**
     * The quantity of tokens minted must be more than zero.
     */
    error MintZeroQuantity();

    /**
     * The token does not exist.
     */
    error OwnerQueryForNonexistentToken();

    /**
     * The caller must own the token or be an approved operator.
     */
    error TransferCallerNotOwnerNorApproved();

    /**
     * The token must be owned by `from`.
     */
    error TransferFromIncorrectOwner();

    /**
     * Cannot safely transfer to a contract that does not implement the
     * ERC721Receiver interface.
     */
    error TransferToNonERC721ReceiverImplementer();

    /**
     * Cannot transfer to the zero address.
     */
    error TransferToZeroAddress();

    /**
     * The token does not exist.
     */
    error URIQueryForNonexistentToken();

    /**
     * The `quantity` minted with ERC2309 exceeds the safety limit.
     */
    error MintERC2309QuantityExceedsLimit();

    /**
     * The `extraData` cannot be set on an unintialized ownership slot.
     */
    error OwnershipNotInitializedForExtraData();

    // =============================================================
    //                            STRUCTS
    // =============================================================

    struct TokenOwnership {
        // The address of the owner.
        address addr;
        // Stores the start time of ownership with minimal overhead for tokenomics.
        uint64 startTimestamp;
        // Whether the token has been burned.
        bool burned;
        // Arbitrary data similar to `startTimestamp` that can be set via {_extraData}.
        uint24 extraData;
    }

    // =============================================================
    //                         TOKEN COUNTERS
    // =============================================================

    /**
     * @dev Returns the total number of tokens in existence.
     * Burned tokens will reduce the count.
     * To get the total number of tokens minted, please see {_totalMinted}.
     */
    function totalSupply() external view returns (uint256);

    // =============================================================
    //                            IERC165
    // =============================================================

    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * [EIP section](https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified)
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);

    // =============================================================
    //                            IERC721
    // =============================================================

    /**
     * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
     */
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
     */
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables or disables
     * (`approved`) `operator` to manage all of its assets.
     */
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    /**
     * @dev Returns the number of tokens in `owner`'s account.
     */
    function balanceOf(address owner) external view returns (uint256 balance);

    /**
     * @dev Returns the owner of the `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function ownerOf(uint256 tokenId) external view returns (address owner);

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`,
     * checking first that contract recipients are aware of the ERC721 protocol
     * to prevent tokens from being forever locked.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must be have been allowed to move
     * this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement
     * {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        bytes calldata data
    ) external payable;

    /**
     * @dev Equivalent to `safeTransferFrom(from, to, tokenId, '')`.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId
    ) external payable;

    /**
     * @dev Transfers `tokenId` from `from` to `to`.
     *
     * WARNING: Usage of this method is discouraged, use {safeTransferFrom}
     * whenever possible.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token
     * by either {approve} or {setApprovalForAll}.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) external payable;

    /**
     * @dev Gives permission to `to` to transfer `tokenId` token to another account.
     * The approval is cleared when the token is transferred.
     *
     * Only a single account can be approved at a time, so approving the
     * zero address clears previous approvals.
     *
     * Requirements:
     *
     * - The caller must own the token or be an approved operator.
     * - `tokenId` must exist.
     *
     * Emits an {Approval} event.
     */
    function approve(address to, uint256 tokenId) external payable;

    /**
     * @dev Approve or remove `operator` as an operator for the caller.
     * Operators can call {transferFrom} or {safeTransferFrom}
     * for any token owned by the caller.
     *
     * Requirements:
     *
     * - The `operator` cannot be the caller.
     *
     * Emits an {ApprovalForAll} event.
     */
    function setApprovalForAll(address operator, bool _approved) external;

    /**
     * @dev Returns the account approved for `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function getApproved(uint256 tokenId) external view returns (address operator);

    /**
     * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
     *
     * See {setApprovalForAll}.
     */
    function isApprovedForAll(address owner, address operator) external view returns (bool);

    // =============================================================
    //                        IERC721Metadata
    // =============================================================

    /**
     * @dev Returns the token collection name.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the token collection symbol.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
     */
    function tokenURI(uint256 tokenId) external view returns (string memory);

    // =============================================================
    //                           IERC2309
    // =============================================================

    /**
     * @dev Emitted when tokens in `fromTokenId` to `toTokenId`
     * (inclusive) is transferred from `from` to `to`, as defined in the
     * [ERC2309](https://eips.ethereum.org/EIPS/eip-2309) standard.
     *
     * See {_mintERC2309} for more details.
     */
    event ConsecutiveTransfer(uint256 indexed fromTokenId, uint256 toTokenId, address indexed from, address indexed to);
}

// File: @openzeppelin/contracts/utils/Base64.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Base64.sol)

pragma solidity ^0.8.20;

/**
 * @dev Provides a set of functions to operate with Base64 strings.
 */
library Base64 {
    /**
     * @dev Base64 Encoding/Decoding Table
     */
    string internal constant _TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

    /**
     * @dev Converts a `bytes` to its Bytes64 `string` representation.
     */
    function encode(bytes memory data) internal pure returns (string memory) {
        /**
         * Inspired by Brecht Devos (Brechtpd) implementation - MIT licence
         * https://github.com/Brechtpd/base64/blob/e78d9fd951e7b0977ddca77d92dc85183770daf4/base64.sol
         */
        if (data.length == 0) return "";

        // Loads the table into memory
        string memory table = _TABLE;

        // Encoding takes 3 bytes chunks of binary data from `bytes` data parameter
        // and split into 4 numbers of 6 bits.
        // The final Base64 length should be `bytes` data length multiplied by 4/3 rounded up
        // - `data.length + 2`  -> Round up
        // - `/ 3`              -> Number of 3-bytes chunks
        // - `4 *`              -> 4 characters for each chunk
        string memory result = new string(4 * ((data.length + 2) / 3));

        /// @solidity memory-safe-assembly
        assembly {
            // Prepare the lookup table (skip the first "length" byte)
            let tablePtr := add(table, 1)

            // Prepare result pointer, jump over length
            let resultPtr := add(result, 32)

            // Run over the input, 3 bytes at a time
            for {
                let dataPtr := data
                let endPtr := add(data, mload(data))
            } lt(dataPtr, endPtr) {

            } {
                // Advance 3 bytes
                dataPtr := add(dataPtr, 3)
                let input := mload(dataPtr)

                // To write each character, shift the 3 bytes (18 bits) chunk
                // 4 times in blocks of 6 bits for each character (18, 12, 6, 0)
                // and apply logical AND with 0x3F which is the number of
                // the previous character in the ASCII table prior to the Base64 Table
                // The result is then added to the table to get the character to write,
                // and finally write it in the result pointer but with a left shift
                // of 256 (1 byte) - 8 (1 ASCII char) = 248 bits

                mstore8(resultPtr, mload(add(tablePtr, and(shr(18, input), 0x3F))))
                resultPtr := add(resultPtr, 1) // Advance

                mstore8(resultPtr, mload(add(tablePtr, and(shr(12, input), 0x3F))))
                resultPtr := add(resultPtr, 1) // Advance

                mstore8(resultPtr, mload(add(tablePtr, and(shr(6, input), 0x3F))))
                resultPtr := add(resultPtr, 1) // Advance

                mstore8(resultPtr, mload(add(tablePtr, and(input, 0x3F))))
                resultPtr := add(resultPtr, 1) // Advance
            }

            // When data `bytes` is not exactly 3 bytes long
            // it is padded with `=` characters at the end
            switch mod(mload(data), 3)
            case 1 {
                mstore8(sub(resultPtr, 1), 0x3d)
                mstore8(sub(resultPtr, 2), 0x3d)
            }
            case 2 {
                mstore8(sub(resultPtr, 1), 0x3d)
            }
        }

        return result;
    }
}

// File: @openzeppelin/contracts/utils/introspection/IERC165.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

// File: @openzeppelin/contracts/utils/Context.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)

pragma solidity ^0.8.20;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}