Spaces:
Running
Running
| /* Semantique — paper & ink. */ | |
| .sq-root { | |
| --paper: #faf8f2; | |
| --ink: #1c1b18; | |
| --ink-soft: #5a564c; | |
| --accent: #b3402e; | |
| /* The board is always paper-light; Gradio's ".prose *" rule sets | |
| color: var(--body-text-color), which flips to white in browser dark | |
| mode, so pin the var to ink here. No backticks in this file: Gradio | |
| evals css_template inside a JS template literal. */ | |
| --body-text-color: var(--ink); | |
| position: relative; | |
| width: 100%; | |
| height: 100vh; | |
| height: 100dvh; /* dynamic vh: don't get clipped by mobile browser chrome */ | |
| overflow: hidden; | |
| background: var(--paper); | |
| color: var(--ink); | |
| font-family: "Patrick Hand", cursive; | |
| user-select: none; | |
| /* swipes drive the game — stop the browser from scrolling/zooming on them */ | |
| touch-action: none; | |
| overscroll-behavior: none; | |
| /* identity filter + transition so the glitch flip (below) eases in smoothly | |
| from light instead of snapping — interpolates to invert(1) hue-rotate(180) */ | |
| filter: invert(0) hue-rotate(0deg); | |
| transition: filter 0.85s ease-in-out; | |
| } | |
| /* faint ruled-notebook lines, like the sketch */ | |
| .sq-root::before { | |
| content: ""; | |
| position: absolute; | |
| inset: 0; | |
| background: repeating-linear-gradient( | |
| to bottom, | |
| transparent 0 31px, | |
| rgba(110, 140, 170, 0.13) 31px 32px | |
| ); | |
| pointer-events: none; | |
| } | |
| .sq-stage { | |
| position: absolute; | |
| left: 0; | |
| right: 0; | |
| /* The board centres in the stage, so its on-screen size tracks the stage | |
| height. To keep every board's grid the SAME size, drop the stage by a | |
| constant baseline (~a two-row checklist) regardless of this board's HUD: | |
| otherwise a board with a short checklist (the bonus board's 3 chips fit one | |
| row) keeps a taller stage and frames its grid bigger than the full-checklist | |
| boards, riding up over the sentence. Keep growing past the baseline as the | |
| checklist wraps even taller on narrow windows, so the sentence never lands | |
| on the board. */ | |
| top: max(90px, calc((var(--hud-h, 70px) - 70px) * 1.6)); | |
| /* Reserve the bottom band the hint line lives in: the board reframes into the | |
| shorter stage, so tiles never render under "arrow keys / WASD to hop". */ | |
| bottom: calc(max(18px, env(safe-area-inset-bottom)) + 34px); | |
| } | |
| .sq-stage canvas { | |
| display: block; | |
| } | |
| /* ---- HUD ---- */ | |
| .sq-hud { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| display: flex; | |
| align-items: baseline; | |
| justify-content: space-between; | |
| gap: 22px; | |
| padding: max(18px, env(safe-area-inset-top)) 28px 0; | |
| pointer-events: none; | |
| z-index: 2; | |
| } | |
| .sq-title { | |
| font-family: "Caveat", cursive; | |
| font-size: 28px; | |
| font-weight: 700; | |
| color: var(--ink-soft); | |
| transform: rotate(-2deg); | |
| } | |
| .sq-targets { | |
| font-family: "Caveat", cursive; | |
| font-size: 28px; | |
| font-weight: 700; | |
| color: var(--ink-soft); | |
| } | |
| .sq-target-list { | |
| display: inline-flex; | |
| flex-wrap: wrap; | |
| align-items: baseline; | |
| justify-content: center; | |
| gap: 6px 8px; | |
| margin-left: 6px; | |
| font-size: 32px; | |
| } | |
| .sq-target-item { | |
| position: relative; | |
| display: inline-block; | |
| padding: 0 8px 2px; | |
| border: 2px solid var(--ink); | |
| border-radius: 255px 25px 225px 25px / 25px 225px 25px 255px; | |
| color: var(--ink); | |
| transform: rotate(var(--tilt, -1.5deg)); | |
| } | |
| /* checked: fade the word and strike it through */ | |
| .sq-target-item.sq-checked { | |
| color: var(--ink-soft); | |
| border-color: var(--ink-soft); | |
| opacity: 0.65; | |
| } | |
| .sq-target-item.sq-checked::after { | |
| content: ""; | |
| position: absolute; | |
| left: 7px; | |
| right: 7px; | |
| top: 52%; | |
| height: 2.5px; | |
| background: var(--ink-soft); | |
| transform: rotate(-4deg); | |
| } | |
| /* hops: a "N/budget" counter */ | |
| .sq-context { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| .sq-context-count { | |
| font-family: "Caveat", cursive; | |
| font-size: 30px; | |
| font-weight: 700; | |
| min-width: 52px; | |
| } | |
| .sq-context-count.sq-full { | |
| color: var(--accent); | |
| } | |
| /* ---- audio toggles (music + sfx), grouped with the hop counter ---- */ | |
| .sq-hud-right { | |
| display: flex; | |
| align-items: center; | |
| gap: 16px; | |
| } | |
| .sq-audio { | |
| position: relative; /* anchors the mixer popover */ | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| pointer-events: auto; /* the HUD is pointer-events:none — re-enable hits here */ | |
| } | |
| .sq-audio-btn { | |
| display: block; | |
| width: 30px; | |
| height: 30px; | |
| padding: 0; | |
| background: transparent; | |
| border: none; | |
| cursor: pointer; | |
| line-height: 0; | |
| } | |
| .sq-audio-btn canvas { | |
| display: block; | |
| width: 30px; | |
| height: 30px; | |
| } | |
| /* mixer popover: two hand-drawn volume sliders, one per channel */ | |
| .sq-audio-pop { | |
| position: absolute; | |
| top: calc(100% + 8px); | |
| right: 0; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 10px; | |
| padding: 12px 14px; | |
| background: #fffdf7; | |
| border: 2.5px solid var(--ink); | |
| border-radius: 18px 7px 20px 7px / 7px 20px 7px 18px; | |
| box-shadow: 3px 4px 0 rgba(28, 27, 24, 0.12); | |
| z-index: 5; | |
| } | |
| .sq-vol-row { | |
| display: flex; | |
| align-items: center; | |
| gap: 9px; | |
| } | |
| .sq-vol-icon { | |
| flex: none; | |
| width: 24px; | |
| height: 24px; | |
| padding: 0; | |
| background: transparent; | |
| border: none; | |
| cursor: pointer; | |
| line-height: 0; | |
| } | |
| .sq-vol-icon canvas { | |
| display: block; | |
| width: 24px; | |
| height: 24px; | |
| } | |
| .sq-slider { | |
| width: 120px; /* must equal SLIDER_W in game.js — the drag math assumes it */ | |
| height: 26px; | |
| cursor: pointer; | |
| touch-action: none; /* dragging the nib must not scroll the page */ | |
| } | |
| .sq-slider canvas { | |
| display: block; | |
| width: 120px; | |
| height: 26px; | |
| } | |
| /* ---- hint bulb (boards 1 & 3): a line-drawn lightbulb + its nudge bubble ---- */ | |
| .sq-help { | |
| position: relative; /* anchors the hint bubble */ | |
| display: flex; | |
| align-items: center; | |
| pointer-events: auto; /* the HUD is pointer-events:none — re-enable hits here */ | |
| } | |
| .sq-help-btn { | |
| display: block; | |
| width: 40px; | |
| height: 40px; | |
| padding: 0; | |
| background: transparent; | |
| border: none; | |
| cursor: pointer; | |
| line-height: 0; | |
| } | |
| .sq-help-btn canvas { | |
| display: block; | |
| width: 40px; | |
| height: 40px; | |
| } | |
| /* the hint modal: a confirm step ("view the hint?") that, on yes, reveals one | |
| nudge. Modelled on the welcome card so it reads as the same hand-drawn note. */ | |
| .sq-hintbox { | |
| position: absolute; | |
| inset: 0; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| background: rgba(250, 248, 242, 0.82); | |
| z-index: 4; | |
| } | |
| .sq-hintbox-card { | |
| position: relative; | |
| width: min(420px, 86vw); | |
| padding: 28px 32px 28px; | |
| background: #fffdf7; | |
| border: 3px solid var(--ink); | |
| border-radius: 235px 25px 245px 20px / 25px 235px 20px 245px; | |
| box-shadow: 4px 5px 0 rgba(28, 27, 24, 0.12); | |
| transform: rotate(-1deg); | |
| text-align: center; | |
| } | |
| .sq-hintbox-q { | |
| font-family: "Caveat", cursive; | |
| font-size: 34px; | |
| font-weight: 700; | |
| line-height: 1.3; | |
| text-wrap: balance; | |
| color: var(--ink); | |
| margin-bottom: 22px; | |
| } | |
| .sq-hintbox-text { | |
| font-family: "Patrick Hand", cursive; | |
| font-size: 24px; | |
| line-height: 1.45; | |
| text-wrap: balance; | |
| color: var(--ink); | |
| margin-bottom: 22px; | |
| } | |
| .sq-hintbox-actions { | |
| display: flex; | |
| justify-content: center; | |
| gap: 16px; | |
| } | |
| .sq-hintbox-yes, | |
| .sq-hintbox-no, | |
| .sq-hintbox-close { | |
| font-family: "Patrick Hand", cursive; | |
| font-size: 21px; | |
| color: var(--ink); | |
| background: transparent; | |
| border: 2.5px solid var(--ink); | |
| border-radius: 255px 15px 225px 15px / 15px 225px 15px 255px; | |
| padding: 5px 24px 7px; | |
| cursor: pointer; | |
| transform: rotate(-0.6deg); | |
| } | |
| .sq-hintbox-no { transform: rotate(0.8deg); } | |
| .sq-hintbox-yes:hover, | |
| .sq-hintbox-no:hover, | |
| .sq-hintbox-close:hover { background: var(--ink); color: var(--paper); } | |
| /* ---- prompt strip ---- */ | |
| .sq-prompt { | |
| position: absolute; | |
| left: 0; | |
| right: 0; | |
| /* Sit a fixed gap below the target checklist. The HUD grows taller as the | |
| checklist wraps on narrower windows, so track its measured height (--hud-h, | |
| published by game.js) instead of a fixed offset — the sentence then clears | |
| the targets at every width, not just the widest. */ | |
| top: calc(var(--hud-h, 69px) + 36px); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; /* sentence grows from the centre */ | |
| flex-wrap: wrap; | |
| gap: 8px; | |
| padding: 6px 28px 0; | |
| font-size: 22px; | |
| z-index: 2; | |
| } | |
| .sq-chips { | |
| display: inline-flex; | |
| flex-wrap: wrap; | |
| align-items: baseline; | |
| justify-content: center; | |
| gap: 14px; | |
| } | |
| .sq-chip { | |
| display: inline-block; | |
| font-family: "Caveat", cursive; | |
| font-size: 42px; | |
| font-weight: 700; | |
| line-height: 1; | |
| color: var(--ink); | |
| transform: rotate(var(--tilt, 0deg)); | |
| } | |
| /* a context-bomb file dumped into the prompt: raw monospace bytes, clamped to | |
| one line so the "flood" reads without smearing across the screen */ | |
| .sq-chip-file { | |
| font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace ; | |
| font-size: 16px ; | |
| font-weight: 400 ; | |
| line-height: 1.3; | |
| color: var(--accent); | |
| max-width: min(70vw, 620px); | |
| white-space: nowrap; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| vertical-align: middle; | |
| } | |
| .sq-chip-text { | |
| display: inline-block; | |
| /* The write-on (game.js) clips this box left→right. We clip with a plain | |
| inset(0) — Safari clamps negative clip-path insets to 0 and won't repaint a | |
| cleared clip-path, so any negative-inset reveal shaves Caveat's slant / | |
| ascenders / descenders until a reflow. Pad the ink box and cancel the pad | |
| in layout, so inset(0) always wraps every glyph with room to spare. */ | |
| padding: 0.25em 0.35em 0.2em 0; | |
| margin: -0.25em -0.35em -0.2em 0; | |
| } | |
| /* ---- hint ---- */ | |
| .sq-hint { | |
| position: absolute; | |
| bottom: max(18px, env(safe-area-inset-bottom)); | |
| left: 0; | |
| right: 0; | |
| text-align: center; | |
| font-size: 19px; | |
| color: var(--ink-soft); | |
| z-index: 2; | |
| pointer-events: none; | |
| } | |
| /* ---- verdict overlay ---- */ | |
| .sq-overlay { | |
| position: absolute; | |
| inset: 0; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| background: rgba(250, 248, 242, 0.82); | |
| z-index: 3; | |
| } | |
| .sq-card { | |
| position: relative; | |
| width: min(480px, 86vw); | |
| padding: 30px 34px 26px; | |
| background: #fffdf7; | |
| border: 3px solid var(--ink); | |
| border-radius: 235px 25px 245px 20px / 25px 235px 20px 245px; | |
| box-shadow: 4px 5px 0 rgba(28, 27, 24, 0.12); | |
| transform: rotate(-1deg); | |
| text-align: center; | |
| } | |
| .sq-card-sentence { | |
| font-family: "Caveat", cursive; | |
| font-size: 34px; | |
| font-weight: 600; | |
| margin-bottom: 18px; | |
| min-height: 40px; | |
| } | |
| /* file-bomb death card: a caption + the overrun bytes in monospace. | |
| margin-top drops the caption clear of the "context window exceeded" | |
| stamp, which dips into the top-right corner. */ | |
| .sq-card-dumpcap { | |
| font-family: "Caveat", cursive; | |
| font-weight: 600; | |
| font-size: 26px; | |
| margin-top: 20px; | |
| margin-bottom: 8px; | |
| } | |
| .sq-card-dump { | |
| font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; | |
| font-size: 13px; | |
| line-height: 1.35; | |
| color: var(--accent); | |
| white-space: nowrap; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| border: 2px dashed var(--ink-soft); | |
| border-radius: 8px; | |
| padding: 6px 8px; | |
| margin-bottom: 4px; | |
| } | |
| /* file-specific aside under the dump — the game's reaction to the file */ | |
| .sq-card-tagline { | |
| font-family: "Caveat", cursive; | |
| font-size: 22px; | |
| color: var(--ink-soft); | |
| margin-top: 8px; | |
| } | |
| .sq-bars { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 10px; | |
| margin-bottom: 8px; | |
| } | |
| .sq-bar-row { | |
| display: grid; | |
| grid-template-columns: 76px 1fr 52px; | |
| gap: 10px; | |
| align-items: center; | |
| font-size: 20px; | |
| } | |
| .sq-bar-label { text-align: right; } | |
| .sq-bar-track { | |
| height: 18px; | |
| border: 2px solid var(--ink); | |
| border-radius: 155px 15px 145px 15px / 15px 145px 15px 155px; | |
| overflow: hidden; | |
| background: rgba(255, 255, 255, 0.7); | |
| } | |
| .sq-bar-fill { | |
| height: 100%; | |
| width: 0%; | |
| background: repeating-linear-gradient( | |
| -55deg, | |
| var(--ink-soft) 0 2px, | |
| transparent 2px 6px | |
| ); | |
| } | |
| .sq-bar-row.sq-bar-target .sq-bar-fill { | |
| background: repeating-linear-gradient( | |
| -55deg, | |
| var(--ink) 0 2.5px, | |
| transparent 2.5px 5.5px | |
| ); | |
| } | |
| .sq-bar-pct { text-align: left; color: var(--ink-soft); } | |
| .sq-stamp { | |
| position: absolute; | |
| top: -22px; | |
| right: -18px; | |
| font-family: "Caveat", cursive; | |
| font-size: 40px; | |
| font-weight: 700; | |
| padding: 0 16px; | |
| border: 4px solid var(--accent); | |
| border-radius: 12px; | |
| color: var(--accent); | |
| background: rgba(255, 253, 247, 0.9); | |
| transform: rotate(8deg); | |
| opacity: 0; | |
| } | |
| .sq-stamp.sq-win { --accent: #2c6e3f; border-color: #2c6e3f; color: #2c6e3f; } | |
| .sq-card-actions { margin-top: 14px; } | |
| .sq-card-actions button { | |
| font-family: "Patrick Hand", cursive; | |
| font-size: 21px; | |
| color: var(--ink); | |
| background: transparent; | |
| border: 2.5px solid var(--ink); | |
| border-radius: 255px 15px 225px 15px / 15px 225px 15px 255px; | |
| padding: 4px 20px 6px; | |
| cursor: pointer; | |
| transform: rotate(0.5deg); | |
| } | |
| .sq-card-actions button:hover { | |
| background: var(--ink); | |
| color: var(--paper); | |
| } | |
| /* ---- welcome modal ---- */ | |
| /* Same paper-and-ink card as the verdict overlay, shown once on game open. */ | |
| .sq-welcome { | |
| position: absolute; | |
| inset: 0; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| background: rgba(250, 248, 242, 0.82); | |
| z-index: 4; /* above the verdict overlay (3), though they never coexist */ | |
| } | |
| .sq-welcome-card { | |
| position: relative; | |
| width: min(440px, 86vw); | |
| padding: 30px 34px 30px; | |
| background: #fffdf7; | |
| border: 3px solid var(--ink); | |
| border-radius: 235px 25px 245px 20px / 25px 235px 20px 245px; | |
| box-shadow: 4px 5px 0 rgba(28, 27, 24, 0.12); | |
| transform: rotate(-1deg); | |
| text-align: center; | |
| } | |
| .sq-welcome-title { | |
| font-family: "Caveat", cursive; | |
| font-size: 38px; | |
| font-weight: 700; | |
| line-height: 1.3; /* room for the title to wrap onto two lines */ | |
| text-wrap: balance; /* even out the two wrapped lines */ | |
| color: var(--ink); | |
| margin-bottom: 16px; | |
| } | |
| /* "targets" boxed like the real HUD target chips (.sq-target-item) — kept | |
| light (thin rim, slight tilt) so it echoes the chips instead of shouting */ | |
| .sq-welcome-target { | |
| display: inline-block; | |
| padding: 0 8px 1px; | |
| border: 2px solid var(--ink); | |
| border-radius: 255px 25px 225px 25px / 25px 225px 25px 255px; | |
| transform: rotate(-1deg); | |
| } | |
| .sq-welcome-body { | |
| font-family: "Patrick Hand", cursive; | |
| font-size: 21px; | |
| line-height: 1.5; | |
| color: var(--ink-soft); | |
| margin-bottom: 22px; | |
| } | |
| /* the close button: a small carriage-return keycap drawn like the submit tile — | |
| soft ink, a dashed "press me" rim, and the ⏎ glyph centred. */ | |
| .sq-welcome-close { | |
| display: inline-flex; | |
| align-items: center; | |
| justify-content: center; | |
| width: 66px; | |
| height: 66px; | |
| padding: 0 0 4px; | |
| font-family: "Patrick Hand", cursive; | |
| font-size: 42px; | |
| line-height: 1; | |
| color: var(--ink-soft); | |
| background: #faf8f2; | |
| border: 3px dashed var(--ink-soft); | |
| border-radius: 18px 7px 20px 7px / 7px 20px 7px 18px; | |
| box-shadow: 3px 4px 0 rgba(28, 27, 24, 0.12); | |
| cursor: pointer; | |
| transform: rotate(-1.5deg); | |
| } | |
| .sq-welcome-close:hover { | |
| color: var(--ink); | |
| border-color: var(--ink); | |
| } | |
| .sq-welcome-close:active { | |
| transform: rotate(-1.5deg) translate(3px, 4px); /* press the keycap down */ | |
| box-shadow: 0 0 0 rgba(28, 27, 24, 0.12); | |
| } | |
| /* ---- you-win modal ---- */ | |
| /* Shown over the bonus board once build + small + hackathon are all collected. | |
| It inherits the glitch invert (the board is inverted when this fires). */ | |
| .sq-victory { | |
| position: absolute; | |
| inset: 0; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| background: rgba(250, 248, 242, 0.82); | |
| z-index: 6; /* above the verdict (3) and welcome (4) */ | |
| } | |
| .sq-victory-card { | |
| position: relative; | |
| width: min(460px, 86vw); | |
| padding: 34px 34px 30px; | |
| background: #fffdf7; | |
| border: 3px solid var(--ink); | |
| border-radius: 245px 18px 235px 22px / 20px 240px 18px 245px; | |
| box-shadow: 5px 6px 0 rgba(28, 27, 24, 0.14); | |
| transform: rotate(-1.2deg); | |
| text-align: center; | |
| } | |
| .sq-victory-title { | |
| font-family: "Caveat", cursive; | |
| font-size: 52px; | |
| font-weight: 700; | |
| line-height: 1.05; | |
| margin-bottom: 14px; | |
| /* an animated rainbow sweeping through the letters (the @keyframes lives in | |
| app.py's un-scoped BLOCKS_CSS). Stays a rainbow under the glitch invert. */ | |
| background: linear-gradient( | |
| 90deg, #ff004d, #ff8a00, #ffe000, #36d800, #00b3ff, #8b5cf6, #ff004d | |
| ); | |
| background-size: 200% 100%; | |
| -webkit-background-clip: text; | |
| background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| color: transparent; | |
| animation: sq-rainbow 3s linear infinite; | |
| } | |
| .sq-victory-body { | |
| font-family: "Patrick Hand", cursive; | |
| font-size: 21px; | |
| line-height: 1.5; | |
| color: var(--ink-soft); | |
| margin-bottom: 18px; | |
| } | |
| /* The run tally — a tidy ledger of the session, ruled like a notebook line. */ | |
| .sq-victory-stats { | |
| width: min(300px, 100%); | |
| margin: 0 auto 22px; | |
| text-align: left; | |
| font-family: "Patrick Hand", cursive; | |
| } | |
| .sq-stat { | |
| display: flex; | |
| align-items: baseline; | |
| justify-content: space-between; | |
| gap: 12px; | |
| padding: 5px 2px; | |
| border-bottom: 2px dotted rgba(28, 27, 24, 0.18); | |
| } | |
| .sq-stat:last-child { border-bottom: 0; } | |
| .sq-stat dt { | |
| margin: 0; | |
| font-size: 19px; | |
| color: var(--ink-soft); | |
| } | |
| .sq-stat dd { | |
| margin: 0; | |
| font-size: 22px; | |
| color: var(--ink); | |
| } | |
| .sq-victory-close { | |
| font-family: "Patrick Hand", cursive; | |
| font-size: 21px; | |
| color: var(--ink); | |
| background: transparent; | |
| border: 2.5px solid var(--ink); | |
| border-radius: 255px 15px 225px 15px / 15px 225px 15px 255px; | |
| padding: 5px 22px 7px; | |
| cursor: pointer; | |
| transform: rotate(0.5deg); | |
| } | |
| .sq-victory-close:hover { background: var(--ink); color: var(--paper); } | |
| /* The "delete all data" 404. Sits above every other modal; like the victory card | |
| it inherits the glitch invert (it only ever fires on the bonus board), reading | |
| as a dark system-error dialog over the negated screen. */ | |
| .sq-errorbox { | |
| position: absolute; | |
| inset: 0; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| background: rgba(250, 248, 242, 0.82); | |
| z-index: 7; /* top of the stack — above the win modal (6) */ | |
| } | |
| .sq-errorbox-card { | |
| position: relative; | |
| width: min(440px, 86vw); | |
| padding: 30px 30px 26px; | |
| background: #fffdf7; | |
| border: 3px solid var(--ink); | |
| border-radius: 235px 20px 245px 18px / 18px 245px 20px 235px; | |
| box-shadow: 5px 6px 0 rgba(28, 27, 24, 0.14); | |
| transform: rotate(-1deg); | |
| text-align: center; | |
| } | |
| .sq-errorbox-title { | |
| font-family: "Caveat", cursive; | |
| font-size: 42px; | |
| font-weight: 700; | |
| line-height: 1.05; | |
| color: var(--ink); | |
| margin-bottom: 12px; | |
| } | |
| .sq-errorbox-body { | |
| font-family: "Patrick Hand", cursive; | |
| font-size: 21px; | |
| line-height: 1.45; | |
| color: var(--ink-soft); | |
| margin-bottom: 22px; | |
| } | |
| .sq-errorbox-ok { | |
| font-family: "Patrick Hand", cursive; | |
| font-size: 21px; | |
| color: var(--ink); | |
| background: transparent; | |
| border: 2.5px solid var(--ink); | |
| border-radius: 255px 15px 225px 15px / 15px 225px 15px 255px; | |
| padding: 5px 30px 7px; | |
| cursor: pointer; | |
| transform: rotate(0.6deg); | |
| } | |
| .sq-errorbox-ok:hover { background: var(--ink); color: var(--paper); } | |
| .sq-hidden { display: none ; } | |
| /* ---- glitch / bonus mode ---- */ | |
| /* The whole board photo-negates: a single filter on the root flips paper to | |
| black and every inked line to white (the WebGL canvas inverts along with the | |
| DOM), while the hue-rotate keeps the red accent roughly red instead of cyan. | |
| One declaration, and tiles/doodle/cards/HUD all come along for free. The | |
| @keyframes + the page-chrome darkening live in app.py's (un-scoped) BLOCKS_CSS, | |
| because this css_template is injected NESTED under the component id — global | |
| selectors and at-rules don't survive here, but .sq-root-anchored ones do. */ | |
| .sq-root.sq-glitch { | |
| filter: invert(1) hue-rotate(180deg); /* eased by the transition on .sq-root */ | |
| } | |
| /* faint moving scanlines over the negative, for that decoded-signal hum */ | |
| .sq-root.sq-glitch::after { | |
| content: ""; | |
| position: absolute; | |
| inset: 0; | |
| z-index: 1; | |
| pointer-events: none; | |
| background: repeating-linear-gradient( | |
| to bottom, | |
| rgba(0, 0, 0, 0) 0 2px, | |
| rgba(0, 0, 0, 0.05) 2px 3px | |
| ); | |
| animation: sq-scanline 7s linear infinite; | |
| } | |
| /* ---- compact / mobile ---- */ | |
| /* Phones (and short landscape viewports) only. The tablet/desktop single-row HUD | |
| holds well down to phone widths, so only here does the HUD WRAP: the title and | |
| the hop counter / audio cluster keep the first row (title left, counter right | |
| via space-between), and the checklist drops to its own full-width second row | |
| (order:3, flex-basis:100%) in a smaller font, where it wraps over fewer, wider | |
| lines. The wrapped HUD is a variable height, so game.js publishes it as --hud-h | |
| and the prompt strip + board drop below it (instead of colliding). */ | |
| @media (max-width: 560px), (max-height: 540px) { | |
| .sq-hud { padding-left: 12px; padding-right: 12px; gap: 8px 10px; flex-wrap: wrap; } | |
| .sq-title { font-size: 18px; order: 1; } | |
| .sq-targets { order: 3; flex: 1 1 100%; min-width: 0; } | |
| .sq-target-list { display: flex; width: 100%; gap: 5px 6px; margin-left: 0; font-size: 16px; } | |
| .sq-target-item { padding: 0 6px 1px; } | |
| .sq-context { gap: 4px; } | |
| .sq-context-count { font-size: 18px; min-width: 28px; } | |
| .sq-hud-right { order: 2; gap: 10px; } | |
| .sq-audio { gap: 4px; } | |
| .sq-audio-btn, .sq-audio-btn canvas { width: 24px; height: 24px; } | |
| .sq-help-btn, .sq-help-btn canvas { width: 32px; height: 32px; } | |
| /* drop the prompt + board below the variable-height wrapped checklist */ | |
| .sq-prompt { top: var(--hud-h, 52px); padding-left: 12px; padding-right: 12px; gap: 6px; } | |
| .sq-stage { top: calc(var(--hud-h, 52px) + 38px); } | |
| .sq-chips { gap: 9px; } | |
| .sq-chip { font-size: 26px; } | |
| .sq-hint { font-size: 16px; } | |
| .sq-card { padding: 24px 22px 22px; } | |
| .sq-card-sentence { font-size: 27px; } | |
| .sq-bar-row { grid-template-columns: 60px 1fr 44px; font-size: 17px; } | |
| .sq-stamp { font-size: 32px; top: -16px; right: -4px; } /* keep the sticker on-screen */ | |
| .sq-hintbox-card { width: min(380px, 88vw); padding: 22px 22px 24px; } | |
| .sq-hintbox-q { font-size: 29px; margin-bottom: 18px; } | |
| .sq-hintbox-text { font-size: 21px; margin-bottom: 18px; } | |
| .sq-welcome-card { width: min(400px, 88vw); padding: 24px 22px 26px; } | |
| .sq-welcome-title { font-size: 32px; } | |
| .sq-welcome-body { font-size: 18px; margin-bottom: 20px; } | |
| .sq-welcome-close { width: 56px; height: 56px; font-size: 34px; } | |
| .sq-errorbox-card { width: min(380px, 88vw); padding: 24px 22px 22px; } | |
| .sq-errorbox-title { font-size: 36px; margin-bottom: 10px; } | |
| .sq-errorbox-body { font-size: 18px; margin-bottom: 18px; } | |
| .sq-victory-stats { width: min(280px, 100%); margin-bottom: 18px; } | |
| .sq-stat dt { font-size: 17px; } | |
| .sq-stat dd { font-size: 20px; } | |
| } | |