/* Real Nigga Network — web.
   Colours are jarvis/hud.py's: black, one profile accent, soft
   lavender text. --accent is overwritten at runtime with the logged-in
   profile's own accent_left, so every access key looks like its own
   colour, and every surface below is a blend of black and THAT accent
   — nothing here has a colour of its own.

   Drago, 2026-10-04: "a major UI change across all platforms, make the
   theme of the app and website look modern as fuck, built similar to
   major social media platforms ... while preserving the theme i have
   going." So the HUD's identity stays — black field, the starfield,
   one accent, uppercase letter-spaced mono labels on every card and
   button — and the chrome around it is the chrome a modern social app
   has: rounded glass panels tinted with the accent, avatars with
   presence rings, grouped chat rows with a name and a time, a pill
   composer with a filled send button, a floating nav, a bottom tab bar
   on a phone, sheets that slide up on a phone and float on a PC.

   The one rule this relaxes, deliberately: the accent used to be shown
   ONLY as its exact hex (Drago, 2026-09-14). Text, buttons, names, the
   orb and every primary element still are. Surfaces, hairlines and
   glows are the accent at a low alpha over black, because that is what
   makes a panel read as a panel instead of a wireframe. The desktop
   HUD makes the same move (jarvis/hud.py's SURFACE colours). */

:root {
  --bg: #07020c;
  --panel: #0d0714;
  --accent: #5900ff;
  /* The right end of hud.py's left-to-right accent gradient; app.js
     derives it from --accent on login the way hudbg.js does. */
  --accent-r: #0022ff;
  --text: #ece3ff;
  --dim: #8577a3;
  --danger: #ff5566;
  --ok: #56d364;
  --success: #2ecc71;
  /* What sits on a filled accent button: near-black, so the accent
     stays the accent and the label is still readable on a neon green
     and on a deep purple alike. */
  --on-accent: #06020b;

  /* Surfaces: the accent over black at a low alpha, translucent so the
     starfield shows through, blurred behind. Three steps up from the
     field — a card, a control on a card, a control that is active. */
  --surface:   color-mix(in srgb, var(--accent) 7%, rgba(7, 2, 12, 0.78));
  --surface-2: color-mix(in srgb, var(--accent) 12%, rgba(9, 3, 16, 0.88));
  --surface-3: color-mix(in srgb, var(--accent) 22%, #0a0414);
  --line: color-mix(in srgb, var(--accent) 42%, transparent);
  --hair: color-mix(in srgb, var(--accent) 18%, transparent);
  --tint: color-mix(in srgb, var(--accent) 14%, transparent);
  --glow: 0 0 0 1px var(--line), 0 24px 60px -30px color-mix(in srgb, var(--accent) 70%, transparent);

  --r-sm: 10px;
  --r-md: 16px;
  --r-lg: 22px;
  --r-pill: 999px;
  --ease: 180ms cubic-bezier(0.2, 0.7, 0.2, 1);

  /* Two typefaces. The mono IS the HUD — every card title, nav item,
     button and timestamp wears it, the same Consolas the desktop is
     drawn in. The sans is for what people actually read: chat, names,
     notes, forms. */
  --font-mono: Consolas, "Cascadia Mono", ui-monospace, "SF Mono", Menlo, monospace;
  --font-ui: "Inter", "Segoe UI Variable Text", "Segoe UI", system-ui, -apple-system, Roboto, "Helvetica Neue", Arial, sans-serif;

  /* The sign-in screens are monochrome: before a key is accepted the
     page belongs to nobody's colour yet (see login_gate.py). */
  --mono: #f2f2f2;
  --mono-dim: #8c8c8c;
  --mono-border: #3a3a3a;
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body { margin: 0; padding: 0; height: 100%; }
body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-ui);
  font-size: 15px;
  line-height: 1.45;
  overflow-wrap: anywhere;
  -webkit-font-smoothing: antialiased;
}
button { font: inherit; color: inherit; cursor: pointer; }
button:focus-visible, input:focus-visible, select:focus-visible, [tabindex]:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}
.hidden { display: none !important; }
.dim { color: var(--dim); font-size: 13px; line-height: 1.6; }
.dim.small { font-size: 11px; }
.center { text-align: center; }
.error { color: var(--danger); font-size: 13px; margin-top: 10px; }

/* Thin accent scrollbars, everywhere something scrolls. */
* { scrollbar-width: thin; scrollbar-color: var(--line) transparent; }
*::-webkit-scrollbar { width: 8px; height: 8px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb { background: var(--line); border-radius: 8px; border: 2px solid transparent; background-clip: padding-box; }
*::-webkit-scrollbar-thumb:hover { background: var(--accent); background-clip: padding-box; }

/* Only the screen matching data-screen is shown. */
.screen { display: none; }
body[data-screen="landing"] #screen-landing,
body[data-screen="device"]  #screen-device,
body[data-screen="login"]   #screen-login,
body[data-screen="banned"]  #screen-banned,
body[data-screen="register"] #screen-register,
body[data-screen="reset"]   #screen-reset,
/* Claiming an original key — the form somebody whose key predates
   passwords lands on instead of being let in. */
body[data-screen="claim"]   #screen-claim,
/* Turning notifications on, before the network (static/push.js). */
body[data-screen="push"]    #screen-push,
body[data-screen="app"]     #screen-app { display: block; }

/* ---------------- shared bits ---------------- */
/* A card is a rounded glass panel: the accent over black at a low
   alpha, one hairline in the accent, the starfield blurred behind it. */
.card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-lg); padding: 20px; margin-bottom: 14px;
  backdrop-filter: blur(18px) saturate(1.2); -webkit-backdrop-filter: blur(18px) saturate(1.2);
  box-shadow: var(--glow), inset 0 1px 0 color-mix(in srgb, var(--accent) 22%, transparent);
}
.card.narrow { max-width: 440px; margin: 0 auto; }
.card h1 {
  font-family: var(--font-mono); font-size: 16px; margin: 0 0 8px; color: var(--accent);
  letter-spacing: 2.4px; text-transform: uppercase;
}
.card h2 {
  font-family: var(--font-mono); font-size: 13px; margin: 0; color: var(--accent);
  letter-spacing: 2.4px; text-transform: uppercase;
}
/* Every section title is this: the HUD's Consolas 9 bold, uppercase,
   dim — "YOUR PROFILE", "DIRECT MESSAGES", "CHAT HISTORY". */
.label {
  font-family: var(--font-mono); color: var(--dim); font-size: 11px; font-weight: 700;
  letter-spacing: 1.8px; text-transform: uppercase; margin-bottom: 8px;
}
.note { color: var(--dim); font-size: 12px; line-height: 1.6; margin-bottom: 10px; }
.empty { color: var(--dim); font-size: 13px; padding: 10px 4px; line-height: 1.5; }
.lock-glyph { font-size: 42px; margin-bottom: 8px; }

input[type="text"], input[type="password"], input[type="url"], select {
  width: 100%; font-family: var(--font-ui); font-size: 15px;
  background: var(--surface-2); color: var(--text);
  border: 1px solid var(--hair); border-radius: 12px; padding: 11px 14px;
  margin-top: 10px; transition: border-color var(--ease), box-shadow var(--ease);
}
input[type="text"]:focus, input[type="password"]:focus, input[type="url"]:focus, select:focus {
  outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--tint);
}
input::placeholder { color: color-mix(in srgb, var(--dim) 75%, transparent); }
select { appearance: none; -webkit-appearance: none; padding-right: 32px;
  background-image: linear-gradient(45deg, transparent 50%, var(--dim) 50%), linear-gradient(135deg, var(--dim) 50%, transparent 50%);
  background-position: calc(100% - 17px) 55%, calc(100% - 12px) 55%; background-size: 5px 5px, 5px 5px; background-repeat: no-repeat; }

/* Buttons. The primary one is the accent, filled, with near-black
   text; the ghost is an outline. Both are pills, both wear the mono. */
.btn {
  display: block; width: 100%; text-align: center; font-family: var(--font-mono);
  font-weight: 700; font-size: 12px; letter-spacing: 2px; text-transform: uppercase;
  background: var(--accent); color: var(--on-accent); border: 1px solid transparent;
  border-radius: var(--r-pill); padding: 13px 18px; margin-top: 14px; text-decoration: none;
  box-shadow: 0 10px 28px -14px var(--accent);
  transition: transform var(--ease), box-shadow var(--ease), background var(--ease), color var(--ease);
}
.btn:hover { transform: translateY(-1px); box-shadow: 0 14px 32px -12px var(--accent); }
.btn.ghost { background: var(--surface-2); color: var(--text); border-color: var(--line); box-shadow: none; }
.btn.ghost:hover { color: var(--accent); border-color: var(--accent); box-shadow: none; }
/* Destructive actions must not look identical to safe ones — Ban and
   the locks sit right next to their own undo in the admin panel. */
.btn.danger { background: transparent; border-color: var(--danger); color: var(--danger); box-shadow: none; }
.btn.danger:hover { background: rgba(255, 85, 102, 0.14); box-shadow: none; }
/* The one card in Settings holding something that cannot be undone. */
.danger-card { border-color: color-mix(in srgb, var(--danger) 55%, transparent); }
.danger-card .label { color: var(--danger); }
.btn.big { max-width: 320px; margin: 22px auto 0; padding: 16px 22px; font-size: 13px; }
/* The two hero actions, stacked. Never side by side: at 390px a row of
   two uppercase labels with this letter-spacing wraps mid-word. */
.hero-actions { display: flex; flex-direction: column; align-items: center; }
.hero-actions .btn.big { margin-top: 12px; }
.hero-actions .btn.big:first-child { margin-top: 22px; }
.signup-block { margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--hair); }
.signup-block .dim { margin: 0 0 4px; text-align: center; }
.signup-block .btn { margin-top: 8px; }
.signup-block.center { max-width: 420px; margin-left: auto; margin-right: auto; }
.btn:active { transform: translateY(0) scale(0.99); opacity: 0.86; }
a.navlink { text-decoration: none; display: inline-block; }

.linkbtn {
  display: block; width: 100%; margin-top: 12px; padding: 8px;
  background: none; border: none; color: var(--dim); font-size: 12px; border-radius: var(--r-sm);
  transition: color var(--ease);
}
.linkbtn:hover { color: var(--text); }
.gohome { cursor: pointer; }
.gohome:hover .brand-name,
.gohome:hover .pc-mark { text-shadow: 0 0 14px var(--accent); }

.swatches { display: flex; flex-wrap: wrap; gap: 10px; margin: 10px 0; }
.swatch {
  width: 36px; height: 36px; border-radius: 50%; border: 2px solid transparent; cursor: pointer;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08) inset; transition: transform var(--ease);
}
.swatch:hover { transform: scale(1.08); }
.swatch.on { border-color: #fff; box-shadow: 0 0 0 2px var(--bg), 0 0 18px -4px currentColor; }
.picture-preview {
  display: block; width: 72px; height: 72px; object-fit: cover; border-radius: 50%;
  margin-bottom: 12px; border: 2px solid var(--accent); box-shadow: 0 0 24px -8px var(--accent);
}

/* ---------------- the landing sky ---------------- */
/* Fixed and full-viewport, behind everything, and only painted on the
   landing screen. Both layers are inert to the pointer so the hero's
   button is never blocked by a star or a drifting emoji. */
#sky, #emoji-field {
  position: fixed; inset: 0; z-index: 0; pointer-events: none;
  /* Explicit, not left to `inset: 0`. A canvas is a REPLACED element,
     so with width:auto it keeps its intrinsic 300x150 instead of being
     stretched by the offsets the way a div would be — which put the
     entire starfield in a small box in the top-left corner. */
  width: 100%; height: 100%;
  opacity: 0; transition: opacity 600ms ease;
}
/* No centre mask any more. It faded emoji out as they wandered over
   the wordmark, and a thing that fades in and out while it moves draws
   more attention than one that was simply never there. sky.js keeps
   them out of the hero by placement instead (see placeOutsideHero). */
#emoji-field {
  overflow: hidden;
  /* Where a falling emoji dissolves: just off the bottom of the screen
     while the planet is still below the fold, and exactly on the
     horizon once it's up. --rise is the globe's own eased rise, written
     by earth.js, and #earth's top is calc(100vh - var(--rise) * 42vh) —
     these two numbers have to stay in step or the emoji will burn up in
     mid-air or sink through the planet. */
  --fall-end: calc(104vh - var(--rise, 0) * 46vh);
}
body[data-screen="landing"] #sky,
body[data-screen="login"] #sky,
body[data-screen="register"] #sky,
body[data-screen="reset"] #sky,
body[data-screen="claim"] #sky,
body[data-screen="device"] #sky,
body[data-screen="landing"] #emoji-field { opacity: 1; }
/* And behind the app once you're in — Drago, 2026-10-05: "let the
   emojis stay visible in the background after being logged in on the
   website they should be able to see the emojis and shit while they
   fall down and then disappear like the home page." Same field, same
   fall; they dissolve just off the bottom, since the planet they land
   on is a landing-page thing (--rise is whatever the landing left it).
   The field sits above #hudbg (later in the DOM, same z-index) and
   under #screen-app's cards (z-index 1), and takes no pointer. */
body[data-screen="app"] #emoji-field { opacity: 1; --fall-end: 104vh; }
/* Settings › APPEARANCE › Falling emoji off (the account's falling_emoji). */
body.no-emoji #emoji-field { display: none; }
/* Above the sky, so the wordmark and the Join button stay readable. */
body[data-screen="landing"] #screen-landing,
.sitenav { position: relative; z-index: 1; }

/* Two elements, two animations, because one element can only run one
   transform animation at a time. The wrapper takes a long slow sweep
   sideways; the emoji inside bobs and tilts on a much shorter cycle.
   The periods don't divide into each other, so the combined path
   wanders instead of retracing one line. */
/* The fall. Each emoji comes in above the top of the screen, drifts all
   the way down, dissolves as it reaches the atmosphere and starts over
   from the top — Drago, 2026-09-20: "have the emojies be a loop going
   down to earths atmosphere then come back up to the top."

   It used to be a one-way translate driven by scroll position, which
   meant the field simply stopped once you reached the bottom of the
   page. Now the motion runs on its own clock and the SCROLL moves where
   it ends: --rise (the globe's horizon, written by earth.js) pulls the
   landing line up the screen as the planet comes into view, so at the
   top of the page they fall to the bottom of the screen and by the
   bottom of the page they burn up on the horizon.

   Each one starts at a random point in its own cycle (negative
   animation-delay), so at any instant the field looks scattered rather
   than like a curtain dropping in unison. */
.emoji-fall {
  position: absolute;
  top: 0;
  animation-name: descend;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  will-change: transform, opacity;
}
@keyframes descend {
  0%   { transform: translate3d(0, -16vh, 0); opacity: 0; }
  9%   { opacity: 1; }
  /* Held bright almost the whole way, then gone over the last stretch:
     that last stretch IS the atmosphere. */
  84%  { opacity: 1; }
  100% { transform: translate3d(0, var(--fall-end, 104vh), 0); opacity: 0; }
}

.emoji-orbit {
  position: relative;
  display: block;
  animation-name: sweep;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
  animation-direction: alternate;
  will-change: transform;
}
.float-emoji {
  display: block;
  /* Faded in by sky.js once the image has actually decoded. The bob is
     its wrapper's (.emoji-flame) now. */
  transition: opacity 900ms ease;
}

/* Each emoji burns, and bobs. The wrapper .emoji-flame carries the bob
   (.emoji-orbit already animates `transform` for the sweep) and its
   ::before is the glow behind the emoji, in the emoji's own colour
   (--glow, --glow-core, set by sky.js), flickering on its own clock. */
.emoji-flame {
  display: block;
  position: relative;
  isolation: isolate;
  /* The bob lives here now (it was the <img>'s), so the glow behind the
     emoji bobs with it. */
  animation-name: bob;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
  animation-direction: alternate;
  will-change: transform;
}
/* THE GLOW, CHEAPLY (2026-10-17, "website and PC apps are super laggy").
   It was three drop-shadow() filters ANIMATED on every emoji: a filter
   that changes every frame — over a GIF that changes every frame — is
   re-rasterised on the main thread every frame, two dozen times, and
   measured it held the whole page to ~9 frames a second on a PC with
   nothing else on. Now one soft radial glow in the emoji's own colour
   sits behind it on a layer of its own, and the flicker is its opacity
   and scale — the compositor's work, nothing repainted. */
.emoji-flame::before {
  content: ""; position: absolute; inset: -42%; z-index: -1; pointer-events: none;
  border-radius: 50%;
  background: radial-gradient(circle closest-side,
    rgba(var(--glow-core, 255, 214, 120), 0.62) 0%,
    rgba(var(--glow, 255, 122, 26), 0.40) 34%,
    rgba(var(--glow, 138, 43, 226), 0.14) 62%,
    transparent 100%);
  animation: ember var(--burn, 1.1s) ease-in-out var(--burn-delay, 0s) infinite alternate;
  will-change: opacity, transform;
}

/* A phone: the fall and the sweep only — the bob and the glow's flicker
   were a third and fourth animation per emoji for a small screen to tick. */
@media (max-width: 900px) {
  .emoji-flame, .emoji-flame::before { animation: none; }
}

@keyframes ember {
  0%   { opacity: 0.78; transform: scale(0.92); }
  35%  { opacity: 1;    transform: scale(1.06); }
  65%  { opacity: 0.86; transform: scale(0.96); }
  100% { opacity: 1;    transform: scale(1.14); }
}
/* A backgrounded tab keeps running CSS animations otherwise, and there
   are a dozen of them. */
#emoji-field.paused .emoji-fall,
#emoji-field.paused .emoji-orbit,
#emoji-field.paused .emoji-flame,
#emoji-field.paused .emoji-flame::before,
#emoji-field.paused .float-emoji { animation-play-state: paused; }

@keyframes sweep {
  from { transform: translate3d(0, 0, 0) rotate(0deg); }
  to   { transform: translate3d(var(--sweep-x, 10vw), 0, 0) rotate(var(--roll, 0deg)); }
}
/* translate, rotate and scale in one keyframe — an element can only
   animate `transform` once, so the bob, the tilt and the breathing all
   have to travel together. */
@keyframes bob {
  from {
    transform: translate3d(0, calc(var(--bob-y, 16px) * -1), 0)
               rotate(calc(var(--tilt, 8deg) * -1)) scale(1);
  }
  to {
    transform: translate3d(0, var(--bob-y, 16px), 0)
               rotate(var(--tilt, 8deg)) scale(var(--swell, 1.1));
  }
}

/* Someone who's asked for less motion gets a STILL sky, not a missing
   one: the stars are painted once and the emoji sit where they were
   placed. Hiding both outright meant anyone with "reduce motion" set —
   a Windows performance tweak plenty of people have on without
   thinking about it — saw a flat black page and had no way to tell it
   from the feature being broken. */
@media (prefers-reduced-motion: reduce) {
  /* .emoji-fall keeps its inline transform (set by sky.js) so they stay
     spread down the screen instead of stacking at the top. */
  .emoji-fall, .emoji-orbit, .float-emoji, .emoji-flame { animation: none !important; }
  /* The flame stays, frozen at one frame — it's a glow, not motion, so
     it costs nothing to leave lit and the page still looks designed. */
  .emoji-flame::before { animation-play-state: paused !important; }
  #sky, #emoji-field { transition: none; }
}

/* ---------------- the descent to Earth ---------------- */
/* The landing page scrolls; the sky, the emoji and the globe are all
   fixed behind it. So what moves is the CONTENT, which reads as
   falling toward the planet rather than as art sliding past. */
body[data-screen="landing"] #screen-landing { display: block; }
.landing-scroll {
  position: relative; z-index: 1; width: 100%;
  height: 100dvh; overflow-y: auto; overflow-x: hidden;
  scroll-behavior: smooth;
}
.landing-hero-wrap, .landing-earth-wrap {
  min-height: 100dvh; display: flex; align-items: center; justify-content: center;
}
/* Room beneath the outro for the globe to sit in: the text holds the
   upper third, the horizon comes up under it. */
.landing-wrap.landing-earth-wrap { align-items: flex-start; padding-top: 16vh; }

.scroll-hint {
  margin-top: 40px; color: var(--dim); font-size: 11px;
  letter-spacing: 3px; text-transform: uppercase;
  display: flex; flex-direction: column; align-items: center; gap: 2px;
}
.scroll-arrow { font-size: 20px; animation: nudge 2s ease-in-out infinite; }
@keyframes nudge {
  0%, 100% { transform: translateY(0); opacity: 0.45; }
  50%      { transform: translateY(6px); opacity: 1; }
}

.story {
  /* Centred, because the page is now one paragraph between a centred
     hero and a centred closing pitch — left-aligned it sat off-axis
     from everything above and below it. */
  max-width: 620px; margin: 0 auto; padding: 22vh 20px; text-align: center;
  /* The feature copy dissolves as the planet comes up, so the two are
     never fighting over the same pixels: you read in open space, then
     the reading gives way to the arrival. Gone well before the globe
     is high enough to reach the text. */
  opacity: calc(1 - var(--rise, 0) * 3);
}
/* Quiet stretch after the last feature: the planet needs somewhere to
   come up through that isn't the middle of a paragraph. */
.story:last-of-type { padding-bottom: 80vh; }
.story-title {
  font-size: clamp(22px, 3.4vw, 34px); color: var(--text);
  letter-spacing: 1px; margin: 0 0 18px; line-height: 1.25;
  text-shadow: 0 0 26px rgba(89, 0, 255, 0.55);
}
.story-lead { font-size: clamp(15px, 1.9vw, 18px); line-height: 1.75; color: var(--text); margin: 0 0 16px; }
.story-body { font-size: 14px; line-height: 1.8; color: var(--dim); margin: 0; }

/* The founders, and where the network came from. */
.story-subtitle {
  font-size: clamp(15px, 2vw, 18px); color: var(--text); letter-spacing: 1px;
  margin: 34px 0 14px; line-height: 1.3;
  text-shadow: 0 0 18px rgba(89, 0, 255, 0.45);
}
.founders { list-style: none; margin: 0 0 26px; padding: 0; display: grid; gap: 12px; }
.founders li {
  border: 1px solid var(--line); border-radius: 10px; padding: 12px 14px;
  background: rgba(0, 0, 0, 0.35);
  /* LEFT-aligned inside a centred section, deliberately: three blocks
     of prose centred one under another is a stack of ragged triangles,
     and the name has to sit at a predictable place for the eye to run
     down the list. */
  text-align: left;
}
.founder-name {
  display: inline; font-weight: 700; color: var(--text); font-size: 15px;
  letter-spacing: .5px;
}
.founder-role {
  display: inline-block; margin-left: 8px; padding: 1px 7px; border-radius: 999px;
  border: 1px solid var(--line); color: var(--accent);
  font-size: 10px; letter-spacing: 1.4px; text-transform: uppercase;
  /* Nudged up so it sits on the name's cap height rather than its
     baseline, which is where a pill next to bold text wants to be. */
  vertical-align: 2px;
}
.founder-note {
  display: block; margin-top: 6px; color: var(--dim);
  font-size: 13px; line-height: 1.65;
  /* The middle founder's alias is one 44-character word. Without this
     it pushes the card wider than the column at 390px and takes the
     whole page into a horizontal scroll. */
  overflow-wrap: anywhere;
}
.landing-outro { text-align: center; width: 100%; max-width: 560px; margin: 0 auto; }
.landing-outro .story-title { margin-bottom: 10px; }
.landing-outro-note { color: var(--dim); font-size: 13px; line-height: 1.7; margin: 0; }
.discord-link { color: var(--accent); margin-top: 18px; display: inline-block; width: auto; }

/* The planet. Fixed below the fold, rising as the page descends.
   Positioned by its TOP edge rather than its bottom, deliberately:
   the top edge is the horizon the reader actually sees, and pinning
   that means the horizon lands at the same height whatever diameter
   the viewport gives the globe. Sizing it off the bottom instead put
   the horizon off-screen on a phone and over the text on a desktop. */
#earth {
  position: fixed; left: 50%; z-index: 0; pointer-events: none;
  /* Wider than the viewport on purpose — only the top cap is ever
     visible, and a cap of a big sphere is a gentle curve rather than
     a beach ball sitting on the page. */
  width: min(124vh, 168vw); height: min(124vh, 168vw);
  /* Scaled up from a 132px grid, so it stays pixel art rather than
     being smoothed into a blur. */
  image-rendering: pixelated;
  image-rendering: crisp-edges;
  /* Horizon from just off the bottom of the screen to 42vh above it,
     on the eased --rise rather than raw --descent. */
  top: calc(100vh - (var(--rise, 0) * 42vh));
  transform: translateX(-50%) rotate(calc(var(--descent, 0) * -4deg));
  opacity: calc(0.25 + var(--rise, 0) * 0.75);
  filter: drop-shadow(0 0 70px rgba(89, 0, 255, 0.5));
  transition: opacity 200ms linear;
}
/* Space giving way to the glow of an atmosphere on the way down. */
#atmosphere {
  position: fixed; inset: 0; z-index: 0; pointer-events: none; opacity: 0;
  background:
    radial-gradient(120% 60% at 50% 118%, rgba(122, 62, 255, 0.55) 0%,
                    rgba(58, 28, 168, 0.30) 38%, transparent 68%),
    linear-gradient(to bottom, transparent 40%, rgba(24, 8, 64, 0.55) 100%);
}
body:not([data-screen="landing"]) #earth,
body:not([data-screen="landing"]) #atmosphere { display: none; }

@media (max-width: 640px) {
  /* Less rise on a phone: there is far less room above the horizon
     for the outro to live in. */
  #earth { width: 210vw; height: 210vw; top: calc(100vh - (var(--rise, 0) * 34vh)); }
  .story { padding: 16vh 22px; }
  .founders li { padding: 11px 12px; }
  .story-subtitle { margin-top: 28px; }
  .landing-wrap.landing-earth-wrap { padding-top: 12vh; }
}

/* ---------------- site nav (landing + downloads only) ------------- */
/* A floating glass pill, pinned to the top: the home screen is kept
   clear for the sky, so anything that isn't the hero lives up here. */
.sitenav { display: none; }
body[data-screen="landing"] .sitenav,
body[data-screen="downloads"] .sitenav {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  position: fixed; top: 0; left: 0; right: 0; z-index: 20;
  padding: 10px max(16px, env(safe-area-inset-left)) 10px max(16px, env(safe-area-inset-right));
  padding-top: max(10px, env(safe-area-inset-top));
  background: color-mix(in srgb, var(--bg) 78%, transparent); border-bottom: 1px solid var(--hair);
  backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
}
.sitenav-mark { font-family: var(--font-mono); font-weight: 700; letter-spacing: 4px; font-size: 13px; color: var(--accent); cursor: pointer; }
.sitenav-links { display: flex; gap: 4px; }
.navlink {
  background: none; border: 1px solid transparent; border-radius: var(--r-pill);
  font-family: var(--font-mono); color: var(--dim); font-size: 11px; font-weight: 700;
  letter-spacing: 1.5px; text-transform: uppercase; padding: 8px 14px;
  transition: color var(--ease), background var(--ease), border-color var(--ease);
}
.navlink:hover { color: var(--text); background: var(--tint); }
.navlink.on { color: var(--accent); border-color: var(--line); background: var(--tint); }
.navlink.cta { color: var(--on-accent); background: var(--accent); border-color: transparent; }
.navlink.cta:hover { color: var(--on-accent); box-shadow: 0 10px 24px -12px var(--accent); }

/* ---------------- landing + downloads ---------------- */
#screen-landing, #screen-downloads { min-height: 100dvh; }
body[data-screen="downloads"] #screen-downloads { display: block; padding-top: 74px; }
.page-title { font-family: var(--font-mono); font-size: 20px; letter-spacing: 2px; color: var(--accent); margin: 0 0 18px; }
.landing-wrap { width: 100%; max-width: 940px; margin: 0 auto; padding: 40px 20px 60px; }
.landing-hero { text-align: center; margin-bottom: 44px; }
.landing-mark {
  font-family: var(--font-mono); font-size: clamp(28px, 8vw, 64px); font-weight: 700;
  letter-spacing: clamp(4px, 1.6vw, 14px); color: var(--text);
  text-shadow: 0 0 34px var(--accent);
  margin-right: calc(-1 * clamp(4px, 1.6vw, 14px));
}
.landing-sub {
  font-family: var(--font-mono); font-size: clamp(13px, 3.4vw, 19px); letter-spacing: clamp(6px, 2.4vw, 15px);
  color: var(--accent); font-weight: 700; margin-top: 4px;
}
.landing-tag {
  font-family: var(--font-mono); color: var(--dim); font-size: clamp(11px, 2.6vw, 13px);
  letter-spacing: clamp(2px, 0.9vw, 5px); margin: 18px auto 0;
}
.landing-cards { display: grid; grid-template-columns: 1fr; gap: 16px; }
@media (min-width: 720px) { .landing-cards { grid-template-columns: 1fr 1fr; } }
.lcard { border: 1px solid var(--line); background: var(--surface); border-radius: var(--r-lg); padding: 22px;
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px); }
.lcard-icon { font-size: 26px; margin-bottom: 8px; }
.lcard-title { font-weight: 700; font-size: 16px; letter-spacing: 0.2px; margin-bottom: 8px; }
.lcard-text { color: var(--dim); font-size: 13px; line-height: 1.7; margin: 0 0 4px; }
.lcard-note { color: var(--dim); font-size: 11px; margin-top: 10px; opacity: 0.8; }
.founders li { background: var(--surface); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); }

/* ---------------- device picker / login / banned ---------------- */
#screen-device, #screen-login, #screen-banned,
#screen-register, #screen-reset { min-height: 100dvh; }
body[data-screen="device"] #screen-device,
body[data-screen="login"] #screen-login,
body[data-screen="banned"] #screen-banned,
body[data-screen="register"] #screen-register,
body[data-screen="reset"] #screen-reset {
  display: flex; justify-content: center; padding: 24px 16px;
}
body[data-screen="device"] #screen-device > .card,
body[data-screen="login"] #screen-login > .card,
body[data-screen="banned"] #screen-banned > .card,
body[data-screen="register"] #screen-register > .card,
body[data-screen="reset"] #screen-reset > .card { margin: auto; width: 100%; }
body[data-screen="login"] #screen-login > .gate-wrap,
body[data-screen="register"] #screen-register > .gate-wrap,
body[data-screen="reset"] #screen-reset > .gate-wrap { margin: auto; width: 100%; }
.gate-wrap > .card { margin: 0; width: 100%; }

/* The sign-in screens wear login_gate.py's look, not the app's: white
   and grey on black, no accent anywhere, because they show before
   anybody has logged in and can't belong to any one person's colour.
   Same shapes as the app — rounded glass, pill buttons — in grey. */
body[data-screen="login"],
body[data-screen="register"],
body[data-screen="reset"],
body[data-screen="device"] { background: #000; }
body[data-screen="login"] .card,
body[data-screen="register"] .card,
body[data-screen="reset"] .card,
body[data-screen="device"] .card {
  background: rgba(10, 10, 12, 0.72); border-color: var(--mono-border); position: relative; z-index: 1;
  box-shadow: 0 30px 80px -40px #000, inset 0 1px 0 rgba(255, 255, 255, 0.06);
}
body[data-screen="login"] .card h1,
body[data-screen="register"] .card h1,
body[data-screen="reset"] .card h1,
body[data-screen="device"] .card h1 { color: var(--mono); }
body[data-screen="login"] .dim, body[data-screen="login"] .note, body[data-screen="login"] .linkbtn,
body[data-screen="register"] .dim, body[data-screen="register"] .note, body[data-screen="register"] .linkbtn,
body[data-screen="reset"] .dim, body[data-screen="reset"] .note, body[data-screen="reset"] .linkbtn,
body[data-screen="device"] .dim, body[data-screen="device"] .note,
body[data-screen="device"] .devbtn-sub { color: var(--mono-dim); }
body[data-screen="login"] .linkbtn:hover,
body[data-screen="register"] .linkbtn:hover,
body[data-screen="reset"] .linkbtn:hover { color: var(--mono); }
body[data-screen="register"] .field-block > label { color: var(--mono); }
body[data-screen="login"] input,
body[data-screen="register"] input,
body[data-screen="reset"] input {
  background: #0a0b0d; color: var(--mono); border-color: var(--mono-border);
}
body[data-screen="login"] input:focus,
body[data-screen="register"] input:focus,
body[data-screen="reset"] input:focus { border-color: var(--mono); box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.08); }
/* The primary action on the gate is white, filled, like UNLOCK. */
body[data-screen="login"] .btn,
body[data-screen="register"] .btn,
body[data-screen="reset"] .btn { background: var(--mono); color: #000; border-color: transparent; box-shadow: none; }
body[data-screen="login"] .btn.ghost,
body[data-screen="register"] .btn.ghost,
body[data-screen="reset"] .btn.ghost,
body[data-screen="device"] .devbtn { background: transparent; color: var(--mono); border-color: var(--mono-border); }
body[data-screen="login"] .btn:hover,
body[data-screen="register"] .btn:hover,
body[data-screen="reset"] .btn:hover { box-shadow: 0 12px 30px -16px #fff; }
body[data-screen="login"] .btn.ghost:hover,
body[data-screen="register"] .btn.ghost:hover,
body[data-screen="reset"] .btn.ghost:hover,
body[data-screen="device"] .devbtn:hover { color: #fff; border-color: #fff; box-shadow: none; }
body[data-screen="login"] .keyout,
body[data-screen="register"] .keyout { color: var(--mono); border-color: var(--mono-border); background: #0a0b0d; }

/* The wordmark is three words of spaced letters. Each word is one
   unbreakable span, so a narrow window wraps BETWEEN words, never
   inside one ("R E A L N I G G / A N E T W O R K", as it did at 830px),
   and the size and spacing track the window so it stays on one line
   down to a laptop and two centred lines on a phone. */
.gate-mark {
  font-family: var(--font-mono); text-align: center; color: var(--mono); font-weight: 700;
  font-size: clamp(14px, 2.6vw, 30px); letter-spacing: clamp(2px, 0.72vw, 6px); margin-bottom: 10px;
  position: relative; z-index: 1;
  display: flex; flex-wrap: wrap; justify-content: center; column-gap: 1.1em; row-gap: 2px;
  /* Wider than the 440px card it sits above: centred on the page, as
     wide as its words need up to the window, so it is one line on any
     laptop and wraps between words only on a phone. */
  width: max-content; max-width: 94vw; left: 50%; transform: translateX(-50%);
}
.gate-mark span { white-space: nowrap; }
/* Sign-up, step by step. */
.steps { list-style: none; margin: 6px 0 18px; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.step { display: flex; gap: 10px; align-items: flex-start; font-size: 13px; line-height: 1.45; color: var(--text); }
.step b { color: var(--accent); font-weight: 700; }
.step-n {
  flex: 0 0 22px; height: 22px; border-radius: 50%; background: var(--accent); color: var(--on-accent);
  font-family: var(--font-mono); font-weight: 700; font-size: 12px; line-height: 22px; text-align: center;
}
.check { display: flex; gap: 10px; align-items: flex-start; font-size: 13px; line-height: 1.4; color: var(--text); margin: 2px 0 12px; cursor: pointer; }
.check input { width: 18px; height: 18px; margin: 1px 0 0; accent-color: var(--accent); flex: 0 0 auto; }
/* Discord's own purple, under Create account. Spelled with the sign-in
   screens' own selector too, because those screens repaint every .btn
   in their mono palette (body[data-screen="register"] .btn) and that
   rule would otherwise win over this one. */
.btn.discord-btn,
body[data-screen="register"] .btn.discord-btn {
  display: flex; align-items: center; justify-content: center; gap: 10px; width: 100%; box-sizing: border-box;
  margin-top: 10px; background: #5865f2; border-color: #5865f2; color: #fff; text-decoration: none;
  box-shadow: 0 10px 24px -14px #5865f2;
}
.btn.discord-btn:hover,
body[data-screen="register"] .btn.discord-btn:hover { background: #4752c4; border-color: #4752c4; color: #fff; }
.btn.discord-btn svg { width: 20px; height: 20px; fill: currentColor; flex: 0 0 auto; }
.gate-prompt {
  font-family: var(--font-mono); text-align: center; color: var(--mono-dim); font-size: 12px;
  letter-spacing: 2px; margin-bottom: 18px; position: relative; z-index: 1;
}
.gate-wrap { margin: auto; width: 100%; max-width: 440px; }

/* ---------------- signup / reset ---------------- */
.field-block { margin-top: 16px; }
.field-block > label {
  display: block; color: var(--text); font-size: 13px; font-weight: 600; letter-spacing: 0.2px;
}
.field-block .dim.small { margin: 3px 0 0; }
.field-block input { margin-top: 7px; }
.linkrow { display: flex; gap: 10px; }
.linkrow .linkbtn { flex: 1; }
.keyout {
  font-family: var(--font-mono); font-size: 32px; font-weight: 700; letter-spacing: 7px; color: var(--accent);
  background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--r-md);
  padding: 18px 10px; margin: 16px 0 4px; text-align: center;
  user-select: all; -webkit-user-select: all;
}
.note-ok { color: var(--ok); font-size: 13px; margin-top: 10px; line-height: 1.5; }
.device-choice { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 18px; }
.devbtn {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--r-md);
  padding: 22px 12px; text-align: center; transition: transform var(--ease), border-color var(--ease);
}
.devbtn:hover, .devbtn:active { border-color: var(--accent); transform: translateY(-1px); }
.devbtn-icon { font-size: 30px; }
.devbtn-label { font-weight: 700; font-size: 15px; letter-spacing: 0.2px; }
.devbtn-sub { color: var(--dim); font-size: 10px; line-height: 1.4; }

/* ================= APP ================= */
/* One shell. Pinned to the corners when there's room, stacked into a
   column when there isn't — the phone gets the SAME cards, in the same
   order, wearing the same chrome, plus a tab bar along the bottom. */
#app-shell { display: none; }
body[data-screen="app"] #app-shell {
  display: block; position: relative; height: 100dvh; overflow: hidden;
}

/* ---------------- the HUD's own background ---------------- */
/* hudbg.js is the boot canvas the desktop never destroys: the
   starfield, the ring bursts and the dim wordmark keep running behind
   the panels for the whole session. */
#hudbg {
  position: fixed; inset: 0; z-index: 0; pointer-events: none;
  width: 100%; height: 100%;
  opacity: 0; transition: opacity 500ms ease;
}
body[data-screen="app"] #hudbg { opacity: 1; }
body[data-screen="app"] #screen-app { position: relative; z-index: 1; }
/* A soft accent haze across the field, the way a modern dark UI lights
   its background from one corner rather than leaving it flat. */
body[data-screen="app"] #screen-app::before {
  content: ""; position: fixed; inset: 0; z-index: 0; pointer-events: none;
  background:
    radial-gradient(60% 50% at 50% 0%, color-mix(in srgb, var(--accent) 16%, transparent), transparent 70%),
    radial-gradient(40% 40% at 100% 100%, color-mix(in srgb, var(--accent) 10%, transparent), transparent 70%);
}

.live-dot {
  display: inline-block; width: 7px; height: 7px; border-radius: 50%;
  background: var(--success); margin-right: 6px; vertical-align: middle;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--success) 25%, transparent);
}
.live-dot.bad { background: var(--danger); box-shadow: 0 0 0 3px color-mix(in srgb, var(--danger) 25%, transparent); }
.banner { display: none; padding: 10px 16px; font-size: 12px; border-radius: var(--r-md); }
.banner.on {
  display: block; position: absolute; top: 104px; left: 50%; transform: translateX(-50%);
  z-index: 6; max-width: 520px; width: max-content;
  background: color-mix(in srgb, var(--danger) 16%, var(--bg)); color: var(--danger);
  border: 1px solid color-mix(in srgb, var(--danger) 50%, transparent);
  backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
}
.banner.update.on { background: var(--surface-2); color: var(--text); border: 1px solid var(--line); }
.linkbtn.inline {
  display: inline; width: auto; margin: 0 0 0 4px; padding: 0;
  color: var(--accent); font-weight: 700; text-decoration: underline;
}
.badge {
  display: none; min-width: 20px; height: 20px; padding: 0 7px; border-radius: var(--r-pill);
  background: var(--accent); color: var(--on-accent); font-family: var(--font-mono);
  font-size: 10px; line-height: 20px; text-align: center; font-weight: 700;
}
.badge.on { display: inline-block; }
.badge.quiet { background: transparent; color: var(--accent); border: 1px solid var(--line); line-height: 18px; }

/* ---------------- the top row ---------------- */
/* One floating glass pill, centred, carrying the orb and the nav — the
   desktop's `controls` row, the way a modern app draws a toolbar. */
.hud-top {
  position: absolute; top: 12px; left: 0; right: 0;
  z-index: 8; text-align: center; pointer-events: none;
  display: flex; flex-direction: column; align-items: center; gap: 0;
}
.hud-row {
  display: inline-flex; align-items: center; justify-content: center;
  gap: 2px; flex-wrap: nowrap; padding: 6px 8px 6px 8px;
  pointer-events: auto; max-width: calc(100% - 24px);
  background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--r-pill);
  backdrop-filter: blur(20px) saturate(1.3); -webkit-backdrop-filter: blur(20px) saturate(1.3);
  box-shadow: var(--glow), inset 0 1px 0 color-mix(in srgb, var(--accent) 24%, transparent);
}
.hud-orb {
  flex: 0 0 auto; width: 26px; height: 26px; margin: 0 8px 0 2px; border-radius: 50%; background: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent), 0 0 22px -2px var(--accent);
  animation: hud-pulse 2.4s ease-in-out infinite;
}
@keyframes hud-pulse {
  0%, 100% { transform: scale(0.9); }
  50%      { transform: scale(1.06); }
}
@media (prefers-reduced-motion: reduce) { .hud-orb { animation: none; } }
/* Nav items: the accent gradient text the desktop paints (app.js's
   paintHudRow), on pills that light up under the pointer. */
.hudbtn {
  background: none; border: 1px solid transparent; border-radius: var(--r-pill);
  padding: 9px 14px; font-family: var(--font-mono);
  font-size: 12px; font-weight: 700; letter-spacing: 2px;
  color: var(--accent); white-space: nowrap;
  transition: background var(--ease), transform var(--ease), text-shadow var(--ease);
}
a.hudbtn { text-decoration: none; display: inline-flex; align-items: center; }
.hudbtn:hover { background: var(--tint); text-shadow: 0 0 12px currentColor; }
.hudbtn:active { transform: scale(0.97); }
.hudbtn.on { background: var(--tint); border-color: var(--line); text-shadow: 0 0 12px currentColor; }
.hudbtn.close { color: var(--dim); font-weight: 400; letter-spacing: 0; font-size: 15px; padding: 9px 12px; margin-left: 2px; }
.hudbtn.close:hover { color: var(--danger); background: color-mix(in srgb, var(--danger) 14%, transparent); }

/* ---------------- the top row's dropdowns ---------------- */
/* GAMES and ADMIN each hang a menu off their own label — a floating
   glass panel, items as pills. Opened by a class app.js puts on, never
   by :hover: a hover menu does not exist on a phone. */
.huddrop { position: relative; display: inline-flex; }
.huddrop-menu {
  display: none; position: absolute; top: calc(100% + 10px); left: 50%;
  transform: translateX(-50%);
  min-width: 190px; padding: 6px;
  background: var(--surface-3); border: 1px solid var(--line); border-radius: var(--r-md);
  backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
  box-shadow: 0 30px 60px -24px #000, var(--glow);
  z-index: 1; text-align: left;
  animation: drop-in 160ms cubic-bezier(0.2, 0.7, 0.2, 1);
}
@keyframes drop-in { from { opacity: 0; transform: translateX(-50%) translateY(-6px); } to { opacity: 1; transform: translateX(-50%) translateY(0); } }
.huddrop.open .huddrop-menu { display: block; }
.huddrop-item {
  display: block; width: 100%; min-height: 40px; padding: 10px 14px; border-radius: var(--r-sm);
  background: none; border: none; color: var(--text); font-family: var(--font-mono);
  font-size: 12px; font-weight: 700; letter-spacing: 2px; text-align: left;
  white-space: nowrap; transition: background var(--ease), color var(--ease);
}
.huddrop-item:hover, .huddrop-item:focus { color: var(--accent); background: var(--tint); }
.huddrop-item:active { background: var(--accent); color: var(--on-accent); }
.hud-status {
  display: inline-flex; align-items: center; gap: 8px; margin-top: 8px; padding: 6px 14px; align-self: center;
  border-radius: var(--r-pill); background: color-mix(in srgb, var(--bg) 60%, transparent);
  border: 1px solid var(--hair); font-family: var(--font-mono);
  font-size: 11px; font-weight: 700; color: var(--text); letter-spacing: 0.4px;
  pointer-events: none;
}
.hud-mark { color: var(--accent); letter-spacing: 3px; margin-right: 2px; }

/* ---------------- the cards ---------------- */
/* Pinned to the four corners of the field, 24px in, the same offsets
   hud.py place()s them at. Rounded glass panels tinted with the
   accent, the starfield blurred behind them. */
.hud-card {
  position: absolute; z-index: 4;
  display: flex; flex-direction: column; min-height: 0;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-lg);
  backdrop-filter: blur(18px) saturate(1.2); -webkit-backdrop-filter: blur(18px) saturate(1.2);
  box-shadow: var(--glow), inset 0 1px 0 color-mix(in srgb, var(--accent) 22%, transparent);
  overflow: hidden;
  animation: card-rise 420ms cubic-bezier(0.2, 0.7, 0.2, 1) both;
}
@keyframes card-rise { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
.card-profile { animation-delay: 60ms; } .card-games { animation-delay: 120ms; } .card-chat { animation-delay: 180ms; }
@media (prefers-reduced-motion: reduce) { .hud-card { animation: none; } }
.card-online  { top: 24px; left: 24px; width: 300px; max-height: 38vh; }
.card-profile { top: 24px; right: 24px; width: 280px; max-height: 46vh; }
.card-games   { bottom: 24px; left: 24px; width: 300px; height: 44vh; }
.card-chat    { bottom: 24px; right: 24px; width: 480px; height: 64vh; }
.hud-card-head {
  flex: 0 0 auto; display: flex; align-items: center; gap: 0;
  padding: 14px 16px 6px; font-family: var(--font-mono);
  color: var(--dim); font-size: 11px; font-weight: 700; letter-spacing: 1.8px;
  text-transform: uppercase;
}
/* A small accent mark before every title — the one decoration a
   section header gets. */
.hud-card-head::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--accent);
  margin-right: 8px; box-shadow: 0 0 10px -1px var(--accent); flex: 0 0 auto;
}
.hud-card-head .gamesclose { margin-left: auto; }
.hud-card.flash { animation: card-flash 900ms ease-out; }
@keyframes card-flash {
  0%, 100% { box-shadow: var(--glow); }
  30%      { box-shadow: 0 0 0 2px var(--accent), 0 0 26px -4px var(--accent); }
}
@media (prefers-reduced-motion: reduce) { .hud-card.flash { animation: none; } }
.hud-card-head .badge { margin-left: 8px; }
.hud-card-head .head-sub {
  margin-left: auto; color: var(--accent); letter-spacing: 1px; text-transform: none;
  max-width: 55%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.hud-card-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 4px 12px 10px; }
.hud-card-body.scroller { padding: 6px 8px; }
.hudpill {
  flex: 0 0 auto; white-space: nowrap; border-radius: var(--r-pill);
  background: var(--surface-2); border: 1px solid var(--line); color: var(--accent);
  padding: 8px 14px; font-family: var(--font-mono); font-size: 11px; font-weight: 700; letter-spacing: 1.5px;
  transition: background var(--ease), color var(--ease), transform var(--ease), box-shadow var(--ease);
}
.hudpill:hover { background: var(--tint); box-shadow: 0 8px 20px -12px var(--accent); transform: translateY(-1px); }
.hudpill:active { transform: translateY(0); }
.hudpill.on { background: var(--accent); color: var(--on-accent); border-color: transparent; }
.hud-stack { display: flex; flex-direction: column; min-height: 0; min-width: 0; flex: 1 1 50%; }
/* The two chats share the card by --dm-share (app.js chatSplit): the
   DMs take that fraction, PUBLIC CHAT the rest. Neither can be dragged
   below its own heading and composer. */
.hud-stack.dm-stack { flex: 0 1 calc(var(--dm-share, 0.5) * 100%); min-height: 100px; }
.hud-stack.public-stack { flex: 1 1 0; min-height: 100px; }
/* The seam between them, as a grip: the hairline the stacks used to
   share, with a short bar in the middle that lights up in the accent
   under the pointer. Horizontal in the stacked card, vertical once the
   chats sit side by side in focus (below). */
.chat-divider {
  position: relative; flex: 0 0 10px; cursor: row-resize; touch-action: none; user-select: none;
  -webkit-user-select: none;
}
.chat-divider::before {
  content: ""; position: absolute; left: 0; right: 0; top: 50%; height: 1px; background: var(--hair);
}
.chat-divider::after {
  content: ""; position: absolute; left: 50%; top: 50%; width: 44px; height: 4px; border-radius: 2px;
  transform: translate(-50%, -50%); background: var(--line);
  transition: background var(--ease), box-shadow var(--ease), width var(--ease), height var(--ease);
}
.chat-divider:hover::after, .chat-divider.dragging::after {
  width: 64px; background: var(--accent); box-shadow: 0 0 12px -2px var(--accent);
}
.card-chat.dragging, .card-chat.dragging * { user-select: none; -webkit-user-select: none; }
.card-chat.dragging { cursor: row-resize; }
.hud-card-head .chatfocus { margin-left: auto; padding: 4px 10px; font-size: 9.5px; letter-spacing: 1.3px; }

/* The profile card: the avatar large with its ring, the name, the role
   chip, the row of pills under it. */
.prof-row { display: flex; align-items: center; gap: 12px; margin: 4px 0 12px; }
.prof-row .avatar { width: 56px; height: 56px; font-size: 22px; border-width: 2px;
  box-shadow: 0 0 0 3px var(--bg), 0 0 28px -8px var(--accent); }
.prof-name { flex: 1 1 auto; min-width: 0; font-weight: 700; font-size: 17px; letter-spacing: -0.2px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.prof-role {
  font-family: var(--font-mono); color: var(--accent); font-size: 10px; letter-spacing: 1.6px; text-transform: uppercase;
  padding: 4px 9px; border-radius: var(--r-pill); border: 1px solid var(--line); background: var(--tint);
}
.prof-btns { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.prof-btns .hudpill { padding: 7px 12px; font-size: 10px; }
.js-profile-card > .hudpill { width: 100%; text-align: center; }

/* ---------------- list rows ---------------- */
/* The online list: an avatar with its presence dot sitting on its
   corner, the name in that person's colour, device badges, CALL. */
.listrow {
  display: flex; align-items: center; gap: 10px; cursor: pointer; position: relative;
  padding: 7px 8px; border-radius: 14px;
  transition: background var(--ease);
}
.listrow:hover { background: var(--tint); }
.listrow.on { background: var(--tint); box-shadow: inset 0 0 0 1px var(--line); }
.listrow-main { flex: 1 1 auto; min-width: 0; }
.listrow-title { display: block; font-weight: 600; font-size: 14px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.listrow-sub { display: block; color: var(--dim); font-size: 11px; margin-top: 1px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.listrow-meta { flex: 0 0 auto; color: var(--dim); font-family: var(--font-mono); font-size: 10px; text-align: right; }
/* Avatars: round, ringed in the person's own colour (app.js's
   ringColor), a letter when they have no picture. */
.avatar {
  flex: 0 0 auto; width: 36px; height: 36px; border-radius: 50%;
  background: var(--surface-3) center / cover no-repeat; border: 1.5px solid var(--accent); color: var(--accent);
  font-family: var(--font-mono); font-weight: 700; font-size: 14px; text-transform: uppercase;
  display: inline-flex; align-items: center; justify-content: center; overflow: hidden;
  box-shadow: 0 0 0 2px var(--bg);
}
img.avatar { object-fit: cover; }
/* The presence dot rides the avatar's corner. ONLINE_DOT_COLOR — a
   fixed presence green, independent of whichever accent is active. */
.listrow .dot {
  position: absolute; left: 33px; top: 33px; width: 12px; height: 12px; border-radius: 50%;
  background: #00ff3c; border: 2px solid var(--bg); box-shadow: 0 0 8px -1px #00ff3c; margin: 0;
}
.dot { flex: 0 0 auto; width: 8px; height: 8px; border-radius: 50%; background: #00ff3c; }
.pill {
  display: inline-block; margin-left: 6px; min-width: 18px; padding: 0 6px; border-radius: var(--r-pill);
  height: 18px; line-height: 18px;
  background: var(--danger); color: #fff; font-size: 10px; font-weight: 700; text-align: center;
}
.tag { color: var(--dim); font-weight: 400; font-size: 11px; }
.badges { flex: 0 0 auto; display: inline-flex; align-items: center; gap: 4px; }
.devbadge {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 22px; height: 20px; padding: 0 5px; font-size: 11px; border-radius: var(--r-pill);
  background: var(--tint); border: 1px solid var(--hair); color: var(--accent); line-height: 1;
}
/* A person's row is in THAT PERSON'S colour, end to end: app.js's
   onlineRowHtml sets --row-accent to the accent their own access key is
   themed with, and every control on the row — the device badges, the
   CALL and +VC pills — is a blend of black and THAT, never of the
   viewer's. Somebody with no colour known yet inherits --accent, so
   nothing about them looks like a choice they made. */
.listrow { --row-accent: var(--accent); }
/* In NIGGA CITY: the network's logo as the badge, its own colours. */
img.devbadge.citybadge { box-sizing: border-box; width: 22px; min-width: 22px; height: 20px; padding: 2px 3px;
  object-fit: contain; image-rendering: pixelated; }
.listrow .devbadge {
  color: var(--row-accent);
  border-color: color-mix(in srgb, var(--row-accent) 34%, transparent);
  background: color-mix(in srgb, var(--row-accent) 10%, transparent);
}
.listrow .hudpill {
  color: var(--row-accent);
  border-color: color-mix(in srgb, var(--row-accent) 45%, transparent);
  background: color-mix(in srgb, var(--row-accent) 12%, var(--surface-2));
}
.listrow .hudpill:hover {
  background: color-mix(in srgb, var(--row-accent) 24%, var(--surface-2));
  box-shadow: 0 8px 20px -12px var(--row-accent);
}
.rowgroup {
  font-family: var(--font-mono); color: var(--dim); font-size: 10px; font-weight: 700; letter-spacing: 1.8px;
  text-transform: uppercase; padding: 10px 8px 4px;
}
.rowgroup:first-child { padding-top: 4px; }

/* ---------------- messages ---------------- */
/* Rows like a modern chat: the avatar in a gutter, the name in that
   person's own colour with the time beside it, the text under it, and
   consecutive messages from the same person grouped under one header
   (app.js's messageRowHtml). Still a transcript, top to bottom — your
   own messages are not pulled to the right, they are tinted. */
.scroller { flex: 1 1 auto; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch; }
.msg {
  display: grid; grid-template-columns: 36px minmax(0, 1fr); column-gap: 10px;
  padding: 3px 8px; margin-top: 8px; border-radius: 14px; font-size: 14px; line-height: 1.5;
  transition: background var(--ease);
}
.msg:hover { background: color-mix(in srgb, var(--accent) 7%, transparent); }
.msg.mine { background: color-mix(in srgb, var(--accent) 6%, transparent); }
.msg.mine:hover { background: color-mix(in srgb, var(--accent) 10%, transparent); }
.msg.cont { margin-top: 0; }
.msg-av { display: flex; align-items: flex-start; padding-top: 2px; position: relative; min-height: 1px; }
.msg-av .avatar { width: 36px; height: 36px; }
/* A grouped row shows its time in the gutter when hovered. */
.msg.cont .msg-av::after {
  content: attr(data-time); position: absolute; left: 0; top: 4px; width: 36px; text-align: center;
  font-family: var(--font-mono); font-size: 9px; color: var(--dim); opacity: 0; white-space: nowrap;
  transition: opacity var(--ease);
}
.msg.cont:hover .msg-av::after { opacity: 1; }
.msg-main { min-width: 0; }
.msg.cont .msg-main { display: flex; align-items: flex-start; gap: 8px; }
.msg.cont .msg-main > .msg-body { flex: 1 1 auto; min-width: 0; }
.msg-head { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.msg-name { font-weight: 700; font-size: 14px; letter-spacing: -0.1px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.msg-time { font-family: var(--font-mono); color: var(--dim); font-size: 10px; letter-spacing: 0.4px; flex: 0 0 auto; }
.msg-avatar { width: 18px; height: 18px; object-fit: cover; border-radius: 50%; vertical-align: -4px; margin-right: 4px; border: 1px solid var(--accent); }
.msg-body { white-space: pre-wrap; color: var(--text); }
.msg.media .msg-body { display: block; margin: 4px 0 6px; position: relative; max-width: 340px; }
.chat-image { max-width: 100%; max-height: 280px; display: block; border-radius: 14px; border: 1px solid var(--hair);
  box-shadow: 0 16px 40px -24px #000; }
.media-loading, .media-fallback {
  display: block; min-height: 40px; padding: 10px 50px 10px 12px; border-radius: 12px; background: var(--surface-2);
  font-size: 11px; color: var(--dim); word-break: break-all;
}
.media-loading { font-style: italic; }
.media-fallback a { color: var(--dim); }
.custom-emoji { height: 1.3em; width: auto; vertical-align: -0.3em; }
.favorite-star {
  position: absolute; right: 6px; bottom: 6px;
  min-width: 40px; min-height: 40px; padding: 0 6px 2px; border-radius: 12px;
  display: flex; align-items: flex-end; justify-content: center;
  font-size: 28px; line-height: 0.95; color: #fff; cursor: pointer;
  text-shadow: 0 0 5px #000, 0 2px 12px #000; transition: transform var(--ease);
}
.favorite-star:hover { transform: scale(1.1); }
.favorite-star.on { color: #ffd23f; }
/* The per-message DEL control (app.js's deleteControlHtml), which only
   an admin is ever sent the markup for: a quiet chip at the end of the
   header that turns red only when it means something. */
.msg-del {
  margin-left: auto; background: none; border: 1px solid transparent; border-radius: var(--r-pill); color: var(--dim);
  font-family: var(--font-mono); font-size: 9px; font-weight: 700; letter-spacing: 1px;
  display: inline-block; min-width: 30px; padding: 3px 8px; opacity: 0.45; flex: 0 0 auto;
  transition: opacity var(--ease), color var(--ease), border-color var(--ease);
}
.msg:hover .msg-del { opacity: 0.85; }
.msg-del:hover, .msg-del:focus { color: var(--danger); border-color: var(--danger); opacity: 1; }
.msg-del:active { opacity: 0.7; }

/* ---------------- composer ---------------- */
/* A pill: GIF chip, the field, a filled round send button. */
.composer {
  flex: 0 0 auto; display: flex; align-items: center; gap: 6px;
  padding: 10px 12px 12px; background: transparent; border-top: 1px solid var(--hair);
}
.composer input {
  flex: 1 1 auto; min-width: 0; margin-top: 0; padding: 10px 16px; font-size: 14px;
  border-radius: var(--r-pill); background: var(--surface-2); border: 1px solid var(--hair);
}
.composer select {
  flex: 0 0 auto; width: auto; max-width: 148px; margin-top: 0;
  padding: 9px 30px 9px 12px; font-size: 12px; border-radius: var(--r-pill);
}
.cbtn {
  flex: 0 0 auto; height: 38px; min-width: 44px; padding: 0 12px; border-radius: var(--r-pill);
  background: var(--surface-2); border: 1px solid var(--hair); color: var(--accent);
  display: flex; align-items: center; justify-content: center; font-family: var(--font-mono);
  font-size: 11px; font-weight: 700; letter-spacing: 1px;
  transition: background var(--ease), transform var(--ease), box-shadow var(--ease);
}
.cbtn:hover { background: var(--tint); border-color: var(--line); }
.cbtn.send {
  width: 38px; min-width: 38px; padding: 0; font-size: 15px;
  background: var(--accent); color: var(--on-accent); border-color: transparent;
  box-shadow: 0 8px 20px -10px var(--accent);
}
.cbtn.send:hover { transform: translateY(-1px); box-shadow: 0 12px 24px -10px var(--accent); }
.cbtn.on { color: var(--text); border-color: var(--text); }
.cbtn:active { transform: scale(0.96); }

/* ---------------- the phone's tab bar ---------------- */
/* Hidden until the layout stacks (see the media query below): four
   tabs along the bottom that jump to a card, the way every phone app
   does it. Each one is a card that is already on the page. */
.tabbar { display: none; }
.tabbar-btn {
  flex: 1 1 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  min-height: 52px; padding: 6px 4px; border: none; border-radius: var(--r-md); background: none;
  font-family: var(--font-mono); font-size: 9px; font-weight: 700; letter-spacing: 1.6px; color: var(--dim);
  transition: color var(--ease), background var(--ease);
}
.tabbar-btn .tabbar-ico { width: 20px; height: 20px; display: block; }
.tabbar-btn .tabbar-ico svg { width: 20px; height: 20px; display: block; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.tabbar-btn.on { color: var(--accent); background: var(--tint); }
.tabbar-btn .badge { position: absolute; }

/* ---------------- tray (GIF / emoji) ---------------- */
.tray {
  display: none; position: fixed; left: 0; right: 0; bottom: 0; z-index: 30;
  max-height: 56vh; overflow: hidden; flex-direction: column;
  background: var(--surface-3); border-top: 1px solid var(--line); border-radius: 22px 22px 0 0;
  backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
  box-shadow: 0 -30px 80px -40px var(--accent);
  animation: sheet-up 220ms cubic-bezier(0.2, 0.7, 0.2, 1);
}
@keyframes sheet-up { from { transform: translateY(24px); opacity: 0; } to { transform: none; opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .tray, .huddrop-menu, .sheet .card { animation: none; } }
.tray.open { display: flex; }
.tray-head { flex: 0 0 auto; display: flex; align-items: center; gap: 6px; padding: 12px 12px 8px; border-bottom: 1px solid var(--hair); }
.tray-subtab {
  background: transparent; color: var(--dim); border: 1px solid transparent; border-radius: var(--r-pill);
  font-family: var(--font-mono); font-size: 11px; font-weight: 700; letter-spacing: 1px; padding: 8px 14px;
  transition: background var(--ease), color var(--ease);
}
.tray-subtab:hover { color: var(--text); background: var(--tint); }
.tray-subtab.on { color: var(--on-accent); background: var(--accent); }
.tray-close { margin-left: auto; width: 34px; height: 34px; border-radius: 50%; background: var(--surface-2); border: 1px solid var(--hair); color: var(--dim); font-size: 14px;
  display: inline-flex; align-items: center; justify-content: center; }
.tray-close:hover { color: var(--text); border-color: var(--line); }
.tray-body { flex: 1 1 auto; overflow-y: auto; padding: 12px 12px 20px; }
.fav-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); gap: 8px; }
.fav-item { position: relative; }
.fav-thumb {
  width: 100%; aspect-ratio: 1; object-fit: cover; display: block; cursor: pointer; border-radius: 12px;
  border: 1px solid var(--hair); background: var(--surface-2); transition: transform var(--ease), border-color var(--ease);
}
.fav-thumb:hover { border-color: var(--accent); transform: scale(1.03); }
/* The C9 editor an owner/admin sees at the top of the C9 tab (app.js's
   renderTray). The ✕ on a tile mirrors the favourite star's place and
   size so a thumb finds it on a phone. */
.c9-editor { display: flex; gap: 8px; align-items: stretch; margin: 0 0 8px; }
.c9-editor input {
  flex: 1 1 auto; min-width: 0; margin: 0; font-family: inherit; font-size: 14px; border-radius: var(--r-pill);
  background: var(--surface-2); color: var(--text); border: 1px solid var(--hair); padding: 10px 16px;
}
.c9-editor input:focus { outline: none; border-color: var(--accent); }
.c9-editor button {
  flex: 0 0 auto; background: var(--accent); color: var(--on-accent); border: 1px solid transparent; border-radius: var(--r-pill);
  font-family: var(--font-mono); font-weight: 700; font-size: 11px; letter-spacing: 1.5px; padding: 0 16px; min-height: 40px;
}
.c9-editor button:hover { box-shadow: 0 8px 20px -10px var(--accent); }
.c9-editor button:disabled { background: var(--surface-2); color: var(--dim); border-color: var(--hair); cursor: default; box-shadow: none; }
.c9-note { color: var(--dim); font-size: 12px; min-height: 16px; margin: 0 0 8px; line-height: 1.4; }
.c9-remove {
  position: absolute; right: 4px; top: 4px; min-width: 32px; min-height: 32px; border-radius: 50%;
  background: rgba(0, 0, 0, 0.6); color: #fff; border: 1px solid var(--hair);
  font-size: 14px; line-height: 1; backdrop-filter: blur(6px);
}
.c9-remove:hover { color: var(--danger); border-color: var(--danger); }
.emoji-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(44px, 1fr)); gap: 4px; }
.emoji-cell {
  height: 44px; display: flex; align-items: center; justify-content: center; border-radius: 10px;
  font-size: 24px; cursor: pointer; transition: background var(--ease);
}
.emoji-cell:hover { background: var(--tint); }
.emoji-cell img { width: 27px; height: 27px; object-fit: contain; display: block; }
.panel-label {
  font-family: var(--font-mono); color: var(--dim); font-size: 10px; font-weight: 700; letter-spacing: 1.5px;
  text-transform: uppercase; margin: 4px 0 8px;
}

/* ---------------- settings / admin sheets ---------------- */
/* A floating panel on a PC; on a phone it slides up from the bottom
   (see the stacked rules below). */
.sheet {
  position: fixed; inset: 0; z-index: 40; background: rgba(3, 0, 8, 0.62);
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  display: flex; align-items: center; justify-content: center; padding: 20px;
  overflow-y: auto;
}
.sheet .card {
  max-width: 540px; width: 100%; max-height: 86vh; overflow-y: auto; background: var(--surface-3);
  box-shadow: 0 40px 100px -40px #000, var(--glow);
  animation: sheet-pop 200ms cubic-bezier(0.2, 0.7, 0.2, 1);
}
@keyframes sheet-pop { from { opacity: 0; transform: translateY(12px) scale(0.98); } to { opacity: 1; transform: none; } }
/* The sections inside a sheet are sections, not cards of their own —
   separated by nothing but their dim heading and a hairline. */
.sheet .card .card {
  border: none; padding: 16px 0 0; margin: 0; border-radius: 0; background: transparent;
  box-shadow: none; backdrop-filter: none; -webkit-backdrop-filter: none;
  border-top: 1px solid var(--hair); animation: none;
}
.sheet .card .card:first-child { border-top: none; padding-top: 0; }
.sheet .card .card.danger-card { border-top-color: color-mix(in srgb, var(--danger) 45%, transparent); }
.sheet-head { display: flex; align-items: center; margin-bottom: 14px; }
.sheet-head .tray-close { margin-left: auto; }
input[type="file"] { color: var(--dim); font-size: 12px; font-family: inherit; }
input[type="file"]::file-selector-button {
  font-family: var(--font-mono); font-size: 11px; font-weight: 700; letter-spacing: 1.5px;
  text-transform: uppercase; cursor: pointer; margin-right: 10px; border-radius: var(--r-pill);
  background: var(--surface-2); color: var(--accent); border: 1px solid var(--line);
  padding: 8px 14px;
}
input[type="file"]::file-selector-button:hover { background: var(--tint); }

/* ------- stacked: phone, a window too small, or a TALL monitor ------ */
/* Four corners need corners to sit in. Below this the cards stop being
   pinned and become one scrolling column with a tab bar along the
   bottom — the phone layout, and what a rotated monitor gets too. */
@media (max-width: 1180px), (max-height: 700px), (orientation: portrait) {
  body[data-screen="app"] #app-shell { overflow-y: auto; -webkit-overflow-scrolling: touch; padding-bottom: 84px; }
  .hud-top {
    position: sticky; top: 0; padding: 8px 10px 8px; text-align: left; align-items: stretch;
    background: color-mix(in srgb, var(--bg) 72%, transparent); border-bottom: 1px solid var(--hair);
    backdrop-filter: blur(18px); -webkit-backdrop-filter: blur(18px);
  }
  .hud-row {
    display: flex; justify-content: flex-start; overflow-x: auto; max-width: none;
    background: none; border: none; box-shadow: none; backdrop-filter: none; -webkit-backdrop-filter: none;
    padding: 2px 0; gap: 0; scrollbar-width: none;
  }
  .hud-row::-webkit-scrollbar { display: none; }
  .hudbtn { font-size: 11px; letter-spacing: 1.6px; padding: 8px 10px; }
  .hud-status { margin: 6px 0 0 4px; padding: 0; border: none; background: none; font-size: 10px; align-self: flex-start; }
  .banner.on { position: static; transform: none; margin: 8px 12px 0; width: auto; max-width: none; }
  .hud-card {
    position: static; width: auto; max-height: none; height: auto;
    margin: 0 auto 12px; max-width: 760px; animation: none;
  }
  .hud-card:first-of-type { margin-top: 12px; }
  #app-shell > .hud-card { margin-left: 12px; margin-right: 12px; }
  .card-chat { height: 78vh; }
  .card-games { height: auto; }
  .card-online { max-height: 46vh; }
  /* Thumbs, not mouse pointers. */
  .cbtn { height: 42px; min-width: 46px; }
  .cbtn.send { width: 42px; min-width: 42px; }
  .composer input { padding: 11px 16px; }
  .listrow { padding: 9px 8px; }
  .tabbar {
    display: flex; position: fixed; left: 10px; right: 10px; bottom: max(10px, env(safe-area-inset-bottom)); z-index: 9;
    gap: 2px; padding: 6px; border-radius: 22px;
    background: var(--surface-3); border: 1px solid var(--line);
    backdrop-filter: blur(20px) saturate(1.3); -webkit-backdrop-filter: blur(20px) saturate(1.3);
    box-shadow: 0 20px 50px -20px #000, var(--glow);
  }
  /* Sheets slide up from the bottom, with a grab handle. */
  .sheet { align-items: flex-end; padding: 0; }
  .sheet .card {
    max-width: none; max-height: 92dvh; border-radius: 22px 22px 0 0; padding-top: 28px;
    animation: sheet-up 240ms cubic-bezier(0.2, 0.7, 0.2, 1);
  }
  .sheet .card::before {
    content: ""; position: absolute; top: 10px; left: 50%; width: 42px; height: 4px; margin-left: -21px;
    border-radius: 4px; background: var(--line);
  }
  .sheet .card { position: relative; }
}
/* ---------------- layout, chosen rather than measured ---------------- */
/* data-layout is the DISPLAY setting (Settings -> Display): auto follows
   the media queries above, tall forces the column, wide forces the
   corners back. */
body[data-layout="tall"][data-screen="app"] #app-shell {
  overflow-y: auto; -webkit-overflow-scrolling: touch; padding-bottom: 84px;
}
body[data-layout="tall"] .hud-top {
  position: sticky; top: 0; padding: 8px 10px; text-align: left; align-items: stretch;
  background: color-mix(in srgb, var(--bg) 72%, transparent); border-bottom: 1px solid var(--hair);
  backdrop-filter: blur(18px); -webkit-backdrop-filter: blur(18px);
}
body[data-layout="tall"] .hud-row { display: flex; justify-content: flex-start; overflow-x: auto; max-width: none;
  background: none; border: none; box-shadow: none; padding: 2px 0; gap: 0; }
body[data-layout="tall"] .banner.on { position: static; transform: none; margin: 8px 12px 0; width: auto; max-width: none; }
body[data-layout="tall"] .hud-card {
  position: static; width: auto; max-height: none; height: auto;
  margin: 12px; max-width: 760px; animation: none;
}
body[data-layout="tall"] .card-chat { height: 78vh; }
body[data-layout="tall"] .card-games { height: auto; }
body[data-layout="tall"] .card-online { max-height: 46vh; }
body[data-layout="tall"] .tabbar { display: flex; position: fixed; left: 10px; right: 10px; bottom: max(10px, env(safe-area-inset-bottom)); z-index: 9;
  gap: 2px; padding: 6px; border-radius: 22px; background: var(--surface-3); border: 1px solid var(--line);
  backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); box-shadow: 0 20px 50px -20px #000, var(--glow); }

body[data-layout="wide"][data-screen="app"] #app-shell { overflow: hidden; padding-bottom: 0; }
body[data-layout="wide"] .hud-top { position: absolute; top: 12px; border-bottom: 0; background: none; backdrop-filter: none; -webkit-backdrop-filter: none; text-align: center; padding: 0; align-items: center; }
body[data-layout="wide"] .hud-row { display: inline-flex; overflow: visible; max-width: calc(100% - 24px); justify-content: center;
  background: var(--surface-2); border: 1px solid var(--line); box-shadow: var(--glow); padding: 6px 8px; gap: 2px; }
body[data-layout="wide"] .hud-card { position: absolute; margin: 0; max-width: none; }
body[data-layout="wide"] .card-online  { top: 24px; left: 24px; right: auto; bottom: auto; width: 300px; max-height: 38vh; height: auto; }
body[data-layout="wide"] .card-profile { top: 24px; right: 24px; left: auto; bottom: auto; width: 280px; max-height: 46vh; height: auto; }
body[data-layout="wide"] .card-games   { bottom: 24px; left: 24px; right: auto; top: auto; width: 300px; height: 44vh; max-height: none; }
body[data-layout="wide"] .card-chat    { bottom: 24px; right: 24px; left: auto; top: auto; width: 480px; height: 64vh; max-height: none; }
body[data-layout="wide"] .tabbar { display: none; }
/* The corners forced on a window the auto rule would stack (under
   1180 x 700 — a laptop at 200%, a key set to the PC layout): the
   status pill back under the menu, not along the left over the ONLINE
   card, and the chat no taller than leaves the profile card its room. */
body[data-layout="wide"] .hud-status { margin: 8px 0 0; padding: 6px 14px; align-self: center; font-size: 11px;
  border: 1px solid var(--hair); background: color-mix(in srgb, var(--bg) 60%, transparent); }
body[data-layout="wide"] .card-chat { height: min(64vh, calc(100vh - 290px)); }
@media (max-width: 1180px) {
  /* Too narrow for the menu to sit between the top two: they start under it. */
  body[data-layout="wide"] .card-online,
  body[data-layout="wide"] .card-profile { top: 116px; }
  body[data-layout="wide"] .card-online { max-height: min(38vh, calc(56vh - 164px)); }
  body[data-layout="wide"] .card-chat { height: min(64vh, calc(100vh - 360px)); }
}

/* UI scale. zoom rather than transform: scale(): scale() leaves
   position:fixed elements laid out against the unscaled viewport. */
#app-shell, .hud-top { zoom: var(--ui-scale, 1); }

/* data-device is a preference, not a measurement: somebody on a big
   screen who picked "phone" gets the stacked column anyway. */
body[data-device="phone"][data-screen="app"] #app-shell { overflow-y: auto; padding-bottom: 84px; }
body[data-device="phone"] .hud-top {
  position: sticky; top: 0; padding: 8px 10px; text-align: left; align-items: stretch;
  background: color-mix(in srgb, var(--bg) 72%, transparent); border-bottom: 1px solid var(--hair);
  backdrop-filter: blur(18px); -webkit-backdrop-filter: blur(18px);
}
body[data-device="phone"] .hud-row { display: flex; justify-content: flex-start; overflow-x: auto; max-width: none;
  background: none; border: none; box-shadow: none; padding: 2px 0; gap: 0; }
body[data-device="phone"] .banner.on { position: static; transform: none; margin: 8px 12px 0; width: auto; }
body[data-device="phone"] .hud-card {
  position: static; width: auto; max-height: none; height: auto;
  margin: 12px; max-width: 760px; animation: none;
}
body[data-device="phone"] .card-chat { height: 78vh; }
body[data-device="phone"] .card-games { height: auto; }
body[data-device="phone"] .tabbar { display: flex; position: fixed; left: 10px; right: 10px; bottom: max(10px, env(safe-area-inset-bottom)); z-index: 9;
  gap: 2px; padding: 6px; border-radius: 22px; background: var(--surface-3); border: 1px solid var(--line);
  backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); box-shadow: 0 20px 50px -20px #000, var(--glow); }

/* ---------------- the phone: one section at a time ---------------- */
/* Drago, 2026-10-05: "the phone website layout is very compact, hide
   more things behind buttons, like for example the public chat should
   be its own section, i shouldnt see every feature all at once because
   id have to scroll all the way to the bottom to see the chat."
   body[data-section] (app.js setPhoneTab) names the section; the card that
   wears .tab-on is the only one shown, filling the screen between the
   top row and the tab bar, and the chat card shows ONE of its two
   chats depending on whether CHAT or DMS is the tab. Not in the
   pop-out window, whose body is also data-device="phone": there the
   two cards it holds stack as before. */
body[data-device="phone"]:not(.popout)[data-screen="app"] #app-shell {
  display: flex; flex-direction: column; height: 100dvh; overflow: hidden; padding-bottom: 0;
}
body[data-device="phone"]:not(.popout) .hud-top { flex: 0 0 auto; position: relative; }
body[data-device="phone"]:not(.popout) .banner.on { flex: 0 0 auto; }
body[data-device="phone"]:not(.popout) .hud-card { display: none; }
body[data-device="phone"]:not(.popout) #app-shell > .hud-card.tab-on {
  display: flex; flex: 1 1 auto; min-height: 0; height: auto; max-height: none;
  width: auto; max-width: none; margin: 10px 12px calc(84px + env(safe-area-inset-bottom, 0px));
}
body[data-device="phone"]:not(.popout) .card-games.playing.tab-on { height: auto; }
/* The chat card: CHAT is the public chat alone, DMS the DMs alone. */
body[data-device="phone"]:not(.popout)[data-section="chat"] .card-chat .dm-stack,
body[data-device="phone"]:not(.popout)[data-section="dms"] .card-chat .public-stack,
body[data-device="phone"]:not(.popout) .card-chat .chat-divider { display: none; }
body[data-device="phone"]:not(.popout) .card-chat .hud-stack { flex: 1 1 auto; min-height: 0; }
/* The DM composer at phone width: who-to on its own line, the field
   under it with room to read what you are typing. */
body[data-device="phone"]:not(.popout) .dm-stack .composer { flex-wrap: wrap; row-gap: 8px; }
body[data-device="phone"]:not(.popout) .dm-stack .composer select { flex: 1 1 100%; max-width: none; }
body[data-device="phone"]:not(.popout) .dm-stack .composer input { flex: 1 1 0; width: 0; min-width: 100px; }
/* The section already fills the screen; nothing to FOCUS. */
body[data-device="phone"]:not(.popout) .js-chat-focus { display: none; }
/* A live stage (stage.js's card, .gone while there is none) belongs
   with the people: it shows on the ONLINE section, under the list. */
body[data-device="phone"]:not(.popout)[data-section="online"] #app-shell > .card-stage:not(.gone) {
  display: flex; flex: 0 1 auto; min-height: 0; max-height: 46%; width: auto; max-width: none;
  margin: 0 12px calc(84px + env(safe-area-inset-bottom, 0px));
}
body[data-device="phone"]:not(.popout)[data-section="online"] #app-shell > .card-stage:not(.gone) ~ .card-online.tab-on,
body[data-device="phone"]:not(.popout)[data-section="online"] #app-shell > .card-online.tab-on:has(~ .card-stage:not(.gone)) {
  margin-bottom: 10px;
}
/* Five tabs on a phone, a little tighter; DMS exists only there, and
   the CHAT tab's copy of the DM badge only elsewhere. */
.tabbar-btn.tab-dms { display: none; }
body[data-device="phone"]:not(.popout) .tabbar-btn.tab-dms { display: flex; }
body[data-device="phone"]:not(.popout) .tabbar-btn .tab-badge-chat { display: none; }
body[data-device="phone"] .tabbar-btn { letter-spacing: 1.2px; padding: 6px 2px; }

/* ---------------- chat focus ---------------- */
/* Drago, 2026-10-05: "a really nice way of making the public chat
   bigger on the website than just being stuck in the corner." FOCUS on
   the PUBLIC CHAT heading (app.js setChatFocus, body.chat-focus): the
   chat card grows from its corner to the whole field right of the
   ONLINE / GAMES column and under the top row, and the two chats turn
   into columns — DMs narrow on the left, PUBLIC CHAT wide. The profile
   card steps out of the way. Stacked (a phone, a window too small for
   corners, a tall monitor) it takes the whole screen instead. Esc or
   the same button puts it back; the state is remembered per browser,
   and a game opening drops it so the two never fight for the middle.
   The card keeps its bottom-right anchor in both states so the growth
   is one smooth transition, not a jump. */
.card-chat {
  transition: width 380ms cubic-bezier(0.2, 0.7, 0.2, 1), height 380ms cubic-bezier(0.2, 0.7, 0.2, 1),
              box-shadow 380ms var(--ease);
}
@media (prefers-reduced-motion: reduce) { .card-chat { transition: none; } }
body.chat-focus .card-chat {
  /* --chat-focus-top is where the top row (and its status line) ends,
     measured by app.js measureChatFocusTop; the fallback is what it
     measures at the default UI size. */
  width: calc(100% - 372px); height: calc(100% - var(--chat-focus-top, 118px) - 24px); z-index: 6;
  flex-direction: row;
  box-shadow: 0 40px 90px -40px #000, var(--glow), inset 0 1px 0 color-mix(in srgb, var(--accent) 22%, transparent);
}
body.chat-focus .card-chat .dm-stack {
  flex: 0 1 calc(var(--dm-focus-share, 0.32) * 100%); min-width: 240px; min-height: 0;
}
body.chat-focus .card-chat .public-stack { min-height: 0; min-width: 320px; }
body.chat-focus .card-chat .chat-divider { cursor: col-resize; }
body.chat-focus .card-chat.dragging { cursor: col-resize; }
body.chat-focus .card-chat .chat-divider::before { left: 50%; right: auto; top: 0; bottom: 0; width: 1px; height: auto; }
body.chat-focus .card-chat .chat-divider::after { width: 4px; height: 44px; }
body.chat-focus .card-chat .chat-divider:hover::after, body.chat-focus .card-chat .chat-divider.dragging::after { width: 4px; height: 64px; }
body.chat-focus .card-chat .hud-card-head .chatfocus { background: var(--accent); color: var(--on-accent); border-color: transparent; }
/* The DM column is narrow: who-to on its own line, the field under it
   with room to read what you are typing. */
body.chat-focus .card-chat .dm-stack .composer { flex-wrap: wrap; row-gap: 8px; }
body.chat-focus .card-chat .dm-stack .composer select { flex: 1 1 100%; max-width: none; }
body.chat-focus .card-chat .dm-stack .composer input { flex: 1 1 0; width: 0; min-width: 100px; }
/* visibility rather than opacity: the cards' rise animation fills
   forwards with opacity 1, which would win over a plain opacity: 0.
   Leaving focus replays the rise, so the card comes back the way it
   first arrived. */
body.chat-focus .card-profile { visibility: hidden; }
/* A bigger card is room for a bigger font. */
body.chat-focus .card-chat .msg-body, body.chat-focus .card-chat .composer input { font-size: 15px; }

/* Stacked: no corners to grow out of, so FOCUS is the whole screen,
   over the top row and the tab bar; the button in the heading is the
   way back. The chats stay stacked (a phone is not wide enough for
   two columns), the DMs with their own remembered share. */
@media (max-width: 1180px), (max-height: 700px), (orientation: portrait) {
  body.chat-focus:not([data-layout="wide"]) .card-chat {
    position: fixed; top: 0; left: 0; right: 0; bottom: 0; width: auto; height: auto; max-height: none;
    margin: 0; border-radius: 0; border-width: 0; z-index: 12; flex-direction: column; transition: none;
  }
  body.chat-focus:not([data-layout="wide"]) .card-chat .dm-stack {
    flex: 0 1 calc(var(--dm-focus-share, 0.32) * 100%); min-width: 0; min-height: 100px;
  }
  body.chat-focus:not([data-layout="wide"]) .card-chat .public-stack { min-width: 0; min-height: 100px; }
  body.chat-focus:not([data-layout="wide"]) .card-chat .chat-divider { cursor: row-resize; }
  body.chat-focus:not([data-layout="wide"]) .card-chat .chat-divider::before { left: 0; right: 0; top: 50%; bottom: auto; width: auto; height: 1px; }
  body.chat-focus:not([data-layout="wide"]) .card-chat .chat-divider::after { width: 44px; height: 4px; }
  body.chat-focus:not([data-layout="wide"]) .card-profile { visibility: visible; }
}
body.chat-focus[data-layout="tall"] .card-chat, body.chat-focus[data-device="phone"] .card-chat {
  position: fixed; top: 0; left: 0; right: 0; bottom: 0; width: auto; height: auto; max-height: none;
  margin: 0; border-radius: 0; border-width: 0; z-index: 12; flex-direction: column; transition: none;
}
body.chat-focus[data-layout="tall"] .card-chat .dm-stack, body.chat-focus[data-device="phone"] .card-chat .dm-stack {
  flex: 0 1 calc(var(--dm-focus-share, 0.32) * 100%); min-width: 0; min-height: 100px;
}
body.chat-focus[data-layout="tall"] .card-chat .public-stack, body.chat-focus[data-device="phone"] .card-chat .public-stack { min-width: 0; min-height: 100px; }
body.chat-focus[data-layout="tall"] .card-chat .chat-divider, body.chat-focus[data-device="phone"] .card-chat .chat-divider { cursor: row-resize; }
body.chat-focus[data-layout="tall"] .card-chat .chat-divider::before, body.chat-focus[data-device="phone"] .card-chat .chat-divider::before {
  left: 0; right: 0; top: 50%; bottom: auto; width: auto; height: 1px;
}
body.chat-focus[data-layout="tall"] .card-chat .chat-divider::after, body.chat-focus[data-device="phone"] .card-chat .chat-divider::after { width: 44px; height: 4px; }
body.chat-focus[data-layout="tall"] .card-profile, body.chat-focus[data-device="phone"] .card-profile { visibility: visible; }
/* Stacked and full-screen, whichever rule got it there: no side margins
   (the stacked #app-shell > .hud-card margins carry an id, hence this
   one does too), room under the composer for a phone's home indicator,
   and a background solid enough that the column behind does not show
   through the glass. */
body.chat-focus #app-shell > .card-chat { margin: 0; }
body.chat-focus:not([data-layout="wide"]) #app-shell > .card-chat { padding-bottom: env(safe-area-inset-bottom, 0px); }
@media (max-width: 1180px), (max-height: 700px), (orientation: portrait) {
  body.chat-focus:not([data-layout="wide"]) .card-chat { background: color-mix(in srgb, var(--accent) 6%, var(--bg)); }
}
body.chat-focus[data-layout="tall"] .card-chat, body.chat-focus[data-device="phone"] .card-chat { background: color-mix(in srgb, var(--accent) 6%, var(--bg)); }
/* Forced WIDE on a small window: the corners version, like any other
   wide rule. */
body.chat-focus[data-layout="wide"] .card-chat { position: absolute; width: calc(100% - 372px); height: calc(100% - 96px); border-radius: var(--r-lg); border-width: 1px; }
/* The pop-out window is its own small screen; FOCUS means nothing
   there, and the state belongs to the page behind it. */
body.popout .js-chat-focus { display: none; }

/* ================= GAMES ================= */
/* The games the desktop keeps behind its top-row buttons, as a list of
   tiles: an accent sheen, the name, a chevron. Everything inside a game
   stays in the mono — pixel games wear pixel type. */
/* line-height: normal, not the page's 1.45 — every game lays its own
   lobby out in pixels and wraps its own text; the roomier line the chat
   got would push a game's launch button off the bottom of a phone. */
.card-games .hud-card-body { font-family: var(--font-mono); line-height: normal; }
.gamelist { display: flex; flex-direction: column; gap: 8px; padding: 4px 0; }
.gamebtn {
  display: flex; align-items: center; justify-content: space-between; gap: 10px; width: 100%; text-align: left;
  background: linear-gradient(120deg, color-mix(in srgb, var(--accent) 24%, transparent), transparent 58%), var(--surface-2);
  border: 1px solid var(--hair); border-radius: 14px; padding: 14px 16px;
  transition: transform var(--ease), border-color var(--ease), box-shadow var(--ease);
}
.gamebtn::after { content: "\203A"; color: var(--accent); font-size: 20px; line-height: 1; opacity: 0.8; flex: 0 0 auto; }
.gamebtn:hover { transform: translateY(-1px); border-color: var(--line); box-shadow: 0 14px 30px -18px var(--accent); }
.gamebtn:active { transform: translateY(0); }
.gamebtn-name {
  display: block; color: var(--text);
  font-size: 12px; font-weight: 700; letter-spacing: 2px;
}
/* A game takes the whole card body — the list is gone while one is
   open, and CLOSE in the heading is the way back. */
.card-games.playing .hud-card-body { padding: 0; overflow: hidden; }
/* And it takes the field with it, centred over the wordmark. */
.card-games.playing {
  left: 50%; right: auto; bottom: auto; top: 96px;
  transform: translateX(-50%);
  width: min(1060px, calc(100vw - 660px));
  min-width: 420px;
  height: min(72vh, 660px);
  z-index: 7; animation: none;
}

/* ---------------- PIXEL WINGS ---------------- */
/* A column, not a stack of overlays: the control strip sits ABOVE the
   canvas rather than over it. Floated on top it landed square on the
   leaderboard, and a button that covers the score is worse than a
   button thirty pixels further up. */
.wings {
  position: relative; width: 100%; height: 100%; background: #000;
  display: flex; flex-direction: column; min-height: 0;
}
/* Nothing in a game is text you meant to select. Without this, a
   long-press — which is just "holding fire" to a player — pops iOS's
   Copy / Look Up / Translate / Search Web bar over the middle of the
   screen and highlights everything behind it, and the round is over
   until you tap it away. -webkit-touch-callout is the one that
   actually stops that bar; user-select alone only stops the
   highlight, and touch-action stops the double-tap zoom and the
   pull-to-refresh that come with it. All three, on everything. */
.wings, .wings * {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}
.wings, .wings-rot, .wings-stage, .wings-pad { touch-action: none; }
.wings-screen {
  /* The buffer is upscaled by a whole number and then let sit in the
     middle of whatever's left. A fractional stretch is the one thing
     that turns pixel art back into a blurry photo, so the canvas is
     never asked to fill its box. */
  margin: auto; display: block; image-rendering: pixelated;
  max-width: 100%; max-height: 100%; touch-action: none;
}
/* The menu and the thumb pads live INSIDE the stage, so the
   control strip above stays lit and clickable while the hangar
   overlay is up instead of sitting greyed out behind it. */
.wings-stage { flex: 1 1 auto; min-height: 0; display: flex; overflow: hidden; position: relative; }
/* Everything that is "the game" lives in here, so that turning the
   playfield a quarter turn (see wings.js's resize) brings the canvas,
   the hangar and the thumb pads round together rather than rotating
   the canvas out from under its own buttons. */
.wings-rot {
  position: absolute; top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
}
.wings.turned .wings-rot { transform: translate(-50%, -50%) rotate(90deg); }
/* The other way round, for a player who turned the phone the other
   way — see wings.js's S.flip for why this cannot be detected. */
.wings.turned.flipped .wings-rot { transform: translate(-50%, -50%) rotate(-90deg); }
/* Only worth saying while the phone is still upright. */
.wings-turn { display: none; font-size: 10px; letter-spacing: 2px; color: var(--accent); }
.wings-turn::after { content: " \2014 FLIP IF IT\2019S UPSIDE DOWN"; color: var(--dim); }
.wings.turned .wings-turn { display: block; }
.wings-menu {
  position: absolute; inset: 0; z-index: 2;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 8px; padding: 14px; background: rgba(0, 0, 0, 0.86); overflow-y: auto;
}
.wings-menu.hidden { display: none; }
.wings-title { font-size: 18px; font-weight: 700; letter-spacing: 6px; color: var(--accent); }
.wings-sub { font-size: 10px; letter-spacing: 2px; color: var(--dim); }
.wings-planes { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; }
.wings-plane {
  background: var(--bg); border: 1px solid var(--hair); padding: 7px 8px;
  display: flex; flex-direction: column; align-items: center; gap: 5px;
}
.wings-plane.on { border-color: var(--accent); box-shadow: 0 0 10px -3px var(--accent); }
/* The airframe drawn from the same 11x9 stencil the game renders, one
   grid cell per pixel — the picker isn't a picture OF the plane, it is
   the plane. */
.wings-plane-art { display: grid; gap: 0; width: 44px; }
.wings-plane-art i { display: block; aspect-ratio: 1; }
.wings-plane-art i.a { background: var(--accent); }
.wings-plane-art i.b { background: color-mix(in srgb, var(--accent) 72%, #000); }
.wings-plane-art i.c { background: #fff; }
.wings-plane-art i.e { background: transparent; }
.wings-plane-name { font-size: 9px; letter-spacing: 1.5px; color: var(--dim); }
.wings-plane.on .wings-plane-name { color: var(--accent); }
.wings-note { font-size: 10px; color: var(--dim); min-height: 12px; }
/* Only worth saying to somebody who has a keyboard. */
.wings-keys { font-size: 10px; letter-spacing: 1.5px; color: var(--dim); }

/* ---- per-sound volume, inside each game -------------------------- */
/* Drago, 2026-09-21: "add settings on all games to adjust sound volumes
   for each sound in the game, should be a slider and toggle setup
   similar to the top UI bar. add it in each game individually." So this
   is the HUD's own volume row, repeated once per cue and scoped to the
   game it belongs to. */
.wings-audio-panel{position:absolute;z-index:6;
  left:calc(8px + env(safe-area-inset-left));
  top:calc(8px + env(safe-area-inset-top));
  max-height:calc(100% - 16px - env(safe-area-inset-top) - env(safe-area-inset-bottom));
  overflow-y:auto;padding:8px;display:flex;flex-direction:column;gap:4px;
  background:rgba(0,0,0,.86);border:1px solid var(--line)}
.wings-audio-panel.hidden{display:none}
.wings-audio-head{font-size:10px;letter-spacing:2px;color:var(--accent);
  padding-bottom:4px;border-bottom:1px solid var(--line)}
.wings-audio-row{display:flex;align-items:center;gap:6px}
.wings-audio-name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;
  font-size:10px;letter-spacing:1px;color:var(--accent);white-space:nowrap}
/* Wide enough for a thumb, per the phone rule — a 20px toggle is a
   40-minute argument with your own hand. */
.wings-audio-toggle{flex:0 0 auto;min-width:40px;padding:5px 7px;
  font:600 9px/1 Consolas,monospace;white-space:nowrap;
  letter-spacing:1px;background:#000;color:var(--accent);
  border:1px solid var(--accent);cursor:pointer}
.wings-audio-toggle.off{color:var(--line);border-color:var(--line)}
.wings-audio-slider{flex:0 0 auto;width:96px;accent-color:var(--accent);background:transparent}
.wings-audio-panel button.on{background:var(--accent);color:#000}
.hudpill.wings-audio.on{background:var(--accent);color:#000}

/* A phone in a rotated game has less room and a bigger thumb. */
@media (max-width: 560px){
  .wings-audio-slider{width:72px}
  .wings-audio-panel{padding:6px;gap:3px}
}

@media (pointer: coarse) { .wings-keys { display: none; } }
.wings-bar {
  flex: 0 0 auto; display: flex; gap: 6px; justify-content: flex-end;
  padding: 6px 8px; border-bottom: 1px solid var(--hair);
}
.wings-bar .hudpill { padding: 5px 9px; font-size: 10px; }
.wings:not(.flying) .wings-leave,
.wings:not(.flying) .wings-ai { display: none; }
/* Thumb controls. Only shown on a touch screen — a mouse has a button
   for fire and a spacebar for boost, and pads over the sky would just
   be covering the game.
   
   The whole play area is split in two: the left half steers and the
   right half shoots, because on a phone your thumbs are at the two
   bottom corners and nowhere else. The halves are the FULL height of
   the view rather than a strip along the bottom, so a thumb that
   drifts up the screen mid-fight doesn't fall off its own control. */
.wings-pad {
  position: absolute; inset: 0; z-index: 3;
  display: flex; pointer-events: none;
}
.wings-pad.hidden { display: none; }
.wings-steer {
  flex: 1 1 58%; position: relative; pointer-events: auto;
  touch-action: none; -webkit-user-select: none; user-select: none;
  /* Same reasoning as .wings-actions: a thumb that lands in the last
     few millimetres of the screen is talking to the home indicator,
     not to us. */
  margin-bottom: env(safe-area-inset-bottom);
  margin-left: env(safe-area-inset-left);
}
/* The stick is drawn where the thumb lands, not in a fixed corner it
   would have to be dragged to first. */
.wings-stick {
  position: absolute; width: 0; height: 0; opacity: 0;
  transition: opacity 120ms ease;
}
.wings-stick.on { opacity: 1; }
.wings-stick::before {
  content: ""; position: absolute; left: -46px; top: -46px;
  width: 92px; height: 92px; border-radius: 50%;
  border: 1px solid var(--accent); opacity: 0.35;
}
.wings-stick i {
  position: absolute; left: -14px; top: -14px; width: 28px; height: 28px;
  border-radius: 50%; background: var(--accent); opacity: 0.55;
}
.wings-actions {
  flex: 0 0 auto; pointer-events: none;
  display: flex; flex-direction: column; justify-content: flex-end;
  align-items: flex-end; gap: 10px;
  /* The home indicator sits over the bottom of the screen and the
     notch over one side, and neither is part of the viewport you are
     given — so a button laid out flush against the bottom edge ends up
     half underneath it, visible and untappable. Measured: FIRE was
     landing with its lower third under the bar. The insets are 0 on
     anything that doesn't have them, so this costs nothing elsewhere. */
  padding: 10px max(10px, env(safe-area-inset-right))
           max(14px, calc(env(safe-area-inset-bottom) + 10px)) 10px;
}
.wings-pad-btn {
  pointer-events: auto; padding: 14px 10px;
  background: rgba(0, 0, 0, 0.5); border: 1px solid var(--accent); color: var(--accent);
  font-size: 13px; font-weight: 700; letter-spacing: 2px;
  touch-action: none; user-select: none; -webkit-user-select: none;
}
/* Fire is the one you hold for most of a round, so it gets the corner
   and the size. */
.wings-fire { width: 108px; height: 108px; border-radius: 50%; font-size: 15px; }
.wings-boost { width: 92px; }
.wings-pad-btn:active { background: var(--accent); color: #000; }
/* Fullscreen is the element's own, so the bars have to come with it. */
/* The one that works everywhere, iPhone included. Pinned to the
   viewport, over the whole site, with the page behind it frozen so a
   drag on the stick flies the plane instead of scrolling the page. */
.wings.maxed {
  position: fixed; inset: 0; z-index: 200;
  width: 100vw; height: 100dvh; background: #000;
  /* Under the notch and the home bar rather than beside them: the
     playfield wants every pixel, and the controls sit inside it. */
  padding: 0;
}
body.wings-maxed { overflow: hidden; }
/* The tray, the sheets and the site's own bars must not float over a
   game that has taken the screen. The top row is now one of those
   bars: it sits above the cards so its dropdowns can, which puts it
   above a maximised game too unless it is taken out of the way here. */
body.wings-maxed .tray, body.wings-maxed .sheet, body.wings-maxed .hud-top {
  display: none !important;
}
.wings:fullscreen { width: 100vw; height: 100vh; background: #000; }
/* Fullscreen on a phone means the game, not the game plus a toolbar.
   The strip is still reachable — it just stops taking a slice of a
   screen that is already small. */
/* Taking the screen means the game, not the game plus a toolbar. The
   strip is still reachable — it just stops taking a slice out of a
   screen that is already small. */
.wings.maxed .wings-bar,
.wings:fullscreen .wings-bar {
  position: absolute; top: 0; right: 0; z-index: 5;
  border-bottom: none; background: rgba(0, 0, 0, 0.5);
  padding: max(6px, env(safe-area-inset-top)) 8px 6px;
}

/* ---------------- RAISE A RETARD ---------------- */
.rar { display: flex; flex-direction: column; height: 100%; min-height: 0; }
.rar-tabs { flex: 0 0 auto; display: flex; gap: 6px; padding: 8px 10px; border-bottom: 1px solid var(--hair); }
.rar-tabs .hudpill { padding: 6px 10px; font-size: 11px; }
.rar-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 10px 12px 14px; }
.rar-intro { text-align: center; }
.rar-intro .hudpill { margin: 12px auto 0; display: block; padding: 10px 24px; }
.rar-icon { height: 18px; width: auto; vertical-align: -4px; }
.rar-arena { display: flex; align-items: flex-start; gap: 8px; }
.rar-side { flex: 1 1 0; min-width: 0; text-align: center; }
.rar-vs { flex: 0 0 auto; color: var(--dim); font-size: 11px; letter-spacing: 2px; padding-top: 22px; }
.rar-portrait-img { width: 56px; height: 56px; object-fit: contain; }
.rar-name {
  font-weight: 700; font-size: 12px; margin-top: 4px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.rar-hpbar { height: 4px; background: var(--hair); margin: 5px 0 3px; }
.rar-hpbar i { display: block; height: 100%; background: var(--accent); transition: width 260ms ease; }
.rar-hp { font-size: 10px; color: var(--dim); }
.rar-stats { font-size: 11px; margin-top: 4px; }
.rar-gear { margin-top: 4px; display: flex; gap: 3px; justify-content: center; }
.rar-log { margin-top: 10px; font-size: 12px; line-height: 1.6; min-height: 40px; }
.rar-turn { color: var(--text); }
.rar-crit { color: var(--accent); font-style: normal; font-size: 10px; letter-spacing: 1px; }
.rar-result { margin-top: 10px; padding: 8px; border: 1px solid var(--accent); font-size: 13px; }
.rar-result.hidden { display: none; }
.rar-result.lost { border-color: var(--danger); color: var(--danger); }
.rar-loot { margin-top: 6px; display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.rar-line { display: flex; justify-content: space-between; font-size: 12px; color: var(--dim); margin-bottom: 8px; }
.rar-coins { color: var(--accent); font-weight: 700; }
.rar-stacks { display: flex; flex-direction: column; }
.rar-stack { display: flex; align-items: center; gap: 8px; padding: 7px 0; }
.rar-stack-main { flex: 1 1 auto; min-width: 0; }
.rar-stack-name {
  display: block; font-size: 13px; font-weight: 700;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.rar-stack .hudpill { padding: 5px 9px; font-size: 10px; }
.rar-wide { width: 100%; margin-top: 10px; padding: 9px; }
.rar-tiers { display: flex; flex-direction: column; }
.rar-tier { display: flex; align-items: center; gap: 8px; padding: 6px 0; }
.rar-tier-name { flex: 0 0 86px; font-size: 11px; font-weight: 700; letter-spacing: 1px; }
.rar-tier-icons { flex: 1 1 auto; display: flex; gap: 4px; }
.rar-error { color: var(--danger); font-size: 12px; margin-top: 8px; }

/* A game wants the room. Stacked, it gets most of the screen rather
   than a letterbox at the bottom of a column. */
@media (max-width: 1180px), (max-height: 700px) {
  .card-games.playing {
    position: static; transform: none; width: auto; min-width: 0;
    margin-left: 0; margin-right: 0; height: 76vh;
  }
}
body[data-device="phone"] .card-games.playing {
  position: static; transform: none; width: auto; min-width: 0;
  margin-left: 0; margin-right: 0; height: 76vh;
}

/* ================= GAME CHROME ================= */
/* One layer over every game's own stylesheet — Drago, 2026-10-04:
   "make the UI better across all games". Tanks, Cells and Hangout
   inject their CSS from their scripts, which lands AFTER this file in
   the cascade, so every rule here hangs off #card-games (an id beats a
   class whatever the order) and changes LOOKS only: colours, radii,
   type, shadows. Nothing here moves a pad, resizes a stage or touches
   a canvas — the pixel games keep their integer upscale, their
   quarter-turn transforms and their full-height thumb zones exactly
   as they were. The three families of control every game has — the
   bar of pills, the menu with its title and one big launch button,
   the settings sheet — now look like the rest of the app. */
#card-games { --game-glass: color-mix(in srgb, var(--accent) 9%, rgba(5, 1, 10, 0.88)); }
/* Two variables the injected sheets read but never set. */
#card-games .hg { --hg-line: color-mix(in srgb, var(--accent) 38%, transparent); }
#card-games .cells { --cells-dim: var(--dim); }

/* ---- the bar of pills above every game ---- */
#card-games .wings-bar, #card-games .cells-bar, #card-games .tk-bar, #card-games .hg-bar, #card-games .rar-tabs {
  align-items: center; gap: 6px; padding: 8px 10px; border-bottom: 1px solid var(--hair);
}
#card-games .wings-bar .hudpill, #card-games .cells-bar button, #card-games .tk-pill, #card-games .hg-pill,
#card-games .rar-tabs .hudpill,
#card-games .wings-audio-toggle, #card-games .tk-audio-toggle, #card-games .tk-rgb, #card-games .tk-class,
#card-games .hg-opt, #card-games .hg-buy, #card-games .hg-send, #card-games .hg-close-panel,
#card-games .cells-audio-mute, #card-games .cells-audio-done, #card-games .cells-rgb {
  border-radius: var(--r-pill); background: var(--surface-2); border: 1px solid var(--line);
  color: var(--accent); font-family: var(--font-mono); font-weight: 700; letter-spacing: 1.5px;
  transition: background var(--ease), color var(--ease), border-color var(--ease), transform var(--ease);
}
#card-games .wings-bar .hudpill, #card-games .cells-bar button, #card-games .rar-tabs .hudpill { padding: 7px 13px; font-size: 10px; min-height: 32px; }
#card-games .wings-bar .hudpill:hover, #card-games .cells-bar button:hover, #card-games .tk-pill:hover, #card-games .hg-pill:hover,
#card-games .rar-tabs .hudpill:hover, #card-games .tk-class:hover, #card-games .hg-opt:hover, #card-games .hg-buy:hover,
#card-games .wings-audio-toggle:hover, #card-games .tk-audio-toggle:hover, #card-games .cells-audio-mute:hover {
  background: var(--tint); border-color: var(--accent);
}
#card-games .wings-bar .hudpill.on, #card-games .cells-bar button.on, #card-games .tk-pill.on, #card-games .hg-pill.on,
#card-games .rar-tabs .hudpill.on, #card-games .hg-opt.on, #card-games .hg-buy.on, #card-games .cells-rgb.on,
#card-games .hudpill.wings-audio.on, #card-games .wings-audio-panel button.on {
  background: var(--accent); color: var(--on-accent); border-color: transparent;
}
#card-games .wings-audio-toggle.off, #card-games .tk-audio-toggle.off, #card-games .tk-rgb.off {
  color: var(--dim); border-color: var(--hair); background: transparent;
}

/* ---- the menu: a title with a glow, a sub-line, one big button ---- */
#card-games .wings-menu, #card-games .tk-menu, #card-games .cells-menu, #card-games .hg-menu {
  gap: 12px;
  background:
    radial-gradient(70% 55% at 50% 28%, color-mix(in srgb, var(--accent) 20%, transparent), transparent 72%),
    rgba(4, 1, 9, 0.9);
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
}
#card-games .wings-title, #card-games .tk-title, #card-games .cells-title, #card-games .hg-title {
  font-family: var(--font-mono); font-size: clamp(20px, 3.4vw, 28px); font-weight: 700; letter-spacing: 8px;
  color: var(--text); text-shadow: 0 0 30px var(--accent), 0 0 2px var(--accent); margin-right: -8px;
}
#card-games .wings-sub, #card-games .tk-sub, #card-games .cells-sub, #card-games .hg-sub,
#card-games .wings-keys, #card-games .tk-keys, #card-games .cells-keys, #card-games .hg-keys,
#card-games .wings-turn, #card-games .cells-turn, #card-games .hg-turn {
  font-family: var(--font-mono); color: var(--dim); font-size: 10px; letter-spacing: 2px; line-height: 1.6;
}
#card-games .wings-note, #card-games .tk-note, #card-games .cells-note, #card-games .hg-note, #card-games .hg-note2 {
  color: var(--accent); font-size: 10px; letter-spacing: 1.5px;
}
#card-games .wings-launch, #card-games .tk-launch, #card-games .cells-launch, #card-games .hg-spawn,
#card-games .rar-go, #card-games .rar-adopt {
  background: var(--accent); color: var(--on-accent); border: 1px solid transparent; border-radius: var(--r-pill);
  padding: 14px 34px; min-height: 48px; font-family: var(--font-mono); font-size: 13px; font-weight: 700; letter-spacing: 3px;
  box-shadow: 0 16px 36px -14px var(--accent); cursor: pointer;
  transform: none; margin: 4px 0 0; transition: transform var(--ease), box-shadow var(--ease), opacity var(--ease);
}
#card-games .wings-launch:hover, #card-games .tk-launch:hover, #card-games .cells-launch:hover, #card-games .hg-spawn:hover,
#card-games .rar-go:hover, #card-games .rar-adopt:hover { transform: translateY(-1px); box-shadow: 0 20px 44px -14px var(--accent); }
#card-games .wings-launch:active, #card-games .tk-launch:active, #card-games .cells-launch:active, #card-games .hg-spawn:active,
#card-games .rar-go:active { transform: scale(0.98); }
#card-games .rar-go:disabled, #card-games .rar-adopt:disabled, #card-games .wings-launch:disabled { opacity: 0.55; transform: none; box-shadow: none; }

/* ---- pickers: planes, skins ---- */
#card-games .wings-planes, #card-games .tk-skins { gap: 8px; }
#card-games .wings-plane, #card-games .tk-skin {
  border-radius: 14px; background: var(--surface-2); border: 1px solid var(--hair); padding: 9px 10px;
  transition: transform var(--ease), border-color var(--ease), box-shadow var(--ease), background var(--ease);
}
#card-games .wings-plane:hover, #card-games .tk-skin:hover { border-color: var(--line); transform: translateY(-1px); }
#card-games .wings-plane.on, #card-games .tk-skin.on {
  border-color: var(--accent); background: var(--tint);
  box-shadow: 0 0 0 1px var(--accent), 0 12px 26px -14px var(--accent);
}
#card-games .wings-plane-name, #card-games .tk-skin span { font-family: var(--font-mono); font-size: 9px; letter-spacing: 1.5px; }

/* ---- settings sheets: sounds, difficulty, RGB, the wardrobe ---- */
#card-games .wings-audio-panel, #card-games .tk-audio, #card-games .cells-audio-box, #card-games .tk-classes {
  background: var(--game-glass); border: 1px solid var(--line); border-radius: 16px; padding: 12px;
  backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
  box-shadow: 0 30px 60px -30px #000, var(--glow);
}
#card-games .cells-audio { background: rgba(3, 0, 8, 0.72); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px); }
#card-games .hg-panel { background: rgba(4, 1, 9, 0.94); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); padding: 12px 14px; gap: 10px; }
#card-games .wings-audio-head, #card-games .tk-audio-head, #card-games .cells-audio-head, #card-games .hg-panel h4,
#card-games .tk-classes > span {
  font-family: var(--font-mono); font-size: 10px; letter-spacing: 2.4px; color: var(--accent); text-transform: uppercase;
  padding-bottom: 6px; border-bottom: 1px solid var(--hair);
}
#card-games .hg-panel h5 { font-family: var(--font-mono); color: var(--dim); letter-spacing: 2px; }
#card-games .wings-audio-name, #card-games .tk-audio-name, #card-games .tk-set-name, #card-games .cells-audio-name,
#card-games .hg-item-name { color: var(--text); font-family: var(--font-mono); font-weight: 600; }
#card-games .wings-audio-slider, #card-games .tk-audio-slider { accent-color: var(--accent); }
#card-games .tk-stat, #card-games .hg-item, #card-games .hg-row {
  border-radius: 12px; background: var(--surface-2); border: 1px solid var(--hair);
}
#card-games .hg-row { padding: 6px; border: none; background: transparent; }
#card-games .tk-stat { padding: 7px 9px; color: var(--text); }
#card-games .tk-stat i { color: var(--accent); }
#card-games .hg-item { padding: 8px 10px; }
#card-games .hg-item-cost { color: var(--accent); }
#card-games .hg-opt, #card-games .hg-buy { padding: 8px 10px; }

/* ---- the hangout's chat bar ---- */
#card-games .hg-chatbar { background: var(--surface-2); border-top: 1px solid var(--hair); padding: 8px; gap: 6px; }
#card-games .hg-input {
  border-radius: var(--r-pill); background: color-mix(in srgb, var(--accent) 8%, #000); border: 1px solid var(--hair);
  color: var(--text); padding: 8px 14px;
}
#card-games .hg-input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--tint); }
#card-games .hg-send { background: var(--accent); color: var(--on-accent); border-color: transparent; padding: 8px 16px; }

/* ---- thumb controls: glass discs, lit when held ---- */
#card-games .wings-pad-btn, #card-games .cells-pad-btn, #card-games .hg-pad-btn, #card-games .tk-boost {
  background: color-mix(in srgb, var(--accent) 12%, rgba(0, 0, 0, 0.55)); border: 1px solid var(--line);
  color: var(--accent); border-radius: 18px; font-family: var(--font-mono); font-weight: 700;
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  box-shadow: 0 10px 30px -18px var(--accent);
}
#card-games .wings-fire, #card-games .cells-split, #card-games .hg-pad-btn { border-radius: 50%; }
#card-games .wings-pad-btn:active, #card-games .cells-pad-btn:active, #card-games .hg-pad-btn.on, #card-games .tk-boost.on {
  background: var(--accent); color: var(--on-accent); border-color: transparent;
}
#card-games .wings-stick i, #card-games .cells-stick i { box-shadow: 0 0 18px -2px var(--accent); }

/* ---- RAISE A RETARD: two fighter cards, a bar, a log ---- */
#card-games .rar-tabs { padding: 10px 12px; }
#card-games .rar-body { padding: 12px 14px 16px; }
#card-games .rar-intro { padding: 14px 6px; }
#card-games .rar-intro .dim { max-width: 420px; margin: 0 auto 6px; }
#card-games .rar-arena { gap: 10px; align-items: stretch; }
#card-games .rar-side {
  background: var(--surface-2); border: 1px solid var(--hair); border-radius: 16px; padding: 12px 10px 10px;
}
#card-games .rar-side.rar-player { border-color: var(--line); box-shadow: inset 0 1px 0 color-mix(in srgb, var(--accent) 22%, transparent); }
#card-games .rar-portrait { display: flex; justify-content: center; }
#card-games .rar-portrait-img {
  width: 64px; height: 64px; border-radius: 50%; padding: 5px; background: var(--surface-3);
  border: 1.5px solid var(--accent); box-shadow: 0 0 22px -8px var(--accent);
}
#card-games .rar-name { font-size: 13px; margin-top: 8px; }
#card-games .rar-vs { padding: 0; align-self: center; font-size: 11px; letter-spacing: 3px; }
#card-games .rar-hpbar { height: 6px; border-radius: 6px; background: var(--hair); overflow: hidden; margin: 7px 0 4px; }
#card-games .rar-hpbar i { border-radius: 6px; background: linear-gradient(90deg, var(--accent), color-mix(in srgb, var(--accent) 55%, #fff)); }
#card-games .rar-hp { font-size: 10px; letter-spacing: 1px; }
#card-games .rar-log {
  margin-top: 10px; padding: 10px 12px; border-radius: 14px; background: var(--surface-2); border: 1px solid var(--hair);
  font-size: 12px;
}
#card-games .rar-turn { padding: 2px 0; }
#card-games .rar-crit { color: var(--accent); background: var(--tint); border-radius: var(--r-pill); padding: 1px 7px; margin-left: 4px; }
#card-games .rar-result {
  border-radius: 14px; padding: 12px 14px; background: var(--tint); border-color: var(--line);
  font-weight: 700; letter-spacing: 1px; text-align: center;
}
#card-games .rar-result.lost { background: color-mix(in srgb, var(--danger) 12%, transparent); border-color: color-mix(in srgb, var(--danger) 55%, transparent); }
#card-games .rar-loot { justify-content: center; margin-top: 8px; }
#card-games .rar-line { background: var(--tint); border-radius: 12px; padding: 8px 12px; color: var(--text); }
#card-games .rar-stack, #card-games .rar-tier {
  background: var(--surface-2); border: 1px solid var(--hair); border-radius: 12px; padding: 8px 10px; margin-bottom: 6px;
}
#card-games .rar-stack:hover, #card-games .rar-tier:hover { border-color: var(--line); }
#card-games .rar-stack .hudpill { padding: 6px 11px; }
#card-games .rar-wide { border-radius: var(--r-pill); }
#card-games .rar-tier-name { color: var(--accent); }

/* ---- STAGE card: the same chrome, in whatever hue a live stage cycles ---- */
#card-stage .stage-err { color: var(--danger); }
#card-stage .stage-connecting { color: var(--stage-rgb, var(--accent)); }
#card-stage .stage-note { font-size: 12px; }
#card-stage .seat-pic { border-radius: 50%; background: var(--surface-3); box-shadow: 0 0 0 2px var(--bg); }
#card-stage .seat-up { border-radius: 50%; }
#card-stage .stage-shout { border-radius: 12px; background: var(--tint); }
#card-stage .stage-menu {
  border-radius: 14px; padding: 6px; background: var(--surface-3); border-color: var(--line);
  backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); box-shadow: 0 30px 60px -24px #000, var(--glow);
}
#card-stage .stage-menu button { border-radius: 10px; }
#card-stage .stage-menu button:hover { background: var(--tint); }

/* Safari on iOS zooms the whole page in whenever you focus a field
   whose text is smaller than 16px, and then leaves you there — Drago,
   2026-09-21: "phone version zooms in on the entire window when i go
   to type in any chat on the phone website version." 16px is the
   threshold, so every field gets at least that on a touch screen.
   Deliberately NOT fixed with maximum-scale=1 on the viewport, which
   stops the zoom by taking pinch-to-zoom away from everybody.

   Last in the file on purpose: .composer input and the other field
   rules above are more specific than a bare element selector, so this
   has to win on source order instead. */
@media (pointer: coarse) {
  input[type="text"], input[type="password"], input[type="number"],
  input[type="email"], input[type="search"], select, textarea,
  .composer input, .composer select, .hud-card-foot select {
    font-size: 16px;
  }
}

/* A CALL button on each online row. Greyed while a call is already up
   — RNNVoice writes the phase onto the body, and one call at a time is
   the server's rule, so offering a second is offering a refusal. */
.rowcall { padding: 4px 8px; font-size: 10px; }
body[data-voice]:not([data-voice="idle"]) .rowcall { opacity: .35; pointer-events: none; }
/* A browser with no microphone to give (an insecure origin is the usual
   one — see voice.js's header) keeps a PRESSABLE call button, dimmed.
   Removing it would leave somebody on a phone with nothing to press and
   no explanation; pressing this is how they get the sentence and the
   one-tap hop to the https site. */
body[data-voice-blocked="1"] .rowcall { opacity: .55; border-style: dashed; }
/* +VC only exists while there is a call to add somebody TO, and the
   server is what decides that (voice_calls.py's invite: an active call
   between two people, and you have to be on it). data-voice-invite is
   that answer carried down from the poll, not a guess made here. */
.rowadd { padding: 4px 8px; font-size: 10px; display: none; margin-left: 6px; }
body[data-voice-invite="1"] .rowadd { display: inline-block; }
.oncall-tag { color: var(--success); font-size: 9px; letter-spacing: 1px; }

/* ================= POP-OUT ================= */
/* app.js's popOut: ONLINE and the chats in a small window Chrome keeps
   on top of other apps (Document Picture-in-Picture) — the browser's
   version of the desktop overlay, for a game in windowed fullscreen.
   The pop-out document is marked data-device="phone", so the two cards
   stack in one column exactly as they do on a phone. */
body.popout { margin: 0; min-height: 100dvh; background: var(--bg); }
body.popout[data-device="phone"][data-screen="app"] #app-shell {
  height: auto; min-height: 100dvh; padding: 10px 10px 12px;
  display: flex; flex-direction: column; gap: 10px; overflow-y: auto;
}
body.popout .hud-card { flex: 0 0 auto; }
body.popout #card-chat { flex: 1 1 auto; min-height: 380px; }
body[data-device="phone"] .js-popout-btn { display: none; }
.hudbtn.js-popout-btn.on { text-decoration: underline; text-underline-offset: 3px; }

/* The pop-out's own look (Settings › Overlay, app.js overlayPrefs):
   small text, see-through cards, and the strip of whoever is talking
   — each in THEIR colour, the face ringed and pulsing while they do. */
.overlay-toggles { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }

/* ---------- SETTINGS: one area, tabs, a search box (app.js renderSettings) ----------
   Drago, 2026-10-16: "organize the settings area better". A row is the
   label left, the control right, a dim hint under; .set-block stacks the
   control under the label for the wide ones (bio, swatches). 44px targets
   on a phone. */
.set-search {
  width: 100%; margin: 0 0 12px; min-height: 40px; padding: 8px 12px; border-radius: 10px;
  background: var(--surface-2); color: var(--text); border: 1px solid var(--hair); font: inherit; font-size: 14px;
}
.set-search:focus { outline: none; border-color: var(--accent); }
.set-tabs { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 14px; }
.set-tabs.searching .set-tab { opacity: 0.45; }
.set-tab {
  flex: 1 1 auto; min-height: 36px; padding: 6px 10px; border-radius: 10px; cursor: pointer;
  background: var(--surface-2); color: var(--dim); border: 1px solid var(--hair);
  font: inherit; font-size: 11px; font-weight: 700; letter-spacing: 1.2px; text-transform: uppercase;
}
.set-tab:hover { color: var(--accent); border-color: var(--accent); }
.set-tab.on { background: var(--accent); color: #000; border-color: var(--accent); }
.set-head { color: var(--dim); font-size: 11px; font-weight: 700; letter-spacing: 1.6px; text-transform: uppercase; margin: 4px 0 6px; }
.set-section .set-head { display: none; }
.set-tabs.searching ~ .set-body .set-section .set-head { display: block; margin-top: 14px; }
.set-row {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  padding: 10px 0; border-top: 1px solid var(--hair); min-height: 44px;
}
.set-row:first-of-type { border-top: none; }
.set-row.set-sub { padding-left: 12px; }
.set-text { min-width: 0; flex: 1 1 auto; }
.set-label { font-size: 14px; color: var(--text); }
.set-hint { font-size: 12px; color: var(--dim); line-height: 1.45; margin-top: 2px; }
.set-note { font-size: 12px; color: var(--dim); }
.set-ctl { flex: 0 0 auto; display: flex; align-items: center; gap: 8px; max-width: 60%; }
.set-ctl .btn { margin: 0; min-height: 40px; padding: 8px 14px; width: auto; }
.set-row.set-block { flex-direction: column; align-items: stretch; }
.set-row.set-block .set-ctl { max-width: none; flex-wrap: wrap; justify-content: flex-start; margin-top: 8px; }
.set-row.set-block .set-ctl > * { margin: 0; }
.set-row.set-block .set-ctl .set-in, .set-row.set-block .set-ctl textarea, .set-row.set-block .set-ctl .js-set-link { flex: 1 1 100%; width: 100%; }
.set-row.set-block .set-ctl .set-choice { width: 100%; margin-top: 0; gap: 8px; }
.set-row.set-block .set-ctl .swatches { width: 100%; }
.set-sel { min-height: 40px; max-width: 220px; }
.set-list { width: 100%; }
.set-learned { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 6px 0; border-top: 1px dashed var(--hair); }
.set-learned:first-child { border-top: none; }
.set-learned-text { min-width: 0; display: flex; flex-direction: column; }
.set-learned-text b { font-weight: 600; color: var(--text); }
.set-learned .btn { margin: 0; min-height: 40px; }
.set-danger { border-top: 1px solid color-mix(in srgb, var(--danger) 45%, transparent); }
.set-danger .danger-card { border-top: none; padding-top: 0; margin-top: 0; width: 100%; }
.set-switch {
  position: relative; width: 48px; height: 28px; border-radius: 14px; border: 1px solid var(--hair);
  background: var(--surface-2); cursor: pointer; padding: 0; flex: 0 0 auto;
  transition: background 160ms, border-color 160ms;
}
.set-switch .set-knob {
  position: absolute; top: 3px; left: 3px; width: 20px; height: 20px; border-radius: 50%;
  background: var(--dim); transition: transform 160ms, background 160ms;
}
.set-switch.on { background: var(--accent); border-color: var(--accent); }
.set-switch.on .set-knob { transform: translateX(20px); background: #000; }
.set-switch:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (max-width: 600px) {
  .set-switch { width: 56px; height: 32px; }
  .set-switch .set-knob { width: 24px; height: 24px; }
  .set-switch.on .set-knob { transform: translateX(24px); }
  .set-ctl .btn, .set-sel, .set-tab { min-height: 44px; }
  .set-ctl { max-width: 50%; }
}
/* Settings › APPEARANCE › Reduced motion: one still frame, like the system's preference. */
body.reduced-motion *, body.reduced-motion *::before, body.reduced-motion *::after {
  animation: none !important; transition: none !important;
}
body.popout.compact #app-shell { zoom: .82; }
body.popout.glass { background: #000; }
body.popout.glass .hud-card {
  background: transparent; box-shadow: none; backdrop-filter: none; -webkit-backdrop-filter: none;
  border-color: color-mix(in srgb, var(--accent) 18%, transparent);
}
.popout-talk { display: flex; flex-wrap: wrap; gap: 12px; padding: 6px 8px 2px; }
.popout-talker {
  display: inline-flex; align-items: center; gap: 8px; font-weight: 700; font-size: 13px;
  color: var(--seat-accent, var(--accent)); text-shadow: 0 1px 2px #000;
}
.popout-talker .avatar {
  width: 28px; height: 28px; border-color: var(--seat-accent, var(--accent));
  animation: popout-talk-pulse 900ms ease-in-out infinite;
}
@keyframes popout-talk-pulse {
  0%, 100% { box-shadow: 0 0 0 2px var(--bg), 0 0 8px 1px var(--seat-accent, var(--accent)); }
  50%      { box-shadow: 0 0 0 2px var(--bg), 0 0 18px 5px var(--seat-accent, var(--accent)); }
}
@media (prefers-reduced-motion: reduce) { .popout-talker .avatar { animation: none; } }


/* The sign-in screen's link step (index.html #login-discord-wrap,
   app.js showDiscordStep): the id field and, under it, the purple
   button to the Discord — the login screen paints every other .btn
   white, so the purple is said again at the same strength here. */
#login-discord-wrap { display: flex; flex-direction: column; gap: 8px; margin: 10px 0 4px; }
#login-discord-wrap label { font-family: var(--font-mono); font-size: 10px; letter-spacing: 1.5px; text-transform: uppercase; color: var(--mono-dim, var(--dim)); }
body[data-screen="login"] .btn.discord-btn { background: #5865f2; color: #fff; border-color: transparent; }
body[data-screen="login"] .btn.discord-btn:hover { background: #4752c4; box-shadow: 0 12px 30px -16px #5865f2; }

/* ---------------- bios ---------------- */
/* A bio and up to three links on every profile (Settings › Bio, the
   profile card, the profile sheet off somebody's avatar). */
.prof-bio { margin: 10px 0 0; color: var(--text); font-size: 13px; line-height: 1.5; white-space: pre-wrap; word-break: break-word; }
.prof-links { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.prof-links a { display: inline-flex; align-items: center; max-width: 100%; padding: 6px 12px; border-radius: var(--r-pill);
  background: var(--surface-2); border: 1px solid var(--line); color: var(--accent); font-family: var(--font-mono);
  font-size: 10px; font-weight: 700; letter-spacing: 1px; text-decoration: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.prof-links a:hover { background: var(--tint); }
.js-profile-view .prof-row { margin-bottom: 4px; }
.js-profile-view .avatar { width: 56px; height: 56px; font-size: 22px; }
.js-profile-view .prof-name { font-size: 20px; }
.prof-since { margin: 2px 0 6px; font-size: 11px; letter-spacing: 1px; color: var(--muted, #8b7fa8); }
.listrow-title[data-act], .msg-name[data-act], .msg-av[data-act] { cursor: pointer; }
.listrow-title[data-act]:hover, .msg-name[data-act]:hover { text-decoration: underline; }
.js-profile-view .prof-btns { margin-top: 14px; }
.listrow .avatar[data-act] { cursor: pointer; }
textarea.js-set-bio { width: 100%; min-height: 84px; resize: vertical; margin-top: 8px; }
.js-set-link { margin-top: 8px; }

/* The pop-out's chat lines go after ten seconds (app.js
   renderPopoutFade) so the window over a game never fills with chat. */
body.popout.fade .msg.faded { display: none; }

/* ---------------- video and audio in chat ---------------- */
/* A whole message that is one video or sound link draws as a player
   with the browser's own controls — pause, volume, scrub, full screen
   (app.js mediaKindOf, the same rule as cogs/gif_links.py). */
.msg.media .msg-body { max-width: 420px; }
.chat-video { display: block; width: 100%; max-height: 320px; border-radius: 14px; background: #000; border: 1px solid var(--hair); }
.chat-audio { display: block; width: 100%; max-width: 420px; margin: 2px 0; border-radius: 22px; }
.chat-media-name { font-family: var(--font-mono); font-size: 11px; letter-spacing: 1px; color: var(--accent);
  margin: 0 0 4px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The file you are about to send, over the composer. */
.attach-card { flex: 0 0 auto; display: flex; align-items: center; gap: 10px; margin: 0 12px 6px;
  padding: 8px 10px; border-radius: 14px; background: var(--surface-2); border: 1px solid var(--hair); }
.attach-card.hidden { display: none; }
.attach-icon { flex: 0 0 auto; width: 32px; height: 32px; border-radius: 10px; display: flex; align-items: center;
  justify-content: center; background: var(--surface); border: 1px solid var(--hair); color: var(--accent); font-size: 15px; }
.attach-text { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.attach-text b { font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text); }
.attach-text small { font-family: var(--font-mono); font-size: 10px; letter-spacing: 1px; color: var(--muted, #888); }
.attach-card .cbtn { height: 32px; min-width: 40px; }
.attach-card .cbtn.busy { opacity: .6; }
body.chat-focus .card-chat .chat-video { max-height: 56vh; }
.cbtn.js-attach-btn { font-size: 15px; padding: 0 10px; min-width: 40px; }
.cbtn.js-attach-btn.busy { opacity: .55; pointer-events: none; }

/* ---------------- @mentions (app.js mentionTextHtml / updateMention) ---------------- */
.mention { font-weight: 700; background: color-mix(in srgb, currentColor 16%, transparent); border-radius: 4px; padding: 0 2px; }
.msg.pinged { background: color-mix(in srgb, #f0b232 12%, transparent); box-shadow: inset 2px 0 0 #f0b232; }
.msg.pinged:hover { background: color-mix(in srgb, #f0b232 18%, transparent); }
.mention-pop {
  position: fixed; z-index: 9000; width: 240px; max-width: calc(100vw - 16px); max-height: 50vh; overflow-y: auto;
  background: var(--surface, #111); border: 1px solid var(--hair, #333); border-radius: 10px; padding: 4px;
  box-shadow: 0 8px 24px rgba(0,0,0,.5);
}
.mention-pop.hidden { display: none; }
.mention-opt { display: flex; align-items: center; gap: 8px; min-height: 40px; padding: 4px 8px; border-radius: 6px; cursor: pointer; font-weight: 700; font-size: 14px; }
.mention-opt.sel, .mention-opt:hover { background: var(--surface-2, #222); }
.composer .js-emoji-btn { font-size: 17px; line-height: 1; }
.mention-opt.emoji-opt img.ce { width: 22px; height: 22px; object-fit: contain; }
.mention-opt.emoji-opt small { margin-left: auto; opacity: .55; font-size: 11px; font-weight: 600; }
.mention-av { width: 24px; height: 24px; flex: 0 0 24px; display: inline-flex; overflow: hidden; border-radius: 50%; }
.mention-av img, .mention-av > * { width: 24px; height: 24px; }
.mention-toast {
  position: fixed; z-index: 9001; left: 50%; top: 16px; transform: translate(-50%, -150%); max-width: calc(100vw - 32px);
  background: var(--surface, #111); border: 1px solid #f0b232; border-radius: 10px; padding: 10px 14px;
  transition: transform .25s; pointer-events: none; font-size: 14px;
}
/* Under the iPhone's clock and battery once shown (Drago, 2026-10-19: "users online
   notification needs to be lowered on iphone slightly down"); 0 everywhere else. */
.mention-toast.on { transform: translate(-50%, env(safe-area-inset-top, 0px)); }

/* ================= HOLO UI (the owner's key only) =================
   Drago, 2026-10-16: "make me feel like this, integrated as one with the
   network" — a JARVIS holographic HUD. app.js puts `holo` on <body> only
   when the bot answers may_ai; everything here is under body.holo, so
   nobody else's page changes. Only the key's accent: lines are it mixed
   toward black, glow is it. Other people's colours are untouched. */
body.holo {
  --holo-line: color-mix(in srgb, var(--accent) 55%, #000);
  --holo-dim: color-mix(in srgb, var(--accent) 22%, #000);
  --holo-glow: 0 0 0 1px var(--holo-line), 0 0 18px -6px var(--accent), inset 0 0 22px -14px var(--accent);
  --holo-cut: 12px;
  --surface: color-mix(in srgb, var(--accent) 5%, rgba(2, 4, 6, 0.86));
  --surface-2: color-mix(in srgb, var(--accent) 8%, rgba(3, 5, 8, 0.92));
  --surface-3: color-mix(in srgb, var(--accent) 9%, #030507);
  --hair: color-mix(in srgb, var(--accent) 26%, transparent);
  background-color: #020306;
}
/* The fixed backdrop: concentric rings only. No grid and no scan lines
   (Drago, 2026-10-16: "a bunch of black lines in the background for what
   reason? they do nothing"). */
body.holo::before {
  content: ""; position: fixed; inset: 0; z-index: 0; pointer-events: none;
  background:
    radial-gradient(circle at 50% 46%, color-mix(in srgb, var(--accent) 7%, transparent) 0, transparent 60%);
  -webkit-mask-image: radial-gradient(circle at 50% 46%, #000 0 45%, transparent 85%);
  mask-image: radial-gradient(circle at 50% 46%, #000 0 45%, transparent 85%);
}
/* Cards and sheets: chamfered, a glowing hairline, corner brackets. */
body.holo .hud-card, body.holo .sheet .card, body.holo .card:not(.sheet .card .card) {
  border-radius: 0; border: 1px solid var(--holo-line); box-shadow: var(--holo-glow);
  clip-path: polygon(var(--holo-cut) 0, 100% 0, 100% calc(100% - var(--holo-cut)), calc(100% - var(--holo-cut)) 100%, 0 100%, 0 var(--holo-cut));
  background-color: var(--surface);
  background-image:
    linear-gradient(var(--accent), var(--accent)), linear-gradient(var(--accent), var(--accent)),
    linear-gradient(var(--accent), var(--accent)), linear-gradient(var(--accent), var(--accent)),
    linear-gradient(135deg, transparent calc(var(--holo-cut) - 1px), var(--accent) calc(var(--holo-cut) - 1px) var(--holo-cut), transparent var(--holo-cut)),
    linear-gradient(-45deg, transparent calc(var(--holo-cut) - 1px), var(--accent) calc(var(--holo-cut) - 1px) var(--holo-cut), transparent var(--holo-cut));
  background-size: 22px 2px, 2px 22px, 22px 2px, 2px 22px, 100% 100%, 100% 100%;
  background-position: right 0 top 0, right 0 top 0, left 0 bottom 0, left 0 bottom 0, 0 0, 0 0;
  background-repeat: no-repeat;
}
body.holo .sheet .card .card { background-image: none; clip-path: none; border-top: 1px solid var(--hair); box-shadow: none; }
/* Headers: small, spaced, uppercase, with a line and tick marks. */
body.holo .hud-card-head, body.holo .sheet-head {
  color: var(--accent); letter-spacing: 3px; font-size: 10.5px; text-shadow: 0 0 8px color-mix(in srgb, var(--accent) 60%, transparent);
  border-bottom: 1px solid var(--holo-dim); margin: 0 10px; padding-left: 4px; padding-right: 4px;
  background: repeating-linear-gradient(90deg, var(--holo-line) 0 1px, transparent 1px 8px) left bottom / 64px 4px no-repeat;
}
body.holo .hud-card-head::before { border-radius: 0; width: 7px; height: 7px; transform: rotate(45deg); background: none; border: 1px solid var(--accent); }
/* Buttons: angular, thin-framed, glowing on hover and focus. */
body.holo .btn, body.holo .hudpill, body.holo .cbtn, body.holo .gamebtn, body.holo .huddrop-item, body.holo .tabbar-btn,
body.holo [class^="rnnai-"] button, body.holo .linkbtn {
  border-radius: 0;
  clip-path: polygon(7px 0, 100% 0, 100% calc(100% - 7px), calc(100% - 7px) 100%, 0 100%, 0 7px);
  letter-spacing: 1.8px;
}
body.holo .btn, body.holo .hudpill, body.holo .cbtn, body.holo .gamebtn {
  background: color-mix(in srgb, var(--accent) 6%, rgba(2, 4, 6, 0.9));
  border: 1px solid var(--holo-line); color: var(--accent);
  box-shadow: inset 0 0 12px -8px var(--accent);
  transition: box-shadow var(--ease), background var(--ease), color var(--ease);
}
body.holo .btn:hover, body.holo .hudpill:hover, body.holo .cbtn:hover, body.holo .gamebtn:hover,
body.holo .btn:focus-visible, body.holo .hudpill:focus-visible, body.holo .cbtn:focus-visible, body.holo .gamebtn:focus-visible {
  background: color-mix(in srgb, var(--accent) 16%, #000); box-shadow: inset 0 0 16px -6px var(--accent), 0 0 14px -4px var(--accent);
  text-shadow: 0 0 8px var(--accent); transform: none;
}
body.holo .btn.primary, body.holo .hudpill.on, body.holo .btn-primary, body.holo .primary {
  background: var(--accent); color: var(--on-accent); border-color: var(--accent);
  box-shadow: 0 0 18px -4px var(--accent);
}
body.holo .hudbtn { border-radius: 0; letter-spacing: 3px; }
body.holo .hudbtn.on, body.holo .hudbtn:hover { background: none; border-color: transparent; box-shadow: inset 0 -2px 0 var(--accent); }
body.holo .huddrop-menu { border-radius: 0; border-color: var(--holo-line); box-shadow: var(--holo-glow); }
/* Fields: underlined frames. */
body.holo input[type="text"], body.holo input[type="password"], body.holo input[type="url"], body.holo select, body.holo textarea {
  border-radius: 0; background: rgba(2, 4, 6, 0.7); border: 1px solid var(--holo-dim); border-bottom-color: var(--accent);
}
body.holo input:focus, body.holo select:focus, body.holo textarea:focus { box-shadow: 0 2px 0 -1px var(--accent), 0 0 14px -6px var(--accent); }
body.holo * { scrollbar-width: thin; scrollbar-color: var(--holo-line) transparent; }
body.holo *::-webkit-scrollbar { width: 6px; height: 6px; }
body.holo *::-webkit-scrollbar-thumb { border-radius: 0; background: var(--holo-line); border: none; }

/* The ring cluster: the network as gauges round the voice's core. */
.holo-hub { display: none; }
body.holo .holo-hub {
  display: block; position: fixed; left: 50%; bottom: 18px; transform: translateX(-50%);
  width: 340px; height: 132px; z-index: 3; pointer-events: none; color: var(--accent);
  font-family: var(--font-mono);
}
.holo-hub svg { width: 100%; height: 100%; overflow: visible; filter: drop-shadow(0 0 4px color-mix(in srgb, var(--accent) 70%, transparent)); }
.holo-hub text { fill: var(--accent); font-size: 7px; letter-spacing: 1.6px; text-anchor: middle; }
.holo-hub .big { font-size: 13px; letter-spacing: 0.5px; }
.holo-hub .track { stroke: color-mix(in srgb, var(--accent) 22%, #000); fill: none; }
.holo-hub .seg { stroke: var(--accent); fill: none; }
.holo-hub .spin { transform-box: fill-box; transform-origin: center; animation: holo-spin 18s linear infinite; }
.holo-hub .spin.rev { animation-duration: 11s; animation-direction: reverse; }
.holo-core { fill: var(--accent); transform-box: fill-box; transform-origin: center; opacity: 0.55; }
.holo-hub[data-ai="listening"] .holo-core { animation: holo-pulse 1.6s ease-in-out infinite; opacity: 0.85; }
.holo-hub[data-ai="thinking"] .spin { animation-duration: 2.4s; }
.holo-hub[data-ai="thinking"] .holo-core { opacity: 0.75; }
.holo-hub[data-ai="speaking"] .holo-core { animation: holo-pulse 0.45s ease-in-out infinite; opacity: 1; }
@keyframes holo-spin { to { transform: rotate(360deg); } }
@keyframes holo-pulse { 0%, 100% { transform: scale(0.82); } 50% { transform: scale(1.12); } }
body.holo-paused .holo-hub *, body.holo-paused .holo-hub { animation-play-state: paused !important; }
body.holo-paused .holo-hub { display: none; }
@media (prefers-reduced-motion: reduce) { .holo-hub .spin, .holo-core { animation: none !important; } }
/* ===== ALIVE + THE WORKSHOP (Drago, 2026-10-16) =====
   The cards breathe (each its own phase), a streak of light runs along
   their frame, they flare when the core noticed something about them
   (holo.js adds .holo-flare), scan while it thinks and brighten with the
   voice (--holo-lvl, set by holo.js). Glass: a reflection along the
   bottom; on a wide screen the side panels angle in like Tony Stark's
   workshop. holo.js paints the wireframe globe, dust, connectors and
   ripples on .holo-alive behind the cards. */
.holo-alive { display: none; }
body.holo .holo-alive { display: block; position: fixed; inset: 0; width: 100vw; height: 100vh; z-index: 1; pointer-events: none; }
body.holo-paused .holo-alive { display: none; }
/* NO FROSTED GLASS OVER A MOVING BACKGROUND (2026-10-17, "super laggy"):
   the cards and the bars blurred what was behind them — the turning Earth,
   the rings, the falling emoji, all repainting — so every one of them was
   blurred again on every frame (measured: the PC page held at ~43 frames
   a second with the SERVERS card at ~28; without, 60 and 50). Under holo
   the cards are near-opaque glass anyway; a touch more opaque instead. */
body.holo .hud-card, body.holo .hud-row, body.holo .hud-top, body.holo .tabbar {
  backdrop-filter: none; -webkit-backdrop-filter: none;
}
body.holo .hud-card { background-color: color-mix(in srgb, var(--accent) 5%, rgba(2, 4, 6, 0.9)); }
/* THE BREATH IS OPACITY (2026-10-17, "super laggy"): it animated the
   card's own box-shadow, which repaints the whole card every frame. Now
   the card's glow is still and the breath is its ::before — the glass's
   reflection and the brighter inner glow — fading in and out on a layer
   of its own, which the compositor does without repainting anything. */
body.holo .hud-card {
  --holo-lvl-k: calc(0.35 + 0.65 * var(--holo-lvl, 0));
  border-color: color-mix(in srgb, var(--accent) calc(45% + 55% * var(--holo-lvl, 0)), #000);
  box-shadow: 0 0 0 1px var(--holo-line), inset 0 0 18px -14px var(--accent), inset 0 -26px 22px -24px color-mix(in srgb, var(--accent) 40%, transparent);
  animation: card-rise 420ms cubic-bezier(0.2, 0.7, 0.2, 1) both;
}
body.holo .hud-card::before { animation: holo-breathe 5.5s ease-in-out infinite; }
body.holo .card-online { animation-delay: 0ms; }
body.holo .card-profile { animation-delay: 60ms; }
body.holo .card-games { animation-delay: 120ms; }
body.holo .card-chat { animation-delay: 180ms; }
body.holo .card-profile::before { animation-delay: -1.4s; }
body.holo .card-games::before { animation-delay: -2.9s; }
body.holo .card-chat::before { animation-delay: -4.1s; }
@keyframes holo-breathe {
  0%, 100% { opacity: 0.35; }
  50% { opacity: 1; }
}
body.holo[data-layout="tall"] .hud-card { position: relative; }
/* The streak along the top edge, and while it thinks a scan line across. */
body.holo .hud-card::after {
  content: ""; position: absolute; top: 0; left: 0; width: 30%; height: 2px; pointer-events: none; z-index: 2;
  background: linear-gradient(90deg, transparent, var(--accent), #fff8, var(--accent), transparent);
  filter: drop-shadow(0 0 4px var(--accent));
  will-change: transform, opacity;
  animation: holo-streak 6s linear infinite; animation-delay: inherit;
}
body.holo .card-profile::after { animation-delay: -1.5s; }
body.holo .card-games::after { animation-delay: -3.1s; }
body.holo .card-chat::after { animation-delay: -4.4s; }
@keyframes holo-streak { 0% { transform: translateX(-100%); opacity: 1; } 27% { transform: translateX(333.4%); opacity: 1; } 28%, 100% { transform: translateX(333.4%); opacity: 0; } }
body.holo[data-holo-ai="speaking"] .hud-card::after { animation-duration: 2.4s; }
body.holo[data-holo-ai="thinking"] .hud-card::after {
  top: 0; bottom: 0; height: auto; width: 2px; left: 0; opacity: 0.6; transform: none;
  background: linear-gradient(transparent, var(--accent), transparent);
  animation: holo-scan 1.8s ease-in-out infinite alternate;
}
@keyframes holo-scan { from { left: 0; } to { left: calc(100% - 2px); } }
/* The glass's reflection along the bottom. */
body.holo .hud-card::before {
  content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 1;
  background: linear-gradient(to top, color-mix(in srgb, var(--accent) 14%, transparent), transparent 34px);
  box-shadow: inset 0 0 30px -12px var(--accent), inset 0 -26px 22px -22px color-mix(in srgb, var(--accent) 55%, transparent);
  will-change: opacity;
}
body.holo .hud-card.holo-flare { animation: holo-flare 1.5s ease-out !important; }
@keyframes holo-flare {
  0% { box-shadow: 0 0 0 2px #fff, inset 0 0 60px -10px var(--accent); }
  100% { box-shadow: 0 0 0 1px var(--holo-line), inset 0 0 18px -14px var(--accent); }
}
body.holo.holo-idle .hud-card { filter: brightness(0.75); }
body.holo.holo-idle .hud-card::before { animation-duration: 11s; }
body.holo.holo-idle .hud-card::after { animation-duration: 14s; }
/* The arc: side panels angled in, on a wide screen only. */
@media (min-width: 1240px) {
  /* card-rise's fill would hold transform: none, so here only the breath. */
  body.holo[data-layout="wide"] .hud-card { animation: none; }
  body.holo[data-layout="wide"] .card-online, body.holo[data-layout="wide"] .card-games { transform: perspective(1400px) rotateY(9deg); transform-origin: left center; }
  body.holo[data-layout="wide"] .card-profile, body.holo[data-layout="wide"] .card-chat { transform: perspective(1400px) rotateY(-9deg); transform-origin: right center; }
  body.holo.holo-idle[data-layout="wide"] .hud-card::before { animation-duration: 11s; }
}
body.holo-paused .hud-card, body.holo-paused .hud-card::before, body.holo-paused .hud-card::after { animation-play-state: paused !important; }
@media (max-width: 900px) {
  body.holo .hud-card::after { display: none; }   /* the phone: no streaks */
}
@media (prefers-reduced-motion: reduce) {
  body.holo .hud-card, body.holo .hud-card::before, body.holo .hud-card::after, body.holo .hud-card.holo-flare { animation: none !important; }
  body.holo .hud-card::after { display: none; }
  body.holo .hud-card { box-shadow: 0 0 0 1px var(--holo-line), inset 0 0 24px -12px var(--accent); }
}
/* A phone has no room for it, and the cut corners stay small there. */
@media (max-width: 1240px) { body.holo .holo-hub { display: none; } }
@media (max-width: 900px) {
  body.holo { --holo-cut: 8px; }
}

/* ===== HOLO EVERYWHERE (Drago, 2026-10-17: "make sure every single UI card
   even after you click on things has the new holo theme, a lot of UI cards
   dont") =====
   Every surface reached by a click — sheets, the GIF tray, menus, pop-ups,
   modals, context menus, the mention list and toasts, the games' bars,
   SERVERS, ANNOUNCEMENTS — wears the cards' look: near-black glass, cut
   corners, a thin accent frame with corner brackets, square controls with
   the primary still filled. The radius tokens go to nothing, so anything
   drawn off them follows; faces, presence dots, swatches and the thumb pads
   inside a game stay round. */
body.holo {
  --r-sm: 0px; --r-md: 0px; --r-lg: 0px; --r-pill: 0px;
  --holo-chamfer: polygon(var(--holo-cut) 0, 100% 0, 100% calc(100% - var(--holo-cut)), calc(100% - var(--holo-cut)) 100%, 0 100%, 0 var(--holo-cut));
  --holo-chamfer-sm: polygon(7px 0, 100% 0, 100% calc(100% - 7px), calc(100% - 7px) 100%, 0 100%, 0 7px);
  --holo-brackets:
    linear-gradient(var(--accent), var(--accent)), linear-gradient(var(--accent), var(--accent)),
    linear-gradient(var(--accent), var(--accent)), linear-gradient(var(--accent), var(--accent)),
    linear-gradient(135deg, transparent calc(var(--holo-cut) - 1px), var(--accent) calc(var(--holo-cut) - 1px) var(--holo-cut), transparent var(--holo-cut)),
    linear-gradient(-45deg, transparent calc(var(--holo-cut) - 1px), var(--accent) calc(var(--holo-cut) - 1px) var(--holo-cut), transparent var(--holo-cut));
}
/* The globe and its rings are BEHIND the cards (the canvas is appended to
   <body> after #screen-app, so z-index 1 had put it over them), and the
   ring cluster steps aside while anything is open over the HUD. */
body.holo .holo-alive { z-index: 0; }
body.holo:has(.sheet:not(.hidden), #tray.open, .rnnsv-overlay:not(.hidden), .rnnan-overlay:not(.hidden), .rnnrw-overlay) .holo-hub { display: none; }

/* Panels opened over the HUD: the cards' frame. */
body.holo .tray, body.holo .huddrop-menu, body.holo .mention-pop, body.holo .mention-toast,
body.holo #card-stage .stage-menu, body.holo .rnnsv-box, body.holo .rnnsv-pop, body.holo .rnnsv-ctx,
body.holo .rnnsv-shell, body.holo .rnnan-card, body.holo .rnnsv-tip, body.holo .rnnsv-dcard,
body.holo #card-games .hg-panel, body.holo .attach-card {
  border-radius: 0; border: 1px solid var(--holo-line); box-shadow: var(--holo-glow);
  clip-path: var(--holo-chamfer);
  background-color: color-mix(in srgb, var(--accent) 6%, rgba(2, 4, 6, 0.96));
  background-image: var(--holo-brackets);
  background-size: 22px 2px, 2px 22px, 22px 2px, 2px 22px, 100% 100%, 100% 100%;
  background-position: right 0 top 0, right 0 top 0, left 0 bottom 0, left 0 bottom 0, 0 0, 0 0;
  background-repeat: no-repeat;
}
body.holo .rnnsv-tip, body.holo .attach-card, body.holo .rnnsv-dcard { --holo-cut: 7px; box-shadow: 0 0 0 1px var(--holo-line); }
body.holo .mention-toast { border-color: #f0b232; }
body.holo .rnnsv-box.wide, body.holo .rnnsv-shell { background-color: color-mix(in srgb, var(--accent) 5%, #020306); }
/* The tray and a phone's sheets rise from the bottom: only the top corners cut. */
body.holo .tray { clip-path: polygon(var(--holo-cut) 0, calc(100% - var(--holo-cut)) 0, 100% var(--holo-cut), 100% 100%, 0 100%, 0 var(--holo-cut)); }
body.holo .tray-head { border-bottom-color: var(--holo-dim); }
@media (max-width: 760px) {
  body.holo .rnnsv-shell, body.holo .rnnsv-box.wide, body.holo .rnnan-card { clip-path: none; background-image: none; }
}
/* Headers of what opens: small, spaced, uppercase, a rule under them. */
body.holo .sheet-head h2, body.holo .rnnsv-box h2, body.holo .rnnsv-box h3, body.holo .rnnsv-tabs h5,
body.holo .rnnsv-box h4, body.holo #card-games .hg-panel h4, body.holo .panel-label, body.holo .set-head,
body.holo .rnnsv-ctx .lbl, body.holo .rnnan-badge {
  font-family: var(--font-mono); text-transform: uppercase; letter-spacing: 3px; color: var(--accent);
  text-shadow: 0 0 8px color-mix(in srgb, var(--accent) 55%, transparent);
}
body.holo .sheet-head h2, body.holo .rnnsv-box h2, body.holo .rnnsv-box h3 { font-size: 13px; font-weight: 700; }
body.holo .rnnsv-box h2, body.holo .rnnsv-box h3 {
  padding-bottom: 8px; border-bottom: 1px solid var(--holo-dim);
  background: repeating-linear-gradient(90deg, var(--holo-line) 0 1px, transparent 1px 8px) left bottom / 64px 4px no-repeat;
}
body.holo .rnnsv-box h2 { text-align: left; }
body.holo .rnnsv-box .sub { text-align: left; }

/* Controls: square and thin-framed, cut at two corners. */
body.holo .tray-subtab, body.holo .tray-close, body.holo .set-tab, body.holo .rnnsv-btn, body.holo .rnnsv-tab,
body.holo .rnnsv-gifs .tabs button, body.holo .rnnsv-opt, body.holo .rnnan-btn, body.holo .rnnan-primary, body.holo .rnnan-x,
body.holo .msg-del, body.holo .prof-links a, body.holo .c9-editor button, body.holo .favorite-star,
body.holo #card-games .rar-go, body.holo #card-games .rar-adopt, body.holo #card-games .hg-spawn,
body.holo #card-games .hg-pill, body.holo #card-games .hg-opt, body.holo #card-games .hg-buy, body.holo #card-games .hg-send,
body.holo #card-games .hg-close-panel, body.holo .rnnai-gear, body.holo #card-stage .stage-menu button,
body.holo .rnnv-btn, body.holo .rnnmus button, body.holo .rnndon button, body.holo .huddrop-item {
  border-radius: 0; clip-path: var(--holo-chamfer-sm);
}
body.holo .tray-subtab, body.holo .set-tab, body.holo .rnnsv-tab, body.holo .rnnsv-gifs .tabs button,
body.holo .tray-close, body.holo .rnnan-x, body.holo .rnnsv-btn:not(.fill):not(.link):not(.bad),
body.holo .rnnan-btn, body.holo #card-games .hg-pill, body.holo #card-games .hg-opt,
body.holo #card-games .hg-buy, body.holo #card-games .hg-close-panel {
  background: color-mix(in srgb, var(--accent) 6%, rgba(2, 4, 6, 0.9)); border: 1px solid var(--holo-line);
  color: var(--accent); font-family: var(--font-mono); letter-spacing: 1.6px;
}
body.holo .tray-subtab:hover, body.holo .set-tab:hover, body.holo .rnnsv-tab:hover, body.holo .tray-close:hover,
body.holo .rnnan-x:hover, body.holo .rnnsv-btn:not(.fill):not(.link):not(.bad):hover, body.holo .rnnan-btn:hover {
  background: color-mix(in srgb, var(--accent) 16%, #000); box-shadow: inset 0 0 16px -6px var(--accent); color: var(--accent);
}
body.holo .tray-subtab.on, body.holo .set-tab.on, body.holo .rnnsv-tab.on, body.holo .rnnsv-gifs .tabs button.on,
body.holo .rnnsv-btn.fill, body.holo .rnnan-primary, body.holo #card-games .hg-opt.on, body.holo #card-games .hg-pill.on,
body.holo #card-games .hg-buy.on, body.holo #card-games .hg-send {
  background: var(--accent); color: var(--on-accent); border-color: var(--accent); box-shadow: 0 0 18px -4px var(--accent);
}
body.holo .rnnsv-btn.bad.fill { background: var(--danger, #ff5566); border-color: var(--danger, #ff5566); color: #fff; }
body.holo .rnnsv-opt { border: 1px solid var(--holo-dim); }
body.holo .rnnsv-opt:hover { border-color: var(--accent); box-shadow: inset 0 0 16px -8px var(--accent); }
body.holo input[type="file"]::file-selector-button { border-radius: 0; border-color: var(--holo-line); }
/* Fields: every kind, the settings search, the server composer. */
body.holo input[type="search"], body.holo input[type="number"], body.holo input[type="email"], body.holo input:not([type]),
body.holo .set-search, body.holo .rnnsv-in, body.holo .rnnsv-cbox, body.holo .c9-editor input, body.holo #card-games .hg-input,
body.holo .rnnan-label input, body.holo .rnnan-label textarea, body.holo .rnnan-in-link {
  border-radius: 0; background: rgba(2, 4, 6, 0.7); border: 1px solid var(--holo-dim); border-bottom-color: var(--accent);
}
/* What is not a control but had rounded corners: rows, lists, menus' items. */
body.holo .listrow, body.holo .msg, body.holo .media-loading, body.holo .media-fallback, body.holo .fav-thumb,
body.holo .emoji-cell, body.holo .mention-opt, body.holo .rnnsv-list, body.holo .rnnsv-ctx button, body.holo .rnnsv-chan,
body.holo .rnnsv-mem, body.holo .rnnsv-aopt, body.holo .rnnsv-qs .r, body.holo .rnnsv-emoji .grid button,
body.holo .rnnsv-gifs .grid button, body.holo .rnnsv-note, body.holo .rnnsv-replybar, body.holo .rnnsv-rx, body.holo .rnnsv-chip,
body.holo .rnnsv-tri, body.holo .rnnsv-upl, body.holo .rnnai-row, body.holo .rnnai-msg, body.holo .rnnai-vlog li,
body.holo #card-games .rar-side, body.holo #card-games .rar-log, body.holo #card-games .rar-result, body.holo #card-games .rar-line,
body.holo #card-games .rar-stack, body.holo #card-games .rar-tier, body.holo #card-games .hg-item, body.holo #card-games .hg-row,
body.holo #card-stage .stage-shout, body.holo .attach-icon, body.holo .gamebtn, body.holo .rnnsv-ib,
body.holo .rnnai-cmd, body.holo .sheet details, body.holo .sheet details > summary, body.holo .sheet ol, body.holo .sheet pre {
  border-radius: 0;
}
/* A server's tile: a cut square when it is the open one (Discord's rounded square). */
body.holo .rnnsv-ri.on .rnnsv-icon, body.holo .rnnsv-icon:hover { border-radius: 0; clip-path: var(--holo-chamfer-sm); }
body.holo .rnnai-row, body.holo .rnnai-msg, body.holo #card-games .rar-side, body.holo #card-games .rar-stack,
body.holo #card-games .rar-tier, body.holo #card-games .rar-log, body.holo #card-games .rar-result, body.holo #card-games .hg-item {
  border: 1px solid var(--holo-dim); clip-path: var(--holo-chamfer-sm);
}
body.holo .rnnsv-ctx button:hover, body.holo .mention-opt.on, body.holo .mention-opt:hover {
  box-shadow: inset 2px 0 0 var(--accent);
}
/* A switch is a square track with a square knob. */
body.holo .set-switch, body.holo .set-switch .set-knob { border-radius: 0; }
body.holo .set-switch { border-color: var(--holo-line); box-shadow: inset 0 0 10px -6px var(--accent); }
body.holo .set-switch.on { box-shadow: 0 0 14px -4px var(--accent); }
/* The top bar: a band with cut ends, like the cards. */
/* No clip-path on these two: the GAMES / ADMIN menus and the badges hang
   out of them. Brackets at the corners instead. */
body.holo .hud-row, body.holo .tabbar {
  border-radius: 0; border-color: var(--holo-line); box-shadow: var(--holo-glow);
  background-color: color-mix(in srgb, var(--accent) 6%, rgba(2, 4, 6, 0.9));
  background-image: var(--holo-brackets);
  background-size: 22px 2px, 2px 22px, 22px 2px, 2px 22px, 0 0, 0 0;
  background-position: right 0 top 0, right 0 top 0, left 0 bottom 0, left 0 bottom 0, 0 0, 0 0;
  background-repeat: no-repeat;
}
/* No clip-path here either: it would make the status a layer over the GAMES menu. */
body.holo .hud-status { border-radius: 0; border-color: var(--holo-line); }
body.holo .tabbar-btn.on { box-shadow: inset 0 -2px 0 var(--accent); }
/* A phone's sheet rises from the bottom too. */
@media (max-width: 1180px), (max-height: 700px), (orientation: portrait) {
  body.holo .sheet .card { border-radius: 0; clip-path: polygon(var(--holo-cut) 0, calc(100% - var(--holo-cut)) 0, 100% var(--holo-cut), 100% 100%, 0 100%, 0 var(--holo-cut)); }
  body.holo .sheet .card::before { border-radius: 0; }
}
/* A settings modal's side tabs read as a list, not a column of boxes. */
body.holo .rnnsv-tab { border-color: transparent; background: none; text-transform: uppercase; font-size: 11px; }
body.holo .rnnsv-tab:hover { border-color: var(--holo-dim); }
body.holo .rnnsv-tab.on { background: var(--accent); color: var(--on-accent); }
body.holo .rnnsv-tab.bad, body.holo .rnnsv-btn.bad:not(.fill) { color: var(--danger, #ff5566); border-color: color-mix(in srgb, var(--danger, #ff5566) 60%, #000); }
body.holo .rnnsv-btn, body.holo .rnnan-btn, body.holo .rnnan-primary, body.holo .tray-subtab {
  text-transform: uppercase; font-family: var(--font-mono); letter-spacing: 1.6px; font-size: 11px;
}
/* NIGGA CITY's bar on a touch screen (its own sheet, hangout.js) — the pads keep their shapes. */
body.holo .hg .hg-pill, body.holo .hg.touchui .hg-pill, body.holo .hg.touchui .hg-opt {
  border-radius: 0; clip-path: var(--holo-chamfer-sm);
}
body.holo .hg.touchui.moreopen .hg-bar { border-radius: 0; border-color: var(--holo-line); box-shadow: var(--holo-glow); }

/* ---------------- the main screen, 2026-10-17 ---------------- */
/* Drago: "move public chat and direct messages to the servers UI card",
   "Remove the stage UI card from main screen. Remove the games UI card from
   the main screen on website until games at the top is clicked on. Remove
   the settings UI card from the main screen". GLOBAL CHAT, the DMs, the
   STAGE and the SETTINGS are views of the SERVERS card (servers.js moves
   #card-chat, #card-stage and #settings-sheet into it); the GAMES card shows
   only with body.games-shown (app.js showGames); ONLINE has the left side. */
body:not(.popout) #screen-app #app-shell > #card-chat, #screen-app #app-shell > #card-stage { display: none !important; }
body:not(.games-shown) #card-games { display: none !important; }
body:not(.games-shown) .card-online { max-height: calc(100vh - 150px); }
body[data-layout="wide"]:not(.games-shown) .card-online { max-height: calc(100vh - 150px); }
/* What a click opens over the SERVERS card (z 900) stays over it. */
.tray { z-index: 950; }
.sheet { z-index: 960; }
/* On a phone the SERVERS card is the screen above the tab bar, and the tab
   bar stays the way between ONLINE, CHAT, DMS, GAMES and PROFILE. */
body[data-device="phone"] .rnnsv-overlay { top: var(--hud-top-h, 0px); bottom: calc(80px + env(safe-area-inset-bottom, 0px)); }
body[data-device="phone"].sv-open .tabbar { z-index: 955; }
body[data-device="phone"] .tabbar-btn.tab-dms { display: flex; }
body[data-device="phone"] .tabbar-btn .tab-badge-chat { display: none; }
/* A PROFILE BANNER (2026-10-17): a picture or a GIF over the top of a
   profile; none, the person's own colour. */
.banner-preview { width: 100%; height: 96px; background: center / cover no-repeat; border: 1px solid var(--line);
  display: flex; align-items: center; justify-content: center; font-family: var(--font-mono); font-size: 10px;
  letter-spacing: 1.6px; color: rgba(0,0,0,.6); margin-bottom: 8px; }
.banner-actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.banner-actions .set-in { flex: 1 1 200px; min-width: 0; }
.prof-banner { height: 92px; margin: -4px 0 -28px; background: center / cover no-repeat; border-radius: var(--r-md, 8px); }

/* THE TOP ROW CLEARS THE iPHONE'S OWN BAR (Drago, 2026-10-19, a screenshot of
   the clock, signal and battery over HOME / GAMES / SERVERS: "mobile top bar is
   covered by iphone ui"). The page is viewport-fit=cover, so wherever the top
   row sits at the top of the screen it starts under the notch / status bar and
   clear of the rounded corners in landscape. Last, so holo and every layout
   above keep everything else they set. */
@media (max-width: 1180px), (max-height: 700px), (orientation: portrait) {
  body .hud-top { padding-top: max(8px, env(safe-area-inset-top)) !important;
    padding-left: max(10px, env(safe-area-inset-left)) !important; padding-right: max(10px, env(safe-area-inset-right)) !important; }
}
body[data-layout="tall"] .hud-top, body[data-device="phone"] .hud-top {
  padding-top: max(8px, env(safe-area-inset-top)) !important;
  padding-left: max(10px, env(safe-area-inset-left)) !important; padding-right: max(10px, env(safe-area-inset-right)) !important;
}
