*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

/* ── Accessibility ── */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
button:focus-visible, a:focus-visible, input:focus-visible, select:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}
::selection { background: rgba(255,158,61,.35); color: #fff; }

/* Reelay. Projection-booth colours: warm light thrown on a dark wall, rather
   than the purple every streaming app arrives in. Amber is the light, the
   ground is nearly black with a little violet left in it so blacks in a film
   still read as blacker than the page. */
:root {
  --bg: #100e16;
  --surface: #191622;
  --surface2: #221e2d;
  --border: #322c40;
  --accent: #ff9e3d;
  --accent-hover: #ffb865;
  --accent-deep: #f2683c;
  --text: #f2eef8;
  --text-muted: #9a92ad;
  --danger: #ff5d5d;
  --success: #5fd39a;
  --warning: #ffc36b;
}

/* The root is what shows through when a phone rubber-bands past the end of
   a page, and what iOS paints behind the status bar in an installed app.
   Left as the browser's white, every overscroll flashed. */
html {
  background: var(--bg);
  color-scheme: dark;
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  background: var(--bg);
  color: var(--text);
  font-family: 'Inter', system-ui, -apple-system, sans-serif;
  font-size: 14px;
  line-height: 1.5;
  min-height: 100dvh;
  -webkit-tap-highlight-color: transparent;
}

/* Pull down past the top of an installed app on Android and Chrome reloads
   the page. On /home that is a nuisance; in a room it is the film restarting
   and everybody's place lost, because a reload rejoins the room from the
   beginning of the page's life. watch.html has said `overscroll-behavior:
   none` on its own shell since it was written; nothing else did, so /home,
   /clips, /games, /puzzle and the rest were all one over-pull from a reload
   (21 Sep 2026). -y only: sideways chaining is still the browser's business.
   Both elements, because whichever one is the scrolling box is the one the
   gesture lands on, and that differs by page. */
html, body { overscroll-behavior-y: none; }

a { color: var(--accent); text-decoration: none; }
a:hover { color: var(--accent-hover); }

/* ── Layout ── */
.container { max-width: 960px; margin: 0 auto; padding: 24px 16px; }
/* Centred, but not pinned: when the keyboard takes half the screen the card
   is taller than what is left, and a flex-centred child overflows off the
   TOP, where nothing can scroll to it. Auto margins centre it when there is
   room and let it scroll when there is not. */
.page-center {
  display: flex; flex-direction: column; align-items: center;
  min-height: 100dvh;
  padding: max(16px, env(safe-area-inset-top)) max(16px, env(safe-area-inset-right))
           max(16px, env(safe-area-inset-bottom)) max(16px, env(safe-area-inset-left));
}
.page-center > * { margin: auto 0; }

/* ── Card ── */
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 24px;
}

/* ── Form elements ── */
input[type=text], input[type=password], textarea {
  width: 100%;
  background: var(--surface2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 10px 14px;
  color: var(--text);
  font-size: 14px;
  outline: none;
  transition: border-color .15s;
}
input:focus, textarea:focus { border-color: var(--accent); }

label { display: block; margin-bottom: 6px; color: var(--text-muted); font-size: 12px; text-transform: uppercase; letter-spacing: .06em; }

.field { margin-bottom: 16px; }

/* ── Buttons ── */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 9px 18px;
  border-radius: 8px;
  border: none;
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  transition: opacity .15s, background .15s;
}
.btn:disabled { opacity: .45; cursor: not-allowed; }
.btn-primary { background: var(--accent); color: #fff; }
.btn-primary:hover:not(:disabled) { background: var(--accent-hover); }
.btn-danger { background: transparent; color: var(--danger); border: 1px solid var(--danger); }
.btn-danger:hover:not(:disabled) { background: rgba(255,85,85,.1); }
.btn-ghost { background: transparent; color: var(--text-muted); border: 1px solid var(--border); }
.btn-ghost:hover:not(:disabled) { color: var(--text); border-color: var(--text-muted); }
.btn-sm { padding: 5px 12px; font-size: 12px; }

/* ── Badge ── */
.badge { display: inline-flex; align-items: center; padding: 2px 8px; border-radius: 20px; font-size: 11px; font-weight: 600; }
.badge-downloading { background: rgba(124,106,255,.12); color: var(--accent); }

/* ── Nav ── */
.nav { display: flex; justify-content: space-between; align-items: center; margin-bottom: 28px; }

/* ── Login ── */
.login-card { width: min(360px, 100%); }
.login-card h1 { font-size: 22px; font-weight: 700; margin-bottom: 4px; }
.login-card .subtitle { color: var(--text-muted); font-size: 13px; margin-bottom: 24px; }

/* ── Alert ── */
.alert { padding: 10px 14px; border-radius: 8px; font-size: 13px; margin-bottom: 12px; }
.alert-error { background: rgba(255,85,85,.12); color: var(--danger); border: 1px solid rgba(255,85,85,.25); }
.alert-success { background: rgba(85,255,136,.12); color: var(--success); border: 1px solid rgba(85,255,136,.25); }

/* ── Watch page ── */
.video-wrapper { flex: 1; display: flex; align-items: center; justify-content: center; position: relative; background: #000; }
video { width: 100%; height: 100%; object-fit: contain; max-height: calc(100dvh - 110px); }
.controls-bar {
  background: var(--surface);
  border-top: 1px solid var(--border);
  padding: 10px 16px;
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}
.controls-left { display: flex; align-items: center; gap: 10px; }
.controls-right { display: flex; align-items: center; gap: 10px; margin-left: auto; }
.play-btn { font-size: 22px; background: none; border: none; color: var(--text); cursor: pointer; line-height: 1; }
.play-btn:hover { color: var(--accent); }
.seek-bar { flex: 1; min-width: 120px; accent-color: var(--accent); cursor: pointer; }
.time-display { font-size: 12px; color: var(--text-muted); white-space: nowrap; font-variant-numeric: tabular-nums; }
.sync-status { font-size: 11px; padding: 3px 8px; border-radius: 12px; }
.sync-ok { background: rgba(85,255,136,.12); color: var(--success); }
.sync-lag { background: rgba(255,170,51,.12); color: var(--warning); }
.sync-offline { background: rgba(255,85,85,.12); color: var(--danger); }
.viewer-count { font-size: 12px; color: var(--text-muted); }

/* The owner's mark beside his name, in the room's chat and list and the
   games' chats (4 Oct 2026). Drawn from the server's isAdmin, never from the
   name, so nothing a guest types can wear it. */
.rl-owner {
  display: inline-block; margin-left: 6px; padding: 0 5px; border-radius: 6px;
  font-size: 10px; line-height: 15px; font-weight: 700; letter-spacing: .05em;
  text-transform: uppercase; white-space: nowrap; vertical-align: 1px;
  color: var(--warning); background: rgba(255,195,107,.12); border: 1px solid rgba(255,195,107,.45);
}
.rl-owner[hidden] { display: none; }

/* ── Invite modal ── */
.modal-overlay {
  display: none;
  position: fixed; inset: 0;
  background: rgba(0,0,0,.7);
  z-index: 100;
  align-items: center;
  justify-content: center;
  /* Room to scroll when the keyboard leaves less height than the card
     needs; auto margins on the card centre it when there is room. */
  overflow-y: auto; overscroll-behavior: contain;
  padding: max(12px, env(safe-area-inset-top)) max(12px, env(safe-area-inset-right))
           max(12px, env(safe-area-inset-bottom)) max(12px, env(safe-area-inset-left));
}
.modal-overlay.open { display: flex; }
.modal-overlay > * { margin: auto; }
.modal { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 24px; width: 500px; max-width: calc(100vw - 24px); max-height: calc(100dvh - 24px); overflow-y: auto; overscroll-behavior: contain; }
.modal h3 { margin-bottom: 16px; }
.copy-field { display: flex; gap: 8px; }
.copy-field input { flex: 1; }

/* ── Toast ── */
.toast {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 8px; padding: 10px 14px; font-size: 13px;
  color: var(--text); box-shadow: 0 8px 24px rgba(0,0,0,.4);
  min-width: 220px; max-width: 340px;
  opacity: 0; transform: translateX(40px);
  transition: opacity .2s, transform .2s;
  pointer-events: auto;
}
.toast.show { opacity: 1; transform: translateX(0); }


/* ── Motion ──────────────────────────────────────────────────────────────
   The app was completely still, which is what made it read as a web page
   rather than something installed. Everything here is short, 120-260ms, and
   all of it stops dead for anyone who has asked their phone for less motion. */
button, .btn, .icon-btn, .card, .tab {
  transition: transform .12s cubic-bezier(.2,.8,.2,1), background-color .15s ease,
              border-color .15s ease, color .15s ease, opacity .15s ease;
}
button:active, .btn:active, .icon-btn:active { transform: scale(.96); }
.card:active { transform: scale(.985); }

@keyframes rl-fade-up {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}
@keyframes rl-sheet-up {
  from { transform: translateY(100%); }
  to   { transform: none; }
}
@keyframes rl-backdrop {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes rl-shimmer {
  from { background-position: -140% 0; }
  to   { background-position: 240% 0; }
}

/* A placeholder in the shape of the thing that is coming, so a slow library
   looks like it is arriving rather than broken. */
.skeleton {
  background: linear-gradient(100deg, var(--surface) 30%, var(--surface2) 48%, var(--surface) 66%);
  background-size: 220% 100%;
  animation: rl-shimmer 1.35s linear infinite;
  border-radius: 12px;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
}

/* .btn sets display:inline-flex, which quietly beats the browser's own
   [hidden] { display: none }. So marking a button hidden did nothing and it
   sat there being clickable. This is a whole class of bug, not one button. */
[hidden] { display: none !important; }

/* ── Touch ──────────────────────────────────────────────────────────────
   Everything below is for a finger rather than a pointer, so it is gated on
   the input device and not on the width of the window: an iPad in landscape
   is wider than a laptop and still has no mouse.

   manipulation keeps pinch-zoom and scroll but drops the double-tap-to-zoom
   wait, which is the 300ms every tap used to sit through. */
button, [role="button"], a, input, select, textarea, label, summary, .tab {
  touch-action: manipulation;
}

@media (pointer: coarse) {
  /* 44x44 is the size a fingertip actually covers. Bounding box, not
     visual: a small-looking key can still have a full-size hit area. */
  .btn, .btn-sm, .btn-ghost, .icon-btn, .tab, [role="button"],
  .bnav button, .menu button, .topbar .btn-ghost, .topbar .links .btn-ghost {
    min-height: 44px; min-width: 44px;
  }
  .btn-sm { padding-top: 0; padding-bottom: 0; }

  /* iOS zooms the page into any field smaller than 16px and does not zoom
     back out when you leave it. So no field is smaller than 16px. */
  input[type="text"], input[type="password"], input[type="search"], input[type="url"],
  input[type="email"], input[type="number"], input[type="tel"], select, textarea,
  .chat-input, .searchbar input, .filterbar select, .fm-search, .copy-field input {
    font-size: 16px;
  }

  /* Hover is not a thing here, so the pressed state has to carry the
     feedback a hover used to give. */
  .btn:active:not(:disabled), .btn-ghost:active:not(:disabled), .icon-btn:active:not(:disabled) {
    filter: brightness(1.18);
  }

  /* Any list that scrolls on its own keeps the gesture to itself: a flick
     that reaches the end must not hand off to the page and trigger the
     browser's pull-to-refresh or rubber-band the body. */
  .row, .grow, .chat-messages, .chat-reactions, .ecats, .esheet-body, .src-list,
  .sheet, .modal, .pane, .torrent-table-wrap, .tabs, .results, .queue-section,
  .sidebar, .menu, .detail-section {
    overscroll-behavior: contain;
  }
}

/* Faces on the game pages (shared.js gameFaces, 22 Sep 2026): one living
   face per player in the top bar, a ring under whoever's go it is, the same
   jelly knock as the film page. */
/* The padding is room for what hangs off a face (a name wider than it, a
   turn ring), which the scroller would clip; the margins keep the faces where
   they were. */
.game-faces { display: flex; gap: 10px; align-items: flex-end; min-height: 44px; padding: 8px 8px 14px; margin: -6px -8px 0;
              /* It shrinks and scrolls inside itself; it never widens the page. Four seated
                 Ludo players made a 360px phone scroll sideways (audit, 22 Sep 2026). */
              flex: 1 1 100px; min-width: 100px; max-width: 100%; overflow-x: auto; scrollbar-width: none; }
.top > .grow, .top > .who { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.game-faces::-webkit-scrollbar { display: none; }
.game-face { position: relative; display: block; flex: 0 0 auto; }
.game-face svg { display: block; transform-origin: 50% 100%; }
/* Faces sit 38px apart (28 + the 10px gap), so a name wider than 36 ran into
   its neighbour's: "Mani (.Rida Sar" (audit, 4 Oct 2026). The first starts at
   its face's left edge, so it has 32 before the second name begins. */
.game-face .nm { position: absolute; left: 50%; transform: translateX(-50%); bottom: -14px; font-size: 12px; line-height: 14px; letter-spacing: .02em;
                 color: var(--text-muted); max-width: 36px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.game-face:first-child .nm { left: 0; transform: none; min-width: 100%; max-width: 32px; text-align: center; }
.game-face.is-turn::after { content: ""; position: absolute; left: 50%; bottom: -3px; width: 20px; height: 3px; border-radius: 2px;
                            transform: translateX(-50%); background: var(--accent); }
.game-face.is-away { opacity: .45; }
@keyframes gameFaceJelly {
  0%   { transform: scale(1, 1); }
  18%  { transform: scale(calc(1 + .28 * var(--pop, 1)), calc(1 - .22 * var(--pop, 1))); }
  42%  { transform: scale(calc(1 - .12 * var(--pop, 1)), calc(1 + .18 * var(--pop, 1))); }
  66%  { transform: scale(calc(1 + .06 * var(--pop, 1)), calc(1 - .05 * var(--pop, 1))); }
  100% { transform: scale(1, 1); }
}
.game-face.pop svg { animation: gameFaceJelly .9s cubic-bezier(.3, .9, .4, 1); }
@media (prefers-reduced-motion: reduce) { .game-face.pop svg { animation: none; } }
/* Phones (audit, 4 Oct 2026): the line saying whose go it is was cut to
   "Man..." while the head count beside it kept its words. The count goes,
   as the faces already show who is in, and its room goes to the turn line
   and the faces. Still too long, the turn line is what gives way, never the
   bar: it does not widen the page. */
@media (max-width: 480px) {
  .top > #who { display: none; }
}
