/* ============================================================
   MOBILE STYLES  (the compact / touch layout)
   Kept in its own file so the main stylesheet doesn't balloon.
   The layout-only rules sit behind the same media query as useIsMobile()
   in ui/mobile/MobileKit.js (COMPACT_LAYOUT_QUERY — phones, iPad portrait,
   narrow windows, coarse-pointer tablets up to 1279px); the classes that
   only the phone components render (bottom bar, sheets) are unscoped.

   Follows established mobile UI guidelines:
   • Touch targets ≥ 44×44px          (Apple HIG · WCAG 2.1 SC 2.5.5)
   • Bottom tab bar: 62px row + safe area, 3–5 destinations, with a raised
     centre RESUME button              (Material Design 3)
   • Safe-area insets for notch/home    (env(safe-area-inset-*), needs
     viewport-fit=cover — set in index.html)
   • Full height via dvh/svh, not vh    (avoids mobile browser-chrome jump)
   • ≥16px font on inputs               (stops iOS focus auto-zoom)
   • overscroll containment + momentum  (no page rubber-banding)
   ============================================================ */

/* --- Global mobile hardening (all viewports, but cheap) --- */
:root {
  /* Height of the tab bar's content row, excluding the safe area.
     .has-mobile-nav / .mobile-above-nav clear it. */
  --mobile-nav-h: 62px;
  /* The centre RESUME orb's negative top margin. It lifts the orb out of the
     row so it overhangs the bar: with the tab's contents centred, the orb
     ends up ~20px above the bar's top edge (~26px counting its ring).
     .has-mobile-nav adds it to its bottom clearance so the orb never covers
     the last row of a scrolled-to-the-end page. */
  --mobile-nav-lift: 26px;
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-t: env(safe-area-inset-top, 0px);
  --safe-l: env(safe-area-inset-left, 0px);
  --safe-r: env(safe-area-inset-right, 0px);
}

html, body {
  /* Never allow a stray element to create a sideways scroll on a phone.
     `clip`, not `hidden` — `hidden` forces the CSS-spec-mandated computed
     overflow-y to `auto` (since it's left at the `visible` default), turning
     body into a reference box for every `position: sticky`/`fixed`
     descendant on the page and breaking their "stick to the real viewport"
     behavior. `clip` blocks the same horizontal bleed without that side
     effect. This rule (loaded after styles.css) previously re-introduced
     `hidden` on body even after fixing it there. */
  overflow-x: clip;
}

/* --- Touch hardening — ALL touch devices, incl. iPad (coarse pointer), not
   just phones. iPad renders the desktop layout but is still touchscreen, so it
   needs the same native-feel guards. Gated on (pointer: coarse) so mouse/desktop
   users are unaffected. --- */
@media (pointer: coarse) {
  /* Kill the 300ms tap delay AND double-tap-to-zoom on rapidly-mashed controls
     (SPIN, bet ±, nav). Previously scoped to <=640px, so iPad still zoomed. */
  button,
  [role="button"],
  a[href],
  summary,
  label {
    touch-action: manipulation;
    -webkit-touch-callout: none; /* no iOS long-press "save image / copy" menu */
  }
  /* Buttons shouldn't select their own text when double/triple-tapped fast. */
  button,
  [role="button"] {
    -webkit-user-select: none;
    user-select: none;
  }
}

/* Inner scroll regions: momentum on older iOS + keep an overscroll bounce from
   chaining up to the page (so a modal/sheet scroll doesn't drag the lobby). */
.custom-scrollbar {
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
}

/* Horizontal chip / card scrollers (lobby reward strip, filter chips, …):
   swipeable with momentum, but no scrollbar — on a phone a bar under a row
   of chips is just a grey line. Pair with overflow-x:auto on the element. */
.m-noscroll {
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.m-noscroll::-webkit-scrollbar {
  display: none;
}

/* The phone lobby stays mounted (hidden) while TODAY / PASS / ME is showing,
   so switching back is instant and keeps its filters. content-visibility:
   hidden skips its whole subtree — no style, layout or paint, and nothing in
   it ticks — while keeping the DOM and its rendering state for the way back.
   It replaces a `.m-parked *` play-state rule, which restyled every lobby
   node (~2,300) on each tab switch: ~40ms per switch on a phone. Engines
   without content-visibility (iOS < 18) keep that freeze. */
.m-parked {
  content-visibility: hidden;
}
@supports not (content-visibility: hidden) {
  .m-parked,
  .m-parked * {
    animation-play-state: paused !important;
  }
}

@media (max-width: 1024px), (pointer: coarse) and (max-width: 1279px) {
  body {
    /* Stop pull-to-refresh / overscroll chaining while playing. */
    overscroll-behavior-y: contain;
  }

  /* iOS zooms the page when focusing an input whose font is <16px. Force the
     minimum on real text inputs so tapping a field never jolts the layout. */
  input:not([type="range"]):not([type="checkbox"]):not([type="radio"]),
  textarea,
  select {
    font-size: max(16px, 1em);
  }

  /* The in-slot play screen has no bottom nav, so its last control row (SPIN)
     can sit under the home indicator. Callers add `.game-screen-safe`. */
  .game-screen-safe {
    /* !important for the same reason as .has-mobile-nav — the in-slot root also
       carries Tailwind `py-4`, which would otherwise drop the home-indicator gap. */
    padding-bottom: calc(1rem + var(--safe-b)) !important;
  }
}

/* --- Fixed bottom tab bar (62px row + safe area) ---
   Glass look without a backdrop blur: the bar is full-width and fixed over
   scrolling content (and the animated page background), so a blur would be
   recomputed on every frame of either. Instead a near-opaque smoked fill, a
   static sheen across its top third and a bright specular hairline that
   reads as the glass lip; a soft shadow lifts it off the page.
   Markup: MobileBottomNav / MobileNavTab in ui/mobile/MobileKit.js. */
.mobile-bottom-nav {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 90;
  /* Its own compositing layer: iOS Safari was seen leaving this fixed bar
     riding up with the page on scroll; a promoted layer keeps it pinned. */
  -webkit-transform: translateZ(0);
  transform: translateZ(0);
  padding-left: var(--safe-l);
  padding-right: var(--safe-r);
  padding-bottom: var(--safe-b);
  background:
    radial-gradient(120% 90% at 50% -30%, rgba(255, 244, 214, 0.075), transparent 62%),
    linear-gradient(180deg, rgba(30, 26, 22, 0.965), rgba(11, 10, 9, 0.985) 55%, rgba(8, 7, 6, 0.995));
  border-top: 1px solid rgba(255, 255, 255, 0.07);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.13), 0 -10px 30px rgba(0, 0, 0, 0.42);
  /* No overflow clipping anywhere on the bar: the centre orb hangs above it. */
}
/* The tab row. Items align to the bottom so the lifted centre button grows
   upward out of the bar instead of pushing the row taller. */
.mobile-bottom-nav-row {
  display: flex;
  align-items: flex-end;
  height: var(--mobile-nav-h);
  max-width: 720px;
  margin: 0 auto;
}
/* One tab: the full row height (62px ≥ the 44px touch minimum), contents
   centred. Colours are set inline per state by MobileNavTab. */
.mobile-nav-tab {
  position: relative;
  flex: 1 1 0;
  min-width: 0;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 0;
  background: none;
  border: 0;
  font-family: "Oswald", sans-serif;
  -webkit-tap-highlight-color: transparent;
}
.mobile-nav-tab:focus-visible {
  outline: 2px solid #fbbf24;
  outline-offset: -3px;
  border-radius: 14px;
}
/* 24px icon in a 28px box; the box anchors the notification dot. */
.mobile-nav-icon {
  position: relative;
  isolation: isolate; /* keeps the active capsule (z -1) under the icon */
  width: 28px;
  height: 28px;
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
}
/* Active tab: a soft gold capsule blooms behind the icon and the icon rises
   a hair. Drawn on every tab and only faded/scaled in (transform + opacity),
   so a switch never repaints the bar. The label colour eases in beside it. */
.mobile-nav-icon::before {
  content: "";
  position: absolute;
  z-index: -1;
  inset: -4px -15px;
  border-radius: 999px;
  background: radial-gradient(closest-side, rgba(251, 191, 36, 0.24), rgba(251, 191, 36, 0.09) 68%, rgba(251, 191, 36, 0));
  opacity: 0;
  transform: scale(0.55);
  transition: opacity 0.2s ease, transform 0.34s var(--prm-snap, ease-out);
  pointer-events: none;
}
.mobile-nav-tab[aria-current="page"] .mobile-nav-icon::before {
  opacity: 1;
  transform: none;
}
.mobile-nav-art {
  line-height: 0;
  opacity: 0.6;
  transition: opacity 0.18s ease, transform 0.3s var(--prm-snap, ease-out);
}
.mobile-nav-tab[aria-current="page"] .mobile-nav-art {
  opacity: 1;
  transform: translateY(-1px) scale(1.06);
}
.mobile-nav-label {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  line-height: 1.5;
  white-space: nowrap;
}
/* The raised centre RESUME button: a 58px orb lifted out of the row so it
   overhangs the bar. Its fill and 3px ring are the machine's colours (set
   inline); it stays inside its <button>, so the overhang is tappable too. */
.mobile-nav-orb {
  position: relative;
  width: 58px;
  height: 58px;
  flex: none;
  margin-top: calc(-1 * var(--mobile-nav-lift));
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Content that sits above a bottom nav needs breathing room so the last row
   isn't hidden behind it. Callers add `.has-mobile-nav` to the scroll region. */
@media (max-width: 1024px), (pointer: coarse) and (max-width: 1279px) {
  .has-mobile-nav {
    /* !important: the roots that carry this class also carry Tailwind's `py-4`,
       which is injected AFTER this stylesheet at equal specificity and would
       otherwise win — dropping the clearance so the last row hid behind the
       fixed bottom nav. (Same footgun `.mobile-above-nav` already guards.)
       The clearance covers the RESUME orb's overhang (--mobile-nav-lift) as
       well as the bar: a flat +12px left the orb and its ring sitting over
       the last row at full scroll (and over the bottom of Me's LOG OUT, where
       a tap resumed a machine instead). 62 + 26 + 2 = 90px above the safe
       area — the handoff's 120px scroller padding less its 30px home
       indicator, so the last row clears the ring with the mock's ~1.5px. */
    padding-bottom: calc(var(--mobile-nav-h) + var(--mobile-nav-lift) + var(--safe-b) + 2px) !important;
  }

  /* Fixed bottom-anchored banners/pills that would land under the bottom nav
     add `.mobile-above-nav` to float just above it instead. */
  .mobile-above-nav {
    bottom: calc(var(--mobile-nav-h) + var(--safe-b) + 14px) !important;
  }
}

/* --- Wild Free Spins pill (phone LOBBY) ---
   App.js floats the banked-spins pill over the lobby grid: 8px above the
   RESUME orb (bar + orb lift), about 48px tall (py-2 + a text-xl line + two
   2px borders). The lobby's bottom clearance only covered the bar and orb, so
   at full scroll the pulsing pill sat on the last row's names and pots, and
   no scroll position uncovered them. While the pill is up, App tags <html>
   with .mobile-wfs-banner and the clearance grows by the pill plus an 8px
   gap: 152px above the safe area. Only the lobby carries .has-mobile-nav at
   that point (the pill shows on the LOBBY tab alone). The pill's own inline
   bottom reads --mobile-wfs-bottom too, so the two can't drift apart. */
:root {
  --mobile-wfs-bottom: calc(var(--mobile-nav-h) + var(--mobile-nav-lift) + var(--safe-b) + 8px);
}
@media (max-width: 1024px), (pointer: coarse) and (max-width: 1279px) {
  html.mobile-wfs-banner .has-mobile-nav {
    padding-bottom: calc(var(--mobile-wfs-bottom) + 48px + 8px) !important;
  }
}

/* --- Lobby sticky header (both layouts) ---
   The lobby's top bar is sticky, so it needs a backdrop or the machine grid
   scrolls visibly through it. It must NOT be built from `liquid-glass` +
   a solid fill + a border: that material is meant for a floating panel, and
   on a full-width bar its outer drop-shadow and hard bottom border read as a
   black slab sitting behind the (already glassy) wallet panel.
   A gradient that fades to nothing, full-bleed to the screen edges so it
   reads as page chrome rather than a floating rectangle — and no backdrop
   blur: a sticky full-width blur is recomputed on every scroll frame. The
   negative margin cancels the lobby root's px-4; the padding puts the
   content back where it was. */
.lobby-top-bar {
  position: sticky;
  top: 0;
  z-index: 30;
  margin-left: -1rem;
  margin-right: -1rem;
  padding-left: max(1rem, var(--safe-l));
  padding-right: max(1rem, var(--safe-r));
  background: linear-gradient(180deg, rgba(10, 9, 8, 0.98), rgba(10, 9, 8, 0.93) 66%, rgba(10, 9, 8, 0));
}

/* --- Compact phone header (same fill, same reason: no blur) --- */
.mobile-top-bar {
  position: sticky;
  top: 0;
  z-index: 80;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: calc(8px + var(--safe-t)) 12px 8px;
  background: linear-gradient(180deg, rgba(10, 9, 8, 0.98), rgba(10, 9, 8, 0.9) 70%, rgba(10, 9, 8, 0));
}

/* --- Bottom-sheet modal (Material bottom sheet) ---
   A plain dim scrim and an opaque sheet: neither blurs what is behind it
   (a full-screen blur over the animated page is re-rendered every frame).
   The sheet springs up on transform alone. */
.mobile-sheet-scrim {
  position: fixed;
  inset: 0;
  z-index: 210;
  /* Above an open keyboard (--kb comes from ui/PerfKit.js). */
  padding-bottom: var(--kb, 0px);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  background: rgba(0, 0, 0, 0.66);
  animation: sheet-scrim-in 0.2s ease-out;
}
/* Smoked-glass sheet: a warm top sheen and a specular hairline along its
   rounded lip, over a near-opaque fill. */
.mobile-sheet {
  width: 100%;
  max-width: 640px;
  display: flex;
  flex-direction: column;
  background:
    radial-gradient(120% 60% at 50% 0%, rgba(255, 244, 214, 0.06), transparent 60%),
    linear-gradient(180deg, rgba(26, 22, 19, 0.985), #0c0a09 40%);
  border: 1px solid rgba(255, 255, 255, 0.09);
  border-bottom: none;
  border-radius: 22px 22px 0 0;
  padding-top: 8px;
  padding-bottom: calc(12px + var(--safe-b));
  padding-left: var(--safe-l);
  padding-right: var(--safe-r);
  box-shadow:
    inset 0 1px 0 0 rgba(255, 255, 255, 0.16),
    0 -12px 50px rgba(0, 0, 0, 0.7);
  animation: sheet-up 0.34s var(--prm-glide, cubic-bezier(0.22, 1, 0.36, 1));
}
.mobile-sheet-grabber {
  width: 40px;
  height: 4px;
  border-radius: 999px;
  background: rgba(168, 162, 158, 0.5);
  margin: 4px auto 10px;
  cursor: pointer;
  flex-shrink: 0;
}
/* The grabber's tap target: a full-width strip around the 40x4 bar (the
   sheet's own 8px top padding is pulled into it). */
.mobile-sheet-grab {
  flex: none;
  display: block;
  width: 100%;
  height: 36px;
  margin: -8px 0 0;
  padding: 16px 0;
  background: none;
  border: 0;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.mobile-sheet-grab .mobile-sheet-grabber { margin: 0 auto; }
@keyframes sheet-up {
  from { transform: translateY(100%); }
  to { transform: translateY(0); }
}
@keyframes sheet-scrim-in {
  from { opacity: 0; }
  to { opacity: 1; }
}
.reduced-motion .mobile-sheet { animation: none; }
.reduced-motion .mobile-sheet-scrim { animation: none; }

/* --- Tab page entrance ---
   Today / Me content rises 8px and fades in as the tab is switched to: the
   page's background is there from the first frame, so the switch reads as
   instant, and only the content column moves (transform + opacity). */
@keyframes m-tab-in {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: none; }
}
.m-tab-in { animation: m-tab-in 0.24s var(--prm-glide, ease-out) backwards; }
.reduced-motion .m-tab-in { animation: none; }

/* ============================================================
   LIQUID GLASS — LIVE  (mobile chrome)
   A soft specular highlight tracks your finger across the chrome, and
   tappable parts squish on press. The highlight is painted in an ::after
   at z-index:-1 so it sits ABOVE the fill but BELOW content — it never
   touches legibility. It only lights up while a finger is on the surface
   (opacity 0 at rest), so it costs nothing when idle.

   Fully gated: no highlight and no press motion under .reduced-motion
   or .performance-mode (the ui/LiquidGlass.js driver also no-ops there,
   so the CSS vars never even update).
   ============================================================ */
.mobile-bottom-nav,
.mobile-top-bar,
.mobile-sheet {
  /* Vars the pointer driver writes. Resting position is top-centre; the
     driver flips --lg-on to 1 on touch and back to 0 on release. */
  --lg-x: 50%;
  --lg-y: 0%;
  --lg-on: 0;
  /* Each is its own stacking context, so the negative-z sheen below stays
     above the surface's fill and under its content. */
  isolation: isolate;
}
/* nav is fixed, top-bar is sticky → both already positioned. The sheet is
   an in-flow flex child, so anchor its ::after explicitly. */
.mobile-sheet { position: relative; }

.mobile-bottom-nav::after,
.mobile-top-bar::after,
.mobile-sheet::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;                 /* above the fill, below the buttons/content */
  border-radius: inherit;
  pointer-events: none;
  background: radial-gradient(
    130px 130px at var(--lg-x) var(--lg-y),
    rgba(255, 255, 255, 0.14),
    rgba(255, 255, 255, 0.04) 42%,
    transparent 70%
  );
  opacity: var(--lg-on);
  transition: opacity 0.4s ease;
}

/* Tactile press feedback: a quick sink, then a springy release. */
.mobile-bottom-nav button {
  transition: transform 0.3s var(--prm-snap, ease-out), color 0.18s ease;
}
.mobile-bottom-nav button:active {
  transform: scale(0.92);
  transition-duration: 0.08s;
}
.mobile-sheet-grabber {
  transition: transform 0.16s ease, background 0.16s ease;
}
.mobile-sheet-grabber:active,
.mobile-sheet-grab:active .mobile-sheet-grabber {
  transform: scaleX(1.35);
  background: rgba(255, 255, 255, 0.7);
}

/* Accessibility + low-end devices: kill the live sheen and the press
   motion. The driver script also bails on these classes, so nothing
   updates the vars either. */
.reduced-motion .mobile-bottom-nav::after,
.reduced-motion .mobile-top-bar::after,
.reduced-motion .mobile-sheet::after,
.performance-mode .mobile-bottom-nav::after,
.performance-mode .mobile-top-bar::after,
.performance-mode .mobile-sheet::after {
  display: none;
}
.reduced-motion .mobile-bottom-nav button:active,
.reduced-motion .mobile-sheet-grabber:active,
.reduced-motion .mobile-sheet-grab:active .mobile-sheet-grabber,
.reduced-motion .mobile-nav-tab[aria-current="page"] .mobile-nav-art {
  transform: none;
}

/* The Tower runs full-screen and stamps `tof-open` on <html> while it is up:
   the lobby's phone nav is a position:fixed bar portalled to <body>, so it
   sits under the overlay but still ghosts through the backdrop, right where
   the CLIMB / TAKE THE HAUL buttons are. Nothing else on the page moves. */
.tof-open .mobile-bottom-nav { display: none !important; }

/* ============================================================
   SHOP (phone redesign)
   The Shop as a bottom sheet on compact layouts (ShopSheet in
   ui/ShopModal.js). Everything here is class-scoped to the sheet, so the
   desktop dialog never sees it. Inline styles carry the exact design values;
   this block holds what inline styles can't: the dvh fallback, the exit
   animation and the reduced-motion gates.
   ============================================================ */
.shop-sheet-scrim {
  position: fixed;
  inset: 0;
  z-index: 200;               /* same layer the desktop shop dialog uses */
  background: rgba(0, 0, 0, 0.55);
  animation: sheet-scrim-in 0.2s ease-out;
  /* The sheet is modal, so a drag that starts on the scrim, the grabber, the
     SHOP header or the tabs must not chain to the page underneath: it used to
     scroll the Lobby (or Today / Pass / Me) behind the sheet, and closing it
     left the player somewhere else. Only .shop-sheet-body pans — it is a
     scroll container, so it sets its own touch-action below. Taps are
     unaffected. `pinch-zoom` keeps zoom available where it is understood;
     an engine that doesn't know it keeps `none`. */
  touch-action: none;
  touch-action: pinch-zoom;
}
.shop-sheet {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  /* 96px on a 390x844 phone (≈88dvh of sheet); shorter screens keep the
     same 88dvh proportion instead of losing a fixed 96px. */
  top: 96px;
  top: min(96px, 12dvh);
  max-width: 640px;           /* tablets in the compact layout: a centred sheet */
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  border-radius: 26px 26px 0 0;
  background: linear-gradient(180deg, rgba(26, 21, 18, 0.98), #0c0a09);
  border-top: 1px solid rgba(255, 255, 255, 0.1);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14), 0 -12px 50px rgba(0, 0, 0, 0.7);
  /* The design sets type on the font's own line box, not Tailwind's 1.5 —
     it is what the card heights in the handoff are measured against. */
  line-height: normal;
  animation: sheet-up 0.28s cubic-bezier(0.22, 1, 0.36, 1);
}
/* The grabber button is the 22px strip around the 40x4 bar, so a thumb
   doesn't have to find a 4px target. */
.shop-sheet-grabber {
  flex: none;
  display: block;
  margin: 0 auto;
  padding: 10px 40px 8px;
  background: none;
  border: 0;
  cursor: pointer;
}
.shop-sheet-grabber > span {
  display: block;
  width: 40px;
  height: 4px;
  border-radius: 9px;
  background: rgba(168, 162, 158, 0.5);
  transition: transform 0.16s ease, background 0.16s ease;
}
.shop-sheet-grabber:active > span {
  transform: scaleX(1.35);
  background: rgba(255, 255, 255, 0.7);
}
.shop-sheet-body {
  padding: 0 16px calc(34px + var(--safe-b, 0px));
  /* A scroll container starts its own touch-action afresh, so the scrim's
     `none` stops here; this spells out what the body allows — vertical
     scrolling (and zoom) only, nothing sideways. */
  touch-action: pan-y pinch-zoom;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.shop-sheet-body::-webkit-scrollbar { display: none; }

/* Exit: <ModalFade> tags the sheet's root with .modal-closing for 0.18s
   before unmounting. Instead of the dialogs' fade + scale-sink, the scrim
   clears and the sheet drops back down the way it came. */
@keyframes shop-sheet-scrim-out { to { background-color: rgba(0, 0, 0, 0); } }
@keyframes shop-sheet-down { to { transform: translateY(100%); } }
.shop-sheet-scrim.modal-closing {
  animation: shop-sheet-scrim-out 0.18s ease-in forwards !important;
}
.shop-sheet-scrim.modal-closing .shop-sheet {
  animation: shop-sheet-down 0.18s cubic-bezier(0.4, 0, 1, 1) forwards;
}

.reduced-motion .shop-sheet-scrim,
.reduced-motion .shop-sheet,
.reduced-motion .shop-sheet-scrim.modal-closing .shop-sheet { animation: none; }
.reduced-motion .shop-sheet-scrim.modal-closing { animation: none !important; opacity: 0; }
.reduced-motion .shop-sheet-grabber:active > span { transform: none; }

/* ============================================================
   MACHINE (phone redesign) — the compact in-slot screen (game/App.js,
   phoneMachineBody). Owned by the machine layout; nothing else here.
   ============================================================ */
@media (max-width: 1024px), (pointer: coarse) and (max-width: 1279px) {
  /* The phone column pads itself 8px above the home indicator (the handoff's
     calc(8px + safe-b)); .game-screen-safe's generic 1rem gap is for the old
     stacked layout. Same !important footgun as .game-screen-safe itself. */
  .game-screen-safe.machine-phone {
    padding-bottom: calc(8px + var(--safe-b)) !important;
  }
}
/* SPIN: press sinks to .92 (the handoff's SPIN press), eased like every
   other control; while a spin runs the inline scale(.94) holds it down. */
.machine-phone .phone-spin {
  transition: transform 0.12s ease;
}
.machine-phone .phone-spin:not(:disabled):active {
  transform: scale(0.92);
}
.reduced-motion .machine-phone .phone-spin { transition: none; }
/* AUTO / TURBO: the column (.phone-aux-col) is sized by what the fixed-width
   BET column and SPIN leave — ~82px at 390, ~67px at 375, ~52px at 360 — so it
   is a size container and its buttons shed what doesn't fit, measured on the
   column itself rather than guessed from the viewport:
   - below 90px "AUTO ON" (58px of label at .18em + a 16px icon + 6px gap)
     drops its icon;
   - below 76px the decorative art (🔄 / ⚡, UIIcon's .ui-icon) goes and the
     tracking tightens to .1em. A locked TURBO keeps its VIP lock (a plain
     LockIcon, not UIIcon art): it is the only sign the button is gated, so it
     shrinks to 12px with a 4px gap instead (12 + 4 + 40px of "TURBO" fits the
     57px inside a 67px column's padding);
   - below 66px that no longer fits, so the lock stacks over the label, and
     "AUTO ON" (51px at .1em, wider than the 50px inside a 52px column's
     border) steps down to 11px at .04em.
   Icon + label then always sit inside the 44px buttons (the count badge is
   absolute, so it is unaffected). The gaps need !important to beat the inline
   pAuxBtn style; only a locked TURBO still has two in-flow items at these
   widths, so they (and the column direction) change nothing else. */
.machine-phone .phone-aux-col { container-type: inline-size; }
@container (max-width: 89px) {
  .machine-phone .phone-aux-on .phone-aux-icon { display: none !important; }
}
@container (max-width: 75px) {
  .machine-phone .phone-aux-col .phone-aux-icon.ui-icon { display: none !important; }
  .machine-phone .phone-aux-col .phone-aux-icon:not(.ui-icon) svg { width: 12px; height: 12px; }
  .machine-phone .phone-aux-col button { letter-spacing: 0.1em !important; gap: 4px !important; }
}
@container (max-width: 65px) {
  .machine-phone .phone-aux-col button { flex-direction: column; gap: 2px !important; }
  .machine-phone .phone-aux-col button.phone-aux-on { letter-spacing: 0.04em !important; font-size: 11px !important; }
}

/* ============================================================
   LOBBY (phone redesign) — the phone machine card's ♥ toggle
   (LobbyMachineCard in ui/LobbyScreen.js). Owned by the lobby.
   ============================================================ */
/* The ♥ is a sibling of the card button (a button can't nest in a button),
   so it no longer rides the card's entrance; it carries a copy inline.
   Hold it to the card's gates too (styles.css silences .lobby-card's
   animations under reduced motion and performance mode). */
.reduced-motion .lobby-fav,
.performance-mode .lobby-fav {
  animation: none !important;
}

/* ============================================================
   TODAY (phone) — the Daily Circuit, electric
   ui/mobile/MobileToday.js. Navy-black charged panels over circuit traces,
   the streak as charge cells, a forked arc across the bonus card, a plasma
   core, segmented charge bars and the cyan electric CTA. Everything here is
   `tdy-` scoped. Motion is transform / opacity on HTML boxes only; every loop
   plays a few times when the tab opens and then rests (the one open-ended
   loop, the core's containment ring, is a lone composited rotate), and all of
   it is silenced under .reduced-motion and .performance-mode (GUARDS, end of
   block). The animated regions carry data-pause-offscreen.
   ============================================================ */

/* --- title row --- */
.tdy-kicker {
  display: flex;
  align-items: center;
  gap: 5px;
  margin-bottom: 4px;
  font-size: 10px;
  letter-spacing: 0.32em;
  color: #67e8f9;
  text-shadow: 0 0 8px rgba(34, 211, 238, 0.6);
  white-space: nowrap;
}
.tdy-h1 {
  margin: 0;
  font-size: 28px;
  font-weight: 700;
  letter-spacing: 0.1em;
  line-height: 1;
  color: #fff;
  text-shadow: 0 0 18px rgba(34, 211, 238, 0.5), 0 0 2px rgba(236, 254, 255, 0.55);
}
.tdy-streak {
  gap: 5px;
  height: 32px;
  padding: 0 11px;
  border-radius: 999px;
  background: linear-gradient(180deg, #0c2a3d, #071827);
  border: 1px solid rgba(103, 232, 249, 0.6);
  box-shadow: 0 0 14px rgba(34, 211, 238, 0.3), inset 0 1px 0 rgba(236, 254, 255, 0.2);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: #e0f2fe;
}

/* --- HUD corner brackets (colour inline) --- */
.tdy-hud {
  position: absolute;
  width: 12px;
  height: 12px;
  border: 0 solid;
  pointer-events: none;
}
.tdy-hud-tl { top: 7px; left: 7px; border-top-width: 2px; border-left-width: 2px; border-top-left-radius: 5px; }
.tdy-hud-tr { top: 7px; right: 7px; border-top-width: 2px; border-right-width: 2px; border-top-right-radius: 5px; }
.tdy-hud-bl { bottom: 7px; left: 7px; border-bottom-width: 2px; border-left-width: 2px; border-bottom-left-radius: 5px; }
.tdy-hud-br { bottom: 7px; right: 7px; border-bottom-width: 2px; border-right-width: 2px; border-bottom-right-radius: 5px; }

/* --- the daily-bonus card --- */
.tdy-card {
  padding: 14px 14px 20px;
  border-radius: 20px;
  isolation: isolate;
  background:
    radial-gradient(120% 60% at 50% 0%, rgba(34, 211, 238, 0.08), transparent 60%),
    radial-gradient(90% 60% at 0% 100%, rgba(99, 102, 241, 0.1), transparent 70%),
    linear-gradient(180deg, #0a1428, #060b18);
  border: 1px solid rgba(56, 189, 248, 0.22);
  box-shadow: inset 0 1px 0 rgba(165, 243, 252, 0.08), 0 10px 30px rgba(0, 0, 0, 0.45);
}
.tdy-card.is-live {
  background:
    radial-gradient(120% 60% at 50% 0%, rgba(34, 211, 238, 0.14), transparent 60%),
    radial-gradient(90% 60% at 0% 100%, rgba(99, 102, 241, 0.14), transparent 70%),
    linear-gradient(180deg, #0b1830, #060c1a);
  border-color: rgba(103, 232, 249, 0.55);
  box-shadow:
    0 0 0 1px rgba(8, 145, 178, 0.18),
    0 0 30px rgba(34, 211, 238, 0.2),
    inset 0 1px 0 rgba(236, 254, 255, 0.22),
    inset 0 0 40px rgba(14, 165, 233, 0.08),
    0 10px 30px rgba(0, 0, 0, 0.45);
}
.tdy-traces {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border-radius: inherit;
  pointer-events: none;
}
.tdy-status {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 9px;
  border-radius: 999px;
  border: 1px solid rgba(56, 189, 248, 0.22);
  background: rgba(5, 9, 18, 0.6);
  font-family: "Share Tech Mono", monospace;
  font-size: 10px;
  letter-spacing: 0.2em;
  color: #7890ad;
}
.tdy-status i {
  display: block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #334155;
}
.tdy-status.is-live {
  color: #a5f3fc;
  border-color: rgba(103, 232, 249, 0.55);
  background: rgba(8, 47, 73, 0.55);
}
.tdy-status.is-live i { background: #67e8f9; box-shadow: 0 0 6px #22d3ee; }

/* --- the 7-day track: charge cells --- */
.tdy-cell {
  position: relative;
  min-width: 0;
  padding: 7px 0 6px;
  border-radius: 9px;
  border: 1px solid rgba(71, 85, 105, 0.38);
  background: linear-gradient(180deg, #0a1222, #060b16);
  line-height: 1.2;
}
/* the terminal nub on top of the cell */
.tdy-cell::before {
  content: "";
  position: absolute;
  top: -4px;
  left: 50%;
  width: 12px;
  height: 3px;
  margin-left: -6px;
  border-radius: 2px 2px 0 0;
  background: rgba(71, 85, 105, 0.55);
}
.tdy-cell-fill {
  position: absolute;
  inset: 0;
  border-radius: 8px;
  pointer-events: none;
}
.tdy-cell-day,
.tdy-cell-amt,
.tdy-cell-segs { position: relative; }
.tdy-cell-day { font-size: 9px; font-weight: 700; letter-spacing: 0.14em; color: #71829a; }
.tdy-cell-amt { font-size: 11px; margin-top: 1px; color: #7c8aa0; }
.tdy-cell-segs { display: flex; justify-content: center; gap: 2px; margin-top: 5px; }
.tdy-cell-segs i {
  display: block;
  width: 7px;
  height: 3px;
  border-radius: 1px;
  background: rgba(100, 116, 139, 0.28);
}
/* charged — a day already banked */
.tdy-cell.is-past { border-color: rgba(34, 211, 238, 0.42); }
.tdy-cell.is-past::before { background: rgba(34, 211, 238, 0.6); }
.tdy-cell.is-past .tdy-cell-fill { background: linear-gradient(0deg, rgba(34, 211, 238, 0.2), rgba(34, 211, 238, 0.03)); }
.tdy-cell.is-past .tdy-cell-day { color: #67e8f9; }
.tdy-cell.is-past .tdy-cell-amt { color: #cffafe; }
.tdy-cell.is-past .tdy-cell-segs i { background: #22d3ee; box-shadow: 0 0 4px rgba(34, 211, 238, 0.8); }
/* today — white-hot; it pulses and charges up while it waits to be claimed */
.tdy-cell.is-today {
  border-color: #a5f3fc;
  box-shadow: 0 0 14px rgba(34, 211, 238, 0.55), inset 0 0 12px rgba(103, 232, 249, 0.25);
}
.tdy-cell.is-today::before { background: #a5f3fc; box-shadow: 0 0 6px #22d3ee; }
.tdy-cell.is-today .tdy-cell-fill { background: linear-gradient(0deg, rgba(34, 211, 238, 0.5), rgba(14, 165, 233, 0.14)); }
.tdy-cell.is-today .tdy-cell-day { color: #fff; }
.tdy-cell.is-today .tdy-cell-amt { color: #fff; text-shadow: 0 0 8px rgba(34, 211, 238, 0.95); }
.tdy-cell.is-today .tdy-cell-segs i { background: #ecfeff; box-shadow: 0 0 5px #22d3ee; }
.tdy-cell.is-today::after {
  content: "";
  position: absolute;
  inset: -4px;
  border-radius: 12px;
  box-shadow: 0 0 16px 3px rgba(34, 211, 238, 0.7);
  opacity: 0;
  pointer-events: none;
}
.tdy-cell.is-today:not(.is-banked)::after { animation: tdy-pulse 1.6s ease-in-out 6; }
.tdy-cell.is-today:not(.is-banked) .tdy-cell-segs i { animation: tdy-seg 1.6s ease-out 6; }
.tdy-cell.is-today:not(.is-banked) .tdy-cell-segs i:nth-child(2) { animation-delay: 0.18s; }
.tdy-cell.is-today:not(.is-banked) .tdy-cell-segs i:nth-child(3) { animation-delay: 0.36s; }
/* today, claimed: charged and steady */
.tdy-cell.is-today.is-banked { box-shadow: 0 0 10px rgba(34, 211, 238, 0.35), inset 0 0 10px rgba(103, 232, 249, 0.18); }

/* --- the arc across the card --- */
.tdy-seam {
  position: relative;
  height: 36px;
  margin: 6px -14px 4px;
}
.tdy-seam-svg {
  position: absolute;
  top: 0;
  left: 11px;
  width: calc(100% - 22px);
  height: 100%;
  overflow: visible;
}
.tdy-strike {
  position: absolute;
  inset: 0;
  opacity: 0;
  pointer-events: none;
  animation: tdy-strike 4.8s linear -2.2s 4; /* first strike ~0.4s in */
}
.tdy-strike-flash {
  position: absolute;
  left: 0;
  right: 0;
  top: -46px;
  bottom: -46px;
  background: radial-gradient(50% 50% at 50% 50%, rgba(165, 243, 252, 0.4), rgba(34, 211, 238, 0.12) 55%, rgba(34, 211, 238, 0) 78%);
}
.tdy-seam-node {
  position: absolute;
  top: 50%;
  width: 8px;
  height: 8px;
  margin-top: -4px;
  border-radius: 50%;
  background: #0b1426;
  border: 1.5px solid #1e3a4f;
}
.tdy-seam-node-l { left: 7px; }
.tdy-seam-node-r { right: 7px; }
.tdy-seam-node.is-live { background: #ecfeff; border-color: #22d3ee; box-shadow: 0 0 8px 2px rgba(34, 211, 238, 0.8); }

/* --- the plasma core --- */
.tdy-core { position: relative; flex: none; }
.tdy-core > * { position: absolute; }
.tdy-core-ring,
.tdy-core-body { top: 0; left: 0; line-height: 0; }
.tdy-core-halo {
  inset: -12px;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(103, 232, 249, 0.5), rgba(34, 211, 238, 0.16) 58%, rgba(34, 211, 238, 0));
  opacity: 0.6;
  animation: tdy-breathe 2.2s ease-in-out 4;
}
.tdy-core.is-live .tdy-core-ring { animation: tdy-spin 16s linear infinite; }
.tdy-core.is-live .tdy-core-body { filter: drop-shadow(0 0 6px rgba(34, 211, 238, 0.75)); }

/* --- the electric CTA --- */
.tdy-cta {
  gap: 9px;
  height: 52px;
  margin: 16px 0 4px;
  border: 0;
  border-radius: 14px;
  font-size: 17px;
  font-weight: 700;
  letter-spacing: 0.16em;
  color: #04121f;
  background: linear-gradient(180deg, #ecfeff 0%, #67e8f9 45%, #22d3ee 60%, #0891b2 100%);
  box-shadow:
    0 4px 0 #0e4f66,
    0 8px 22px rgba(34, 211, 238, 0.35),
    inset 0 1px 0 rgba(255, 255, 255, 0.95),
    inset 0 -2px 0 rgba(8, 145, 178, 0.55);
  transition: transform 0.12s ease, box-shadow 0.12s ease, filter 0.15s ease;
}
.tdy-cta:not(:disabled):active {
  transform: translateY(3px);
  box-shadow: 0 1px 0 #0e4f66, 0 4px 14px rgba(34, 211, 238, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.95), inset 0 -2px 0 rgba(8, 145, 178, 0.55);
  filter: brightness(1.06);
}
/* breathing glow: a static shadow whose opacity breathes a few times */
.tdy-cta::after {
  content: "";
  position: absolute;
  inset: 0 0 -4px;
  border-radius: 14px;
  box-shadow: 0 0 24px 2px rgba(34, 211, 238, 0.65);
  opacity: 0.35;
  pointer-events: none;
  animation: tdy-breathe 1.8s ease-in-out 4;
}
.tdy-cta-sheen {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  overflow: hidden;
  pointer-events: none;
}
.tdy-cta-sheen::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 40%;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, 0.65), transparent);
  transform: translateX(-180%) skewX(-18deg);
  animation: tdy-sheen 3.4s var(--prm-glide, ease-out) 0.5s 3;
}
/* spent: a discharged, recessed slot */
.tdy-cta.is-off {
  font-size: 14px;
  letter-spacing: 0.18em;
  color: #7dd3fc;
  background: linear-gradient(180deg, #081122, #050a15);
  box-shadow: inset 0 0 0 1px rgba(56, 189, 248, 0.22), inset 0 2px 10px rgba(0, 0, 0, 0.6), inset 0 0 22px rgba(14, 165, 233, 0.06);
}
.tdy-cta.is-off::after { display: none; }

/* --- section header: title + a neon wire a spark runs along --- */
.tdy-h2 {
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0.16em;
  color: #ecfeff;
  text-shadow: 0 0 10px rgba(34, 211, 238, 0.45);
  white-space: nowrap;
}
.tdy-wire {
  position: relative;
  height: 9px;
  min-width: 12px;
  overflow: hidden;
}
.tdy-wire::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 4px;
  height: 1px;
  background: linear-gradient(90deg, rgba(103, 232, 249, 0.7), rgba(56, 189, 248, 0.06));
}
.tdy-wire-spark {
  position: absolute;
  inset: 0;
  transform: translateX(-100%);
  animation: tdy-spark 3s ease-in 0.8s 3 backwards;
}
.tdy-wire-spark::after {
  content: "";
  position: absolute;
  right: 0;
  top: 3px;
  width: 26px;
  height: 3px;
  border-radius: 2px;
  background: linear-gradient(90deg, rgba(34, 211, 238, 0), #a5f3fc 70%, #fff);
  box-shadow: 0 0 6px #22d3ee;
}

/* --- missions: circuits off one bus rail --- */
.tdy-rail { position: relative; padding-left: 16px; }
.tdy-rail::before {
  content: "";
  position: absolute;
  left: 4px;
  top: 30px;
  bottom: 30px;
  width: 2px;
  border-radius: 2px;
  background: linear-gradient(180deg, rgba(34, 211, 238, 0.6), rgba(56, 189, 248, 0.2));
  box-shadow: 0 0 6px rgba(34, 211, 238, 0.35);
}
.tdy-mission {
  position: relative;
  gap: 11px;
  padding: 10px;
  border-radius: 16px;
  background: linear-gradient(180deg, #0b1428, #070d1b);
  border: 1px solid rgba(56, 189, 248, 0.17);
  box-shadow: inset 0 1px 0 rgba(165, 243, 252, 0.06);
}
.tdy-mission.is-pro { border-color: rgba(167, 139, 250, 0.3); }
.tdy-joint {
  position: absolute;
  left: -16px;
  top: 50%;
  width: 16px;
  height: 2px;
  margin-top: -1px;
  background: rgba(56, 189, 248, 0.35);
}
.tdy-joint::before {
  content: "";
  position: absolute;
  left: 0;
  top: -4px;
  width: 10px;
  height: 10px;
  box-sizing: border-box;
  border-radius: 50%;
  background: #0a1426;
  border: 1.5px solid rgba(56, 189, 248, 0.55);
}
.tdy-socket {
  flex: none;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: radial-gradient(circle at 50% 40%, #10223d, #050912 72%);
  box-shadow: inset 0 0 0 1px rgba(56, 189, 248, 0.3), 0 0 10px rgba(14, 165, 233, 0.12);
}
.tdy-mission-desc {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.04em;
  line-height: 1.2;
  color: #e2e8f0;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/* complete: the circuit is live */
.tdy-mission.is-claim {
  border-color: rgba(103, 232, 249, 0.7);
  background:
    radial-gradient(120% 150% at 100% 50%, rgba(34, 211, 238, 0.16), transparent 60%),
    linear-gradient(180deg, #0c1a31, #07101f);
  box-shadow: 0 0 18px rgba(34, 211, 238, 0.2), inset 0 1px 0 rgba(236, 254, 255, 0.18);
}
.tdy-mission.is-claim::after {
  content: "";
  position: absolute;
  inset: -1px;
  border-radius: inherit;
  box-shadow: 0 0 22px rgba(34, 211, 238, 0.55);
  opacity: 0;
  pointer-events: none;
  animation: tdy-pulse 1.8s ease-in-out 4;
}
.tdy-mission.is-claim .tdy-joint { background: #67e8f9; box-shadow: 0 0 6px #22d3ee; }
.tdy-mission.is-claim .tdy-joint::before { background: #ecfeff; border-color: #22d3ee; box-shadow: 0 0 8px 1px rgba(34, 211, 238, 0.9); }
.tdy-mission.is-claim .tdy-socket { box-shadow: inset 0 0 0 1.5px rgba(103, 232, 249, 0.8), 0 0 14px rgba(34, 211, 238, 0.45); }
/* claimed: discharged */
.tdy-mission.is-done { opacity: 0.6; }
.tdy-mission.is-done .tdy-socket { filter: saturate(0.35); }
.tdy-mission.is-done .tdy-joint::before { background: #0e4f66; border-color: #0e7490; }
.tdy-mission.is-done .tdy-bar { box-shadow: inset 0 0 0 1px rgba(56, 189, 248, 0.16); }
.tdy-mission.is-done .tdy-bar-fill { background: linear-gradient(90deg, #164e63, #0e7490); }

/* segmented charge bar (ten cells) */
.tdy-bar {
  position: relative;
  height: 7px;
  border-radius: 3px;
  background: #081120;
  box-shadow: inset 0 0 0 1px rgba(56, 189, 248, 0.16);
  overflow: hidden;
}
.tdy-bar-fill {
  height: 100%;
  background: linear-gradient(90deg, #1d4ed8, #0ea5e9 55%, #22d3ee);
}
.tdy-bar::after {
  content: "";
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(90deg, rgba(6, 11, 22, 0) 0, rgba(6, 11, 22, 0) calc(10% - 2px), #060b16 calc(10% - 2px), #060b16 10%);
  pointer-events: none;
}
.tdy-bar.is-full { box-shadow: inset 0 0 0 1px rgba(165, 243, 252, 0.5), 0 0 10px rgba(34, 211, 238, 0.55); }
.tdy-bar.is-full .tdy-bar-fill { background: linear-gradient(90deg, #22d3ee, #67e8f9 55%, #ecfeff); }

/* mission buttons (≥44px tall), all one width so every bar is one length */
.tdy-btn {
  gap: 4px;
  width: 78px;
  height: 44px;
  padding: 0 4px;
  border-radius: 12px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.1em;
}
/* the bar + its progress figure */
.tdy-barrow { gap: 8px; }
.tdy-fig { min-width: 70px; }
.tdy-vip { letter-spacing: 0.18em; }

/* --- narrow phones (<360px) --- */
@media (max-width: 359px) {
  /* the figure drops under the bar, so the bar keeps a readable length */
  /* (.tdy-mission prefix: out-ranks Tailwind's .items-center, which loads later) */
  .tdy-mission .tdy-barrow { flex-direction: column; align-items: stretch; gap: 4px; }
  .tdy-mission .tdy-barrow > div { flex: none; }
  .tdy-fig { min-width: 0; }
  .tdy-btn { width: 70px; letter-spacing: 0.06em; }
  .tdy-btn-claim svg,
  .tdy-btn-ico { display: none !important; }
  /* the tiles' corner bracket would sit on the titles */
  .tdy-tile-hud { display: none; }
  .tdy-vip { letter-spacing: 0.1em; }
}
@media (max-width: 339px) {
  .tdy-cell-amt { font-size: 10px; letter-spacing: -0.02em; }
}
.tdy-btn-claim {
  border: 0;
  color: #04121f;
  background: linear-gradient(180deg, #ecfeff 0%, #67e8f9 45%, #22d3ee 60%, #0891b2 100%);
  box-shadow: 0 3px 0 #0e4f66, 0 0 14px rgba(34, 211, 238, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.9);
}
.tdy-btn-go {
  color: #a5f3fc;
  border: 1px solid rgba(34, 211, 238, 0.55);
  background: linear-gradient(180deg, #0c1a2e, #07101d);
  box-shadow: inset 0 0 12px rgba(34, 211, 238, 0.12), 0 0 8px rgba(34, 211, 238, 0.12);
}
.tdy-btn-done {
  color: #64748b;
  border: 1px dashed rgba(100, 116, 139, 0.45);
  background: rgba(15, 23, 42, 0.6);
}

/* --- keyframes --- */
/* THE STRIKE — near-instant attack, a ragged double hit, exponential decay */
@keyframes tdy-strike {
  0%, 53.5% { opacity: 0; }
  54%       { opacity: 1; }
  56%       { opacity: 0.18; }
  57.5%     { opacity: 1; }
  60%       { opacity: 0.45; }
  61.5%     { opacity: 0.92; }
  66%       { opacity: 0.3; }
  74%       { opacity: 0; }
  100%      { opacity: 0; }
}
@keyframes tdy-breathe {
  0%, 100% { opacity: 0.35; }
  50%      { opacity: 1; }
}
@keyframes tdy-pulse {
  0%, 100% { opacity: 0; }
  50%      { opacity: 1; }
}
@keyframes tdy-seg {
  0%        { opacity: 0.15; }
  35%, 100% { opacity: 1; }
}
@keyframes tdy-spin { to { transform: rotate(360deg); } }
@keyframes tdy-sheen {
  0%, 58% { transform: translateX(-180%) skewX(-18deg); }
  100%    { transform: translateX(340%) skewX(-18deg); }
}
@keyframes tdy-spark {
  0%        { transform: translateX(-100%); }
  55%, 100% { transform: translateX(110%); }
}

/* --- GUARDS ---
   Reduced motion: nothing moves; everything rests lit.
   Performance mode (the phone default): the loops stop — the core's ring,
   the halo, the breathing glows — but the brief one-shots that greet the
   tab stay, played once: one strike across the card, one charge-up of
   today's cell, one sheen over the CTA, one spark down the wire. */
.reduced-motion .tdy-strike,
.reduced-motion .tdy-cell.is-today::after,
.reduced-motion .tdy-cell.is-today .tdy-cell-segs i,
.reduced-motion .tdy-core-halo,
.reduced-motion .tdy-core-ring,
.reduced-motion .tdy-cta::after,
.reduced-motion .tdy-cta-sheen::before,
.reduced-motion .tdy-wire-spark,
.reduced-motion .tdy-mission.is-claim::after,
.performance-mode .tdy-cell.is-today::after,
.performance-mode .tdy-core-halo,
.performance-mode .tdy-core-ring,
.performance-mode .tdy-cta::after,
.performance-mode .tdy-mission.is-claim::after {
  animation: none !important;
}
.performance-mode .tdy-strike,
.performance-mode .tdy-cell.is-today .tdy-cell-segs i,
.performance-mode .tdy-cta-sheen::before,
.performance-mode .tdy-wire-spark {
  animation-iteration-count: 1 !important;
}
.reduced-motion .tdy-cta:not(:disabled):active { transform: none; }
/* ================= end TODAY (phone) ================= */
