/* Marquee interaction layer -------------------------------------------------
   Three tiers, deliberately unequal. The specular sweep is the expensive,
   attention-grabbing one, so it is reserved for the single primary action on a
   view. Everything else gets clear glass, which is pure CSS and costs nothing
   per-element.

   Why not the React SpecularButton: it spins up one WebGL context, one shader
   program and one rAF loop PER BUTTON. Measured in this browser, only 16 live
   contexts survive before the oldest get dropped. It also needs `npm i ogl`
   plus a JSX build, and this site ships as static files with no build step.
   On touch there is no cursor for it to follow at all.

   Tokens come from the theme; these are fallbacks.
------------------------------------------------------------------------------- */
:root {
  --fx-rim: #FFD9A0;
  --fx-rim-soft: rgba(255, 217, 160, 0.55);
  --fx-glass-fill: rgba(255, 255, 255, 0.055);
  --fx-glass-fill-hi: rgba(255, 255, 255, 0.09);
  --fx-glass-line: rgba(255, 255, 255, 0.13);
  --fx-glass-line-hi: rgba(255, 255, 255, 0.22);
  --fx-shadow: rgba(8, 5, 4, 0.42);
  --fx-ease: cubic-bezier(.2, .8, .2, 1);
}

/* ============ pre-hydration flash guard =====================================
   Browsers do not know <sc-if> / <sc-for>, so before dc-runtime boots they are
   treated as unknown inline elements and their children all render — every
   branch of every conditional at once, plus literal "{{ binding }}" text. That
   is the flash of the Movie Night modal showing its countdown, PLAY NOW and
   rating stars simultaneously.

   dc-runtime ships a rule for this but scopes it to `html.sc-dc-streaming`, a
   class this static build never receives, so it never applies.

   Safe by construction: dc-runtime compiles these tags away during hydration —
   verified 0 sc-if, 0 sc-for and 0 raw tokens in the live DOM afterwards — so
   these selectors match nothing once the app is running. Only .sc-interp
   (resolved values) survives, and it is deliberately untouched. */
sc-if, sc-for { display: none !important; }
.sc-interp.sc-missing { display: none !important; }

/* Interpolations that sit outside any conditional ({{ meInitials }} and
   {{ r.label }} in the header) are plain text pre-hydration, so no selector can
   reach them. Hide the template root instead. dc-runtime removes <x-dc> when it
   renders — verified 0 in the live DOM — so this only covers the boot window.
   marquee-fx.js force-reveals it after 6s so a runtime failure degrades to a
   visible page rather than a blank one. */
x-dc { visibility: hidden; }
x-dc[data-fx-reveal] { visibility: visible; }

/* ============ desktop layout for the narrow views ===========================
   For You, Groups and Polls all render through the same  style,
   which hard-caps them at max-width:780px on desktop. On a 1440px screen that
   leaves ~660px of dead space, while Home/Discover/Surprise get proper grids.

   Hooked via the rendered inline style. dc-runtime hands  to React as an
   object, but React still writes a real style attribute, so this selector does
   match — verified directly against the live DOM.

   Deliberately WIDTH ONLY, not columns. Blind two-track auto-flow was tried and
   rejected: with no per-view hooks the grid cannot tell a section eyebrow from
   a content card, so "TASTE PROFILE" — a 17px label — was handed a 687px track
   while the filter panel sat beside it. Real columns need per-view classes to
   place things deliberately, which is a template change, not a CSS one.

   Widening alone is a strict improvement: the card grids already nested inside
   these views simply get more room and nothing can land in the wrong place.

   920px matches the app's own  test, so CSS and JS agree on
   what desktop means. Below it nothing here applies. */
@media (min-width: 920px) {
  main > section[style*="780px"] { max-width: 1180px !important; }

  /* Empty states are a single card; at 1180px they stretch into a wide thin
     band, so cap and centre them. */
  main > section[style*="780px"] > :only-child {
    max-width: 560px;
    margin-inline: auto;
  }
}

/* ============ tap targets — DEFERRED ========================================
   A hit-area overlay was written and pulled before shipping, for two reasons:
   (1) it targets button::after, which collides with the specular rim on
       [data-fx="spec"]::after — same pseudo-element, so the rim would inherit
       the overlay geometry and deform on touch devices;
   (2) it is gated on (pointer: coarse), which a desktop browser emulating a
       phone viewport does not report, so it could not be verified here.
   The controls work belongs with the Profile view, where the mute button moves
   out of the header into a properly sized row. Deferred rather than guessed. */

/* ============ TIER 2 — clear glass ==========================================
   The default for most buttons. No backdrop-filter here on purpose: blurring
   20+ elements forces a compositing pass each on every scroll frame, which is
   exactly the sort of thing that makes a phone feel cheap. A translucent fill,
   a hairline rim and a top inset highlight read as glass without that cost.
   Real blur is reserved for the few surfaces that sit over artwork (below). */
[data-fx="glass"] {
  position: relative;
  background: var(--fx-glass-fill);
  border: 1px solid var(--fx-glass-line);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .06);
  transition: background .18s var(--fx-ease),
              border-color .18s var(--fx-ease),
              transform .16s var(--fx-ease);
}

@media (hover: hover) and (pointer: fine) {
  [data-fx="glass"]:hover {
    background: var(--fx-glass-fill-hi);
    border-color: var(--fx-glass-line-hi);
  }
}

[data-fx="glass"]:active { transform: scale(.98); }

[data-fx="glass"]:focus-visible,
[data-fx="spec"]:focus-visible {
  outline: 2px solid var(--fx-rim);
  outline-offset: 2px;
}

/* Panels and sheets that genuinely overlay posters — few enough that the
   compositing cost is worth the depth. */
[data-fx="glass-deep"] {
  background: var(--fx-glass-fill);
  border: 1px solid var(--fx-glass-line);
  backdrop-filter: blur(16px) saturate(1.3);
  -webkit-backdrop-filter: blur(16px) saturate(1.3);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .07), 0 12px 34px var(--fx-shadow);
}

/* ============ TIER 1 — specular sweep =======================================
   Primary CTA only. --fx-a (angle) and --fx-p (proximity 0-1) are written by
   marquee-fx.js, which tracks ONLY these elements. */
[data-fx="spec"] {
  position: relative;
  isolation: isolate;
  transition: transform .16s var(--fx-ease), box-shadow .22s var(--fx-ease);
}

[data-fx="spec"]::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  pointer-events: none;
  z-index: 3;
  opacity: var(--fx-p, 0);
  transition: opacity .28s var(--fx-ease);
  background: conic-gradient(
    from calc(var(--fx-a, 0deg) - 26deg),
    transparent 0deg,
    var(--fx-rim-soft) 14deg,
    var(--fx-rim) 26deg,
    var(--fx-rim-soft) 38deg,
    transparent 52deg,
    transparent 180deg,
    var(--fx-rim-soft) 194deg,
    var(--fx-rim) 206deg,
    var(--fx-rim-soft) 218deg,
    transparent 232deg,
    transparent 360deg
  );
  /* border-box minus content-box leaves exactly a 1px ring */
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
}

@media (hover: hover) and (pointer: fine) {
  [data-fx="spec"]:hover { transform: translateY(-1px); }
}
[data-fx="spec"]:active { transform: scale(.975); }

/* ============ TIER 3 — poster lift ==========================================
   Restrained on purpose: a lift and a warmer shadow, no 3D tilt. Tilt across a
   poster grid reads as a template effect and fights the artwork. */
[data-fx="lift"] {
  transition: transform .22s var(--fx-ease), box-shadow .22s var(--fx-ease);
}
@media (hover: hover) and (pointer: fine) {
  [data-fx="lift"]:hover {
    transform: translateY(-4px);
    box-shadow: 0 16px 34px var(--fx-shadow);
  }
}

/* ============ touch feedback ================================================
   Pointer-following light is meaningless without a pointer, so touch gets a
   ring from the real contact point instead of a dead effect. */
.fx-ripple {
  position: absolute;
  border-radius: 50%;
  pointer-events: none;
  z-index: 4;
  transform: translate(-50%, -50%) scale(0);
  background: radial-gradient(circle, var(--fx-rim-soft) 0%, transparent 68%);
  animation: fx-ripple .52s var(--fx-ease) forwards;
}
@keyframes fx-ripple {
  to { transform: translate(-50%, -50%) scale(1); opacity: 0; }
}

@media (hover: none) {
  /* No proximity signal on touch — pin a faint rim so the CTA still reads as
     the primary action rather than going flat. */
  [data-fx="spec"]::after { opacity: .34; }
}

@media (prefers-reduced-motion: reduce) {
  [data-fx="spec"], [data-fx="spec"]::after,
  [data-fx="glass"], [data-fx="lift"] { transition: none; }
  [data-fx="spec"]:hover, [data-fx="lift"]:hover { transform: none; }
  .fx-ripple { display: none; }
}

/* ============ real desktop layouts ==========================================
   The sections already carry data-screen-label, so no template edit is needed.
   Each view is treated on its own terms rather than with one blind grid. */

/* --- Crew: named-area layout. The old grid pinned children by
   :nth-child(), which scrambled the moment a card was added (the Discord card
   proved it). Every layout-managed child now carries data-mq="..." (stamped by
   layouthooks.js / discord.js), and the areas place them by NAME. A future
   untagged child simply auto-flows full-width below the grid instead of
   breaking it. */
@media (min-width: 1100px) {
  section[data-screen-label="Groups"] {
    display: grid;
    grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr);
    column-gap: 20px;
    row-gap: 14px;
    /* Stretch pairs cards to equal height (start left ragged gaps). */
    align-items: stretch;
    grid-template-areas:
      "ghead ghead"
      "gname gname"
      "gavatars gavatars"
      "ghint ghint"
      "gadd gadd"
      "members counts"
      "watchlist chat"
      "insights invite"
      "insights discord"
      "insights delete";
  }
  section[data-screen-label="Groups"] > * { margin-bottom: 0 !important; }
  section[data-screen-label="Groups"] > [data-mq="ghead"]    { grid-area: ghead; }
  section[data-screen-label="Groups"] > [data-mq="gname"]    { grid-area: gname; }
  section[data-screen-label="Groups"] > [data-mq="gavatars"] { grid-area: gavatars; }
  section[data-screen-label="Groups"] > [data-mq="ghint"]    { grid-area: ghint; }
  /* invite-only crews render an alternative hint — same slot */
  section[data-screen-label="Groups"] > [data-mq="ghint2"]   { grid-area: ghint; }
  section[data-screen-label="Groups"] > [data-mq="gadd"]     { grid-area: gadd; }
  section[data-screen-label="Groups"] > [data-mq="members"]  { grid-area: members; }
  section[data-screen-label="Groups"] > [data-mq="counts"]   { grid-area: counts; max-width: none !important; }
  section[data-screen-label="Groups"] > [data-mq="watchlist"]{ grid-area: watchlist; }
  section[data-screen-label="Groups"] > [data-mq="chat"]     { grid-area: chat; }
  section[data-screen-label="Groups"] > [data-mq="insights"] { grid-area: insights; }
  section[data-screen-label="Groups"] > [data-mq="invite"]   { grid-area: invite; }
  section[data-screen-label="Groups"] > [data-mq="discord"]  { grid-area: discord; }
  /* Delete: right-aligned, and its button must not stretch to the row height. */
  section[data-screen-label="Groups"] > [data-mq="delete"]   { grid-area: delete; justify-self: stretch; text-align: right; align-items: center; }
}

/* --- Polls: ballot column beside the live result. lead spans two rows so the
   pass-the-phone chips slot under the ballot without pushing the rail. */
@media (min-width: 1100px) {
  section[data-screen-label="Polls"] {
    display: grid;
    grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
    column-gap: 20px;
    row-gap: 14px;
    align-items: start;
    grid-template-areas:
      "peyebrow peyebrow"
      "ptitle ptitle"
      "ballot lead"
      "chips lead"
      "instr controls"
      "cands share";
  }
  section[data-screen-label="Polls"] > * { margin-bottom: 0 !important; }
  section[data-screen-label="Polls"] > [data-mq="peyebrow"] { grid-area: peyebrow; }
  section[data-screen-label="Polls"] > [data-mq="ptitle"]   { grid-area: ptitle; }
  section[data-screen-label="Polls"] > [data-mq="ballot"]   { grid-area: ballot; }
  section[data-screen-label="Polls"] > [data-mq="chips"]    { grid-area: chips; }
  section[data-screen-label="Polls"] > [data-mq="lead"]     { grid-area: lead; }
  section[data-screen-label="Polls"] > [data-mq="instr"]    { grid-area: instr; }
  section[data-screen-label="Polls"] > [data-mq="controls"] { grid-area: controls; }
  section[data-screen-label="Polls"] > [data-mq="cands"]    { grid-area: cands; }
  section[data-screen-label="Polls"] > [data-mq="share"]    { grid-area: share; }
}

/* --- For You: left as a single column on purpose. Its first child is a 17px
   eyebrow label, so column flow puts a caption where a card belongs. Instead
   the recommendation grid inside it gains a third track, which is what the
   extra width was actually for. */
@media (min-width: 1100px) {
  section[data-screen-label="For You"] div[style*="grid-template-columns"] {
    grid-template-columns: repeat(3, minmax(0, 1fr)) !important;
  }
}

/* Empty states are one card in all three views; never stretch them. */
@media (min-width: 1100px) {
  section[data-screen-label="Groups"] > :only-child,
  section[data-screen-label="Polls"] > :only-child,
  section[data-screen-label="For You"] > :only-child {
    grid-column: 1 / -1;
    max-width: 560px;
    margin-inline: auto;
  }
}

/* The sound toggle measured 38x38 — under the 44px minimum, and it is the one
   header control a thumb actually reaches for. Sizing the element directly
   rather than laying a ::after target over it: that pseudo-element is already
   taken by the specular rim. Matches both the Mute and Unmute labels. */
button[aria-label$="interface sounds"] {
  /* The main stylesheet sets height and display on this button at a higher
     specificity but leaves width alone, so an unflagged rule here came out
     44x38. Force the height only, and keep their display:grid — that is what
     centres the icon. */
  width: 44px !important;
  height: 44px !important;
  min-width: 44px;
  place-items: center;
}
