{"id":114,"date":"2026-10-10T00:17:23","date_gmt":"2026-10-10T00:17:23","guid":{"rendered":"https:\/\/apk.produsenpagarbrc.com\/?p=114"},"modified":"2026-10-10T00:17:23","modified_gmt":"2026-10-10T00:17:23","slug":"mochi-moonbound","status":"publish","type":"post","link":"https:\/\/play.produsenpagarbrc.com\/?p=114","title":{"rendered":"Mochi Moonbound"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\"><strong>Mochi Moonbound<\/strong> is a cozy tile-matching puzzle game set inside a moon rabbit workshop floating above the night clouds. Under soft creamy moonlight, tiny rabbits arrange magical tiles filled with mochi cakes, wooden mallets, moon lanterns, soft clouds, tiny toolboxes, crescent charms, and handmade lunar treasures.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Your task is to find matching open tiles and clear them from the board. But not every tile can be selected right away. Some are covered, some are blocked, and some must wait until the workshop table opens a path. Each level becomes a quiet test of focus, memory, and careful timing.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/play.produsenpagarbrc.com\/wp-content\/uploads\/2026\/10\/Mochi-Moonbound-1024x768.png\" alt=\"\" class=\"wp-image-115\"\/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">A Moonlit Tile Puzzle With Gentle Strategy<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Mochi Moonbound<\/strong> keeps the familiar satisfaction of mahjong-style tile matching, then wraps it in a soft fantasy world full of moon rabbits and handmade workshop magic. You will match identical tiles, reveal hidden pieces, use hints when needed, and shuffle the board when the night shelf feels too tangled.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The rules are simple: only open tiles can be matched. A tile must be free from being covered and must have space on at least one side. This makes every move feel meaningful, because clearing one pair may unlock another pair waiting beneath the moonlight.<\/p>\n\n\n\n<style>\n  .clm01-shell,\n  .clm01-shell *{\n    box-sizing:border-box;\n    user-select:none;\n    -webkit-user-select:none;\n    -webkit-tap-highlight-color:transparent;\n  }\n\n  .clm01-shell{\n    width:100%;\n    max-width:980px;\n    margin:18px auto;\n    position:relative;\n    font-family:system-ui,-apple-system,BlinkMacSystemFont,\"Segoe UI\",Arial,sans-serif;\n    color:#fff1c9;\n  }\n\n  .clm01-stage{\n    --edge:clamp(10px,1.7vw,18px);\n    --icon:clamp(40px,4.7vw,50px);\n    width:100%;\n    aspect-ratio:16\/9;\n    position:relative;\n    overflow:hidden;\n    border-radius:24px;\n    background:\n      radial-gradient(circle at 82% 10%,rgba(255,241,201,.48),transparent 21%),\n      radial-gradient(circle at 18% 18%,rgba(150,121,202,.26),transparent 28%),\n      radial-gradient(circle at 76% 84%,rgba(208,168,91,.18),transparent 32%),\n      linear-gradient(160deg,#171226 0%,#24163d 32%,#3a2457 62%,#6b4d6f 100%);\n    border:1.4px solid rgba(255,241,201,.32);\n    box-shadow:\n      0 24px 58px rgba(24,18,42,.34),\n      inset 0 0 0 1px rgba(255,255,255,.10);\n    touch-action:none;\n  }\n\n  .clm01-stage:before{\n    content:\"\";\n    position:absolute;\n    inset:0;\n    z-index:0;\n    pointer-events:none;\n    background:\n      radial-gradient(circle at 84% 12%,rgba(255,248,223,.24),transparent 16%),\n      radial-gradient(circle at 18% 78%,rgba(189,168,230,.10),transparent 28%),\n      repeating-linear-gradient(90deg,rgba(255,241,201,.032) 0 1px,transparent 1px 44px),\n      repeating-linear-gradient(0deg,rgba(255,241,201,.020) 0 1px,transparent 1px 38px);\n    opacity:.95;\n  }\n\n  .clm01-stage canvas{\n    position:absolute;\n    inset:0;\n    width:100%;\n    height:100%;\n    display:block;\n    z-index:1;\n  }\n\n  .clm01-shell:fullscreen{\n    width:100vw;\n    height:100vh;\n    max-width:none;\n    margin:0;\n    padding:0;\n    background:#171226;\n    display:flex;\n    align-items:center;\n    justify-content:center;\n  }\n\n  .clm01-shell:fullscreen .clm01-stage{\n    width:min(100vw,calc(100vh * 16 \/ 9));\n    height:min(100vh,calc(100vw * 9 \/ 16));\n    border-radius:0;\n  }\n\n  .clm01-shell.clm01-portrait-full:fullscreen .clm01-stage{\n    width:min(100vh,calc(100vw * 16 \/ 9));\n    height:min(100vw,calc(100vh * 9 \/ 16));\n    transform:rotate(90deg);\n    transform-origin:center center;\n  }\n\n  .clm01-top-left,\n  .clm01-top-right,\n  .clm01-top-center,\n  .clm01-bottom{\n    position:absolute;\n    z-index:7;\n  }\n\n  .clm01-top-left{\n    top:var(--edge);\n    left:var(--edge);\n    display:flex;\n    gap:8px;\n  }\n\n  .clm01-top-right{\n    top:var(--edge);\n    right:var(--edge);\n    z-index:35;\n  }\n\n  .clm01-top-center{\n    top:var(--edge);\n    left:50%;\n    transform:translateX(-50%);\n    width:min(47%,500px);\n    min-height:var(--icon);\n    display:flex;\n    flex-direction:column;\n    align-items:center;\n    justify-content:center;\n    gap:4px;\n  }\n\n  .clm01-icon-btn{\n    width:var(--icon);\n    height:var(--icon);\n    border:1.2px solid rgba(255,241,201,.35);\n    border-radius:15px;\n    color:#fff1c9;\n    background:\n      radial-gradient(circle at 32% 22%,rgba(255,248,223,.28),transparent 34%),\n      linear-gradient(180deg,rgba(88,61,116,.78),rgba(38,24,65,.72));\n    backdrop-filter:blur(9px);\n    box-shadow:\n      0 10px 20px rgba(18,13,34,.28),\n      inset 0 1px 0 rgba(255,255,255,.16),\n      0 0 16px rgba(255,241,201,.08);\n    cursor:pointer;\n    display:grid;\n    place-items:center;\n    padding:0;\n    transition:transform .14s ease,background .14s ease,border-color .14s ease,box-shadow .14s ease;\n  }\n\n  .clm01-icon-btn:hover{\n    background:\n      radial-gradient(circle at 32% 22%,rgba(255,248,223,.36),transparent 34%),\n      linear-gradient(180deg,rgba(116,82,142,.84),rgba(54,34,84,.76));\n    border-color:rgba(255,241,201,.62);\n    box-shadow:\n      0 10px 22px rgba(18,13,34,.30),\n      inset 0 1px 0 rgba(255,255,255,.20),\n      0 0 18px rgba(255,241,201,.16);\n  }\n\n  .clm01-icon-btn:active{transform:scale(.94);}\n\n  .clm01-speaker-icon,\n  .clm01-pause-icon,\n  .clm01-full-icon{\n    position:relative;\n    width:24px;\n    height:24px;\n    display:block;\n  }\n\n  .clm01-speaker-box{\n    position:absolute;\n    left:2px;\n    top:9px;\n    width:7px;\n    height:7px;\n    background:currentColor;\n    border-radius:2px;\n  }\n\n  .clm01-speaker-cone{\n    position:absolute;\n    left:8px;\n    top:5px;\n    width:10px;\n    height:15px;\n    background:currentColor;\n    clip-path:polygon(0 32%,100% 0,100% 100%,0 68%);\n    border-radius:2px;\n  }\n\n  .clm01-wave{\n    position:absolute;\n    border:2.3px solid currentColor;\n    border-left:0;\n    border-top-color:transparent;\n    border-bottom-color:transparent;\n    border-radius:0 999px 999px 0;\n  }\n\n  .clm01-wave.w1{width:5px;height:8px;left:16px;top:8px;opacity:.9;}\n  .clm01-wave.w2{width:8px;height:13px;left:18px;top:5.5px;opacity:.75;}\n  .clm01-wave.w3{width:11px;height:18px;left:20px;top:3px;opacity:.55;}\n\n  .clm01-slash{\n    display:none;\n    position:absolute;\n    width:27px;\n    height:3px;\n    left:-1px;\n    top:11px;\n    background:#d86d6d;\n    border-radius:999px;\n    transform:rotate(-42deg);\n    box-shadow:0 0 10px rgba(216,109,109,.32);\n  }\n\n  .clm01-muted .clm01-wave{display:none;}\n  .clm01-muted .clm01-slash{display:block;}\n\n  .clm01-pause-icon:before,\n  .clm01-pause-icon:after{\n    content:\"\";\n    position:absolute;\n    top:4px;\n    width:6px;\n    height:16px;\n    background:currentColor;\n    border-radius:3px;\n  }\n\n  .clm01-pause-icon:before{left:5px;}\n  .clm01-pause-icon:after{right:5px;}\n\n  .clm01-full-icon:before,\n  .clm01-full-icon:after,\n  .clm01-full-icon span:before,\n  .clm01-full-icon span:after{\n    content:\"\";\n    position:absolute;\n    width:8px;\n    height:8px;\n    border-color:currentColor;\n    border-style:solid;\n  }\n\n  .clm01-full-icon:before{left:2px;top:2px;border-width:3px 0 0 3px;border-radius:3px 0 0 0;}\n  .clm01-full-icon:after{right:2px;top:2px;border-width:3px 3px 0 0;border-radius:0 3px 0 0;}\n  .clm01-full-icon span:before{left:2px;bottom:2px;border-width:0 0 3px 3px;border-radius:0 0 0 3px;}\n  .clm01-full-icon span:after{right:2px;bottom:2px;border-width:0 3px 3px 0;border-radius:0 0 3px 0;}\n\n  .clm01-hud{\n    width:100%;\n    display:grid;\n    grid-template-columns:repeat(5,1fr);\n    gap:4px;\n  }\n\n  .clm01-hud-item{\n    min-height:34px;\n    padding:2px 5px 3px;\n    border-radius:11px;\n    background:\n      radial-gradient(circle at 30% 14%,rgba(255,248,223,.22),transparent 36%),\n      linear-gradient(180deg,rgba(69,45,97,.82),rgba(39,25,63,.72));\n    border:1px solid rgba(255,241,201,.30);\n    box-shadow:\n      0 8px 16px rgba(18,13,34,.23),\n      inset 0 1px 0 rgba(255,255,255,.13);\n    backdrop-filter:blur(8px);\n    display:flex;\n    flex-direction:column;\n    align-items:center;\n    justify-content:center;\n    line-height:1;\n    white-space:nowrap;\n  }\n\n  .clm01-label{\n    font-size:8px;\n    letter-spacing:.04em;\n    color:rgba(255,241,201,.64);\n    font-weight:900;\n    line-height:1;\n  }\n\n  .clm01-value{\n    margin-top:1px;\n    font-size:clamp(11px,1.28vw,15px);\n    font-weight:950;\n    line-height:1;\n    color:#fff8df;\n    text-shadow:0 2px 8px rgba(0,0,0,.26);\n  }\n\n  .clm01-hud-item:nth-child(1) .clm01-value{color:#ffd978;}\n  .clm01-hud-item:nth-child(2) .clm01-value{color:#fff1c9;}\n  .clm01-hud-item:nth-child(3) .clm01-value{color:#d8c4ff;}\n  .clm01-hud-item:nth-child(4) .clm01-value{color:#f2b5c8;}\n  .clm01-hud-item:nth-child(5) .clm01-value{color:#cddfff;}\n\n  .clm01-timebar{\n    width:100%;\n    height:8px;\n    padding:1px;\n    border-radius:999px;\n    background:rgba(15,11,30,.36);\n    border:1px solid rgba(255,241,201,.24);\n    box-shadow:inset 0 1px 4px rgba(0,0,0,.26);\n    overflow:hidden;\n  }\n\n  .clm01-timefill{\n    width:100%;\n    height:100%;\n    border-radius:999px;\n    background:linear-gradient(90deg,#d8c4ff,#fff1c9,#f4c542);\n    box-shadow:0 0 14px rgba(255,241,201,.40);\n    transition:width .2s linear,background .2s ease;\n  }\n\n  .clm01-timebar.warning .clm01-timefill{\n    background:linear-gradient(90deg,#fff1c9,#f4a5a5,#d86d6d);\n  }\n\n  .clm01-time-warning .clm01-time-text{\n    animation:clm01PulseTime .72s ease-in-out infinite alternate;\n  }\n\n  @keyframes clm01PulseTime{\n    from{text-shadow:0 0 6px rgba(255,241,201,.36);}\n    to{text-shadow:0 0 18px rgba(216,109,109,.78);}\n  }\n\n  .clm01-bottom{\n    left:50%;\n    bottom:clamp(12px,2.2vw,22px);\n    transform:translateX(-50%);\n    display:flex;\n    gap:clamp(12px,2vw,22px);\n  }\n\n  .clm01-action{\n    min-width:clamp(100px,13vw,150px);\n    height:clamp(40px,5vw,54px);\n    border:1px solid rgba(255,241,201,.30);\n    border-radius:18px;\n    background:\n      radial-gradient(circle at 30% 20%,rgba(255,248,223,.24),transparent 32%),\n      linear-gradient(180deg,#fff1c9,#d9ad61);\n    color:#3a2457;\n    font-size:clamp(13px,1.55vw,17px);\n    font-weight:900;\n    letter-spacing:.04em;\n    cursor:pointer;\n    box-shadow:\n      0 12px 24px rgba(18,13,34,.26),\n      inset 0 1px 0 rgba(255,255,255,.34);\n    backdrop-filter:blur(8px);\n    transition:transform .14s ease,filter .14s ease,box-shadow .14s ease;\n  }\n\n  .clm01-action:hover{\n    filter:brightness(1.05);\n    box-shadow:\n      0 14px 26px rgba(18,13,34,.28),\n      0 0 18px rgba(255,241,201,.18),\n      inset 0 1px 0 rgba(255,255,255,.38);\n  }\n\n  .clm01-action:active{transform:scale(.96);}\n\n  .clm01-bottom .clm01-action:nth-child(2){\n    background:\n      radial-gradient(circle at 30% 20%,rgba(255,248,223,.20),transparent 32%),\n      linear-gradient(180deg,#9d79d6,#594076);\n    color:#fff8df;\n  }\n\n  .clm01-overlay{\n    position:absolute;\n    inset:0;\n    z-index:20;\n    display:none;\n    align-items:center;\n    justify-content:center;\n    background:rgba(20,14,38,.44);\n    backdrop-filter:blur(8px);\n    padding:20px;\n  }\n\n  .clm01-overlay.show{display:flex;}\n\n  .clm01-card{\n    width:min(360px,68%);\n    border-radius:24px;\n    padding:clamp(16px,2.1vw,24px);\n    text-align:center;\n    background:\n      radial-gradient(circle at 22% 12%,rgba(255,255,255,.42),transparent 32%),\n      radial-gradient(circle at 78% 0%,rgba(255,241,201,.20),transparent 34%),\n      linear-gradient(145deg,rgba(255,248,223,.98),rgba(230,215,255,.94));\n    border:1px solid rgba(255,248,223,.62);\n    box-shadow:\n      0 24px 58px rgba(18,13,34,.34),\n      0 0 28px rgba(255,241,201,.18),\n      inset 0 0 0 1px rgba(255,255,255,.20);\n    color:#35244a;\n  }\n\n  .clm01-card h2{\n    margin:0 0 11px;\n    font-size:clamp(16px,2.15vw,23px);\n    letter-spacing:.055em;\n    color:#2a1d40;\n  }\n\n  .clm01-card p{\n    margin:11px auto 0;\n    color:rgba(53,36,74,.78);\n    font-size:clamp(11px,1.25vw,13px);\n    line-height:1.35;\n    max-width:285px;\n    font-weight:700;\n  }\n\n  .clm01-menu{\n    display:grid;\n    gap:8px;\n    justify-items:center;\n  }\n\n  .clm01-menu-btn{\n    width:min(205px,88%);\n    min-height:38px;\n    border:0;\n    border-radius:14px;\n    background:linear-gradient(180deg,#fff1c9,#d9ad61);\n    color:#332345;\n    font-weight:950;\n    letter-spacing:.04em;\n    font-size:clamp(11px,1.32vw,14px);\n    cursor:pointer;\n    box-shadow:\n      0 10px 20px rgba(18,13,34,.24),\n      inset 0 1px 0 rgba(255,255,255,.34);\n    transition:transform .14s ease,filter .14s ease;\n  }\n\n  .clm01-menu-btn.secondary{\n    background:linear-gradient(180deg,#d8c4ff,#8a6ac0);\n    color:#fff8df;\n  }\n\n  .clm01-menu-btn.ghost{\n    background:rgba(255,248,223,.54);\n    color:#332345;\n    border:1px solid rgba(53,36,74,.20);\n  }\n\n  .clm01-menu-btn:active{transform:scale(.96);}\n\n  @media (max-width:640px){\n    .clm01-shell{max-width:100%;margin:10px auto;}\n    .clm01-stage{--icon:clamp(38px,7vw,46px);border-radius:16px;}\n    .clm01-top-left{gap:7px;}\n    .clm01-top-center{width:min(52%,400px);gap:3px;min-height:var(--icon);}\n    .clm01-hud{gap:3px;}\n    .clm01-hud-item{min-height:28px;padding:1px 2px 2px;border-radius:9px;}\n    .clm01-label{font-size:6.2px;letter-spacing:.025em;}\n    .clm01-value{font-size:10.5px;margin-top:1px;}\n    .clm01-timebar{height:6px;padding:1px;}\n    .clm01-icon-btn{border-radius:14px;}\n    .clm01-card{width:min(330px,72%);border-radius:18px;padding:13px 14px 15px;}\n    .clm01-card h2{margin-bottom:9px;font-size:15px;}\n    .clm01-card p{margin-top:9px;font-size:10.5px;line-height:1.3;max-width:240px;}\n    .clm01-menu{gap:7px;}\n    .clm01-menu-btn{width:min(185px,88%);min-height:34px;border-radius:12px;font-size:11px;}\n    .clm01-bottom{bottom:10px;}\n  }\n.clm01-shell.clm01-fallback-full{\n position:fixed!important;inset:0!important;z-index:2147483647!important;\n width:100vw!important;height:100vh!important;max-width:none!important;\n margin:0!important;background:#171226;display:flex;align-items:center;justify-content:center;\n}\n.clm01-shell.clm01-fallback-full .clm01-stage{\n width:min(100vw,calc(100vh * 16 \/ 9));height:min(100vh,calc(100vw * 9 \/ 16));border-radius:0;\n}\n.clm01-shell.clm01-fallback-full.clm01-portrait-full .clm01-stage{\n width:min(100vh,calc(100vw * 16 \/ 9));height:min(100vw,calc(100vh * 9 \/ 16));\n transform:rotate(90deg);transform-origin:center center;\n}\n<\/style>\n\n<div class=\"clm01-shell\" id=\"clm01Shell\">\n  <div class=\"clm01-stage\" id=\"clm01Stage\">\n    <canvas id=\"clm01Canvas\"><\/canvas>\n\n    <div class=\"clm01-top-left\">\n      <button class=\"clm01-icon-btn\" id=\"clm01PauseBtn\" type=\"button\" aria-label=\"Pause\">\n        <span class=\"clm01-pause-icon\"><\/span>\n      <\/button>\n      <button class=\"clm01-icon-btn\" id=\"clm01SoundBtn\" type=\"button\" aria-label=\"Sound\">\n        <span class=\"clm01-speaker-icon\">\n          <span class=\"clm01-speaker-box\"><\/span>\n          <span class=\"clm01-speaker-cone\"><\/span>\n          <span class=\"clm01-wave w1\"><\/span>\n          <span class=\"clm01-wave w2\"><\/span>\n          <span class=\"clm01-wave w3\"><\/span>\n          <span class=\"clm01-slash\"><\/span>\n        <\/span>\n      <\/button>\n    <\/div>\n\n    <div class=\"clm01-top-center\">\n      <div class=\"clm01-hud\" id=\"clm01Hud\">\n        <div class=\"clm01-hud-item\"><span class=\"clm01-label\">LEVEL<\/span><span class=\"clm01-value\" id=\"clm01Level\">1<\/span><\/div>\n        <div class=\"clm01-hud-item\"><span class=\"clm01-label\">SCORE<\/span><span class=\"clm01-value\" id=\"clm01Score\">0<\/span><\/div>\n        <div class=\"clm01-hud-item\"><span class=\"clm01-label\">BEST<\/span><span class=\"clm01-value\" id=\"clm01Best\">0<\/span><\/div>\n        <div class=\"clm01-hud-item\"><span class=\"clm01-label\">MOVES<\/span><span class=\"clm01-value\" id=\"clm01Moves\">0<\/span><\/div>\n        <div class=\"clm01-hud-item\"><span class=\"clm01-label\">TIME<\/span><span class=\"clm01-value clm01-time-text\" id=\"clm01Time\">00:00<\/span><\/div>\n      <\/div>\n      <div class=\"clm01-timebar\" id=\"clm01TimeBar\">\n        <div class=\"clm01-timefill\" id=\"clm01TimeFill\"><\/div>\n      <\/div>\n    <\/div>\n\n    <div class=\"clm01-top-right\">\n      <button class=\"clm01-icon-btn\" id=\"clm01FullBtn\" type=\"button\" aria-label=\"Fullscreen\">\n        <span class=\"clm01-full-icon\"><span><\/span><\/span>\n      <\/button>\n    <\/div>\n\n    <div class=\"clm01-bottom\">\n      <button class=\"clm01-action\" id=\"clm01HintBtn\" type=\"button\">HINT<\/button>\n      <button class=\"clm01-action\" id=\"clm01ShuffleBtn\" type=\"button\">SHUFFLE TILES<\/button>\n    <\/div>\n\n    <div class=\"clm01-overlay\" id=\"clm01Overlay\"><\/div>\n  <\/div>\n<\/div>\n\n<script>\n\n(function () {\n    \"use strict\";\n    function initializeMoonboundWP() {\n        const rootMarker = document.getElementById('clm01Shell');\n        if (!rootMarker || rootMarker.dataset.moonboundWpReady === '1')\n            return;\n        rootMarker.dataset.moonboundWpReady = '1';\n        const W = 1280;\n        const H = 720;\n        const DPR_MAX = 2.75;\n        const FIXED_LAYERS = 3;\n        const shell = document.getElementById(\"clm01Shell\");\n        if ((_wpLeft1 => _wpLeft1 ? !shell.hasAttribute('tabindex') : _wpLeft1)(shell))\n            shell.setAttribute('tabindex', '0');\n        const canvas = document.getElementById(\"clm01Canvas\");\n        const ctx = canvas.getContext(\"2d\");\n        const overlay = document.getElementById(\"clm01Overlay\");\n        const soundBtn = document.getElementById(\"clm01SoundBtn\");\n        const pauseBtn = document.getElementById(\"clm01PauseBtn\");\n        const fullBtn = document.getElementById(\"clm01FullBtn\");\n        const hintBtn = document.getElementById(\"clm01HintBtn\");\n        const shuffleBtn = document.getElementById(\"clm01ShuffleBtn\");\n        const hud = document.getElementById(\"clm01Hud\");\n        const timeBar = document.getElementById(\"clm01TimeBar\");\n        const timeFill = document.getElementById(\"clm01TimeFill\");\n        const levelEl = document.getElementById(\"clm01Level\");\n        const scoreEl = document.getElementById(\"clm01Score\");\n        const bestEl = document.getElementById(\"clm01Best\");\n        const movesEl = document.getElementById(\"clm01Moves\");\n        const timeEl = document.getElementById(\"clm01Time\");\n        const KEY_BEST = \"mochi_moonbound_tiles_best_v1\";\n        const KEY_SAVE = \"mochi_moonbound_tiles_progress_v1\";\n        const KEY_MUTE = \"mochi_moonbound_tiles_muted_v1\";\n        const SYMBOLS = [\n            \"moonrabbit\", \"mochi\", \"mallet\", \"moonlantern\", \"nightcloud\", \"toolbox\", \"mooncharm\", \"woodplane\", \"ricebowl\",\n            \"crescent\", \"starcake\", \"woodspoon\", \"cloudwrench\", \"moonstamp\", \"bunnybell\", \"workbench\", \"mochipestle\", \"nightthread\"\n        ];\n        const LEVELS = [\n            24, 26, 28, 30, 32, 34, 36, 38, 40, 42,\n            44, 46, 48, 50, 52, 54, 56, 58, 60, 62,\n            64, 66, 68, 70, 72, 74, 76, 78, 80, 82,\n            84, 86, 88, 90, 92, 94, 96, 98, 100, 102,\n            104, 106, 108, 110, 112, 114, 116, 118, 120, 122\n        ];\n        function storageGet(key) {\n            try {\n                return window.localStorage.getItem(key);\n            }\n            catch (e) {\n                return null;\n            }\n        }\n        function storageSet(key, value) {\n            try {\n                window.localStorage.setItem(key, value);\n                return true;\n            }\n            catch (e) {\n                return false;\n            }\n        }\n        function storageRemove(key) {\n            try {\n                window.localStorage.removeItem(key);\n                return true;\n            }\n            catch (e) {\n                return false;\n            }\n        }\n        const state = {\n            mode: \"menu\",\n            level: 1,\n            score: 0,\n            best: Number(storageGet(KEY_BEST)) || 0,\n            moves: 0,\n            timeLeft: 0,\n            timeLimit: 0,\n            tiles: [],\n            selectedId: null,\n            particles: [],\n            floats: [],\n            hintIds: [],\n            hintTimer: 0,\n            levelIntro: 0,\n            combo: 0,\n            comboTimer: 0,\n            usedHint: false,\n            usedShuffle: false,\n            pendingClear: false,\n            muted: storageGet(KEY_MUTE) === \"1\",\n            lastTickSecond: -1,\n            bounds: null,\n            rngSeed: Date.now()\n        };\n        let lastTime = performance.now();\n        let audioCtx = null;\n        function clamp(v, a, b) { return Math.max(a, Math.min(b, v)); }\n        function fmt(n) { return Math.floor(n).toLocaleString(\"en-US\"); }\n        function timeFormat(sec) {\n            sec = Math.max(0, Math.ceil(sec));\n            const m = Math.floor(sec \/ 60);\n            const s = sec % 60;\n            return String(m).padStart(2, \"0\") + \":\" + String(s).padStart(2, \"0\");\n        }\n        function seeded(seed) {\n            let t = seed >>> 0;\n            return function () {\n                t += 0x6D2B79F5;\n                let r = Math.imul(t ^ (t >>> 15), 1 | t);\n                r ^= r + Math.imul(r ^ (r >>> 7), 61 | r);\n                return ((r ^ (r >>> 14)) >>> 0) \/ 4294967296;\n            };\n        }\n        function shuffleArray(arr, rng) {\n            for (let i = arr.length - 1; i > 0; i--) {\n                const j = Math.floor(rng() * (i + 1));\n                const t = arr[i];\n                arr[i] = arr[j];\n                arr[j] = t;\n            }\n            return arr;\n        }\n        function resizeCanvas() {\n            const dpr = Math.min(window.devicePixelRatio || 1, window.innerWidth < 700 ? 1.5 : 2);\n            canvas.width = Math.round(W * dpr);\n            canvas.height = Math.round(H * dpr);\n            canvas.style.width = \"100%\";\n            canvas.style.height = \"100%\";\n            ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n            ctx.imageSmoothingEnabled = true;\n            ctx.imageSmoothingQuality = \"high\";\n        }\n        let fallbackFullscreen = false;\n        let previousBodyOverflow = '';\n        let previousHtmlOverflow = '';\n        function fallbackEnter() {\n            if (fallbackFullscreen)\n                return;\n            previousBodyOverflow = document.body.style.overflow;\n            previousHtmlOverflow = document.documentElement.style.overflow;\n            document.body.style.overflow = 'hidden';\n            document.documentElement.style.overflow = 'hidden';\n            fallbackFullscreen = true;\n            shell.classList.add('clm01-fallback-full');\n            updateFullscreenClass();\n        }\n        function fallbackExit() {\n            if (!fallbackFullscreen)\n                return;\n            fallbackFullscreen = false;\n            shell.classList.remove('clm01-fallback-full');\n            document.body.style.overflow = previousBodyOverflow;\n            document.documentElement.style.overflow = previousHtmlOverflow;\n            updateFullscreenClass();\n        }\n        function updateFullscreenClass() {\n            const active = document.fullscreenElement === shell || fallbackFullscreen;\n            if ((_wpLeft2 => _wpLeft2 ? window.innerWidth < window.innerHeight : _wpLeft2)(active)) {\n                shell.classList.add(\"clm01-portrait-full\");\n            }\n            else {\n                shell.classList.remove(\"clm01-portrait-full\");\n            }\n            setTimeout(resizeCanvas, 80);\n        }\n        function tryOrientationLock() {\n            if ((_wpLeft3 => _wpLeft3 ? screen.orientation.lock : _wpLeft3)(screen.orientation)) {\n                screen.orientation.lock(\"landscape\").catch(function () { });\n            }\n        }\n        function unlockOrientation() {\n            if ((_wpLeft4 => _wpLeft4 ? screen.orientation.unlock : _wpLeft4)(screen.orientation)) {\n                try {\n                    screen.orientation.unlock();\n                }\n                catch (e) { }\n            }\n        }\n        function updateHud() {\n            levelEl.textContent = String(state.level);\n            scoreEl.textContent = fmt(state.score);\n            bestEl.textContent = fmt(state.best);\n            movesEl.textContent = fmt(state.moves);\n            timeEl.textContent = timeFormat(state.timeLeft);\n            const ratio = state.timeLimit > 0 ? clamp(state.timeLeft \/ state.timeLimit, 0, 1) : 0;\n            timeFill.style.width = (ratio * 100) + \"%\";\n            hud.classList.toggle(\"clm01-time-warning\", (_wpLeft5 => _wpLeft5 ? state.mode === \"playing\" : _wpLeft5)(state.timeLeft <= 30));\n            timeBar.classList.toggle(\"warning\", (_wpLeft6 => _wpLeft6 ? state.mode === \"playing\" : _wpLeft6)(state.timeLeft <= 30));\n            soundBtn.classList.toggle(\"clm01-muted\", state.muted);\n        }\n        function showOverlay(html) {\n            overlay.innerHTML = html;\n            overlay.classList.add(\"show\");\n        }\n        function hideOverlay() {\n            overlay.classList.remove(\"show\");\n            overlay.innerHTML = \"\";\n        }\n        function bindOverlayButton(action, fn) {\n            const btn = overlay.querySelector('[data-action=\"' + action + '\"]');\n            if (btn)\n                btn.addEventListener(\"click\", fn);\n        }\n        function hasSavedProgress() {\n            try {\n                const raw = storageGet(KEY_SAVE);\n                if (!raw)\n                    return false;\n                const save = JSON.parse(raw);\n                return !!((_wpLeft10 => _wpLeft10 ? (save.pendingNextLevel === true || ((_wpLeft9 => _wpLeft9 ? Number(save.timeLeft) > 0 : _wpLeft9)((_wpLeft8 => _wpLeft8 ? save.tiles.length > 0 : _wpLeft8)(Array.isArray(save.tiles))))) : _wpLeft10)((_wpLeft7 => _wpLeft7 ? save.level : _wpLeft7)(save)));\n            }\n            catch (e) {\n                return false;\n            }\n        }\n        function showStartPopup() {\n            state.mode = \"menu\";\n            updateHud();\n            const continueBtn = hasSavedProgress()\n                ? '\\x3Cbutton class=\"clm01-menu-btn secondary\" data-action=\"continue\"\\x3ECONTINUE CRAFT\\x3C\/button\\x3E'\n                : \"\";\n            showOverlay('\\x3Cdiv class=\"clm01-card\"\\x3E' +\n                '\\x3Ch2\\x3EMOCHI MOONBOUND\\x3C\/h2\\x3E' +\n                '\\x3Cdiv class=\"clm01-menu\"\\x3E' +\n                '\\x3Cbutton class=\"clm01-menu-btn\" data-action=\"start\"\\x3EENTER THE WORKSHOP\\x3C\/button\\x3E' +\n                continueBtn +\n                '\\x3C\/div\\x3E' +\n                '\\x3Cp\\x3EMatch open workshop tiles only. Covered pieces stay tucked beneath moonlit layers.\\x3C\/p\\x3E' +\n                '\\x3C\/div\\x3E');\n            bindOverlayButton(\"start\", function () {\n                sound(\"button\");\n                startNewGame();\n            });\n            bindOverlayButton(\"continue\", function () {\n                sound(\"button\");\n                continueGame();\n            });\n        }\n        function showPausePopup() {\n            if (state.mode !== \"playing\")\n                return;\n            state.mode = \"paused\";\n            saveProgress();\n            showOverlay('\\x3Cdiv class=\"clm01-card\"\\x3E' +\n                '\\x3Ch2\\x3EMOON WORKSHOP PAUSE\\x3C\/h2\\x3E' +\n                '\\x3Cdiv class=\"clm01-menu\"\\x3E' +\n                '\\x3Cbutton class=\"clm01-menu-btn\" data-action=\"resume\"\\x3ERETURN TO THE BENCH\\x3C\/button\\x3E' +\n                '\\x3Cbutton class=\"clm01-menu-btn ghost\" data-action=\"saveGame\"\\x3ESAVE GAME\\x3C\/button\\x3E' +\n                '\\x3Cbutton class=\"clm01-menu-btn secondary\" data-action=\"confirmNew\"\\x3ENEW WORKSHOP\\x3C\/button\\x3E' +\n                '\\x3C\/div\\x3E' +\n                '\\x3Cp\\x3EThe moonlit workbench is resting. Save your gentle puzzle and return under the same creamy glow.\\x3C\/p\\x3E' +\n                '\\x3C\/div\\x3E');\n            bindOverlayButton(\"resume\", function () {\n                sound(\"button\");\n                hideOverlay();\n                state.mode = \"playing\";\n            });\n            bindOverlayButton(\"saveGame\", function () {\n                sound(\"button\");\n                saveProgress();\n                clearSelection();\n                state.mode = \"menu\";\n                showStartPopup();\n            });\n            bindOverlayButton(\"confirmNew\", function () {\n                sound(\"button\");\n                showNewGameConfirm();\n            });\n        }\n        let confirmReturn = null;\n        function showNewGameConfirm() {\n            confirmReturn = state.mode;\n            showOverlay('\\x3Cdiv class=\"clm01-card\"\\x3E' +\n                '\\x3Ch2\\x3ENEW WORKSHOP?\\x3C\/h2\\x3E' +\n                '\\x3Cdiv class=\"clm01-menu\"\\x3E' +\n                '\\x3Cbutton class=\"clm01-menu-btn ghost\" data-action=\"cancelNew\"\\x3ECANCEL\\x3C\/button\\x3E' +\n                '\\x3Cbutton class=\"clm01-menu-btn secondary\" data-action=\"newGame\"\\x3ENEW WORKSHOP\\x3C\/button\\x3E' +\n                '\\x3C\/div\\x3E' +\n                '\\x3Cp\\x3EA fresh moon bench will open, and the current tile craft will be closed.\\x3C\/p\\x3E' +\n                '\\x3C\/div\\x3E');\n            bindOverlayButton(\"cancelNew\", function () {\n                sound(\"button\");\n                const previousMode = confirmReturn;\n                if (previousMode === 'paused') {\n                    \/\/ Restore state briefly so the pause dialog guard succeeds.\n                    state.mode = 'playing';\n                    showPausePopup();\n                }\n                else if (previousMode === 'nomoves') {\n                    state.mode = 'playing';\n                    showNoMovesPopup();\n                }\n                else if (previousMode === 'timeup') {\n                    showTimeUpPopup();\n                }\n                else {\n                    showStartPopup();\n                }\n            });\n            bindOverlayButton(\"newGame\", function () {\n                sound(\"button\");\n                startNewGame();\n            });\n        }\n        function showNoMovesPopup() {\n            if (state.mode !== \"playing\")\n                return;\n            if (!canOfferShuffle()) {\n                showGameOverPopup();\n                return;\n            }\n            state.mode = \"nomoves\";\n            saveProgress();\n            showOverlay('\\x3Cdiv class=\"clm01-card\"\\x3E' +\n                '\\x3Ch2\\x3ENO OPEN PAIR\\x3C\/h2\\x3E' +\n                '\\x3Cdiv class=\"clm01-menu\"\\x3E' +\n                '\\x3Cbutton class=\"clm01-menu-btn\" data-action=\"shuffleNow\"\\x3ESHUFFLE TILES\\x3C\/button\\x3E' +\n                '\\x3Cbutton class=\"clm01-menu-btn secondary\" data-action=\"confirmNew\"\\x3ENEW WORKSHOP\\x3C\/button\\x3E' +\n                '\\x3C\/div\\x3E' +\n                '\\x3Cp\\x3ENo open pair remains on the moon bench. Shuffle the tiles and let the rabbit lanterns guide another move.\\x3C\/p\\x3E' +\n                '\\x3C\/div\\x3E');\n            bindOverlayButton(\"shuffleNow\", function () {\n                sound(\"button\");\n                hideOverlay();\n                state.mode = \"playing\";\n                shuffleTiles(true);\n            });\n            bindOverlayButton(\"confirmNew\", function () {\n                sound(\"button\");\n                showNewGameConfirm();\n            });\n        }\n        function showGameOverPopup() {\n            if (state.mode !== \"playing\")\n                return;\n            state.mode = \"gameover\";\n            storageRemove(KEY_SAVE);\n            showOverlay('\\x3Cdiv class=\"clm01-card\"\\x3E' +\n                '\\x3Ch2\\x3EWORKSHOP SEALED\\x3C\/h2\\x3E' +\n                '\\x3Cdiv class=\"clm01-menu\"\\x3E' +\n                '\\x3Cbutton class=\"clm01-menu-btn secondary\" data-action=\"newGame\"\\x3ENEW WORKSHOP\\x3C\/button\\x3E' +\n                '\\x3C\/div\\x3E' +\n                '\\x3Cp\\x3ENo moonlit pair can be opened further. Begin a fresh workshop table.\\x3C\/p\\x3E' +\n                '\\x3C\/div\\x3E');\n            bindOverlayButton(\"newGame\", function () {\n                sound(\"button\");\n                startNewGame();\n            });\n        }\n        function showTimeUpPopup() {\n            state.mode = \"timeup\";\n            storageRemove(KEY_SAVE);\n            showOverlay('\\x3Cdiv class=\"clm01-card\"\\x3E' +\n                '\\x3Ch2\\x3ETHE MOONLIGHT FADED\\x3C\/h2\\x3E' +\n                '\\x3Cdiv class=\"clm01-menu\"\\x3E' +\n                '\\x3Cbutton class=\"clm01-menu-btn\" data-action=\"retry\"\\x3ERETRY BENCH\\x3C\/button\\x3E' +\n                '\\x3Cbutton class=\"clm01-menu-btn secondary\" data-action=\"confirmNew\"\\x3ENEW WORKSHOP\\x3C\/button\\x3E' +\n                '\\x3C\/div\\x3E' +\n                '\\x3Cp\\x3EThe creamy moonlight faded before the bench was cleared. Try this level again.\\x3C\/p\\x3E' +\n                '\\x3C\/div\\x3E');\n            bindOverlayButton(\"retry\", function () {\n                sound(\"button\");\n                startLevel(state.level, true);\n            });\n            bindOverlayButton(\"confirmNew\", function () {\n                sound(\"button\");\n                showNewGameConfirm();\n            });\n        }\n        function showLevelClearPopup() {\n            state.mode = 'levelclear';\n            \/\/ A finished level may be continued after a reload, without resurrecting an empty board.\n            if (state.level < 50) {\n                storageSet(KEY_SAVE, JSON.stringify({ pendingNextLevel: true, level: state.level + 1, score: state.score, moves: state.moves }));\n            }\n            else {\n                storageRemove(KEY_SAVE);\n            }\n            const isLast = state.level >= 50;\n            const title = isLast ? \"MOONCRAFT COMPLETE\" : \"BENCH CLEARED\";\n            const action = isLast ? \"NEW WORKSHOP\" : \"NEXT BENCH\";\n            const note = isLast\n                ? \"Every moonbound tile has found its pair beneath the soft rabbit workshop light.\"\n                : \"Another moonlit bench is restored. A new rabbit workshop puzzle is waiting.\";\n            showOverlay('\\x3Cdiv class=\"clm01-card\"\\x3E' +\n                '\\x3Ch2\\x3E' + title + '\\x3C\/h2\\x3E' +\n                '\\x3Cdiv class=\"clm01-menu\"\\x3E' +\n                '\\x3Cbutton class=\"clm01-menu-btn\" data-action=\"next\"\\x3E' + action + '\\x3C\/button\\x3E' +\n                '\\x3C\/div\\x3E' +\n                '\\x3Cp\\x3E' + note + '\\x3C\/p\\x3E' +\n                '\\x3C\/div\\x3E');\n            bindOverlayButton(\"next\", function () {\n                sound(\"button\");\n                if (isLast) {\n                    startNewGame();\n                }\n                else {\n                    state.level += 1;\n                    startLevel(state.level, false);\n                }\n            });\n        }\n        function startNewGame() {\n            storageRemove(KEY_SAVE);\n            state.level = 1;\n            state.score = 0;\n            state.moves = 0;\n            state.rngSeed = Date.now() + Math.floor(Math.random() * 999999);\n            hideOverlay();\n            startLevel(1, false);\n        }\n        function continueGame() {\n            try {\n                const save = JSON.parse(storageGet(KEY_SAVE) || '{}');\n                if ((_wpLeft14 => _wpLeft14 ? save.level <= 50 : _wpLeft14)((_wpLeft13 => _wpLeft13 ? save.level >= 1 : _wpLeft13)((_wpLeft12 => _wpLeft12 ? Number.isInteger(save.level) : _wpLeft12)((_wpLeft11 => _wpLeft11 ? save.pendingNextLevel === true : _wpLeft11)(save))))) {\n                    state.score = Math.max(0, Number(save.score) || 0);\n                    state.moves = Math.max(0, Number(save.moves) || 0);\n                    startLevel(save.level, false);\n                    return;\n                }\n                if (!save || !Array.isArray(save.tiles) || !save.tiles.length || !Number.isFinite(Number(save.timeLeft)) || Number(save.timeLeft) <= 0) {\n                    showStartPopup();\n                    return;\n                }\n                state.level = clamp(save.level || 1, 1, 50);\n                state.score = Number(save.score) || 0;\n                state.moves = Number(save.moves) || 0;\n                state.timeLeft = Math.max(0, Number(save.timeLeft));\n                state.timeLimit = Number(save.timeLimit) > 0 ? Number(save.timeLimit) : Math.max(180, state.timeLeft);\n                state.usedHint = !!save.usedHint;\n                state.usedShuffle = !!save.usedShuffle;\n                state.combo = 0;\n                state.comboTimer = 0;\n                state.hintIds = [];\n                state.hintTimer = 0;\n                state.particles = [];\n                state.floats = [];\n                state.pendingClear = false;\n                state.levelIntro = 1.2;\n                state.selectedId = null;\n                state.lastTickSecond = -1;\n                state.tiles = save.tiles.map(function (t, i) {\n                    return {\n                        id: i + 1,\n                        x: t.x,\n                        y: t.y,\n                        z: t.z,\n                        symbol: t.symbol,\n                        removed: !!t.removed,\n                        selected: false,\n                        removing: 0,\n                        deny: 0,\n                        wrong: 0,\n                        unlockGlow: 0,\n                        sx: 0,\n                        sy: 0,\n                        w: 0,\n                        h: 0\n                    };\n                });\n                state.bounds = save.bounds || makeBounds(state.tiles);\n                hideOverlay();\n                state.mode = 'playing';\n                updateHud();\n                if ((_wpLeft15 => _wpLeft15 ? !hasValidMove() : _wpLeft15)(!hasRemovingTiles())) {\n                    setTimeout(function () {\n                        if (state.mode === 'playing') {\n                            if (canOfferShuffle())\n                                showNoMovesPopup();\n                            else\n                                showGameOverPopup();\n                        }\n                    }, 50);\n                }\n            }\n            catch (e) {\n                showStartPopup();\n            }\n        }\n        function startLevel(level, retry) {\n            state.mode = \"playing\";\n            state.level = level;\n            state.selectedId = null;\n            state.hintIds = [];\n            state.hintTimer = 0;\n            state.particles = [];\n            state.floats = [];\n            state.combo = 0;\n            state.comboTimer = 0;\n            state.usedHint = false;\n            state.usedShuffle = false;\n            state.pendingClear = false;\n            state.levelIntro = 1.35;\n            state.lastTickSecond = -1;\n            if (retry)\n                state.moves = Math.max(0, state.moves);\n            const tileCount = LEVELS[level - 1];\n            state.timeLimit = Math.round(120 + tileCount * 4 + level * 2);\n            state.timeLeft = state.timeLimit;\n            generateLevel(level);\n            hideOverlay();\n            updateHud();\n            saveProgress();\n        }\n        function makeBounds(list) {\n            const usable = list.filter(Boolean);\n            if (!usable.length)\n                return { minX: -1, maxX: 1, minY: -1, maxY: 1, maxZ: 2 };\n            let minX = Infinity;\n            let maxX = -Infinity;\n            let minY = Infinity;\n            let maxY = -Infinity;\n            let maxZ = 0;\n            usable.forEach(function (t) {\n                minX = Math.min(minX, t.x);\n                maxX = Math.max(maxX, t.x);\n                minY = Math.min(minY, t.y);\n                maxY = Math.max(maxY, t.y);\n                maxZ = Math.max(maxZ, t.z);\n            });\n            return { minX: minX, maxX: maxX, minY: minY, maxY: maxY, maxZ: maxZ };\n        }\n        function layerCounts(total) {\n            let l1 = Math.max(8, Math.round(total * .52));\n            let l2 = Math.max(6, Math.round(total * .30));\n            let l3 = total - l1 - l2;\n            if (l3 < 4) {\n                const need = 4 - l3;\n                l1 -= Math.ceil(need \/ 2);\n                l2 -= Math.floor(need \/ 2);\n                l3 = 4;\n            }\n            let diff = total - (l1 + l2 + l3);\n            l1 += diff;\n            return [l1, l2, l3];\n        }\n        function generateLayerPositions(count, z, level, rng, used) {\n            const positions = [];\n            const maxCols = z === 0\n                ? Math.min(17, 10 + Math.floor(level \/ 5))\n                : z === 1\n                    ? Math.min(13, 7 + Math.floor(level \/ 7))\n                    : Math.min(9, 4 + Math.floor(level \/ 10));\n            const rows = Math.max(2, Math.ceil(count \/ maxCols));\n            const cols = Math.ceil(count \/ rows);\n            const candidates = [];\n            for (let y = 0; y < rows; y++) {\n                const rowExtra = z === 0 ? 2 : 1;\n                const rowCols = cols + rowExtra;\n                for (let x = 0; x < rowCols; x++) {\n                    const px = x - Math.floor(rowCols \/ 2);\n                    const py = y - Math.floor(rows \/ 2);\n                    const centerWeight = Math.abs(px) + Math.abs(py);\n                    candidates.push({\n                        x: px,\n                        y: py,\n                        score: z === 0 ? rng() * 5 + Math.abs(py) * .15 : centerWeight + rng() * .7\n                    });\n                }\n            }\n            if (z === 0) {\n                shuffleArray(candidates, rng);\n            }\n            else {\n                candidates.sort(function (a, b) { return a.score - b.score; });\n            }\n            for (let i = 0; (_wpLeft16 => _wpLeft16 ? positions.length < count : _wpLeft16)(i < candidates.length); i++) {\n                const c = candidates[i];\n                const key = c.x + \",\" + c.y + \",\" + z;\n                if (!used.has(key)) {\n                    used.add(key);\n                    positions.push({ x: c.x, y: c.y, z: z });\n                }\n            }\n            let guard = 0;\n            while ((_wpLeft17 => _wpLeft17 ? guard < 1000 : _wpLeft17)(positions.length < count)) {\n                const x = Math.floor(rng() * (cols + 2)) - Math.floor((cols + 2) \/ 2);\n                const y = Math.floor(rng() * rows) - Math.floor(rows \/ 2);\n                const key = x + \",\" + y + \",\" + z;\n                if (!used.has(key)) {\n                    used.add(key);\n                    positions.push({ x: x, y: y, z: z });\n                }\n                guard++;\n            }\n            return positions;\n        }\n        function createThreeLayerPositions(total, level, rng) {\n            const counts = layerCounts(total);\n            const used = new Set();\n            let positions = [];\n            for (let z = 0; z < FIXED_LAYERS; z++) {\n                positions = positions.concat(generateLayerPositions(counts[z], z, level, rng, used));\n            }\n            positions = positions.slice(0, total);\n            if (positions.length % 2 === 1)\n                positions.pop();\n            return positions;\n        }\n        function createSafeThreeLayerPositions(total, rng) {\n            const counts = layerCounts(total);\n            let positions = [];\n            counts.forEach(function (count, z) {\n                const rows = z === 0 ? Math.max(2, Math.ceil(count \/ 16)) : z === 1 ? Math.max(2, Math.ceil(count \/ 12)) : Math.max(2, Math.ceil(count \/ 8));\n                const cols = Math.ceil(count \/ rows);\n                for (let i = 0; i < count; i++) {\n                    const row = Math.floor(i \/ cols);\n                    const col = i % cols;\n                    const items = Math.min(cols, count - row * cols);\n                    positions.push({\n                        x: col - Math.floor(items \/ 2),\n                        y: row - Math.floor(rows \/ 2),\n                        z: z\n                    });\n                }\n            });\n            return shuffleArray(positions, rng).slice(0, total);\n        }\n        function generateLevel(level) {\n            const tileCount = LEVELS[level - 1];\n            const baseSeed = (state.rngSeed + level * 99173 + Math.floor(Date.now() % 100000)) >>> 0;\n            for (let attempt = 0; attempt < 120; attempt++) {\n                const rng = seeded((baseSeed + attempt * 17239) >>> 0);\n                const positions = attempt < 100\n                    ? createThreeLayerPositions(tileCount, level, rng)\n                    : createSafeThreeLayerPositions(tileCount, rng);\n                state.tiles = positions.map(function (p, i) {\n                    return {\n                        id: i + 1,\n                        x: p.x,\n                        y: p.y,\n                        z: p.z,\n                        symbol: \"moon\",\n                        removed: false,\n                        selected: false,\n                        removing: 0,\n                        deny: 0,\n                        wrong: 0,\n                        unlockGlow: 0,\n                        sx: 0,\n                        sy: 0,\n                        w: 0,\n                        h: 0\n                    };\n                });\n                state.bounds = makeBounds(state.tiles);\n                computeTileRects();\n                if ((_wpLeft18 => _wpLeft18 ? hasValidMove() : _wpLeft18)(assignSolvableSymbols(rng, level))) {\n                    return;\n                }\n            }\n            const fallbackRng = seeded((baseSeed + 999999) >>> 0);\n            const fallback = createSafeThreeLayerPositions(tileCount, fallbackRng);\n            state.tiles = fallback.map(function (p, i) {\n                return {\n                    id: i + 1,\n                    x: p.x,\n                    y: p.y,\n                    z: p.z,\n                    symbol: \"moon\",\n                    removed: false,\n                    selected: false,\n                    removing: 0,\n                    deny: 0,\n                    wrong: 0,\n                    unlockGlow: 0,\n                    sx: 0,\n                    sy: 0,\n                    w: 0,\n                    h: 0\n                };\n            });\n            state.bounds = makeBounds(state.tiles);\n            computeTileRects();\n            assignSolvableSymbols(fallbackRng, level);\n        }\n        function computeTileRects() {\n            if (!state.bounds)\n                return;\n            const b = state.bounds;\n            const area = { x: 88, y: 108, w: 1104, h: 486 };\n            const cols = Math.max(1, b.maxX - b.minX + 1);\n            const rows = Math.max(1, b.maxY - b.minY + 1);\n            let tileW = Math.min(58, area.w \/ (cols * 1.08 + .8));\n            let tileH = tileW * 1.5;\n            let stepX = tileW * 1.045;\n            let stepY = tileH * 1.025;\n            let depthX = tileW * .11;\n            let depthY = tileH * .085;\n            let totalW = (cols - 1) * stepX + tileW + 2 * depthX;\n            let totalH = (rows - 1) * stepY + tileH + 2 * depthY;\n            const sc = Math.min(1, area.w \/ totalW, area.h \/ totalH);\n            tileW *= sc;\n            tileH *= sc;\n            stepX *= sc;\n            stepY *= sc;\n            depthX *= sc;\n            depthY *= sc;\n            totalW = (cols - 1) * stepX + tileW + 2 * depthX;\n            totalH = (rows - 1) * stepY + tileH + 2 * depthY;\n            const startX = area.x + (area.w - totalW) \/ 2;\n            const startY = area.y + (area.h - totalH) \/ 2 + 2 * depthY;\n            state.tiles.forEach(function (t) {\n                t.w = tileW;\n                t.h = tileH;\n                t.sx = startX + (t.x - b.minX) * stepX + t.z * depthX;\n                t.sy = startY + (t.y - b.minY) * stepY - t.z * depthY;\n            });\n        }\n        function assignSolvableSymbols(rng, level) {\n            const originalRemoved = {};\n            const originalRemoving = {};\n            const assigned = {};\n            const symbolCount = Math.min(18, 6 + Math.floor((level + 1) \/ 4));\n            let pairIndex = 0;\n            let guard = 0;\n            state.tiles.forEach(function (t) {\n                originalRemoved[t.id] = !!t.removed;\n                originalRemoving[t.id] = t.removing || 0;\n                t.removing = 0;\n            });\n            while (true) {\n                const remaining = state.tiles.filter(function (t) { return !t.removed; });\n                if (!remaining.length)\n                    break;\n                if (remaining.length < 2 || guard++ > 500) {\n                    restore();\n                    return false;\n                }\n                computeTileRects();\n                const free = remaining.filter(function (t) {\n                    return isFree(t);\n                });\n                if (free.length < 2) {\n                    restore();\n                    return false;\n                }\n                shuffleArray(free, rng);\n                const a = free[0];\n                const b = free[1];\n                const symbol = SYMBOLS[(pairIndex + Math.floor(rng() * symbolCount)) % symbolCount];\n                assigned[a.id] = symbol;\n                assigned[b.id] = symbol;\n                a.removed = true;\n                b.removed = true;\n                pairIndex++;\n            }\n            state.tiles.forEach(function (t) {\n                t.removed = originalRemoved[t.id];\n                t.removing = originalRemoving[t.id];\n                if ((_wpLeft19 => _wpLeft19 ? assigned[t.id] : _wpLeft19)(!t.removed)) {\n                    t.symbol = assigned[t.id];\n                    t.selected = false;\n                    t.deny = 0;\n                    t.wrong = 0;\n                }\n            });\n            return true;\n            function restore() {\n                state.tiles.forEach(function (t) {\n                    t.removed = originalRemoved[t.id];\n                    t.removing = originalRemoving[t.id];\n                });\n            }\n        }\n        function isActive(t) {\n            return (_wpLeft21 => _wpLeft21 ? !t.removing : _wpLeft21)((_wpLeft20 => _wpLeft20 ? !t.removed : _wpLeft20)(t));\n        }\n        function hasRemovingTiles() {\n            return state.tiles.some(function (t) { return t.removing > 0; });\n        }\n        function rectOverlapAmount(a, b) {\n            const left = Math.max(a.x, b.x);\n            const right = Math.min(a.x + a.w, b.x + b.w);\n            const top = Math.max(a.y, b.y);\n            const bottom = Math.min(a.y + a.h, b.y + b.h);\n            const ow = right - left;\n            const oh = bottom - top;\n            if (ow <= 0 || oh <= 0)\n                return 0;\n            return ow * oh;\n        }\n        function getTileCoverRect(t) {\n            return {\n                x: t.sx + t.w * .04,\n                y: t.sy + t.h * .04,\n                w: t.w * .92,\n                h: t.h * .92\n            };\n        }\n        function isCovered(t) {\n            if (!isActive(t))\n                return true;\n            if (!t.w || !t.h)\n                computeTileRects();\n            const target = getTileCoverRect(t);\n            const targetArea = target.w * target.h;\n            return state.tiles.some(function (u) {\n                if (!isActive(u))\n                    return false;\n                if (u.z <= t.z)\n                    return false;\n                const upper = getTileCoverRect(u);\n                const overlap = rectOverlapAmount(target, upper);\n                return overlap > targetArea * .002;\n            });\n        }\n        function sideBlocked(t, dir) {\n            if (!isActive(t))\n                return true;\n            return state.tiles.some(function (u) {\n                if (!isActive(u))\n                    return false;\n                if (u.z !== t.z)\n                    return false;\n                const sameRow = Math.abs(u.y - t.y) < .8;\n                const side = Math.abs(u.x - (t.x + dir)) < .45;\n                return (_wpLeft22 => _wpLeft22 ? side : _wpLeft22)(sameRow);\n            });\n        }\n        function isFree(t) {\n            if (!isActive(t))\n                return false;\n            if (isCovered(t))\n                return false;\n            const leftOpen = !sideBlocked(t, -1);\n            const rightOpen = !sideBlocked(t, 1);\n            return leftOpen || rightOpen;\n        }\n        function activeTiles() {\n            return state.tiles.filter(isActive);\n        }\n        function getFreeTiles() {\n            computeTileRects();\n            return activeTiles().filter(isFree);\n        }\n        function canOfferShuffle() {\n            return getFreeTiles().length >= 2;\n        }\n        function findValidPairs() {\n            const free = getFreeTiles();\n            const groups = {};\n            const pairs = [];\n            free.forEach(function (t) {\n                if (!groups[t.symbol])\n                    groups[t.symbol] = [];\n                groups[t.symbol].push(t);\n            });\n            Object.keys(groups).forEach(function (k) {\n                const g = groups[k];\n                if (g.length >= 2) {\n                    for (let i = 0; i < g.length - 1; i++) {\n                        pairs.push([g[i], g[i + 1]]);\n                    }\n                }\n            });\n            return pairs;\n        }\n        function hasValidMove() {\n            return findValidPairs().length > 0;\n        }\n        function canvasPoint(ev) {\n            const rect = canvas.getBoundingClientRect();\n            const px = ev.clientX - rect.left;\n            const py = ev.clientY - rect.top;\n            if ((_wpLeft23 => _wpLeft23 ? (document.fullscreenElement === shell || fallbackFullscreen) : _wpLeft23)(shell.classList.contains('clm01-portrait-full'))) {\n                return {\n                    x: (1 - py \/ rect.height) * W,\n                    y: (px \/ rect.width) * H\n                };\n            }\n            return {\n                x: px * W \/ rect.width,\n                y: py * H \/ rect.height\n            };\n        }\n        function pointHitsTileRect(x, y, t) {\n            return (_wpLeft26 => _wpLeft26 ? y <= t.sy + t.h : _wpLeft26)((_wpLeft25 => _wpLeft25 ? y >= t.sy : _wpLeft25)((_wpLeft24 => _wpLeft24 ? x <= t.sx + t.w : _wpLeft24)(x >= t.sx)));\n        }\n        function tileAt(x, y) {\n            computeTileRects();\n            const topMost = state.tiles.slice().sort(function (a, b) {\n                if (a.z !== b.z)\n                    return b.z - a.z;\n                if (a.y !== b.y)\n                    return b.y - a.y;\n                return b.x - a.x;\n            });\n            for (let i = 0; i < topMost.length; i++) {\n                const t = topMost[i];\n                if (t.removed)\n                    continue;\n                if (pointHitsTileRect(x, y, t))\n                    return t;\n            }\n            return null;\n        }\n        function clearSelection() {\n            state.tiles.forEach(function (t) { t.selected = false; });\n            state.selectedId = null;\n        }\n        function freeMap() {\n            const map = {};\n            state.tiles.forEach(function (t) {\n                if ((_wpLeft27 => _wpLeft27 ? isFree(t) : _wpLeft27)(isActive(t))) {\n                    map[t.id] = true;\n                }\n            });\n            return map;\n        }\n        function addMatchEffects(a, b, gain) {\n            [a, b].forEach(function (t) {\n                const cx = t.sx + t.w \/ 2;\n                const cy = t.sy + t.h \/ 2;\n                for (let i = 0; i < 12; i++) {\n                    const colorPick = Math.random();\n                    state.particles.push({\n                        x: cx,\n                        y: cy,\n                        vx: (Math.random() - .5) * 72,\n                        vy: -24 - Math.random() * 70,\n                        life: .55 + Math.random() * .25,\n                        max: .8,\n                        size: 3 + Math.random() * 4,\n                        color: colorPick > .72 ? \"#d8c4ff\" : colorPick > .40 ? \"#fff1c9\" : \"#f4c542\"\n                    });\n                }\n            });\n            state.floats.push({\n                text: \"+\" + gain + (state.combo >= 2 ? \"  COMBO x\" + state.combo : \"\"),\n                x: (a.sx + b.sx) \/ 2 + a.w \/ 2,\n                y: (a.sy + b.sy) \/ 2,\n                vy: -38,\n                life: 1,\n                max: 1,\n                color: \"#fff1c9\"\n            });\n        }\n        function addUnlockSpark(t) {\n            const cx = t.sx + t.w \/ 2;\n            const cy = t.sy + t.h \/ 2;\n            for (let i = 0; i < 5; i++) {\n                state.particles.push({\n                    x: cx + (Math.random() - .5) * t.w * .5,\n                    y: cy + (Math.random() - .5) * t.h * .5,\n                    vx: (Math.random() - .5) * 28,\n                    vy: -20 - Math.random() * 35,\n                    life: .55,\n                    max: .55,\n                    size: 2 + Math.random() * 3,\n                    color: Math.random() > .45 ? \"#F4C542\" : \"#FFF8DF\"\n                });\n            }\n        }\n        function matchTiles(a, b) {\n            const before = freeMap();\n            clearSelection();\n            a.removing = 1;\n            b.removing = 1;\n            state.combo = Math.min(9, state.comboTimer > 0 ? state.combo + 1 : 1);\n            state.comboTimer = 2.4;\n            const gain = 20 + state.level + state.combo * 5;\n            state.score += gain;\n            state.moves += 1;\n            state.timeLeft = Math.min(state.timeLimit + 60, state.timeLeft + 1 + (state.combo >= 2 ? 2 : 0));\n            if (state.score > state.best) {\n                state.best = state.score;\n                storageSet(KEY_BEST, String(state.best));\n            }\n            addMatchEffects(a, b, gain);\n            const after = freeMap();\n            state.tiles.forEach(function (t) {\n                if ((_wpLeft28 => _wpLeft28 ? !before[t.id] : _wpLeft28)(after[t.id])) {\n                    t.unlockGlow = 1;\n                    addUnlockSpark(t);\n                }\n            });\n            sound(\"match\", state.combo);\n            updateHud();\n            if (activeTiles().length === 0) {\n                state.pendingClear = true;\n            }\n            else {\n                setTimeout(function () {\n                    if ((_wpLeft31 => _wpLeft31 ? !hasValidMove() : _wpLeft31)((_wpLeft30 => _wpLeft30 ? !hasRemovingTiles() : _wpLeft30)((_wpLeft29 => _wpLeft29 ? !state.pendingClear : _wpLeft29)(state.mode === \"playing\")))) {\n                        if (canOfferShuffle())\n                            showNoMovesPopup();\n                        else\n                            showGameOverPopup();\n                    }\n                }, 420);\n            }\n            saveProgress();\n        }\n        function useHint() {\n            if (state.mode !== \"playing\" || state.levelIntro > 0 || hasRemovingTiles())\n                return;\n            const pairs = findValidPairs();\n            if (!pairs.length) {\n                if (canOfferShuffle())\n                    showNoMovesPopup();\n                else\n                    showGameOverPopup();\n                return;\n            }\n            clearSelection();\n            const pair = pairs[Math.floor(Math.random() * pairs.length)];\n            state.hintIds = [pair[0].id, pair[1].id];\n            state.hintTimer = 1.8;\n            state.usedHint = true;\n            state.score = Math.max(0, state.score - 30);\n            state.timeLeft = Math.max(0, state.timeLeft - 8);\n            sound(\"hint\");\n            updateHud();\n            saveProgress();\n        }\n        function shuffleTiles(fromNoMove) {\n            if (state.mode !== \"playing\" || hasRemovingTiles())\n                return;\n            if (!canOfferShuffle()) {\n                showGameOverPopup();\n                return;\n            }\n            const rng = seeded(Date.now() + state.level * 77 + Math.floor(Math.random() * 999999));\n            clearSelection();\n            let success = assignSolvableSymbols(rng, state.level);\n            if (!success) {\n                success = relayoutRemainingTiles(rng);\n            }\n            state.usedShuffle = true;\n            if (!fromNoMove) {\n                state.score = Math.max(0, state.score - 80);\n                state.timeLeft = Math.max(0, state.timeLeft - 15);\n            }\n            state.hintIds = [];\n            state.hintTimer = 0;\n            activeTiles().forEach(function (t) {\n                t.unlockGlow = Math.max(t.unlockGlow, .55);\n            });\n            for (let i = 0; i < 42; i++) {\n                state.particles.push({\n                    x: W \/ 2 + (Math.random() - .5) * 560,\n                    y: H \/ 2 + (Math.random() - .5) * 300,\n                    vx: (Math.random() - .5) * 115,\n                    vy: (Math.random() - .5) * 80,\n                    life: .75,\n                    max: .75,\n                    size: 2 + Math.random() * 4,\n                    color: Math.random() > .66 ? \"#FFF8DF\" : Math.random() > .5 ? \"#d8c4ff\" : \"#F4C542\"\n                });\n            }\n            sound(\"shuffle\");\n            updateHud();\n            saveProgress();\n            if (!success || !hasValidMove()) {\n                setTimeout(function () {\n                    if (state.mode === \"playing\") {\n                        if (canOfferShuffle())\n                            showNoMovesPopup();\n                        else\n                            showGameOverPopup();\n                    }\n                }, 140);\n            }\n        }\n        function relayoutRemainingTiles(rng) {\n            const active = activeTiles();\n            if (active.length < 2)\n                return false;\n            const positions = createThreeLayerPositions(active.length, state.level, rng);\n            active.forEach(function (t, i) {\n                t.x = positions[i].x;\n                t.y = positions[i].y;\n                t.z = positions[i].z;\n                t.selected = false;\n                t.deny = 0;\n                t.wrong = 0;\n                t.unlockGlow = Math.max(t.unlockGlow, .55);\n            });\n            state.bounds = makeBounds(state.tiles);\n            computeTileRects();\n            return (_wpLeft32 => _wpLeft32 ? hasValidMove() : _wpLeft32)(assignSolvableSymbols(rng, state.level));\n        }\n        function completeLevelIfReady() {\n            if (state.mode !== 'playing')\n                return;\n            if (!state.pendingClear)\n                return;\n            if (hasRemovingTiles())\n                return;\n            state.pendingClear = false;\n            const clearBonus = state.level * 100;\n            const timeBonus = Math.ceil(state.timeLeft) * 2;\n            const noHint = state.usedHint ? 0 : 150;\n            const noShuffle = state.usedShuffle ? 0 : 200;\n            const perfect = ((_wpLeft34 => _wpLeft34 ? state.timeLeft > state.timeLimit * .28 : _wpLeft34)((_wpLeft33 => _wpLeft33 ? !state.usedShuffle : _wpLeft33)(!state.usedHint))) ? 300 : 0;\n            state.score += clearBonus + timeBonus + noHint + noShuffle + perfect;\n            if (state.score > state.best) {\n                state.best = state.score;\n                storageSet(KEY_BEST, String(state.best));\n            }\n            updateHud();\n            sound(state.level >= 50 ? \"complete\" : \"clear\");\n            showLevelClearPopup();\n        }\n        function saveProgress() {\n            try {\n                if (!state.tiles.length || ((_wpLeft36 => _wpLeft36 ? state.mode !== 'nomoves' : _wpLeft36)((_wpLeft35 => _wpLeft35 ? state.mode !== 'paused' : _wpLeft35)(state.mode !== 'playing'))))\n                    return;\n                const save = {\n                    level: state.level,\n                    score: state.score,\n                    moves: state.moves,\n                    timeLeft: state.timeLeft,\n                    timeLimit: state.timeLimit,\n                    usedHint: state.usedHint,\n                    usedShuffle: state.usedShuffle,\n                    bounds: state.bounds,\n                    tiles: state.tiles.map(function (t) {\n                        return {\n                            x: t.x,\n                            y: t.y,\n                            z: t.z,\n                            symbol: t.symbol,\n                            removed: !!t.removed || t.removing > 0\n                        };\n                    })\n                };\n                storageSet(KEY_SAVE, JSON.stringify(save));\n            }\n            catch (e) { }\n        }\n        function sound(type, combo) {\n            if (state.muted)\n                return;\n            try {\n                if (!audioCtx) {\n                    audioCtx = new (window.AudioContext || window.webkitAudioContext)();\n                }\n                const now = audioCtx.currentTime;\n                const gain = audioCtx.createGain();\n                gain.connect(audioCtx.destination);\n                gain.gain.setValueAtTime(.0001, now);\n                let freq = 420;\n                let dur = .08;\n                let vol = .048;\n                let wave = \"sine\";\n                if (type === \"button\") {\n                    freq = 330;\n                    dur = .060;\n                    vol = .030;\n                    wave = \"triangle\";\n                }\n                if (type === \"tap\") {\n                    freq = 420;\n                    dur = .050;\n                    vol = .030;\n                    wave = \"triangle\";\n                }\n                if (type === \"match\") {\n                    freq = 560 + Math.min(6, combo || 1) * 38;\n                    dur = .155;\n                    vol = .058;\n                }\n                if (type === \"wrong\") {\n                    freq = 190;\n                    dur = .12;\n                    vol = .042;\n                    wave = \"triangle\";\n                }\n                if (type === \"deny\") {\n                    freq = 155;\n                    dur = .085;\n                    vol = .034;\n                    wave = \"triangle\";\n                }\n                if (type === \"hint\") {\n                    freq = 860;\n                    dur = .17;\n                    vol = .044;\n                }\n                if (type === \"shuffle\") {\n                    freq = 360;\n                    dur = .22;\n                    vol = .046;\n                    wave = \"triangle\";\n                }\n                if (type === \"clear\") {\n                    freq = 720;\n                    dur = .25;\n                    vol = .064;\n                }\n                if (type === \"complete\") {\n                    freq = 920;\n                    dur = .34;\n                    vol = .072;\n                }\n                if (type === \"tick\") {\n                    freq = 250;\n                    dur = .04;\n                    vol = .016;\n                }\n                const osc = audioCtx.createOscillator();\n                osc.type = wave;\n                osc.frequency.setValueAtTime(freq, now);\n                gain.gain.exponentialRampToValueAtTime(vol, now + .012);\n                gain.gain.exponentialRampToValueAtTime(.0001, now + dur);\n                osc.connect(gain);\n                osc.start(now);\n                osc.stop(now + dur + .02);\n            }\n            catch (e) { }\n        }\n        function onCanvasDown(ev) {\n            if (state.mode !== \"playing\")\n                return;\n            if (state.levelIntro > 0)\n                return;\n            if (hasRemovingTiles())\n                return;\n            const p = canvasPoint(ev);\n            const t = tileAt(p.x, p.y);\n            if (!t)\n                return;\n            sound(\"tap\");\n            if (!isFree(t)) {\n                t.deny = 1;\n                sound(\"deny\");\n                return;\n            }\n            if (state.selectedId === t.id) {\n                t.selected = false;\n                state.selectedId = null;\n                return;\n            }\n            if (state.selectedId === null) {\n                clearSelection();\n                t.selected = true;\n                state.selectedId = t.id;\n                return;\n            }\n            const first = state.tiles.find(function (tile) {\n                return tile.id === state.selectedId;\n            });\n            if (!first || !isActive(first) || !isFree(first)) {\n                clearSelection();\n                t.selected = true;\n                state.selectedId = t.id;\n                return;\n            }\n            if ((_wpLeft38 => _wpLeft38 ? isFree(t) : _wpLeft38)((_wpLeft37 => _wpLeft37 ? isFree(first) : _wpLeft37)(first.symbol === t.symbol))) {\n                matchTiles(first, t);\n            }\n            else {\n                first.wrong = 1;\n                t.wrong = 1;\n                clearSelection();\n                sound(\"wrong\");\n            }\n        }\n        function update(dt) {\n            if (state.mode === \"playing\") {\n                if (state.levelIntro > 0) {\n                    state.levelIntro = Math.max(0, state.levelIntro - dt);\n                }\n                else if (!state.pendingClear) {\n                    state.timeLeft -= dt;\n                    const sec = Math.ceil(state.timeLeft);\n                    if ((_wpLeft40 => _wpLeft40 ? sec >= 0 : _wpLeft40)((_wpLeft39 => _wpLeft39 ? sec !== state.lastTickSecond : _wpLeft39)(state.timeLeft <= 30))) {\n                        state.lastTickSecond = sec;\n                        sound(\"tick\");\n                    }\n                    if (state.timeLeft <= 0) {\n                        state.timeLeft = 0;\n                        updateHud();\n                        showTimeUpPopup();\n                    }\n                }\n            }\n            if (state.comboTimer > 0) {\n                state.comboTimer = Math.max(0, state.comboTimer - dt);\n                if (state.comboTimer <= 0)\n                    state.combo = 0;\n            }\n            if (state.hintTimer > 0) {\n                state.hintTimer = Math.max(0, state.hintTimer - dt);\n                if (state.hintTimer <= 0)\n                    state.hintIds = [];\n            }\n            state.tiles.forEach(function (t) {\n                if (t.removing > 0) {\n                    t.removing = Math.max(0, t.removing - dt * 3.2);\n                    if (t.removing <= 0) {\n                        t.removed = true;\n                        t.removing = 0;\n                    }\n                }\n                if (t.deny > 0)\n                    t.deny = Math.max(0, t.deny - dt * 4.5);\n                if (t.wrong > 0)\n                    t.wrong = Math.max(0, t.wrong - dt * 4.5);\n                if (t.unlockGlow > 0)\n                    t.unlockGlow = Math.max(0, t.unlockGlow - dt * 1.8);\n            });\n            state.particles = state.particles.filter(function (p) {\n                p.life -= dt;\n                p.x += p.vx * dt;\n                p.y += p.vy * dt;\n                p.vy += 40 * dt;\n                return p.life > 0;\n            });\n            state.floats = state.floats.filter(function (f) {\n                f.life -= dt;\n                f.y += f.vy * dt;\n                return f.life > 0;\n            });\n            completeLevelIfReady();\n            updateHud();\n        }\n        function roundRect(c, x, y, w, h, r) {\n            r = Math.min(r, w \/ 2, h \/ 2);\n            c.beginPath();\n            c.moveTo(x + r, y);\n            c.arcTo(x + w, y, x + w, y + h, r);\n            c.arcTo(x + w, y + h, x, y + h, r);\n            c.arcTo(x, y + h, x, y, r);\n            c.arcTo(x, y, x + w, y, r);\n            c.closePath();\n        }\n        function drawBackground() {\n            const sky = ctx.createLinearGradient(0, 0, 0, H);\n            sky.addColorStop(0, \"#151125\");\n            sky.addColorStop(.34, \"#24163d\");\n            sky.addColorStop(.68, \"#3a2457\");\n            sky.addColorStop(1, \"#5c3e5e\");\n            ctx.fillStyle = sky;\n            ctx.fillRect(0, 0, W, H);\n            ctx.save();\n            function cloud(x, y, scale, alpha) {\n                ctx.save();\n                ctx.translate(x, y);\n                ctx.scale(scale, scale);\n                ctx.globalAlpha = alpha;\n                const cg = ctx.createLinearGradient(-70, -25, 70, 35);\n                cg.addColorStop(0, \"rgba(232,224,255,.42)\");\n                cg.addColorStop(1, \"rgba(255,241,201,.24)\");\n                ctx.fillStyle = cg;\n                ctx.beginPath();\n                ctx.arc(-44, 10, 28, 0, Math.PI * 2);\n                ctx.arc(-14, -5, 36, 0, Math.PI * 2);\n                ctx.arc(24, 4, 30, 0, Math.PI * 2);\n                ctx.arc(54, 14, 22, 0, Math.PI * 2);\n                ctx.closePath();\n                ctx.fill();\n                ctx.restore();\n            }\n            function lantern(x, y, scale) {\n                ctx.save();\n                ctx.translate(x, y);\n                ctx.scale(scale, scale);\n                ctx.strokeStyle = \"rgba(255,241,201,.55)\";\n                ctx.lineWidth = 2;\n                ctx.beginPath();\n                ctx.moveTo(0, -50);\n                ctx.lineTo(0, -22);\n                ctx.stroke();\n                const glow = ctx.createRadialGradient(0, 6, 2, 0, 6, 45);\n                glow.addColorStop(0, \"rgba(255,248,223,.85)\");\n                glow.addColorStop(.42, \"rgba(244,197,66,.36)\");\n                glow.addColorStop(1, \"rgba(255,241,201,0)\");\n                ctx.fillStyle = glow;\n                ctx.beginPath();\n                ctx.arc(0, 6, 45, 0, Math.PI * 2);\n                ctx.fill();\n                ctx.fillStyle = \"rgba(255,241,201,.90)\";\n                roundRect(ctx, -18, -18, 36, 48, 13);\n                ctx.fill();\n                ctx.strokeStyle = \"rgba(93,64,96,.70)\";\n                ctx.lineWidth = 2;\n                ctx.stroke();\n                ctx.fillStyle = \"#5b3a72\";\n                ctx.beginPath();\n                ctx.arc(-4, 3, 8, 0, Math.PI * 2);\n                ctx.arc(4, 3, 8, 0, Math.PI * 2);\n                ctx.arc(-8, -4, 3, 0, Math.PI * 2);\n                ctx.arc(8, -4, 3, 0, Math.PI * 2);\n                ctx.fill();\n                ctx.fillStyle = \"rgba(93,64,96,.75)\";\n                ctx.beginPath();\n                ctx.moveTo(0, 30);\n                ctx.lineTo(-6, 43);\n                ctx.lineTo(6, 43);\n                ctx.closePath();\n                ctx.fill();\n                ctx.restore();\n            }\n            function woodenShelf(x, y, w) {\n                ctx.save();\n                ctx.fillStyle = \"rgba(59,37,32,.82)\";\n                roundRect(ctx, x, y, w, 14, 5);\n                ctx.fill();\n                ctx.strokeStyle = \"rgba(255,241,201,.22)\";\n                ctx.lineWidth = 1.2;\n                ctx.stroke();\n                for (let i = 0; i < Math.floor(w \/ 48); i++) {\n                    const px = x + 24 + i * 46;\n                    ctx.fillStyle = i % 3 === 0 ? \"#d3a866\" : i % 3 === 1 ? \"#6f4c7d\" : \"#8b6240\";\n                    roundRect(ctx, px - 9, y - 34 - (i % 2) * 6, 18, 34 + (i % 2) * 6, 5);\n                    ctx.fill();\n                    ctx.fillStyle = \"rgba(255,241,201,.42)\";\n                    roundRect(ctx, px - 4, y - 28, 8, 22, 3);\n                    ctx.fill();\n                }\n                ctx.restore();\n            }\n            const moon = ctx.createRadialGradient(1012, 92, 12, 1012, 92, 170);\n            moon.addColorStop(0, \"rgba(255,252,226,1)\");\n            moon.addColorStop(.34, \"rgba(255,241,201,.92)\");\n            moon.addColorStop(.58, \"rgba(244,197,66,.18)\");\n            moon.addColorStop(1, \"rgba(255,241,201,0)\");\n            ctx.fillStyle = moon;\n            ctx.beginPath();\n            ctx.arc(1012, 92, 170, 0, Math.PI * 2);\n            ctx.fill();\n            ctx.globalAlpha = .95;\n            ctx.fillStyle = \"#fff1c9\";\n            ctx.beginPath();\n            ctx.arc(1012, 92, 92, 0, Math.PI * 2);\n            ctx.fill();\n            ctx.globalAlpha = .10;\n            ctx.fillStyle = \"#5b3a72\";\n            ctx.beginPath();\n            ctx.arc(970, 60, 20, 0, Math.PI * 2);\n            ctx.arc(1048, 104, 16, 0, Math.PI * 2);\n            ctx.arc(1000, 140, 22, 0, Math.PI * 2);\n            ctx.fill();\n            ctx.globalAlpha = .16;\n            ctx.fillStyle = \"#5b3a72\";\n            ctx.beginPath();\n            ctx.arc(995, 95, 28, 0, Math.PI * 2);\n            ctx.arc(1030, 95, 28, 0, Math.PI * 2);\n            ctx.arc(982, 60, 10, 0, Math.PI * 2);\n            ctx.arc(1043, 60, 10, 0, Math.PI * 2);\n            ctx.fill();\n            ctx.globalAlpha = 1;\n            cloud(190, 148, 1.4, .55);\n            cloud(920, 172, 1.05, .42);\n            cloud(640, 565, 1.8, .18);\n            cloud(104, 515, 1.15, .22);\n            lantern(70, 104, .82);\n            lantern(1198, 118, .75);\n            \/\/ wooden workshop sides\n            ctx.fillStyle = \"rgba(38,24,28,.72)\";\n            roundRect(ctx, 0, 0, 54, H, 0);\n            ctx.fill();\n            roundRect(ctx, W - 54, 0, 54, H, 0);\n            ctx.fill();\n            ctx.fillStyle = \"rgba(86,56,43,.78)\";\n            roundRect(ctx, 22, 0, 20, H, 0);\n            ctx.fill();\n            roundRect(ctx, W - 42, 0, 20, H, 0);\n            ctx.fill();\n            woodenShelf(56, 118, 300);\n            woodenShelf(926, 118, 300);\n            woodenShelf(58, 222, 278);\n            woodenShelf(948, 226, 270);\n            \/\/ tiny dangling workshop charms\n            ctx.strokeStyle = \"rgba(255,241,201,.28)\";\n            ctx.lineWidth = 1.3;\n            for (let i = 0; i < 7; i++) {\n                const x = 210 + i * 130;\n                ctx.beginPath();\n                ctx.moveTo(x, 0);\n                ctx.lineTo(x, 32 + (i % 3) * 10);\n                ctx.stroke();\n                ctx.fillStyle = i % 2 ? \"#d8c4ff\" : \"#fff1c9\";\n                drawStar(x, 42 + (i % 3) * 10, 6, 2.6, 5, ctx.fillStyle);\n            }\n            \/\/ floating stars and moon dust\n            for (let i = 0; i < 84; i++) {\n                const x = (i * 157 + 33) % W;\n                const y = 28 + ((i * 79) % 520);\n                const tw = .65 + Math.sin((performance.now() * .001) + i) * .35;\n                ctx.globalAlpha = (.10 + (i % 4) * .035) * tw;\n                if (i % 8 === 0) {\n                    drawStar(x, y, 4.2, 1.8, 5, i % 2 ? \"#fff1c9\" : \"#d8c4ff\");\n                }\n                else {\n                    ctx.fillStyle = i % 3 === 0 ? \"#fff8df\" : i % 3 === 1 ? \"#d8c4ff\" : \"#f4c542\";\n                    ctx.beginPath();\n                    ctx.arc(x, y, 1.1 + (i % 5) * .35, 0, Math.PI * 2);\n                    ctx.fill();\n                }\n            }\n            ctx.globalAlpha = 1;\n            \/\/ lower wooden workbench\n            const desk = ctx.createLinearGradient(0, 548, 0, H);\n            desk.addColorStop(0, \"rgba(103,66,47,.88)\");\n            desk.addColorStop(.48, \"rgba(64,39,36,.96)\");\n            desk.addColorStop(1, \"rgba(31,22,31,1)\");\n            ctx.fillStyle = desk;\n            ctx.beginPath();\n            ctx.moveTo(0, 564);\n            ctx.bezierCurveTo(160, 538, 310, 580, 470, 550);\n            ctx.bezierCurveTo(650, 518, 820, 580, 1000, 548);\n            ctx.bezierCurveTo(1120, 528, 1190, 554, W, 538);\n            ctx.lineTo(W, H);\n            ctx.lineTo(0, H);\n            ctx.closePath();\n            ctx.fill();\n            ctx.globalAlpha = .20;\n            ctx.strokeStyle = \"#fff1c9\";\n            ctx.lineWidth = 1.4;\n            for (let k = 0; k < 6; k++) {\n                ctx.beginPath();\n                ctx.moveTo(42, 592 + k * 14);\n                ctx.quadraticCurveTo(610, 558 + k * 7, 1238, 592 + k * 14);\n                ctx.stroke();\n            }\n            ctx.globalAlpha = 1;\n            \/\/ mochi bowl and tools\n            ctx.save();\n            ctx.globalAlpha = .72;\n            ctx.fillStyle = \"rgba(255,241,201,.34)\";\n            roundRect(ctx, 92, 594, 118, 44, 16);\n            ctx.fill();\n            ctx.strokeStyle = \"rgba(255,241,201,.28)\";\n            ctx.stroke();\n            [\"#fff8df\", \"#f2b5c8\", \"#d8c4ff\", \"#fff1c9\"].forEach(function (c, i) {\n                ctx.fillStyle = c;\n                ctx.beginPath();\n                ctx.arc(112 + i * 24, 608 + (i % 2) * 8, 13, 0, Math.PI * 2);\n                ctx.fill();\n            });\n            ctx.fillStyle = \"#8b6240\";\n            roundRect(ctx, 1096, 584, 82, 24, 9);\n            ctx.fill();\n            ctx.fillStyle = \"#b88453\";\n            roundRect(ctx, 1136, 548, 18, 70, 8);\n            ctx.fill();\n            ctx.restore();\n            ctx.restore();\n        }\n        function drawBoardPanel() {\n            ctx.save();\n            const x = 76;\n            const y = 92;\n            const w = 1128;\n            const h = 530;\n            ctx.fillStyle = \"rgba(11,8,24,.32)\";\n            roundRect(ctx, x + 18, y + 22, w - 22, h - 4, 36);\n            ctx.fill();\n            const outer = ctx.createLinearGradient(0, y, 0, y + h);\n            outer.addColorStop(0, \"rgba(255,241,201,.24)\");\n            outer.addColorStop(.42, \"rgba(146,116,196,.18)\");\n            outer.addColorStop(1, \"rgba(31,22,31,.28)\");\n            ctx.fillStyle = outer;\n            roundRect(ctx, x, y, w, h, 34);\n            ctx.fill();\n            const inner = ctx.createRadialGradient(x + w \/ 2, y + h * .18, 10, x + w \/ 2, y + h * .18, 620);\n            inner.addColorStop(0, \"rgba(255,241,201,.16)\");\n            inner.addColorStop(.45, \"rgba(216,196,255,.08)\");\n            inner.addColorStop(1, \"rgba(18,12,35,.32)\");\n            ctx.fillStyle = inner;\n            roundRect(ctx, x + 10, y + 10, w - 20, h - 20, 28);\n            ctx.fill();\n            ctx.strokeStyle = \"rgba(255,241,201,.42)\";\n            ctx.lineWidth = 2.2;\n            roundRect(ctx, x + 1.5, y + 1.5, w - 3, h - 3, 33);\n            ctx.stroke();\n            ctx.strokeStyle = \"rgba(216,196,255,.32)\";\n            ctx.lineWidth = 1.4;\n            roundRect(ctx, x + 13, y + 13, w - 26, h - 26, 25);\n            ctx.stroke();\n            ctx.globalAlpha = .10;\n            ctx.strokeStyle = \"#fff1c9\";\n            ctx.lineWidth = 1;\n            for (let i = 0; i < 18; i++) {\n                const gx = x + 44 + i * 58;\n                ctx.beginPath();\n                ctx.moveTo(gx, y + 34);\n                ctx.lineTo(gx, y + h - 34);\n                ctx.stroke();\n            }\n            for (let j = 0; j < 7; j++) {\n                const gy = y + 50 + j * 60;\n                ctx.beginPath();\n                ctx.moveTo(x + 34, gy);\n                ctx.lineTo(x + w - 34, gy);\n                ctx.stroke();\n            }\n            ctx.globalAlpha = 1;\n            \/\/ small moonwork corner ornaments\n            ctx.fillStyle = \"rgba(255,241,201,.42)\";\n            drawStar(x + 42, y + 42, 7, 3, 5, ctx.fillStyle);\n            drawStar(x + w - 42, y + 42, 7, 3, 5, ctx.fillStyle);\n            drawStar(x + 42, y + h - 42, 7, 3, 5, ctx.fillStyle);\n            drawStar(x + w - 42, y + h - 42, 7, 3, 5, ctx.fillStyle);\n            ctx.restore();\n        }\n        function drawStar(x, y, outer, inner, points, color) {\n            ctx.fillStyle = color;\n            ctx.beginPath();\n            for (let i = 0; i < points * 2; i++) {\n                const r = i % 2 === 0 ? outer : inner;\n                const a = -Math.PI \/ 2 + i * Math.PI \/ points;\n                const px = x + Math.cos(a) * r;\n                const py = y + Math.sin(a) * r;\n                if (i === 0)\n                    ctx.moveTo(px, py);\n                else\n                    ctx.lineTo(px, py);\n            }\n            ctx.closePath();\n            ctx.fill();\n        }\n        function drawSymbol(symbol, x, y, s) {\n            ctx.save();\n            ctx.lineCap = \"round\";\n            ctx.lineJoin = \"round\";\n            const cream = \"#fff8df\";\n            const moon = \"#fff1c9\";\n            const gold = \"#f4c542\";\n            const wood = \"#8b5a3f\";\n            const darkWood = \"#4b302e\";\n            const plum = \"#6f4c91\";\n            const lavender = \"#d8c4ff\";\n            const rose = \"#f2a6b9\";\n            const cloud = \"#e9e4ff\";\n            const ink = \"#332345\";\n            function strokeCurrent(width, color) {\n                ctx.strokeStyle = color || darkWood;\n                ctx.lineWidth = width;\n                ctx.stroke();\n            }\n            if (symbol === \"moonrabbit\") {\n                ctx.fillStyle = rose;\n                ctx.beginPath();\n                ctx.ellipse(x - s * .18, y - s * .39, s * .10, s * .28, -.12, 0, Math.PI * 2);\n                ctx.ellipse(x + s * .18, y - s * .39, s * .10, s * .28, .12, 0, Math.PI * 2);\n                ctx.fill();\n                ctx.fillStyle = cream;\n                ctx.beginPath();\n                ctx.ellipse(x - s * .18, y - s * .40, s * .065, s * .20, -.12, 0, Math.PI * 2);\n                ctx.ellipse(x + s * .18, y - s * .40, s * .065, s * .20, .12, 0, Math.PI * 2);\n                ctx.fill();\n                ctx.fillStyle = rose;\n                ctx.beginPath();\n                ctx.arc(x, y, s * .32, 0, Math.PI * 2);\n                ctx.fill();\n                strokeCurrent(s * .045, darkWood);\n                ctx.fillStyle = cream;\n                ctx.beginPath();\n                ctx.arc(x - s * .10, y - s * .03, s * .030, 0, Math.PI * 2);\n                ctx.arc(x + s * .10, y - s * .03, s * .030, 0, Math.PI * 2);\n                ctx.arc(x, y + s * .08, s * .025, 0, Math.PI * 2);\n                ctx.fill();\n            }\n            if (symbol === \"mochi\") {\n                const g = ctx.createRadialGradient(x - s * .12, y - s * .14, 2, x, y, s * .44);\n                g.addColorStop(0, cream);\n                g.addColorStop(.55, \"#f8d5dc\");\n                g.addColorStop(1, \"#d68da2\");\n                ctx.fillStyle = g;\n                ctx.beginPath();\n                ctx.ellipse(x, y + s * .05, s * .34, s * .27, 0, 0, Math.PI * 2);\n                ctx.fill();\n                strokeCurrent(s * .04, darkWood);\n                ctx.fillStyle = gold;\n                for (let i = 0; i < 5; i++) {\n                    const a = i * Math.PI * 2 \/ 5;\n                    ctx.beginPath();\n                    ctx.ellipse(x + Math.cos(a) * s * .08, y + s * .03 + Math.sin(a) * s * .08, s * .035, s * .06, a, 0, Math.PI * 2);\n                    ctx.fill();\n                }\n                ctx.fillStyle = \"rgba(255,255,255,.45)\";\n                ctx.beginPath();\n                ctx.ellipse(x - s * .12, y - s * .07, s * .08, s * .04, -.3, 0, Math.PI * 2);\n                ctx.fill();\n            }\n            if (symbol === \"mallet\") {\n                ctx.save();\n                ctx.rotate(-.68);\n                ctx.fillStyle = wood;\n                roundRect(ctx, x - s * .09, y - s * .10, s * .18, s * .58, s * .05);\n                ctx.fill();\n                strokeCurrent(s * .038, darkWood);\n                ctx.fillStyle = \"#b88453\";\n                roundRect(ctx, x - s * .32, y - s * .30, s * .64, s * .24, s * .08);\n                ctx.fill();\n                strokeCurrent(s * .04, darkWood);\n                ctx.fillStyle = \"rgba(255,241,201,.25)\";\n                roundRect(ctx, x - s * .22, y - s * .27, s * .12, s * .18, s * .04);\n                ctx.fill();\n                ctx.restore();\n            }\n            if (symbol === \"moonlantern\") {\n                const glow = ctx.createRadialGradient(x, y, 2, x, y, s * .46);\n                glow.addColorStop(0, \"rgba(255,248,223,.80)\");\n                glow.addColorStop(.45, \"rgba(244,197,66,.40)\");\n                glow.addColorStop(1, \"rgba(244,197,66,0)\");\n                ctx.fillStyle = glow;\n                ctx.beginPath();\n                ctx.arc(x, y, s * .48, 0, Math.PI * 2);\n                ctx.fill();\n                ctx.fillStyle = moon;\n                roundRect(ctx, x - s * .24, y - s * .32, s * .48, s * .62, s * .14);\n                ctx.fill();\n                strokeCurrent(s * .04, darkWood);\n                ctx.strokeStyle = gold;\n                ctx.lineWidth = s * .03;\n                ctx.beginPath();\n                ctx.moveTo(x - s * .20, y - s * .14);\n                ctx.lineTo(x + s * .20, y - s * .14);\n                ctx.moveTo(x - s * .20, y + s * .05);\n                ctx.lineTo(x + s * .20, y + s * .05);\n                ctx.stroke();\n                ctx.fillStyle = plum;\n                ctx.beginPath();\n                ctx.arc(x - s * .03, y - s * .02, s * .10, 0, Math.PI * 2);\n                ctx.arc(x + s * .05, y - s * .02, s * .10, 0, Math.PI * 2);\n                ctx.fill();\n            }\n            if (symbol === \"nightcloud\") {\n                ctx.fillStyle = lavender;\n                ctx.beginPath();\n                ctx.arc(x - s * .22, y + s * .06, s * .18, 0, Math.PI * 2);\n                ctx.arc(x - s * .02, y - s * .05, s * .25, 0, Math.PI * 2);\n                ctx.arc(x + s * .22, y + s * .05, s * .19, 0, Math.PI * 2);\n                ctx.arc(x + s * .38, y + s * .12, s * .13, 0, Math.PI * 2);\n                ctx.closePath();\n                ctx.fill();\n                strokeCurrent(s * .035, plum);\n                ctx.fillStyle = \"rgba(255,248,223,.38)\";\n                ctx.beginPath();\n                ctx.ellipse(x - s * .12, y - s * .09, s * .12, s * .05, -.2, 0, Math.PI * 2);\n                ctx.fill();\n                drawStar(x + s * .22, y - s * .20, s * .07, s * .03, 5, gold);\n            }\n            if (symbol === \"toolbox\") {\n                ctx.fillStyle = \"#b88453\";\n                roundRect(ctx, x - s * .36, y - s * .16, s * .72, s * .46, s * .09);\n                ctx.fill();\n                strokeCurrent(s * .045, darkWood);\n                ctx.fillStyle = \"#dfb46b\";\n                roundRect(ctx, x - s * .18, y - s * .34, s * .36, s * .20, s * .07);\n                ctx.fill();\n                strokeCurrent(s * .035, darkWood);\n                ctx.fillStyle = gold;\n                roundRect(ctx, x - s * .07, y + s * .02, s * .14, s * .10, s * .025);\n                ctx.fill();\n                ctx.strokeStyle = moon;\n                ctx.lineWidth = s * .03;\n                ctx.beginPath();\n                ctx.moveTo(x - s * .28, y - s * .02);\n                ctx.lineTo(x + s * .28, y - s * .02);\n                ctx.stroke();\n            }\n            if (symbol === \"mooncharm\") {\n                ctx.strokeStyle = gold;\n                ctx.lineWidth = s * .06;\n                ctx.beginPath();\n                ctx.moveTo(x, y - s * .40);\n                ctx.lineTo(x, y - s * .27);\n                ctx.stroke();\n                ctx.fillStyle = plum;\n                ctx.beginPath();\n                ctx.arc(x, y, s * .28, 0, Math.PI * 2);\n                ctx.fill();\n                strokeCurrent(s * .04, darkWood);\n                ctx.fillStyle = gold;\n                ctx.beginPath();\n                ctx.arc(x - s * .03, y, s * .17, 0, Math.PI * 2);\n                ctx.arc(x + s * .04, y - s * .03, s * .17, 0, Math.PI * 2);\n                ctx.fill(\"evenodd\");\n                ctx.fillStyle = moon;\n                drawStar(x + s * .10, y + s * .08, s * .06, s * .025, 5, moon);\n            }\n            if (symbol === \"woodplane\") {\n                ctx.fillStyle = wood;\n                ctx.beginPath();\n                ctx.moveTo(x - s * .40, y + s * .18);\n                ctx.lineTo(x + s * .38, y + s * .02);\n                ctx.lineTo(x + s * .28, y + s * .22);\n                ctx.lineTo(x - s * .32, y + s * .34);\n                ctx.closePath();\n                ctx.fill();\n                strokeCurrent(s * .04, darkWood);\n                ctx.fillStyle = gold;\n                ctx.beginPath();\n                ctx.moveTo(x - s * .04, y + s * .05);\n                ctx.lineTo(x + s * .16, y - s * .22);\n                ctx.lineTo(x + s * .26, y + s * .08);\n                ctx.closePath();\n                ctx.fill();\n                ctx.fillStyle = moon;\n                ctx.beginPath();\n                ctx.arc(x - s * .16, y + s * .17, s * .035, 0, Math.PI * 2);\n                ctx.fill();\n            }\n            if (symbol === \"ricebowl\") {\n                ctx.fillStyle = \"#6f4c91\";\n                ctx.beginPath();\n                ctx.moveTo(x - s * .38, y + s * .02);\n                ctx.quadraticCurveTo(x, y + s * .46, x + s * .38, y + s * .02);\n                ctx.closePath();\n                ctx.fill();\n                strokeCurrent(s * .04, darkWood);\n                ctx.fillStyle = cream;\n                ctx.beginPath();\n                ctx.ellipse(x, y - s * .02, s * .38, s * .14, 0, 0, Math.PI * 2);\n                ctx.fill();\n                ctx.strokeStyle = gold;\n                ctx.lineWidth = s * .03;\n                ctx.beginPath();\n                ctx.moveTo(x - s * .22, y + s * .20);\n                ctx.lineTo(x + s * .22, y + s * .20);\n                ctx.stroke();\n            }\n            if (symbol === \"crescent\") {\n                ctx.fillStyle = gold;\n                ctx.beginPath();\n                ctx.arc(x, y, s * .34, 0, Math.PI * 2);\n                ctx.arc(x + s * .14, y - s * .07, s * .34, 0, Math.PI * 2);\n                ctx.fill(\"evenodd\");\n                ctx.fillStyle = moon;\n                drawStar(x + s * .24, y + s * .10, s * .07, s * .03, 5, moon);\n                drawStar(x - s * .22, y - s * .22, s * .05, s * .02, 5, lavender);\n            }\n            if (symbol === \"starcake\") {\n                ctx.fillStyle = moon;\n                drawStar(x, y, s * .36, s * .18, 5, moon);\n                ctx.strokeStyle = darkWood;\n                ctx.lineWidth = s * .04;\n                ctx.stroke();\n                ctx.fillStyle = rose;\n                for (let i = 0; i < 5; i++) {\n                    const a = i * Math.PI * 2 \/ 5 - Math.PI \/ 2;\n                    ctx.beginPath();\n                    ctx.arc(x + Math.cos(a) * s * .16, y + Math.sin(a) * s * .16, s * .025, 0, Math.PI * 2);\n                    ctx.fill();\n                }\n            }\n            if (symbol === \"woodspoon\") {\n                ctx.save();\n                ctx.rotate(.65);\n                ctx.fillStyle = \"#b88453\";\n                ctx.beginPath();\n                ctx.ellipse(x, y - s * .24, s * .16, s * .24, 0, 0, Math.PI * 2);\n                ctx.fill();\n                strokeCurrent(s * .035, darkWood);\n                roundRect(ctx, x - s * .045, y - s * .02, s * .09, s * .48, s * .04);\n                ctx.fill();\n                ctx.stroke();\n                ctx.restore();\n            }\n            if (symbol === \"cloudwrench\") {\n                ctx.strokeStyle = lavender;\n                ctx.lineWidth = s * .105;\n                ctx.beginPath();\n                ctx.arc(x - s * .16, y - s * .10, s * .16, .75, Math.PI * 1.75);\n                ctx.lineTo(x + s * .32, y + s * .30);\n                ctx.stroke();\n                ctx.strokeStyle = plum;\n                ctx.lineWidth = s * .045;\n                ctx.beginPath();\n                ctx.arc(x - s * .16, y - s * .10, s * .16, .75, Math.PI * 1.75);\n                ctx.lineTo(x + s * .32, y + s * .30);\n                ctx.stroke();\n                ctx.fillStyle = cloud;\n                ctx.beginPath();\n                ctx.arc(x + s * .04, y + s * .04, s * .12, 0, Math.PI * 2);\n                ctx.arc(x + s * .17, y + s * .02, s * .10, 0, Math.PI * 2);\n                ctx.fill();\n            }\n            if (symbol === \"moonstamp\") {\n                ctx.fillStyle = wood;\n                roundRect(ctx, x - s * .16, y - s * .36, s * .32, s * .44, s * .08);\n                ctx.fill();\n                strokeCurrent(s * .04, darkWood);\n                ctx.fillStyle = \"#b88453\";\n                roundRect(ctx, x - s * .32, y + s * .04, s * .64, s * .24, s * .06);\n                ctx.fill();\n                strokeCurrent(s * .04, darkWood);\n                ctx.fillStyle = gold;\n                ctx.beginPath();\n                ctx.arc(x, y + s * .16, s * .07, 0, Math.PI * 2);\n                ctx.fill();\n            }\n            if (symbol === \"bunnybell\") {\n                ctx.fillStyle = gold;\n                ctx.beginPath();\n                ctx.moveTo(x - s * .26, y + s * .22);\n                ctx.quadraticCurveTo(x - s * .20, y - s * .18, x, y - s * .22);\n                ctx.quadraticCurveTo(x + s * .20, y - s * .18, x + s * .26, y + s * .22);\n                ctx.closePath();\n                ctx.fill();\n                strokeCurrent(s * .04, darkWood);\n                ctx.fillStyle = rose;\n                ctx.beginPath();\n                ctx.ellipse(x - s * .10, y - s * .38, s * .055, s * .16, -.16, 0, Math.PI * 2);\n                ctx.ellipse(x + s * .10, y - s * .38, s * .055, s * .16, .16, 0, Math.PI * 2);\n                ctx.fill();\n                ctx.fillStyle = darkWood;\n                ctx.beginPath();\n                ctx.arc(x, y + s * .24, s * .045, 0, Math.PI * 2);\n                ctx.fill();\n            }\n            if (symbol === \"workbench\") {\n                ctx.fillStyle = \"#b88453\";\n                roundRect(ctx, x - s * .38, y - s * .12, s * .76, s * .20, s * .05);\n                ctx.fill();\n                strokeCurrent(s * .035, darkWood);\n                ctx.fillStyle = wood;\n                roundRect(ctx, x - s * .30, y + s * .05, s * .10, s * .34, s * .035);\n                roundRect(ctx, x + s * .20, y + s * .05, s * .10, s * .34, s * .035);\n                ctx.fill();\n                ctx.stroke();\n                ctx.fillStyle = moon;\n                drawStar(x, y - s * .23, s * .08, s * .03, 5, moon);\n            }\n            if (symbol === \"mochipestle\") {\n                ctx.fillStyle = \"#8b6240\";\n                ctx.beginPath();\n                ctx.moveTo(x - s * .30, y + s * .02);\n                ctx.quadraticCurveTo(x, y + s * .40, x + s * .30, y + s * .02);\n                ctx.closePath();\n                ctx.fill();\n                strokeCurrent(s * .04, darkWood);\n                ctx.save();\n                ctx.rotate(-.62);\n                ctx.fillStyle = \"#b88453\";\n                roundRect(ctx, x + s * .06, y - s * .38, s * .12, s * .62, s * .06);\n                ctx.fill();\n                ctx.strokeStyle = darkWood;\n                ctx.lineWidth = s * .032;\n                ctx.stroke();\n                ctx.restore();\n                ctx.fillStyle = cream;\n                ctx.beginPath();\n                ctx.ellipse(x - s * .02, y, s * .20, s * .10, 0, 0, Math.PI * 2);\n                ctx.fill();\n            }\n            if (symbol === \"nightthread\") {\n                ctx.fillStyle = plum;\n                roundRect(ctx, x - s * .22, y - s * .34, s * .44, s * .68, s * .08);\n                ctx.fill();\n                strokeCurrent(s * .04, darkWood);\n                ctx.strokeStyle = lavender;\n                ctx.lineWidth = s * .035;\n                for (let i = 0; i < 5; i++) {\n                    ctx.beginPath();\n                    ctx.moveTo(x - s * .20, y - s * .22 + i * s * .11);\n                    ctx.quadraticCurveTo(x, y - s * .15 + i * s * .09, x + s * .20, y - s * .22 + i * s * .11);\n                    ctx.stroke();\n                }\n                ctx.fillStyle = gold;\n                ctx.beginPath();\n                ctx.arc(x, y, s * .065, 0, Math.PI * 2);\n                ctx.fill();\n            }\n            ctx.restore();\n        }\n        function drawTile(t) {\n            if (t.removed)\n                return;\n            const alpha = t.removing > 0 ? clamp(t.removing, 0, 1) : 1;\n            const cx = t.sx + t.w \/ 2;\n            const cy = t.sy + t.h \/ 2;\n            const scale = t.removing > 0 ? .82 + alpha * .18 : 1;\n            const wiggle = t.deny > 0 ? Math.sin(t.deny * 28) * 5 : 0;\n            ctx.save();\n            ctx.globalAlpha = alpha;\n            ctx.translate(cx + wiggle, cy);\n            ctx.scale(scale, scale);\n            ctx.translate(-cx, -cy);\n            const free = isFree(t);\n            const selected = t.selected;\n            const hint = (_wpLeft41 => _wpLeft41 ? state.hintTimer > 0 : _wpLeft41)(state.hintIds.indexOf(t.id) !== -1);\n            const glow = Math.max(t.unlockGlow, hint ? .7 : 0, selected ? 1 : 0);\n            if (glow > 0) {\n                ctx.save();\n                ctx.globalAlpha = .18 + glow * .28;\n                ctx.fillStyle = selected ? \"#fff1c9\" : \"#d8c4ff\";\n                roundRect(ctx, t.sx - 9, t.sy - 9, t.w + 18, t.h + 18, 18);\n                ctx.fill();\n                ctx.restore();\n            }\n            if (t.wrong > 0 || t.deny > 0) {\n                ctx.save();\n                ctx.globalAlpha = Math.max(t.wrong, t.deny) * .30;\n                ctx.fillStyle = \"#d86d6d\";\n                roundRect(ctx, t.sx - 7, t.sy - 7, t.w + 14, t.h + 14, 16);\n                ctx.fill();\n                ctx.restore();\n            }\n            ctx.save();\n            ctx.fillStyle = \"rgba(13,9,25,.32)\";\n            roundRect(ctx, t.sx + t.w * .10, t.sy + t.h * .11, t.w, t.h, 15);\n            ctx.fill();\n            ctx.restore();\n            ctx.save();\n            const depthGrad = ctx.createLinearGradient(0, t.sy, 0, t.sy + t.h);\n            if (free) {\n                depthGrad.addColorStop(0, \"#8d6a50\");\n                depthGrad.addColorStop(1, \"#4b302e\");\n            }\n            else {\n                depthGrad.addColorStop(0, \"#5c4c6e\");\n                depthGrad.addColorStop(1, \"#302641\");\n            }\n            ctx.fillStyle = depthGrad;\n            roundRect(ctx, t.sx + t.w * .075, t.sy + t.h * .075, t.w, t.h, 15);\n            ctx.fill();\n            ctx.restore();\n            const bodyGrad = ctx.createLinearGradient(0, t.sy, 0, t.sy + t.h);\n            if (free) {\n                bodyGrad.addColorStop(0, \"#fffaf0\");\n                bodyGrad.addColorStop(.56, \"#fff1c9\");\n                bodyGrad.addColorStop(1, \"#d8c4ff\");\n            }\n            else {\n                bodyGrad.addColorStop(0, \"#a89abf\");\n                bodyGrad.addColorStop(.60, \"#817198\");\n                bodyGrad.addColorStop(1, \"#5b4b72\");\n            }\n            ctx.fillStyle = bodyGrad;\n            roundRect(ctx, t.sx, t.sy, t.w, t.h, 15);\n            ctx.fill();\n            ctx.save();\n            ctx.globalAlpha = free ? .46 : .22;\n            ctx.fillStyle = \"#ffffff\";\n            roundRect(ctx, t.sx + t.w * .10, t.sy + t.h * .08, t.w * .80, t.h * .13, 8);\n            ctx.fill();\n            ctx.restore();\n            ctx.strokeStyle = selected ? \"#f4c542\" : (free ? \"#4b302e\" : \"#3b3151\");\n            ctx.lineWidth = selected ? 4 : 3;\n            roundRect(ctx, t.sx + 1.5, t.sy + 1.5, t.w - 3, t.h - 3, 14);\n            ctx.stroke();\n            ctx.strokeStyle = free ? \"rgba(244,197,66,.74)\" : \"rgba(216,196,255,.35)\";\n            ctx.lineWidth = 1.5;\n            roundRect(ctx, t.sx + 6, t.sy + 7, t.w - 12, t.h - 14, 9);\n            ctx.stroke();\n            ctx.save();\n            ctx.globalAlpha = free ? .35 : .18;\n            ctx.strokeStyle = free ? \"#6f4c91\" : \"#d8c4ff\";\n            ctx.lineWidth = 1;\n            ctx.setLineDash([3, 5]);\n            roundRect(ctx, t.sx + 10, t.sy + 11, t.w - 20, t.h - 22, 7);\n            ctx.stroke();\n            ctx.setLineDash([]);\n            ctx.restore();\n            ctx.save();\n            ctx.globalAlpha = free ? 1 : .58;\n            drawSymbol(t.symbol, t.sx + t.w \/ 2, t.sy + t.h \/ 2, Math.min(t.w, t.h) * .58);\n            ctx.restore();\n            ctx.restore();\n        }\n        function drawParticles() {\n            state.particles.forEach(function (p) {\n                const a = clamp(p.life \/ p.max, 0, 1);\n                ctx.save();\n                ctx.globalAlpha = a;\n                ctx.fillStyle = p.color;\n                ctx.beginPath();\n                ctx.arc(p.x, p.y, p.size * (.4 + a * .8), 0, Math.PI * 2);\n                ctx.fill();\n                ctx.restore();\n            });\n        }\n        function drawFloats() {\n            state.floats.forEach(function (f) {\n                const a = clamp(f.life \/ f.max, 0, 1);\n                ctx.save();\n                ctx.globalAlpha = a;\n                ctx.fillStyle = f.color;\n                ctx.font = \"900 24px Arial, Helvetica, sans-serif\";\n                ctx.textAlign = \"center\";\n                ctx.textBaseline = \"middle\";\n                ctx.shadowColor = \"rgba(10,12,30,.34)\";\n                ctx.shadowBlur = 8;\n                ctx.fillText(f.text, f.x, f.y);\n                ctx.restore();\n            });\n        }\n        function drawLevelIntro() {\n            const a = Math.min(1, state.levelIntro);\n            ctx.save();\n            ctx.globalAlpha = clamp(a, 0, 1) * .92;\n            ctx.fillStyle = \"rgba(23,18,38,.36)\";\n            ctx.fillRect(0, 0, W, H);\n            ctx.textAlign = \"center\";\n            ctx.textBaseline = \"middle\";\n            ctx.fillStyle = \"#fff1c9\";\n            ctx.font = \"900 42px system-ui, -apple-system, Segoe UI, sans-serif\";\n            ctx.shadowColor = \"rgba(255,241,201,.34)\";\n            ctx.shadowBlur = 14;\n            ctx.fillText(\"LEVEL \" + state.level, W \/ 2, H \/ 2 - 24);\n            ctx.font = \"800 25px system-ui, -apple-system, Segoe UI, sans-serif\";\n            ctx.fillStyle = \"rgba(216,196,255,.92)\";\n            ctx.shadowBlur = 0;\n            ctx.fillText(\"MATCH OPEN MOON TILES ONLY\", W \/ 2, H \/ 2 + 24);\n            ctx.restore();\n        }\n        function draw() {\n            computeTileRects();\n            drawBackground();\n            drawBoardPanel();\n            const drawList = state.tiles.slice().sort(function (a, b) {\n                if (a.z !== b.z)\n                    return a.z - b.z;\n                if (a.y !== b.y)\n                    return a.y - b.y;\n                return a.x - b.x;\n            });\n            drawList.forEach(drawTile);\n            drawParticles();\n            drawFloats();\n            if ((_wpLeft42 => _wpLeft42 ? state.mode === \"playing\" : _wpLeft42)(state.levelIntro > 0)) {\n                drawLevelIntro();\n            }\n        }\n        let gameVisible = true;\n        if ('IntersectionObserver' in window) {\n            const visibilityObserver = new IntersectionObserver(function (entries) {\n                gameVisible = entries.some(function (item) { return item.isIntersecting; });\n                lastTime = performance.now();\n            }, { threshold: .05 });\n            visibilityObserver.observe(shell);\n        }\n        document.addEventListener('visibilitychange', function () { lastTime = performance.now(); });\n        function loop(now) {\n            const dt = Math.min(.05, (now - lastTime) \/ 1000);\n            lastTime = now;\n            if ((_wpLeft43 => _wpLeft43 ? gameVisible : _wpLeft43)(!document.hidden)) {\n                update(dt);\n                draw();\n            }\n            requestAnimationFrame(loop);\n        }\n        soundBtn.addEventListener(\"click\", function () {\n            state.muted = !state.muted;\n            storageSet(KEY_MUTE, state.muted ? \"1\" : \"0\");\n            sound(\"button\");\n            updateHud();\n        });\n        pauseBtn.addEventListener(\"click\", function () {\n            sound(\"button\");\n            if (state.mode === \"playing\")\n                showPausePopup();\n            else if (state.mode === \"paused\") {\n                hideOverlay();\n                state.mode = \"playing\";\n            }\n        });\n        fullBtn.addEventListener('click', function () {\n            sound('button');\n            if (fallbackFullscreen) {\n                fallbackExit();\n                unlockOrientation();\n            }\n            else if (document.fullscreenElement === shell) {\n                Promise.resolve(document.exitFullscreen()).catch(function () { }).finally(unlockOrientation);\n            }\n            else if (typeof shell.requestFullscreen === 'function') {\n                Promise.resolve().then(function () { return shell.requestFullscreen(); }).then(function () {\n                    tryOrientationLock();\n                    updateFullscreenClass();\n                }).catch(function () { fallbackEnter(); });\n            }\n            else {\n                fallbackEnter();\n            }\n        });\n        hintBtn.addEventListener(\"click\", function () {\n            sound(\"button\");\n            useHint();\n        });\n        shuffleBtn.addEventListener(\"click\", function () {\n            sound(\"button\");\n            if (state.mode === \"playing\")\n                shuffleTiles(false);\n        });\n        canvas.addEventListener(\"pointerdown\", onCanvasDown);\n        document.addEventListener(\"fullscreenchange\", updateFullscreenClass);\n        window.addEventListener(\"resize\", updateFullscreenClass);\n        window.addEventListener(\"orientationchange\", function () {\n            setTimeout(updateFullscreenClass, 250);\n        });\n        window.addEventListener(\"beforeunload\", saveProgress);\n        document.addEventListener('keydown', function (ev) {\n            const el = ev.target;\n            if ((_wpLeft44 => _wpLeft44 ? (el.isContentEditable || \/^(INPUT|TEXTAREA|SELECT)$\/.test(el.tagName || '')) : _wpLeft44)(el))\n                return;\n            if ((_wpLeft47 => _wpLeft47 ? !shell.classList.contains('clm01-fallback-full') : _wpLeft47)((_wpLeft46 => _wpLeft46 ? document.fullscreenElement !== shell : _wpLeft46)((_wpLeft45 => _wpLeft45 ? !shell.contains(document.activeElement) : _wpLeft45)(document.activeElement !== shell))))\n                return;\n            if ((_wpLeft48 => _wpLeft48 ? fallbackFullscreen : _wpLeft48)(ev.key === 'Escape')) {\n                fallbackExit();\n                return;\n            }\n            if ((_wpLeft49 => _wpLeft49 ? state.mode === 'playing' : _wpLeft49)(ev.key === 'Escape'))\n                showPausePopup();\n            if ((_wpLeft50 => _wpLeft50 ? state.mode === \"playing\" : _wpLeft50)((ev.key === \"p\" || ev.key === \"P\"))) {\n                showPausePopup();\n            }\n            if ((ev.key === \"m\" || ev.key === \"M\")) {\n                state.muted = !state.muted;\n                storageSet(KEY_MUTE, state.muted ? \"1\" : \"0\");\n                updateHud();\n            }\n        });\n        resizeCanvas();\n        updateHud();\n        showStartPopup();\n        requestAnimationFrame(loop);\n    }\n    if (document.readyState === 'loading') {\n        document.addEventListener('DOMContentLoaded', initializeMoonboundWP, { once: true });\n    }\n    else {\n        initializeMoonboundWP();\n    }\n})();\n\n\n<\/script>\n\n\n\n<h2 class=\"wp-block-heading\">Clear Tiles, Follow the Glow, and Unlock the Workshop<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Every level brings a new arrangement of layered tiles. As you progress, the boards grow larger, the layouts become more thoughtful, and the timer asks you to stay focused. Matching pairs rewards you with score, combo effects, and small bursts of glowing lunar particles.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The atmosphere stays calm and comfortable even when the challenge rises. The creamy moon colors, warm wooden details, soft clouds, and cute rabbit-inspired symbols create a relaxing puzzle experience that still keeps your mind awake.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Why You Will Love Mochi Moonbound<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Mochi Moonbound<\/strong> is perfect for players who enjoy relaxing tile-matching games with cute visuals and light strategy. It is easy to learn, satisfying to play, and designed for both short breaks and longer puzzle sessions.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">With level progression, timer-based challenge, hint support, shuffle mode, save and continue, fullscreen support, sound control, and a dreamy Moon Rabbit Workshop theme, this game turns a classic matching puzzle into a small magical escape above the clouds.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Mochi Moonbound is a cozy tile-matching puzzle game set inside a moon rabbit workshop floating above the night clouds. Under soft creamy moonlight, tiny rabbits arrange magical tiles filled with mochi cakes, wooden mallets, moon lanterns, soft clouds, tiny toolboxes, crescent charms, and handmade lunar treasures. Your task is to find matching open tiles and &hellip;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[12],"tags":[],"class_list":["post-114","post","type-post","status-publish","format-standard","hentry","category-puzzle"],"_links":{"self":[{"href":"https:\/\/play.produsenpagarbrc.com\/index.php?rest_route=\/wp\/v2\/posts\/114","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/play.produsenpagarbrc.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/play.produsenpagarbrc.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/play.produsenpagarbrc.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/play.produsenpagarbrc.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=114"}],"version-history":[{"count":0,"href":"https:\/\/play.produsenpagarbrc.com\/index.php?rest_route=\/wp\/v2\/posts\/114\/revisions"}],"wp:attachment":[{"href":"https:\/\/play.produsenpagarbrc.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=114"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/play.produsenpagarbrc.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=114"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/play.produsenpagarbrc.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=114"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}