/* ============================================================
   topdevphone — base styles: tokens, frame, shell screens, shared
   app chrome. App-specific styles: css/apps/<app>.css
   ============================================================ */
:root {
  --bg-primary: #1c1c1e;
  --bg-secondary: #2c2c2e;
  --bg-tertiary: #3a3a3c;
  --bg-elevated: #242426;
  --text-primary: #ffffff;
  --text-secondary: #8e8e93;
  --text-tertiary: #636366;
  --separator: rgba(255, 255, 255, 0.08);
  --accent-blue: #0a84ff;
  --accent-green: #30d158;
  --accent-orange: #ff9500;
  --accent-purple: #bf5af2;
  --accent-red: #ff453a;
  --accent-yellow: #ffcc00;
  --accent-pink: #ff2d78;
  --accent-teal: #40c8e0;
  --theme-preview-dark: #151517;
  --theme-preview-light: #ffffff;
  --theme-preview-line: rgba(142, 142, 147, 0.42);
  --theme-preview-overlay: rgba(0, 0, 0, 0.72);
  --camera-cutout: #050506;
  --camera-lens: #17223d;
  --clock-font-system: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  --clock-font-rounded: 'Arial Rounded MT Bold', 'SF Pro Rounded', 'Trebuchet MS', sans-serif;
  --clock-font-serif: Georgia, 'Times New Roman', serif;
  --clock-font-mono: 'SFMono-Regular', Consolas, 'Liberation Mono', monospace;
  --clock-font: var(--clock-font-system);
  --frame-black-a: #34343a; --frame-black-b: #08080b;
  --frame-titanium-a: #8a8d8b; --frame-titanium-b: #454846;
  --frame-white-a: #f7f7f9; --frame-white-b: #c7c8cd;
  --frame-natural-a: #d7cab7; --frame-natural-b: #8e8273;
  --frame-teal-a: #45d0c5; --frame-teal-b: #1f8f88;
  --frame-pink-a: #ff6fa5; --frame-pink-b: #d63384;
  --frame-blue-a: #2f6fe4; --frame-blue-b: #1b3f9e;
  --frame-desert-a: #d9aa72; --frame-desert-b: #8f6037;
  --frame-purple-a: #8f5ad8; --frame-purple-b: #3f1c72;
  --frame-neon-blue-a: #23c8ff; --frame-neon-blue-b: #2357ff;
  --frame-neon-green-a: #61ff7d; --frame-neon-green-b: #00a84f;
  --frame-neon-pink-a: #ff47d7; --frame-neon-pink-b: #e5007d;
  --frame-neon-yellow-a: #fff45b; --frame-neon-yellow-b: #e6a900;
  --radius-card: 12px;
  --radius-card-lg: 16px;
  --radius-app: 22.5%;
  --font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Inter', sans-serif;
  --phone-w: 376px;
  --phone-h: 780px;
  --screen-radius: 44px;
  --statusbar-h: 40px;
}

[data-theme='light'] {
  --bg-primary: #f2f2f7;
  --bg-secondary: #ffffff;
  --bg-tertiary: #e5e5ea;
  --bg-elevated: #ffffff;
  --text-primary: #0a0a0c;
  --text-secondary: #6c6c70;
  --text-tertiary: #aeaeb2;
  --separator: rgba(0, 0, 0, 0.08);
}

* { margin: 0; padding: 0; box-sizing: border-box; user-select: none; -webkit-user-select: none; }
html, body { width: 100%; height: 100%; background: transparent; overflow: hidden; }
body { font-family: var(--font); }
input, textarea { user-select: text; -webkit-user-select: text; font-family: var(--font); }
[data-streamer-mode='on'] [data-phone-input] { -webkit-text-security: disc; }
button { font-family: var(--font); border: 0; background: none; color: inherit; cursor: pointer; }
.ic { width: 1em; height: 1em; display: inline-block; vertical-align: -0.12em; }

/* ---------- positioning: bottom-right like a held phone ----------
   TEXT SHARPNESS DEPENDS ON THIS BLOCK. Read before editing.

   #phone-root is a composited layer (it has an animated transform). The
   compositor can only draw such a layer sharply when it lands on WHOLE
   device pixels. `right: 3vw` and `translateY(-2vh)` are fractional at
   almost every resolution — 3vw is 57.6px at 1080p, -2vh is -21.6px —
   so the entire phone was being resampled onto a half-pixel grid and
   every glyph, hairline and border came out soft.

   The offsets are therefore driven by --phone-edge / --phone-lift, which
   core.js recomputes as ROUNDED INTEGERS on load and on resize. Do not
   put vw/vh/% back into these two rules.

   Scale is deliberately NOT here any more — see #phone-frame's `zoom`. */
#phone-root {
  position: fixed; right: var(--phone-edge, 48px); bottom: 0; z-index: 10;
  transform: translateY(105%);
  transform-origin: bottom right;
  transition: transform 0.45s cubic-bezier(0.32, 0.72, 0.35, 1);
}
#phone-root.open { transform: translateY(calc(-1 * var(--phone-lift, 20px))); }
/* position: left side of the screen */
body.phone-left #phone-root { right: auto; left: var(--phone-edge, 48px); transform-origin: bottom left; }

/* vibrate pulse (vibrate mode / vibrate-on-call) */
#phone-frame.vibrating { animation: phoneBuzz 0.6s cubic-bezier(0.36, 0.07, 0.19, 0.97); }
@keyframes phoneBuzz {
  10%, 30%, 50%, 70% { transform: translateX(-3px) rotate(-0.4deg); }
  20%, 40%, 60%, 80% { transform: translateX(3px) rotate(0.4deg); }
  90%, 100% { transform: translateX(0); }
}
body.dev-mode #phone-root {
  right: 50%; bottom: 50%;
  transform-origin: center center;
  transform: translate(50%, 50%);
}

/* ---------- device frame ---------- */
#phone-frame {
  position: relative;

  /* SIZE IS APPLIED WITH `zoom`, NOT `transform: scale()`.
     This is the single biggest sharpness win and the reason it is on the
     frame rather than the root.

     `transform: scale()` rasterises the phone at 376x780 and then has the
     GPU stretch that bitmap — a 15px font is drawn at 15px and blown up,
     so it is soft by construction. `zoom` is a LAYOUT property: at 1.15 a
     15px font genuinely becomes 17.25px and is rasterised at that size,
     so it stays crisp at every size setting.

     It sits on the frame, not on #phone-root, so the slide-in transform
     above stays a pure translate and can still be cheaply animated.
     Safe here because nothing inside the phone is position:fixed — zoom
     does not apply to a fixed descendant's containing block, so such an
     element would detach. Keep it that way. */
  zoom: var(--phone-scale, 1);

  width: var(--phone-w); height: var(--phone-h);
  border-radius: calc(var(--screen-radius) + 10px);
  padding: 10px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.55), inset 0 0 0 2px rgba(255, 255, 255, 0.06);
}
.frame-blue     { background: linear-gradient(160deg, var(--frame-blue-a), var(--frame-blue-b)); }
.frame-green    { background: linear-gradient(160deg, var(--frame-neon-green-a), var(--frame-neon-green-b)); }
.frame-black    { background: linear-gradient(160deg, var(--frame-black-a), var(--frame-black-b)); }
.frame-white    { background: linear-gradient(160deg, var(--frame-white-a), var(--frame-white-b)); }
.frame-red      { background: linear-gradient(160deg, #e4483b, #9c1f1f); }
.frame-purple   { background: linear-gradient(160deg, #9b5de5, #5f2ba8); }
.frame-pink     { background: linear-gradient(160deg, var(--frame-pink-a), var(--frame-pink-b)); }
.frame-orange   { background: linear-gradient(160deg, #ff9f43, #d3641c); }
.frame-yellow   { background: linear-gradient(160deg, #f8d24a, #caa020); }
.frame-teal     { background: linear-gradient(160deg, var(--frame-teal-a), var(--frame-teal-b)); }
.frame-graphite { background: linear-gradient(160deg, #5c5c64, #313138); }
.frame-gold     { background: linear-gradient(160deg, #e8c37a, #b08d3f); }
.frame-titanium { background: linear-gradient(160deg, var(--frame-titanium-a), var(--frame-titanium-b)); }
.frame-natural { background: linear-gradient(160deg, var(--frame-natural-a), var(--frame-natural-b)); }
.frame-desert { background: linear-gradient(160deg, var(--frame-desert-a), var(--frame-desert-b)); }
.frame-deep-purple { background: linear-gradient(160deg, var(--frame-purple-a), var(--frame-purple-b)); }
.frame-neon-blue { background: linear-gradient(160deg, var(--frame-neon-blue-a), var(--frame-neon-blue-b)); }
.frame-neon-green { background: linear-gradient(160deg, var(--frame-neon-green-a), var(--frame-neon-green-b)); }
.frame-neon-pink { background: linear-gradient(160deg, var(--frame-neon-pink-a), var(--frame-neon-pink-b)); }
.frame-neon-yellow { background: linear-gradient(160deg, var(--frame-neon-yellow-a), var(--frame-neon-yellow-b)); }

.side-button {
  position: absolute; right: -12px; z-index: 4; width: 14px; border-radius: 3px;
  background: transparent; cursor: pointer;
}
.side-button::after { content: ''; position: absolute; inset: 0 auto 0 0; width: 3px; border-radius: 2px; background: rgba(0, 0, 0, 0.55); }
.side-button.power { top: 200px; height: 68px; }
.side-button.left { left: -12px; right: auto; }
.side-button.left::after { left: auto; right: 0; }
.side-button.vol-up { top: 150px; height: 46px; }
.side-button.vol-down { top: 204px; height: 46px; }

#phone-screen {
  position: relative; width: 100%; height: 100%;
  border-radius: var(--screen-radius);
  overflow: hidden;
  background: #000;

  /* Belt-and-braces rounded clip, for the GAME's CEF rather than a browser.
     `border-radius` + `overflow: hidden` is a PAINT-time clip: the compositor
     is still free to treat this box as an opaque black rectangle, and when it
     does, the four corners outside the radius are filled with the background
     instead of being left transparent — small sharp black squares over the
     game world. A browser gets this right, which is why it is invisible in
     the dev harness. clip-path is a geometric clip the compositor honours, so
     the corners can never be painted at all.
     Appearance-neutral: the shape is identical to the border-radius above.
     Safe here (unlike on #phone-frame, whose .side-button children stick out
     past its box on purpose and would be clipped away). */
  clip-path: inset(0 round var(--screen-radius));

  /* NO `filter` HERE — it used to carry brightness, and it was blurring
     the entire UI. A filter (even an identity one like brightness(1))
     forces the element into an offscreen render surface that the
     compositor then resamples. Because this element wraps EVERY app, one
     unused filter softened the whole phone all of the time.

     Brightness now rides on #screen-dim, a plain opacity overlay, which
     is compositor-only and re-rasterises nothing. */

  /* BACKDROP ROOT — do not remove.
     The filter above was accidentally doing a second job: it made this
     element the backdrop root for every `backdrop-filter` inside the
     phone. Without one, those blurs sample all the way up to the DOCUMENT
     root, and because an NUI page is transparent over the game they pull
     in the whole screen — answering a call painted a white haze across
     the player's entire view. `isolation` restores the containment with
     none of the filter's rasterisation cost. */
  isolation: isolate;
}

/* Screen brightness. Opacity animates on the compositor and never forces
   a re-raster, so dimming the screen costs nothing and — unlike a
   filter — leaves text pixel-exact. */
#screen-dim {
  position: absolute; inset: 0; z-index: 999;
  background: #000;
  opacity: var(--screen-dim, 0);
  pointer-events: none;
  border-radius: var(--screen-radius);
}

/* Small camera cut-out: visual-only, above the status bar. */
#phone-screen::before {
  content: '';
  position: absolute;
  top: 8px;
  left: 50%;
  z-index: 64;
  width: 76px;
  height: 22px;
  border-radius: 14px;
  background: var(--camera-cutout);
  transform: translateX(-50%);
  pointer-events: none;
}
#phone-screen::after {
  content: '';
  position: absolute;
  top: 14px;
  left: calc(50% + 23px);
  z-index: 65;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--camera-lens);
  pointer-events: none;
}

/* wallpapers (pure CSS — faithful to the fluid-pink reference) */
.wallpaper { position: absolute; inset: 0; transition: opacity 0.4s; }
.wp-fluid-pink {
  background:
    radial-gradient(90% 70% at 15% 20%, rgba(255, 122, 89, 0.95), transparent 60%),
    radial-gradient(80% 60% at 85% 15%, rgba(255, 45, 120, 0.9), transparent 65%),
    radial-gradient(100% 80% at 75% 75%, rgba(255, 64, 129, 0.95), transparent 60%),
    radial-gradient(70% 60% at 25% 85%, rgba(255, 150, 100, 0.85), transparent 60%),
    radial-gradient(120% 100% at 50% 45%, rgba(255, 82, 140, 0.7), transparent 70%),
    linear-gradient(150deg, #ff5d8f 0%, #ff2d78 40%, #e91e63 70%, #ff7043 100%);
}
.wp-fluid-blue {
  background:
    radial-gradient(90% 70% at 20% 25%, rgba(64, 200, 255, 0.9), transparent 60%),
    radial-gradient(80% 60% at 80% 20%, rgba(10, 132, 255, 0.9), transparent 65%),
    radial-gradient(100% 80% at 70% 80%, rgba(94, 92, 230, 0.9), transparent 60%),
    linear-gradient(150deg, #2193b0 0%, #1a5fd0 50%, #5e5ce6 100%);
}
.wp-fluid-green {
  background:
    radial-gradient(90% 70% at 20% 25%, rgba(48, 209, 88, 0.9), transparent 60%),
    radial-gradient(80% 60% at 85% 25%, rgba(160, 231, 90, 0.85), transparent 65%),
    radial-gradient(100% 80% at 70% 80%, rgba(0, 150, 110, 0.9), transparent 60%),
    linear-gradient(150deg, #2ea86a 0%, #0b8457 55%, #1ec8a5 100%);
}
.wp-aurora {
  background:
    radial-gradient(80% 60% at 70% 15%, rgba(191, 90, 242, 0.75), transparent 60%),
    radial-gradient(90% 70% at 20% 40%, rgba(10, 132, 255, 0.6), transparent 65%),
    radial-gradient(90% 80% at 60% 90%, rgba(48, 209, 88, 0.5), transparent 60%),
    linear-gradient(165deg, #101038 0%, #1b1145 45%, #061530 100%);
}
.wp-sunset {
  background:
    radial-gradient(100% 70% at 50% 100%, rgba(255, 149, 0, 0.85), transparent 65%),
    radial-gradient(90% 60% at 50% 70%, rgba(255, 69, 58, 0.6), transparent 70%),
    linear-gradient(180deg, #2b1055 0%, #7526a8 45%, #ff5e62 100%);
}
.wp-mono-dark { background: linear-gradient(170deg, #17171a, #060607); }
.wp-mono-light { background: linear-gradient(170deg, #e8e8ee, #b9b9c4); }
.wp-ocean {
  background:
    radial-gradient(100% 80% at 30% 90%, rgba(0, 180, 216, 0.7), transparent 60%),
    linear-gradient(180deg, #03045e 0%, #0077b6 60%, #00b4d8 100%);
}

/* ---------- status bar ---------- */
#statusbar {
  position: absolute; top: 0; left: 0; right: 0; height: var(--statusbar-h);
  display: flex; align-items: center; justify-content: space-between;
  padding: 8px 22px 0 26px;
  z-index: 60; color: #fff; pointer-events: none;
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.25);
}
#phone-screen.app-light-statusbar #statusbar { color: #0a0a0c; text-shadow: none; }
#sb-time { font-family: var(--clock-font); font-size: 15px; font-weight: 700; letter-spacing: 0.2px; }
#sb-right {
  display: flex; align-items: center; gap: 6px; font-size: 12px;
  background: rgba(0, 0, 0, 0.28); border-radius: 20px; padding: 4px 10px;
  pointer-events: auto; cursor: pointer;
  backdrop-filter: blur(8px);
}
#sb-right .ic { width: 15px; height: 15px; }
#sb-batt.is-charging { color: #d8ff69; }
#sb-batt-pct { font-size: 11px; font-weight: 700; }
[data-signal-bars='1'] #sb-signal rect:nth-of-type(n+2),
[data-signal-bars='2'] #sb-signal rect:nth-of-type(n+3),
[data-signal-bars='3'] #sb-signal rect:nth-of-type(n+4) { opacity: 0.2; }

/* ---------- screens ---------- */
.screen { position: absolute; inset: 0; display: none; }
.screen.active { display: block; }

/* ---------- lock screen ---------- */
#screen-lock { z-index: 40; }
#lock-clock { text-align: center; margin-top: 84px; color: #fff; text-shadow: 0 2px 16px rgba(0, 0, 0, 0.25); }
#lock-clock .big { font-family: var(--clock-font); font-size: 76px; font-weight: 300; letter-spacing: -1px; line-height: 1; }
#lock-clock .date { margin-top: 8px; font-size: 13px; font-weight: 600; letter-spacing: 3px; text-transform: uppercase; opacity: 0.85; }
#lock-notifs {
  position: absolute; top: 235px; bottom: 130px; left: 16px; right: 16px;
  overflow-y: auto; display: flex; flex-direction: column; gap: 10px;
}
#lock-notifs .clear-row { display: flex; justify-content: flex-end; }
.clear-all-btn {
  color: #fff; font-size: 12px; font-weight: 600;
  background: rgba(255, 255, 255, 0.22); backdrop-filter: blur(10px);
  padding: 6px 14px; border-radius: 16px;
}
.notif-card {
  display: flex; gap: 10px; align-items: flex-start;
  background: rgba(255, 255, 255, 0.78);
  backdrop-filter: blur(18px);
  border-radius: 18px; padding: 11px 13px;
  color: #111; cursor: pointer;
  animation: notifIn 0.35s cubic-bezier(0.32, 0.72, 0.35, 1);
}
@keyframes notifIn { from { transform: translateY(-14px); opacity: 0; } }
.notif-card .app-mini {
  width: 34px; height: 34px; border-radius: 9px; flex: 0 0 auto;
  display: flex; align-items: center; justify-content: center;
  color: var(--tile-ink, #f8fbff); font-size: 17px;
}
.notif-card .app-mini .ic { width: 19px; height: 19px; }
.notif-card .nc-body { flex: 1; min-width: 0; }
.notif-card .nc-top { display: flex; justify-content: space-between; gap: 8px; }
.notif-card .nc-title { font-size: 13px; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.notif-card .nc-time { font-size: 11px; color: rgba(0, 0, 0, 0.45); flex: 0 0 auto; }
.notif-card .nc-text { font-size: 12.5px; color: rgba(0, 0, 0, 0.72); margin-top: 2px; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
.notif-card .nc-badge {
  background: #1c1c1e; color: #fff; min-width: 18px; height: 18px; border-radius: 9px;
  font-size: 11px; font-weight: 700; display: flex; align-items: center; justify-content: center;
  padding: 0 5px; flex: 0 0 auto;
}
#lock-quick {
  position: absolute; bottom: 46px; left: 0; right: 0;
  display: flex; justify-content: space-between; padding: 0 44px;
}
.lock-quick-btn {
  width: 50px; height: 50px; border-radius: 50%;
  background: rgba(255, 255, 255, 0.85); color: #e91e63;
  display: flex; align-items: center; justify-content: center; font-size: 21px;
  backdrop-filter: blur(10px);
  transition: transform 0.15s;
}
.lock-quick-btn:active { transform: scale(0.9); }
.lock-quick-btn.on { background: #fff; color: #ff9500; }
#lock-hint { position: absolute; bottom: 22px; left: 0; right: 0; text-align: center; color: rgba(255,255,255,0.8); font-size: 11px; }

/* Tap feedback when the phone cannot unlock YET (the session has not arrived).
   A motion cue rather than a message: the lock screen visibly answers the tap,
   so the phone never reads as dead, without naming a condition the player
   cannot do anything about. Without this the tap is silent and a phone that is
   merely still fetching is indistinguishable from a broken one. */
#screen-lock.nudge { animation: lockNudge 0.42s cubic-bezier(0.36, 0.07, 0.19, 0.97); }
@keyframes lockNudge {
  20% { transform: translateY(-4px); }
  45% { transform: translateY(2px); }
  70% { transform: translateY(-1px); }
  100% { transform: translateY(0); }
}
#lock-hint.nudge { animation: hintPulse 0.42s ease; }
@keyframes hintPulse { 50% { opacity: 1; transform: scale(1.06); } }

/* Unlock: a light lift into the phone. Deliberately short — it should read as
   instant, not as a transition to sit through. */
#screen-home.unlocking { animation: unlockIn 0.26s cubic-bezier(0.32, 0.72, 0.35, 1); }
@keyframes unlockIn { from { opacity: 0; transform: scale(1.04); } }

/* passcode pad */
#passcode-overlay {
  position: absolute; inset: 0; z-index: 45;
  background: rgba(20, 20, 24, 0.55); backdrop-filter: blur(26px);
  display: none; flex-direction: column; align-items: center; padding-top: 90px; color: #fff;
}
#passcode-overlay.show { display: flex; }
#passcode-overlay .pc-title { font-size: 15px; font-weight: 600; margin-bottom: 18px; }
.pc-dots { display: flex; gap: 14px; margin-bottom: 34px; height: 12px; }
.pc-dots span { width: 12px; height: 12px; border-radius: 50%; border: 1.4px solid #fff; }
.pc-dots span.fill { background: #fff; }
.pc-pad { display: grid; grid-template-columns: repeat(3, 72px); gap: 16px; justify-items: center; }
.pc-face-id {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  min-width: 152px; min-height: 42px; margin: -14px 0 18px; padding: 0 14px;
  border-radius: 22px; background: rgba(255,255,255,0.14); color: #fff;
  font-size: 13px; font-weight: 700;
}
.pc-face-id .ic { width: 20px; height: 20px; }
.pc-face-id.scanning .ic { animation: faceScan 0.7s ease-in-out infinite alternate; }
@keyframes faceScan { to { opacity: 0.35; transform: scale(0.86); } }
.pc-key {
  width: 72px; height: 72px; border-radius: 50%; font-size: 28px; font-weight: 400; color: #fff;
  background: rgba(255, 255, 255, 0.14); backdrop-filter: blur(6px);
  transition: background 0.15s;
}
.pc-key:active { background: rgba(255, 255, 255, 0.4); }
.pc-key.ghost { background: transparent; font-size: 15px; font-weight: 600; }
.pc-shake { animation: shake 0.4s; }
@keyframes shake { 25% { transform: translateX(-8px); } 50% { transform: translateX(8px); } 75% { transform: translateX(-5px); } }

/* ---------- home screen ---------- */
#screen-home { z-index: 20; padding-top: var(--statusbar-h); display: none; flex-direction: column; }
#screen-home.active { display: flex; }
#home-pages { flex: 1; position: relative; overflow: hidden; touch-action: pan-y; }
/* While rearranging, the clip comes off. The dragged ghost is an absolutely
   positioned child of this box, and the dock it must reach sits BELOW it —
   with the clip on, the tile visibly vanished as it crossed the boundary and
   snapped back into existence on release. Neighbouring pages cannot leak in
   its place: they sit a full page-width outside, where #phone-screen's own
   clip already cuts them off.

   min-height: 0 is NOT optional. `overflow: hidden` was silently doing a
   second job: it suppressed this flex item's automatic minimum size. With
   overflow visible and min-height auto, the item refuses to be smaller than
   its tallest page's content, so the moment a drag started the box grew ~100px
   and shoved the page dots and the dock off the bottom of the screen — "the
   whole screen moves and the bottom bar disappears when I drag". */
body.rearranging #home-pages { overflow: visible; min-height: 0; }

/* The held tile must never slide under furniture. Its z-index CANNOT achieve
   that: the track's transform makes the track a stacking context, so the
   ghost's z-index only ranks it against its own page — while the dock, a
   LATER sibling turned stacking context by its backdrop-filter, paints over
   the whole track, ghost included (the tile visibly dips under the dock
   glass). Lifting the track itself is the only ordering that works, and it
   is safe: every page clips or fits its own box, so no page content can ride
   this z-index over the dock — only the ghost escapes, which is the point. */
body.rearranging #home-track { position: relative; z-index: 2; }

/* Paged track: all pages laid out side by side, the TRACK translates.
   Finger-following drag sets transform directly (transition off);
   on release we re-enable the transition so it eases into place.
   Duration/easing come from JS vars so they stay configurable. */
#home-track {
  display: flex; height: 100%;
  will-change: transform;
  transform: translate3d(0, 0, 0);
}
#home-track.settling {
  transition: transform var(--swipe-ms, 340ms) var(--swipe-ease, cubic-bezier(0.25, 0.9, 0.3, 1));
}
.home-page {
  flex: 0 0 100%; min-width: 100%; padding: 14px 18px 6px;
  display: grid; grid-template-columns: repeat(4, 1fr); grid-auto-rows: 86px; gap: 6px 8px;
  align-content: start;
}
.app-tile { display: flex; flex-direction: column; align-items: center; gap: 5px; cursor: pointer; position: relative; }
.app-tile .tile-icon {
  width: 58px; height: 58px; border-radius: var(--radius-app);
  display: flex; align-items: center; justify-content: center;
  color: var(--tile-ink, #f8fbff); font-size: 27px; position: relative; overflow: hidden;
  border: 1px solid rgba(255, 255, 255, 0.16);
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.22), inset 0 -1px 0 rgba(0, 0, 0, 0.12);
  transition: transform 0.15s;
}
.app-tile .tile-icon .ic {
  position: relative; z-index: 1;
  width: 30px; height: 30px;
  /* NO `filter` HERE — this is a SHARPNESS fix, not a style change.
     A filter (even a 1px drop-shadow at 12% opacity) forces the element into
     an offscreen render surface that the compositor then resamples. This rule
     sits on EVERY app icon, so it softened all of them at once, which is a
     large part of why the home screen read as blurry. The shadow it bought
     was barely visible; the cost was every icon on the page. Same reasoning
     that removed filter: brightness() from #phone-screen. */
}
.app-tile .tile-icon .ic-settings { transform: scale(0.92); }
.app-tile .tile-icon .ic-maps,
.app-tile .tile-icon .ic-music,
.app-tile .tile-icon .ic-shield,
.app-tile .tile-icon .ic-cloutgram { transform: scale(1.06); }
.app-tile:active .tile-icon { transform: scale(0.92); }
/* sheen: gloss strength, 0–1, set from Settings */
.tile-icon .shine {
  position: absolute; inset: 0;
  background: linear-gradient(180deg,
    rgba(255, 255, 255, calc(var(--icon-sheen, 0.18))),
    rgba(255, 255, 255, calc(var(--icon-sheen, 0.18) * 0.12)) 44%, transparent 48%);
  pointer-events: none;
}

/* home icon animations — replayed on each home render */
[data-icon-anim='bounce'] .app-tile .tile-icon { animation: iconBounce 0.5s cubic-bezier(0.28, 1.5, 0.5, 1) backwards; }
[data-icon-anim='drop'] .app-tile .tile-icon { animation: iconDrop 0.45s cubic-bezier(0.3, 1.3, 0.5, 1) backwards; }
[data-icon-anim='flip'] .app-tile .tile-icon { animation: iconFlip 0.5s ease backwards; }
[data-icon-anim='focus'] .app-tile .tile-icon { animation: iconFocus 0.45s ease backwards; }
[data-icon-anim='none'] .app-tile .tile-icon { animation: none; }
@keyframes iconBounce { from { transform: scale(0.4); opacity: 0; } 60% { transform: scale(1.08); } }
@keyframes iconDrop { from { transform: translateY(-26px); opacity: 0; } }
@keyframes iconFlip { from { transform: rotateY(90deg); opacity: 0; } }
@keyframes iconFocus { from { filter: blur(9px); opacity: 0; transform: scale(1.15); } }
.app-tile .tile-label {
  color: #fff; font-size: 11px; font-weight: 500;
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.45);
  max-width: 74px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.app-tile .tile-badge {
  position: absolute; top: -5px; right: 2px; min-width: 19px; height: 19px;
  background: var(--accent-red); color: #fff; border-radius: 10px;
  font-size: 11px; font-weight: 700; display: flex; align-items: center; justify-content: center;
  padding: 0 5px; z-index: 2; box-shadow: 0 1px 4px rgba(0,0,0,.3);
}
.app-tile.jiggle { animation: jiggle 0.28s infinite alternate; }
@keyframes jiggle { from { transform: rotate(-2.4deg); } to { transform: rotate(2.4deg); } }

/* the tile being dragged: lifted out of flow, follows the finger */
.app-tile.dragging {
  position: absolute; z-index: 90; pointer-events: none;
  animation: none; transition: none;
  transform: scale(1.14);
  filter: drop-shadow(0 12px 22px rgba(0, 0, 0, 0.45));
  opacity: 0.95;
}
.app-tile.dragging .tile-label { opacity: 0.85; }

/* reserves the slot the dragged tile will land in */
.app-tile.placeholder { pointer-events: none; visibility: hidden; }

/* dock highlights as a drop target while rearranging */
#dock { transition: background 0.2s, box-shadow 0.2s; }
body.rearranging #dock { box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.35); }

#edit-done {
  position: absolute; top: 46px; right: 16px; z-index: 95;
  background: rgba(255, 255, 255, 0.92); color: #111;
  font-size: 13px; font-weight: 700; padding: 7px 16px; border-radius: 16px;
  backdrop-filter: blur(10px); box-shadow: 0 3px 10px rgba(0, 0, 0, 0.25);
}

/* widgets page (first page in the track) */
#home-widgets { flex: 0 0 100%; min-width: 100%; padding: 14px 16px; overflow-y: auto; display: flex; flex-direction: column; gap: 12px; }
.widget-card {
  background: rgba(30, 30, 34, 0.72); backdrop-filter: blur(16px);
  border-radius: var(--radius-card-lg); padding: 14px; color: #fff; cursor: pointer;
}
[data-theme='light'] .widget-card { background: rgba(255, 255, 255, 0.75); color: #0a0a0c; }
.widget-card .w-head { display: flex; align-items: center; gap: 7px; font-size: 11.5px; font-weight: 700; letter-spacing: 0.8px; text-transform: uppercase; opacity: 0.65; margin-bottom: 8px; }

#page-dots { display: flex; justify-content: center; gap: 7px; padding: 6px 0; }
#page-dots span {
  width: 7px; height: 7px; border-radius: 50%;
  background: rgba(255, 255, 255, 0.4);
  /* transform, NOT width/height — animating box dimensions relayouts the row
     on every frame. transform is compositor-only. */
  transition: background .2s, transform .16s ease, box-shadow .16s ease;
}
#page-dots span.on { background: #fff; }

/* While a tile is being dragged the dots are the ONLY way to change page (the
   automatic edge flip is off by default — see Phone.drag.edgeFlip), so they
   grow into visible drop targets: bigger, brighter and ringed. The scale also
   enlarges each dot's hit rect, which is what dotDropTarget() measures, so the
   target grows with the affordance. Container gap opens at the same time so
   the scaled dots and their rings do not collide.
   NOTHING here may change the row's height. Every property is either
   compositor-only (transform, shadow) or horizontal and symmetric (gap) —
   a vertical padding change here measurably shifted the dots row and the
   dock below it on every drag start. */
#page-dots.drop-ready { gap: 12px; }
#page-dots.drop-ready span {
  transform: scale(1.57);            /* 7px -> ~11px, without a relayout */
  background: rgba(255, 255, 255, 0.65);
  box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.18);
}
#page-dots.drop-ready span.on { background: #fff; box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.34); }

#dock {
  margin: 4px 12px 14px;
  background: rgba(255, 255, 255, 0.16); backdrop-filter: blur(20px);
  border-radius: 28px; padding: 10px 0;
  display: flex; justify-content: space-evenly;
}
#dock .app-tile { gap: 0; }
#dock .tile-label { display: none; }

/* ---------- app container ---------- */
#app-container {
  position: absolute; inset: 0; z-index: 30; display: none;
  background: var(--bg-primary); color: var(--text-primary);
  padding-top: var(--statusbar-h);
  transform-origin: center 70%;
}
/* App open/close timing is driven by ONE multiplier, --anim-scale, set from
   Settings → App Animation (see applySettings). The base durations stay the
   values they have always been, so an absent or unrecognised setting scales by
   1 and behaves exactly as before.
   core.js reads the SAME numbers via Phone.appCloseMs() to time the DOM
   teardown — if you change a base duration here, change it there too. */
#app-container.active { display: block; animation: appOpen var(--app-open-ms, 300ms) cubic-bezier(0.32, 0.72, 0.35, 1); }
#app-container.closing { animation: appClose var(--app-close-ms, 220ms) ease forwards; }

/* Instant: no animation at all rather than a zero-length one, so there is no
   chance of a one-frame flash of the `from` keyframe. This is also the
   accessibility escape hatch — a genuine "no animation" choice, reachable
   without relying on an OS-level reduced-motion setting. */
html[data-app-anim='instant'] #app-container.active,
html[data-app-anim='instant'] #app-container.closing { animation: none; }
@keyframes appOpen { from { transform: scale(0.55); opacity: 0; border-radius: 60px; } }
@keyframes appClose { to { transform: scale(0.55); opacity: 0; } }
.app-view { position: absolute; inset: 0; top: var(--statusbar-h); display: flex; flex-direction: column; overflow: hidden; }

/* shared app chrome */
.app-header {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 16px 12px; flex: 0 0 auto;
}
.app-header .ah-back { font-size: 22px; color: var(--accent-blue); display: flex; align-items: center; }
.app-header .ah-title { font-size: 22px; font-weight: 800; flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.app-header .ah-title.centered { text-align: center; font-size: 16px; }
.app-header .ah-action { font-size: 20px; color: var(--accent-blue); display: flex; align-items: center; }
.app-scroll { flex: 1; overflow-y: auto; padding: 0 14px 90px; }
.app-scroll::-webkit-scrollbar, #lock-notifs::-webkit-scrollbar, #home-widgets::-webkit-scrollbar { width: 0; }

.tabbar {
  position: absolute; bottom: 0; left: 0; right: 0;
  z-index: 85;                 /* above #home-indicator (80) so tab taps win */
  display: flex; justify-content: space-around;
  background: color-mix(in srgb, var(--bg-secondary) 88%, transparent);
  backdrop-filter: blur(18px);
  padding: 8px 0 18px; border-top: 1px solid var(--separator);

  /* The bar spans the FULL width at z-index 85, directly over
     #home-indicator (80) — and its 18px bottom padding lands exactly where
     the home bar sits. Left solid, the bar swallows every home-bar tap and
     the nine apps that use a tab bar (Garage, Bank, Clock, Cloutgram,
     Hagglr, Housing, Phone, Police, App Store) cannot be exited by pointer
     at all.
     So the bar itself is inert and only the tabs accept input: taps in the
     strip below the tabs fall through to the home indicator, while tab taps
     still win wherever the two overlap — which is what the z-index is for. */
  pointer-events: none;
}
.tabbar .tb-item {
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  font-size: 10px; color: var(--text-secondary); min-width: 56px;
  pointer-events: auto;        /* re-arm the tabs themselves */
}
.tabbar .tb-item .ic { width: 22px; height: 22px; }
.tabbar .tb-item.on { color: var(--accent-blue); }

.list-group { background: var(--bg-secondary); border-radius: var(--radius-card); overflow: hidden; margin-bottom: 16px; }
.list-row {
  display: flex; align-items: center; gap: 12px;
  padding: 11px 14px; cursor: pointer; position: relative;
}
.list-row + .list-row::before {
  content: ''; position: absolute; top: 0; left: 46px; right: 0; height: 1px; background: var(--separator);
}
.list-row .lr-icon {
  width: 30px; height: 30px; border-radius: 8px; flex: 0 0 auto;
  display: flex; align-items: center; justify-content: center; color: #fff; font-size: 15px;
}
.list-row .lr-main { flex: 1; min-width: 0; }
.list-row .lr-title { font-size: 14.5px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.list-row .lr-sub { font-size: 12px; color: var(--text-secondary); margin-top: 1px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.list-row .lr-side { flex: 0 0 auto; color: var(--text-tertiary); font-size: 12px; display: flex; align-items: center; gap: 6px; }
.section-label { font-size: 12px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase; color: var(--text-secondary); margin: 6px 4px 8px; }

.avatar {
  border-radius: 50%; background: linear-gradient(140deg, #5a5a63, #38383f);
  color: #fff; font-weight: 700; display: flex; align-items: center; justify-content: center;
  flex: 0 0 auto; overflow: hidden;
}
.avatar img { width: 100%; height: 100%; object-fit: cover; }

.badge-dot {
  min-width: 19px; height: 19px; background: var(--accent-red); color: #fff;
  border-radius: 10px; font-size: 11px; font-weight: 700;
  display: inline-flex; align-items: center; justify-content: center; padding: 0 5px;
}

.btn-primary {
  display: block; width: 100%; padding: 13px; border-radius: 14px;
  background: var(--accent-blue); color: #fff; font-size: 15px; font-weight: 700;
  transition: filter 0.15s, transform 0.1s; text-align: center;
}
.btn-primary:active { transform: scale(0.98); filter: brightness(0.85); }
.btn-primary:disabled { opacity: 0.4; }
.btn-danger { background: var(--accent-red); }
.btn-muted { background: var(--bg-tertiary); color: var(--text-primary); }

.input, textarea.input {
  width: 100%; padding: 11px 13px; border-radius: 11px; border: 0;
  background: var(--bg-tertiary); color: var(--text-primary); font-size: 14px;
  outline: none; resize: none;
}
.input::placeholder { color: var(--text-tertiary); }
.seg {
  display: flex; background: var(--bg-tertiary); border-radius: 10px; padding: 3px; margin-bottom: 14px;
}
.seg button { flex: 1; padding: 7px; border-radius: 8px; font-size: 13px; font-weight: 600; color: var(--text-secondary); }
.seg button.on { background: var(--bg-elevated); color: var(--text-primary); box-shadow: 0 1px 4px rgba(0,0,0,0.25); }

.switch { position: relative; width: 46px; height: 28px; border-radius: 14px; background: var(--bg-tertiary); transition: background 0.2s; flex: 0 0 auto; cursor: pointer; }
.switch::after { content: ''; position: absolute; top: 2px; left: 2px; width: 24px; height: 24px; border-radius: 50%; background: #fff; transition: left 0.2s; box-shadow: 0 1px 3px rgba(0,0,0,0.3); }
.switch.on { background: var(--accent-green); }
.switch.on::after { left: 20px; }

.empty-state { text-align: center; color: var(--text-tertiary); padding: 60px 30px; font-size: 13.5px; }
.empty-state .ic { width: 44px; height: 44px; display: block; margin: 0 auto 12px; opacity: 0.5; }

@media (prefers-reduced-motion: reduce) {
  #phone-root,
  #app-container,
  .app-tile,
  .pc-face-id .ic,
  #phone-frame { animation: none !important; transition-duration: 0.01ms !important; }
}

/* ---------- control center ---------- */
#control-center {
  position: absolute; inset: 0; z-index: 70; display: none;
  background: rgba(10, 10, 14, 0.42); backdrop-filter: blur(30px);
  padding: 54px 20px 20px; color: #fff;
}
#control-center.show { display: block; animation: ccIn 0.3s cubic-bezier(0.32, 0.72, 0.35, 1); }
@keyframes ccIn { from { transform: translateY(-30px); opacity: 0; } }
.cc-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.cc-card { background: rgba(120, 120, 128, 0.3); border-radius: 18px; padding: 12px; }
.cc-toggles { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.cc-toggle {
  width: 46px; height: 46px; border-radius: 50%;
  background: rgba(120, 120, 128, 0.4);
  display: flex; align-items: center; justify-content: center; font-size: 19px; color: #fff;
  transition: background 0.2s;
}
.cc-toggle.on { background: var(--accent-blue); }
.cc-toggle.on.orange { background: var(--accent-orange); }
.cc-toggle.on.purple { background: var(--accent-purple); }
.cc-slider-card { display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 14px 10px; }
/* Vertical sliders (brightness / volume).
   The input is a NORMAL horizontal range that is ROTATED, not a
   `writing-mode: vertical-lr` range. Chromium only learned to lay form
   controls out vertically via writing-mode in 121; the game's CEF is older,
   so there the control stayed horizontal inside a 40px-wide box, the thumb
   had almost no travel, and the slider "did not move at all". Rotating a
   plain horizontal range behaves identically on every engine.
   Same 40x120 footprint as before, so the Control Center layout is unchanged. */
.cc-vwrap {
  position: relative; width: 40px; height: 120px;
  border-radius: 14px; overflow: hidden;
  background: rgba(120, 120, 128, 0.4);
}
/* The fill IS the readout. The old track was a flat colour with only a small
   thumb to indicate level, so there was no clear sense of "how bright". */
.cc-vfill {
  position: absolute; left: 0; right: 0; bottom: 0;
  height: 100%; background: #fff; pointer-events: none;
}
.cc-vslider {
  position: absolute; top: 50%; left: 50%;
  width: 120px; height: 40px;          /* length x thickness BEFORE rotation */
  transform: translate(-50%, -50%) rotate(-90deg);
  -webkit-appearance: none; appearance: none;
  margin: 0; padding: 0; border: 0; outline: none;
  background: transparent; cursor: pointer;
  opacity: 0;                          /* invisible: .cc-vfill is the visual */
}
.cc-vslider::-webkit-slider-thumb { -webkit-appearance: none; width: 40px; height: 40px; }
.cc-music { grid-column: 1 / -1; display: flex; align-items: center; gap: 12px; }
.cc-music .cm-controls { display: flex; gap: 18px; font-size: 20px; margin-left: auto; }
.cc-shortcuts { grid-column: 1 / -1; display: flex; gap: 12px; }
.cc-shortcut { flex: 1; background: rgba(120, 120, 128, 0.3); border-radius: 16px; padding: 13px; display: flex; align-items: center; gap: 9px; font-size: 13px; font-weight: 600; color: #fff; }
.cc-shortcut .ic { font-size: 17px; }

/* ---------- notification center ---------- */
#notif-center {
  position: absolute; inset: 0; z-index: 70; display: none;
  background: rgba(10, 10, 14, 0.42); backdrop-filter: blur(30px);
  padding: 54px 16px 20px;
  flex-direction: column; gap: 10px; overflow-y: auto;
}
#notif-center.show { display: flex; animation: ccIn 0.3s cubic-bezier(0.32, 0.72, 0.35, 1); }
#notif-center h3 { color: #fff; font-size: 22px; font-weight: 800; margin: 4px 2px 8px; }

/* banner peek — two homes:
   · phone OPEN  → #banner-area-phone drops from the top of the phone screen
   · phone CLOSED (NUI) → #banner-area floats top-right of the game screen */
#banner-area {
  position: fixed; top: 18px; right: var(--phone-edge, 48px); z-index: 200; width: 330px;
  display: flex; flex-direction: column; gap: 8px; pointer-events: none;
  /* Backdrop root — this one lives OUTSIDE #phone-screen, so it is not
     covered by the isolation there. Its .notif-card children carry
     backdrop-filter: blur(18px); without containment those blurs sample
     the document root and smear the game behind every banner that pops
     while the phone is closed. Same bug class as the call-overlay glare. */
  isolation: isolate;
}
body.dev-mode #banner-area { position: absolute; }
#banner-area .notif-card, #banner-area-phone .notif-card { pointer-events: auto; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.35); }
#banner-area-phone {
  position: absolute; top: 44px; left: 12px; right: 12px; z-index: 110;
  display: flex; flex-direction: column; gap: 8px; pointer-events: none;
}
#banner-area-phone .notif-card { animation: bannerDrop 0.4s cubic-bezier(0.32, 0.72, 0.35, 1); }
@keyframes bannerDrop { from { transform: translateY(-56px); opacity: 0; } }

/* ---------- home indicator ---------- */
/* Home indicator — the ONLY way to leave an app by pointer.
   Its hit area used to be 164x44 while the visible bar is 120x5, so an
   invisible catcher covered the bottom-centre of every screen and stole
   taps meant for tab bars and list rows (the "it randomly exits me" bug).
   The target is now sized close to the bar itself, and .tabbar stacks
   ABOVE it so tab taps always win. Activation additionally requires a
   deliberate tap (see bindShell) — a scroll or flick ending here does
   nothing. */
#home-indicator {
  position: absolute; bottom: 0; left: 50%; transform: translateX(-50%);
  width: 138px; height: 22px; z-index: 80; cursor: pointer;
  display: flex; align-items: flex-end; justify-content: center;
  padding: 0 0 6px; border-radius: 12px;
  touch-action: none;
}
#home-indicator::after {
  content: ''; display: block; width: 120px; height: 5px; border-radius: 3px;
  background: rgba(255, 255, 255, 0.85);
  /* transform, not width: the press feedback is compositor-only. */
  transition: transform 0.12s ease, background 0.12s ease, box-shadow 0.12s ease;
}
#home-indicator:active::after { transform: scaleX(1.1); background: #fff; }
#home-indicator:focus { outline: none; }
#home-indicator:focus-visible::after { box-shadow: 0 0 0 2px var(--accent-blue); }

/* ---------- first-boot splash ----------
   Fixed duration, first open only. Every visual is a CSS animation driven by
   --boot-ms (set inline from the server's firstBootDelayMs), so the arc and
   the status reel finish exactly when the timer does — no JS ticking. The
   overlay sits above the wizard and swallows pointer/key events, so there is
   no click or keypress path that ends it early. */
#boot-splash {
  position: absolute; inset: 0; z-index: 115;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 26px;
  background: radial-gradient(120% 90% at 50% 18%, #24242e 0%, #0b0b0f 60%, #050506 100%);
  color: #fff; text-align: center; padding: 40px 26px;
  touch-action: none; cursor: default;
  animation: bootFadeIn 0.35s ease both;
}
[data-theme='light'] #boot-splash {
  background: radial-gradient(120% 90% at 50% 18%, #ffffff 0%, #edeef4 58%, #d9dae3 100%);
  color: #101014;
}
#boot-splash * { pointer-events: none; }
#boot-splash.done { animation: bootFadeOut 0.3s ease both; }
@keyframes bootFadeIn { from { opacity: 0; } }
@keyframes bootFadeOut { to { opacity: 0; } }

#boot-splash .bs-mark {
  font-size: 28px; font-weight: 800; letter-spacing: 6px; text-transform: uppercase;
  padding-left: 6px; animation: bootMark 2.6s ease-in-out infinite;
}
@keyframes bootMark { 0%, 100% { opacity: 0.55; } 50% { opacity: 1; } }

#boot-splash .bs-arc { width: 92px; height: 92px; }
#boot-splash .bs-arc svg { width: 100%; height: 100%; transform: rotate(-90deg); }
#boot-splash .bs-arc circle { fill: none; stroke-width: 5; stroke-linecap: round; }
#boot-splash .bs-arc-track { stroke: currentColor; opacity: 0.16; }
#boot-splash .bs-arc-fill {
  stroke: var(--accent-blue);
  stroke-dasharray: 263.9;                 /* 2πr, r = 42 */
  stroke-dashoffset: 263.9;
  animation: bootArc var(--boot-ms, 10000ms) linear both;
}
@keyframes bootArc { to { stroke-dashoffset: 0; } }

#boot-splash .bs-status { height: 20px; overflow: hidden; font-size: 13px; opacity: 0.68; }
#boot-splash .bs-status-reel { animation: bootPhrases var(--boot-ms, 10000ms) cubic-bezier(0.4, 0, 0.2, 1) both; }
#boot-splash .bs-status-reel span { display: block; height: 20px; line-height: 20px; }
@keyframes bootPhrases {
  0%, 28%   { transform: translateY(0); }
  33%, 61%  { transform: translateY(-20px); }
  66%, 100% { transform: translateY(-40px); }
}

/* ---------- setup wizard ---------- */
#wizard {
  position: absolute; inset: 0; z-index: 90; display: none; flex-direction: column;
  background: linear-gradient(165deg, #101014, #1c1c24); color: #fff;
  padding: 56px 22px 24px;
}
[data-theme='light'] #wizard { background: linear-gradient(165deg, #fdfdff, #e6e8f0); color: #0a0a0c; }
#wizard.show { display: flex; }
/* Colour step only: the wallpaper is applied to the phone the instant it is
   picked, so the sheet frosts instead of painting over it — otherwise the
   choice cannot be seen until the Finish card. */
#wizard.wz-see-wallpaper { background: rgba(10, 10, 14, 0.46); backdrop-filter: blur(18px); }
[data-theme='light'] #wizard.wz-see-wallpaper { background: rgba(242, 242, 247, 0.52); }
#wizard h2 { font-size: 25px; font-weight: 800; text-align: center; margin-bottom: 6px; }
#wizard .wz-sub { text-align: center; opacity: 0.55; font-size: 13px; line-height: 1.45; margin-bottom: 18px; }
#wizard .wz-body { flex: 1; overflow-y: auto; overflow-x: hidden; }
#wizard .wz-continue { margin-top: 14px; flex: 0 0 auto; }
#wizard .wz-back {
  position: absolute; top: 24px; left: 16px; color: var(--accent-blue);
  font-size: 15px; display: flex; align-items: center; gap: 2px;
}
#wizard .input { background: rgba(127, 127, 140, 0.22); color: inherit; }
#wizard .wz-label { opacity: 0.55; color: inherit; }
#wizard .wz-steps { display: flex; gap: 6px; justify-content: center; margin-bottom: 16px; flex: 0 0 auto; }
#wizard .wz-steps i { width: 22px; height: 4px; border-radius: 2px; background: currentColor; opacity: 0.18; }
#wizard .wz-steps i.on { background: var(--accent-blue); opacity: 1; }

.wz-hint { font-size: 11.5px; opacity: 0.5; line-height: 1.4; margin: 8px 2px 0; }
.wz-hint.bad { opacity: 1; color: var(--accent-red); }
.wz-error {
  margin-top: 12px; padding: 10px 12px; border-radius: 12px; text-align: left;
  background: rgba(255, 69, 58, 0.13); border: 1px solid rgba(255, 69, 58, 0.32);
  color: var(--accent-red); font-size: 12.5px; line-height: 1.4;
}
.wz-error .wz-retry {
  display: block; margin: 8px 0 0 auto; color: var(--accent-blue);
  font-size: 12.5px; font-weight: 700;
}
.wz-loading {
  display: flex; align-items: center; justify-content: center; gap: 9px;
  padding: 30px 0; font-size: 13px; opacity: 0.6;
}
.wz-spinner {
  width: 15px; height: 15px; border-radius: 50%; flex: 0 0 auto;
  border: 2px solid currentColor; border-top-color: transparent;
  animation: wzSpin 0.8s linear infinite;
}
@keyframes wzSpin { to { transform: rotate(360deg); } }

.wz-name-row { display: flex; gap: 8px; align-items: center; }
.wz-name-row .input { flex: 1; min-width: 0; font-size: 16px; font-weight: 600; }
.wz-shuffle {
  flex: 0 0 44px; height: 44px; border-radius: 12px; font-size: 18px; color: inherit;
  background: rgba(127, 127, 140, 0.22); display: flex; align-items: center; justify-content: center;
}
.wz-shuffle:active { transform: scale(0.94); }

.wz-nums { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.wz-num {
  position: relative; padding: 15px 8px; border-radius: 14px; text-align: center; color: inherit;
  background: rgba(127, 127, 140, 0.18); border: 2px solid transparent;
  font-size: 15px; font-weight: 700; letter-spacing: 0.3px;
}
.wz-num.on { border-color: var(--accent-blue); background: rgba(10, 132, 255, 0.16); }
.wz-num:active { transform: scale(0.98); }
.wz-num .wz-num-check {
  position: absolute; top: 6px; right: 7px; font-size: 12px;
  color: var(--accent-blue); opacity: 0; transition: opacity 0.12s;
}
.wz-num.on .wz-num-check { opacity: 1; }
.wz-regen { margin-top: 12px; }

.wz-choice-row { display: flex; gap: 14px; justify-content: center; }
.wz-choice {
  flex: 1; max-width: 140px; border-radius: 16px; padding: 14px; text-align: center;
  background: rgba(127, 127, 140, 0.18); border: 2px solid transparent; color: inherit;
  font-size: 13.5px; font-weight: 600;
}
.wz-choice.on { border-color: var(--accent-blue); }
.wz-choice .preview { height: 110px; border-radius: 10px; margin-bottom: 10px; }
.wz-swatches { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; }
.wz-swatch {
  width: 44px; height: 44px; border-radius: 12px; border: 2px solid transparent;
  cursor: pointer; padding: 0; box-shadow: inset 0 0 0 1px rgba(127, 127, 140, 0.3);
}
.wz-swatch.on { border-color: var(--accent-blue); box-shadow: 0 0 0 2px rgba(10, 132, 255, 0.28); }

.wz-preview {
  width: 96px; height: 164px; border-radius: 20px; padding: 5px; margin: 0 auto 18px;
  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.38);
}
.wz-preview-screen { width: 100%; height: 100%; border-radius: 16px; overflow: hidden; }
.wz-summary { border-radius: 16px; background: rgba(127, 127, 140, 0.16); padding: 2px 14px; }
.wz-sum-row {
  display: flex; align-items: center; gap: 10px; padding: 12px 0; font-size: 13px;
  border-bottom: 1px solid rgba(127, 127, 140, 0.22);
}
.wz-sum-row:last-child { border-bottom: 0; }
.wz-sum-row span { opacity: 0.6; flex: 1 1 auto; }
.wz-sum-row strong { font-weight: 700; font-size: 13.5px; text-align: right; text-transform: capitalize; }
.wz-sum-chip {
  width: 24px; height: 24px; border-radius: 8px; flex: 0 0 auto;
  box-shadow: inset 0 0 0 1px rgba(127, 127, 140, 0.35);
}
.wz-number { text-align: center; background: rgba(127,127,140,0.16); border-radius: 14px; padding: 18px; font-size: 24px; font-weight: 800; letter-spacing: 1px; }

/* ---------- modal / action sheet / toast ---------- */
#modal-root { position: absolute; inset: 0; z-index: 120; display: none; align-items: flex-end; }
#modal-root.show { display: flex; }
#modal-root .m-scrim { position: absolute; inset: 0; background: rgba(0, 0, 0, 0.45); }
.sheet {
  position: relative; width: 100%; background: var(--bg-elevated); color: var(--text-primary);
  border-radius: 22px 22px 0 0; padding: 18px 18px 26px;
  animation: sheetUp 0.28s cubic-bezier(0.32, 0.72, 0.35, 1);
  max-height: 78%; overflow-y: auto;
}
@keyframes sheetUp { from { transform: translateY(60%); } }
.sheet h3 { font-size: 17px; font-weight: 800; margin-bottom: 14px; text-align: center; }
.sheet .sheet-row { margin-bottom: 10px; }
#toast {
  position: absolute; top: 52px; left: 50%; transform: translateX(-50%) translateY(-16px);
  background: rgba(30, 30, 36, 0.92); color: #fff; font-size: 13px; font-weight: 600;
  border-radius: 18px; padding: 9px 18px; z-index: 150; opacity: 0; pointer-events: none;
  transition: all 0.25s; white-space: nowrap; backdrop-filter: blur(8px);
}
#toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }

/* misc */
.mono { font-variant-numeric: tabular-nums; }
.text-green { color: var(--accent-green); }
.text-red { color: var(--accent-red); }
.text-sec { color: var(--text-secondary); }
.hidden { display: none !important; }
