
*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}
html,body{height:100%;overflow:hidden;background:#0a0a12;font-family:system-ui,-apple-system,sans-serif;touch-action:none;user-select:none;-webkit-user-select:none}
#app{position:fixed;inset:0;display:flex;flex-direction:column;align-items:center}
/* SNO-145. Every HUD dimension below is a multiple of --hud-scale, which resize() derives
   from the SHORTER viewport edge (see HUD_BASE/HUD_MAX in the script). The HUD was authored
   in fixed px against a ~360dp handset; on a 10" tablet the playfield roughly doubles and the
   HUD did not, so it read as a postage stamp. One factor, applied to all of it — type,
   tracking, buttons, padding, the combo bar and the width cap — because a HUD where the text
   grew and the buttons did not is a different and worse bug.
   The default is 1 and every value below then resolves to exactly the px it was before, so
   any device with a shorter edge <= 480dp (which is every phone, in either orientation)
   renders identically to 1.3. That is deliberate: 1.3 is the build on Anton's phone.
   Cost: one setProperty per resize, never per frame. tools/capture/hud-scale-check.js
   measures the rendered result at six viewports and fails if any of this drifts apart. */
:root{--hud-scale:1}
#hud{width:100%;max-width:calc(480px * var(--hud-scale));display:flex;justify-content:space-between;align-items:center;padding:max(calc(10px * var(--hud-scale)),env(safe-area-inset-top)) calc(14px * var(--hud-scale)) calc(6px * var(--hud-scale))}
.hb{text-align:center}
.hl{font-size:calc(10px * var(--hud-scale));letter-spacing:calc(1px * var(--hud-scale));text-transform:uppercase;color:#556}
.hv{font-size:calc(20px * var(--hud-scale));font-weight:700;color:#fff}
#cv{color:#a855f7}
#hud-btns{display:flex;gap:calc(8px * var(--hud-scale))}
.icon-btn{width:calc(38px * var(--hud-scale));height:calc(38px * var(--hud-scale));border-radius:calc(10px * var(--hud-scale));border:1px solid #232338;background:#12121e;color:#9aa;font-size:calc(16px * var(--hud-scale));display:flex;align-items:center;justify-content:center;cursor:pointer}
#combo-wrap{width:calc(100% - 28px * var(--hud-scale));max-width:calc(452px * var(--hud-scale));height:calc(4px * var(--hud-scale));background:#1a1a2e;border-radius:99px;margin:calc(2px * var(--hud-scale)) 0 calc(6px * var(--hud-scale));overflow:hidden}
#combo-bar{height:100%;width:0%;background:#a855f7;border-radius:99px}
#stage{position:relative;flex:1;width:100%;display:flex;align-items:center;justify-content:center;padding-bottom:env(safe-area-inset-bottom)}
canvas{display:block;border-radius:16px;background:#0a0a12;touch-action:none}
#overlay{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;background:rgba(10,10,18,0.92);z-index:5}
#overlay h1{font-size:32px;font-weight:800;color:#fff;letter-spacing:2px;margin-bottom:10px;text-shadow:0 0 24px rgba(168,85,247,0.6)}
#ol-score{display:none;font-size:16px;color:#a855f7;font-weight:600;margin-bottom:14px}
#ol-text{font-size:13px;color:#9aa;text-align:center;line-height:1.7;max-width:250px;margin-bottom:10px}
/* The unlock line under the three run numbers. #c084fc, the same one step up from the button
   purple that #brand-footer and the share card's claim line use, so it reads as "new" without
   being a fourth colour in the panel. SNO-149. */
.ship-new{color:#c084fc}
#play-btn{background:#a855f7;color:#fff;border:none;border-radius:99px;padding:14px 44px;font-size:16px;font-weight:700;letter-spacing:1px;cursor:pointer;box-shadow:0 0 30px rgba(168,85,247,0.45)}
#play-btn:active{transform:scale(0.97)}
/* Daily challenge (SNO-147). Deliberately secondary: PLAY keeps its size, colour, glow and
   position, because time to first run is the funnel that actually matters and a second
   equal-weight button is a decision where there was none. Outlined in the combo purple like
   #share-btn, which is this file's existing "secondary action" affordance, rather than a
   third one. display:none until syncDailyUI() decides the player has finished a run, so a
   genuinely first launch sees exactly the menu 1.3 shows. */
#daily-btn{display:none;margin-top:12px;background:transparent;color:#a855f7;border:1.5px solid #a855f7;border-radius:99px;padding:10px 26px;font-size:13px;font-weight:700;letter-spacing:1px;cursor:pointer}
#daily-btn:active{transform:scale(0.97)}
/* The spent state has to read as spent without hue doing the work — it is also the state
   that carries today's score in the note below it. #667 on #0a0a12 is the same grey
   #consent-btn:disabled already uses. */
#daily-btn:disabled{color:#667;border-color:#2c2c44}
#daily-note{display:none;margin-top:8px;font-size:11px;line-height:1.5;color:#8890ac;text-align:center;max-width:250px}
#howto-btn{display:block;margin:14px auto 0;background:transparent;border:none;color:#8890ac;font-size:12px;font-weight:600;letter-spacing:0.5px;cursor:pointer;text-decoration:underline;text-underline-offset:3px}
#howto-overlay{position:absolute;inset:0;display:none;align-items:center;justify-content:center;background:rgba(10,10,18,0.92);z-index:6}
/* max-height/overflow because the guide grew a sixth row in SNO-110 and this panel has no
   other bound: on a short handset the GOT IT button was one row away from being pushed off
   the bottom of the stage, with no way to reach it. Scrolls rather than clips. */
#howto-panel{background:#12121e;border:1px solid #232338;border-radius:16px;padding:24px 22px;width:280px;max-width:88vw;max-height:100%;overflow-y:auto}
#howto-panel h2{font-size:15px;color:#fff;margin-bottom:16px;letter-spacing:1px;text-align:center}
.howto-step{display:flex;align-items:flex-start;gap:10px;font-size:12.5px;color:#ccd;line-height:1.5;margin-bottom:14px}
.step-num{flex-shrink:0;width:18px;height:18px;border-radius:50%;background:#a855f7;color:#fff;font-size:10px;font-weight:800;display:flex;align-items:center;justify-content:center;margin-top:1px}
.howto-step .orb-dot{margin-top:4px;width:11px;height:11px;flex-shrink:0}
#howto-close{width:100%;background:#a855f7;color:#fff;border:none;border-radius:99px;padding:11px;font-size:13px;font-weight:700;letter-spacing:1px;cursor:pointer;margin-top:4px}
/* Outbound now, so it takes the same treatment as #privacy-link below — #c084fc, no
   underline, opacity on :active — rather than inventing a second link affordance. It was a
   10px #445 div: ~2.1:1 on #0a0a12, which is fine for decoration and not fine for a control
   the player is meant to find. Padding is bottom-weighted deliberately: it buys a 200x32 tap
   box (measured, 360dp viewport) without moving the text, which still sits 20px under the
   button above it — only the invisible top edge moved, to 16px. Growing to the 48dp guideline
   would have to eat that gap, and on GAME OVER the thing directly above is a button a thumb
   is stabbing at: an accidental exit to the browser costs more than a slightly small
   deliberate target. tools/capture/brand-footer-check.js measures both. SNO-37. */
#brand-footer{display:block;margin-top:16px;padding:4px 16px 14px;font-size:12px;color:#c084fc;letter-spacing:0.5px;text-decoration:none}
#brand-footer:active{opacity:.6}
.cyan{color:#22d3ee}.gold{color:#facc15}
.orb-dot{display:inline-block;width:9px;height:9px;border-radius:50%;margin-right:4px;vertical-align:middle}
/* The laser's guide icon is a bar, not a dot, because the hazard's whole tell is that it is
   a line: shape carries it in colourblind mode exactly as it does on the field. Sized to the
   same 11px column .howto-step .orb-dot occupies so the guide rows stay aligned. */
.laser-bar{display:inline-block;width:11px;height:3px;border-radius:2px;margin-right:4px;vertical-align:middle}
.howto-step .laser-bar{margin-top:8px;flex-shrink:0}
#share-btn{margin-top:14px;background:transparent;color:#a855f7;border:1.5px solid #a855f7;border-radius:99px;padding:11px 30px;font-size:13px;font-weight:700;letter-spacing:1px;cursor:pointer;display:none}
#share-btn:active{transform:scale(0.97)}
/* Rewarded continue (SNO-150). Same panel vocabulary as #howto-panel and #settings-panel —
   one more dialog, not a third look. The accept button carries the button purple and the
   decline is the underlined #8890ac link #howto-btn already uses, so "no" reads as the cheap
   option it is. Nothing here is carried by hue: both buttons say what they do in words, which
   is what keeps the offer readable in colourblind mode and at speed. */
#continue-overlay{position:absolute;inset:0;display:none;align-items:center;justify-content:center;background:rgba(10,10,18,0.92);z-index:6}
#continue-panel{background:#12121e;border:1px solid #232338;border-radius:16px;padding:22px;width:262px;max-width:88vw;text-align:center}
#continue-panel h2{font-size:15px;color:#fff;margin-bottom:10px;letter-spacing:1px}
#cont-sub{font-size:12px;color:#9aa;line-height:1.6;margin-bottom:16px}
/* Both are padded to a ~45dp tap box, which the rest of this file is not: the icon buttons are
   38dp and #brand-footer deliberately settles for 32. The argument there was that a mis-tap on
   an outbound link costs more than a small target; here it is the other way round. This dialog
   is up for seven seconds over a game the player has just lost, the thumb arriving at it is the
   one that was mid-dodge, and a missed NO THANKS reads as a prompt that will not go away.
   Measured in the real browser, not intended — tools/capture/continue-shots.js prints both. */
#cont-yes{width:100%;background:#a855f7;color:#fff;border:none;border-radius:99px;padding:15px;font-size:13px;font-weight:700;letter-spacing:1px;cursor:pointer}
#cont-yes:active{transform:scale(0.97)}
#cont-no{display:block;margin:8px auto 0;background:transparent;border:none;color:#8890ac;font-size:12px;font-weight:600;letter-spacing:0.5px;cursor:pointer;text-decoration:underline;text-underline-offset:3px;padding:14px 26px}
/* The auto-decline, drawn rather than counted down in text: one CSS animation on a transform,
   which the compositor owns, so the clock on screen costs the main thread nothing. The timer
   that actually resolves the offer is a setTimeout — this bar is the tell, not the mechanism.
   aria-hidden on the wrapper: a draining bar says nothing to a screen reader, and the thing it
   is counting down to is the harmless outcome. */
#cont-bar-wrap{height:3px;margin-top:14px;background:#1a1a2e;border-radius:99px;overflow:hidden}
#cont-bar{height:100%;width:100%;background:#a855f7;transform-origin:left center}
@keyframes nd-cont-drain{from{transform:scaleX(1)}to{transform:scaleX(0)}}
#settings-overlay{position:absolute;inset:0;display:none;align-items:center;justify-content:center;background:rgba(10,10,18,0.92);z-index:6}
/* max-height/overflow for the same reason #howto-panel has it: this panel grew a third row in
   SNO-149 and has no other bound, so on a short handset the DONE button is what gets pushed
   off the bottom of the stage with no way to reach it. Scrolls rather than clips. */
#settings-panel{background:#12121e;border:1px solid #232338;border-radius:16px;padding:22px;width:250px;max-height:100%;overflow-y:auto}
#settings-panel h2{font-size:15px;color:#fff;margin-bottom:18px;letter-spacing:1px;text-align:center}
.setting-row{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:20px}
.setting-label{font-size:13px;color:#ccd;text-align:left}
.setting-sub{font-size:10px;color:#556;margin-top:2px}
/* Ship palette swatches (SNO-149). Each one is the ship as drawn: a disc in the palette's
   hull colour with its core concentric inside it, at the same 0.45 radius ratio draw() uses,
   so the picker is a preview and not a legend. 26px keeps four of them plus their gaps inside
   the 250px panel next to the label column; the tap box is the whole 26px square.
   A locked swatch is dimmed and shows the hull colour only — the core is what the palette is
   for, so it is the part being withheld. Selection is a ring, not a hue change: the selected
   state has to survive both hazard palettes and every form of colour vision. */
#ship-swatches{display:flex;gap:6px;flex-shrink:0}
.swatch{position:relative;width:26px;height:26px;border-radius:50%;cursor:pointer;border:2px solid transparent;display:flex;align-items:center;justify-content:center}
.swatch .disc{width:18px;height:18px;border-radius:50%;display:flex;align-items:center;justify-content:center}
.swatch .core{width:8.1px;height:8.1px;border-radius:50%}
.swatch.sel{border-color:#fff}
.swatch.locked{opacity:.3}
.swatch:focus-visible{outline:2px solid #c084fc;outline-offset:2px}
.switch{position:relative;width:42px;height:24px;background:#232338;border-radius:99px;cursor:pointer;flex-shrink:0;border:1px solid #2c2c44}
.switch.on{background:#a855f7;border-color:#a855f7}
.switch .knob{position:absolute;top:2px;left:2px;width:18px;height:18px;background:#fff;border-radius:50%;transition:left .15s}
.switch.on .knob{left:20px}
#settings-close{width:100%;background:#a855f7;color:#fff;border:none;border-radius:99px;padding:11px;font-size:13px;font-weight:700;letter-spacing:1px;cursor:pointer}
/* Re-opens the Google UMP consent form — one control covering both non-essential streams,
   ads and analytics, because a withdrawal that reached only half of them would leave the
   published policy sentence still false. Rendered unconditionally so the privacy policy's
   "withdraw consent at any time through the in-app controls" is true on every device; where no
   form applies, the tap explains that rather than doing nothing.

   Two lines, not one (SNO-172). The row serves two regimes: for EEA/UK it reopens the GDPR
   form, for the US it opens Google's US-state-regulations message. The statutory phrase a US
   player is told to look for used to appear only one screen deeper, inside Google's message,
   which is defensible but not the most conspicuous reading of "clear and conspicuous". So the
   phrase is surfaced here, scoped to the audience it belongs to, and the row keeps its name
   so the GDPR function is not misdescribed. */
#consent-btn{display:block;width:100%;border:none;border-top:1px solid #232338;background:transparent;padding:14px 0 13px;font-family:inherit;font-size:12.5px;font-weight:600;color:#c084fc;text-align:center;cursor:pointer}
#consent-btn:active{opacity:.6}
#consent-btn:disabled{color:#667}
/* No colour of its own, deliberately: it inherits the button's, so :disabled (#667) and
   :active (opacity .6) dim both lines with the rules that already exist. opacity, not a
   second colour, for the same reason. */
#consent-sub{display:block;margin-top:4px;padding:0 6px;font-size:10px;font-weight:600;line-height:1.4;letter-spacing:0.1px;opacity:.78}
#consent-note{display:none;font-size:10.5px;line-height:1.55;color:#8890ac;text-align:center;padding:0 2px 4px}
/* Opens in the external browser, not this WebView: snodriva.com is not in server.allowNavigation,
   so Capacitor punts the https URL out to an ACTION_VIEW intent. Navigating in-place would trap the
   player — the Android back button is bound to exitApp() below, not to WebView history. */
#privacy-link{display:block;margin-bottom:18px;padding:11px 0 15px;font-size:12.5px;font-weight:600;color:#c084fc;text-decoration:none;text-align:center}
#privacy-link:active{opacity:.6}
/* Capture mode (SNO-102). html.cap is only ever added by the capture block in the script
   below, which no release build can enter — see the comment on CAPTURE. In a release build
   these four selectors match nothing and cost nothing: they are matched once at parse time,
   not per frame. The HUD is 10px labels over 20px values, which at 1080x1920 is ~30px and
   ~60px of a 1080-wide frame — legible on a phone in your hand and unreadable in a 5-second
   vertical clip on someone else's phone. */
   Carried through --hud-scale too (SNO-145), so a take filmed on a tablet-shaped viewport is
   enlarged from the right base rather than snapping back to handset numbers. The rig films at
   360x640, where the factor is 1 and these are the same 13/1.5/30/7 they always were. */
html.cap .hl{font-size:calc(13px * var(--hud-scale));letter-spacing:calc(1.5px * var(--hud-scale));color:#7a7a99}
html.cap .hv{font-size:calc(30px * var(--hud-scale));text-shadow:0 0 14px rgba(168,85,247,0.5)}
html.cap #combo-wrap{height:calc(7px * var(--hud-scale))}
html.cap #combo-bar{box-shadow:0 0 12px rgba(168,85,247,0.9)}
