/* ==========================================================================
   B VISUAL — street-bites.css
   The Street Bites case study. Page-level only: type comes from tokens.css,
   chrome from shell.css, and the closing sections reuse .pj-* from
   project.css and .cta-band from pages.css unchanged.

   ON THE RAW COLOURS BELOW
   tokens.css is the only file on this site that holds a colour value, and
   that rule is worth keeping. These five are the exception it was always
   going to need: they are the CLIENT's palette, sampled off the film Street
   Bites delivered, and they exist to make one case study look like the work
   it is showing rather than like B Visual's own plum and lime. They are
   scoped to this page, they never leak into a shared component, and they do
   not belong in tokens.css — that file describes the studio's identity, not
   a client's. Add the next client's palette the same way, here, not there.
   ========================================================================== */

[data-page="street-bites"] {
  /* The ground is the film's own backdrop, measured off the delivered file
     rather than picked: the median of its border pixels across all 20
     seconds, neutral to the channel. Painting the page in exactly that colour
     is what lets the 16:9 video plane sit on it with no edge to see — so
     film.js hands the frame through ungraded. The two must move together
     (GROUND_SRGB in sb/stage.js). */
  --sb-char:   #090909;
  --sb-smoke:  #101010;
  --sb-orange: #f2873c;                    /* Street Bites' own orange */
  --sb-ember:  #d94b1a;
  --sb-cream:  #ede3d6;

  --sb-ash:    rgba(237, 227, 214, .68);
  --sb-hair:   rgba(237, 227, 214, .13);

  background: var(--sb-char);
}

/* The shared .label rule draws its rule in --purple, which is B Visual's
   brand and reads as a stray violet dash on a page grounded in the client's
   orange. Re-pointed inside this page's own sections only — the nav and the
   footer are shared chrome and stay in the studio's colours, as they do on
   every other page. */
.sb-show .label,
.sb-body .label { color: var(--sb-orange); }
.sb-show .label::before,
.sb-body .label::before { background: var(--sb-ember); }

.sb-body .pj-next__link { color: var(--sb-cream); }
.sb-body .pj-next__link:hover { color: var(--sb-orange); }

/* ==========================================================================
   1. The stage
   ========================================================================== */

.sb-show {
  position: relative;
  background: var(--sb-char);
  isolation: isolate;
}

.sb-stage {
  position: relative;
  min-height: 100svh;
  overflow: clip;
  background: var(--sb-char);
}

.sb-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  z-index: 1;
  /* The canvas is scenery. Every focusable thing on this page is DOM above
     it, so it must never intercept a click meant for a panel link. */
  pointer-events: none;
}

/* Vignette and grain. Here rather than in a post-processing pass: an
   EffectComposer for one full-screen gradient costs a second render target
   and an addon import, and neither survives contact with the brief's
   "nothing that reduces screen readability".

   Vignette only — no coloured wash. The stage is black and the film is
   graded on black, so anything laid over the two of them shows up as a
   halo around the film rather than as atmosphere. */
.sb-veil {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background:
    radial-gradient(122% 92% at 50% 50%, rgba(9, 9, 9, 0) 46%, rgba(0, 0, 0, .72) 100%);
}
.sb-veil::after {
  content: "";
  position: absolute;
  inset: 0;
  opacity: .16;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='.5'/%3E%3C/svg%3E");
}

/* ==========================================================================
   2. Hero — the first thing in the pinned stage, not a separate section
   ========================================================================== */

.sb-hero {
  position: relative;
  z-index: 3;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: calc(var(--nav-h) + var(--s10)) calc(var(--gutter) + var(--safe-r))
           var(--s10) calc(var(--gutter) + var(--safe-l));
  will-change: opacity, transform;
}
.sb-hero__client { color: var(--sb-orange); }
.sb-hero__title {
  color: var(--sb-cream);
  margin-top: var(--s4);
  max-width: 14ch;
}
.sb-hero__title em {
  font-style: normal;
  color: var(--sb-orange);
}
.sb-hero__kicker {
  color: var(--sb-ash);
  margin-top: var(--s6);
  max-width: 44ch;
}
.sb-hero__cue {
  margin-top: var(--s10);
  color: var(--sb-ash);
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
}
.sb-hero__cue i {
  display: block;
  width: 1px;
  height: 34px;
  background: linear-gradient(var(--sb-orange), rgba(242, 135, 60, 0));
}

/* ==========================================================================
   3. Story panels
   ========================================================================== */

.sb-panels {
  position: absolute;
  inset: 0;
  z-index: 3;
  padding: calc(var(--nav-h) + var(--s6)) calc(var(--gutter) + var(--safe-r))
           var(--s8) calc(var(--gutter) + var(--safe-l));
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr) minmax(0, 1fr);
  align-items: center;
  pointer-events: none;
}

.sb-panel {
  grid-row: 1;
  max-width: 30ch;
  will-change: opacity, transform, filter;
}
.sb-panel[data-side="left"]  { grid-column: 1; }
.sb-panel[data-side="right"] { grid-column: 3; justify-self: end; }

.sb-panel__scene {
  color: var(--sb-orange);
  display: flex;
  align-items: flex-start;
  gap: var(--s2);
}
/* Aligned to the first line's optical centre rather than the block's, so a
   label that wraps to two lines does not drag the rule down with it. */
.sb-panel__scene::before {
  content: "";
  flex: 0 0 auto;
  width: 26px;
  height: 1px;
  margin-top: .45em;
  background: var(--sb-ember);
}
.sb-panel__title {
  color: var(--sb-cream);
  margin-top: var(--s3);
  /* Stepped down one token — NOT a new size. Over the film these are captions
     beside a device, not page headings: at --t-head a 44-character scene
     title wraps to six lines in a third of the viewport and buries the film
     it is describing. The flat article puts them back up, where they are
     section headings and have the width for it. */
  font-size: var(--t-sub);
  line-height: var(--lh-sub);
  letter-spacing: var(--tr-sub);
  font-weight: var(--w-max);
}
html:not([data-sb="live"]) .sb-panel__title {
  font-size: var(--t-head);
  line-height: var(--lh-head);
  letter-spacing: var(--tr-head);
}
.sb-panel__body {
  color: var(--sb-ash);
  margin-top: var(--s3);
}
.sb-nowrap { white-space: nowrap; }
.sb-panel__actions { margin-top: var(--s6); display: flex; flex-wrap: wrap; gap: var(--s3); align-items: center; }

/* The one pill on this page, in the client's orange rather than the
   studio's lime — the same component, re-skinned in scope. */
.sb-panel .btn--primary,
.sb-cta .btn--primary {
  background: var(--sb-orange);
  color: #17140f;
}
.sb-panel .btn--primary:hover,
.sb-cta .btn--primary:hover { background: var(--sb-cream); }
.sb-panel .btn--ghost,
.sb-cta .btn--ghost { border-color: var(--sb-hair); color: var(--sb-cream); }
.sb-panel .btn--ghost:hover,
.sb-cta .btn--ghost:hover { border-color: var(--sb-orange); }

/* The still that carries each scene when there is no canvas. Live mode never
   shows it: the device on screen IS the picture. */
.sb-panel__still { display: none; }

/* ==========================================================================
   4. Live vs flat

   `data-sb="live"` is written on <html> by the pre-paint script on the page
   itself, so the panels are hidden before the first frame instead of
   flashing as a stack. Anything that stops that script — no WebGL, reduced
   motion, no JavaScript at all — leaves the flat article in place.
   ========================================================================== */

[data-sb="live"] .sb-panel { opacity: 0; }

/* --- Flat ---------------------------------------------------------------- */
html:not([data-sb="live"]) .sb-stage { min-height: 0; overflow: visible; }
html:not([data-sb="live"]) .sb-canvas,
html:not([data-sb="live"]) .sb-veil { display: none; }
html:not([data-sb="live"]) .sb-hero { min-height: min(88svh, 54rem); }

html:not([data-sb="live"]) .sb-panels {
  position: static;
  display: block;
  padding: 0 0 var(--section);
}
html:not([data-sb="live"]) .sb-panel {
  max-width: none;
  margin-top: var(--section);
  padding-top: var(--s10);
  border-top: 1px solid var(--sb-hair);
  display: grid;
  gap: var(--s8);
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
  align-items: center;
  padding-inline: calc(var(--gutter) + var(--safe-l)) calc(var(--gutter) + var(--safe-r));
}
html:not([data-sb="live"]) .sb-panel__copy { max-width: 46ch; }
html:not([data-sb="live"]) .sb-panel__still {
  display: block;
  width: 100%;
  height: auto;
  background: var(--sb-smoke);
}
html:not([data-sb="live"]) .sb-panel:nth-child(even) .sb-panel__copy { order: 2; }

/* The film itself, offered as a film. Loaded only when asked for. */
.sb-film {
  margin: var(--section) calc(var(--gutter) + var(--safe-r)) 0 calc(var(--gutter) + var(--safe-l));
}
.sb-film video {
  width: 100%;
  height: auto;
  display: block;
  background: var(--sb-smoke);
}
.sb-film figcaption { color: var(--sb-ash); margin-top: var(--s3); }

/* Live mode still needs that <video> — it is the texture source — but it must
   not be seen or take space.

   Deliberately NOT display:none. A display:none video is free to stop
   producing frames, and a texture upload from one is undefined across
   engines; a 1px clipped element is still laid out, still decoding, and
   still a legal source for texImage2D. index.js aria-hides the figure so it
   is not announced either. */
[data-sb="live"] .sb-film {
  position: absolute;
  left: -9999px;
  width: 2px;
  height: 2px;
  margin: 0;
  overflow: hidden;
  pointer-events: none;
}

/* ==========================================================================
   5. Loading
   ========================================================================== */

/* Bottom RIGHT: the hero's own scroll cue sits bottom-left, and the two
   would land on each other for exactly as long as the film takes to buffer. */
.sb-loading {
  position: absolute;
  z-index: 4;
  right: calc(var(--gutter) + var(--safe-r));
  bottom: var(--s6);
  color: var(--sb-ash);
  display: none;
  align-items: center;
  gap: var(--s2);
}
[data-sb-loading="true"] .sb-loading { display: inline-flex; }
.sb-loading i {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--sb-orange);
  animation: sb-pulse 1.6s var(--e-out) infinite;
}
@keyframes sb-pulse { 0%, 100% { opacity: .25; } 50% { opacity: 1; } }

/* ==========================================================================
   6. Closing sections
   ========================================================================== */

.sb-body { background: var(--sb-char); position: relative; z-index: 1; }
.sb-body .pj-sec { border-top-color: var(--sb-hair); }
.sb-body .pj-sec__title,
.sb-body .sb-figure figcaption { color: var(--sb-cream); }
.sb-body .pj-body { color: var(--sb-ash); }

.sb-specs {
  margin-top: var(--s10);
  display: grid;
  gap: var(--s6);
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
}
.sb-specs dt { color: var(--sb-orange); }
.sb-specs dd { color: var(--sb-cream); margin-top: var(--s2); }

/* --- Social: the growth figures ------------------------------------------
   Reuses the .pj-stat idea from project.css rather than extending it: that
   component is a single hero number with a rule above it, and this is a row
   of four that has to wrap. Numbers take the client's orange, not the
   studio's lime, for the same reason everything else on this page does. */
.sb-stats__head { margin-top: var(--section); }
.sb-stats {
  margin-top: var(--s6);
  border-top: 1px solid var(--sb-hair);
  display: grid;
  gap: var(--s8) var(--s6);
  /* Explicit counts rather than auto-fit. auto-fit packs as many as will fit,
     which at around 800px gives three across and one stranded underneath —
     four figures presented as 3 + 1 reads as one of them being an
     afterthought. Stepping 4 → 2 → 1 keeps the row balanced at every width. */
  grid-template-columns: repeat(4, minmax(0, 1fr));
}
@media (max-width: 1100px) { .sb-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 560px)  { .sb-stats { grid-template-columns: minmax(0, 1fr); } }
.sb-stat { padding-top: var(--s6); }
.sb-stat__num {
  color: var(--sb-orange);
  font-variant-numeric: tabular-nums;
}
.sb-stat__label { color: var(--sb-cream); margin-top: var(--s3); }
.sb-stat__note  {
  color: var(--sb-ash);
  margin-top: var(--s2);
  /* These are sentences, not labels. --t-micro carries the uppercase and the
     .14em tracking that make a two-word label read as a label; at three lines
     of prose it reads as shouting and is measurably slower to scan. Size
     stays on the token; only the label treatment comes off. */
  text-transform: none;
  letter-spacing: 0;
  line-height: 1.55;
  max-width: 26ch;
}

/* A [PLACEHOLDER] sitting in display type inherits --t-head, and at that size
   the lime block is the loudest thing on the page — which is the point, but it
   should not also be so wide it breaks the grid. */
.sb-stat__num .is-placeholder {
  font-size: var(--t-sub);
  letter-spacing: var(--tr-sub);
  vertical-align: middle;
}

.sb-stats__foot {
  margin-top: var(--s8);
  color: var(--sb-ash);
  max-width: var(--measure);
  text-transform: none;
  letter-spacing: 0;
  line-height: 1.6;
}

.sb-shots {
  margin-top: var(--s10);
  display: grid;
  gap: var(--s6);
  grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr);
  align-items: start;
}
.sb-figure img {
  width: 100%;
  height: auto;
  display: block;
  background: var(--sb-smoke);
  border: 1px solid var(--sb-hair);
}
.sb-figure figcaption { color: var(--sb-ash); margin-top: var(--s3); }

.sb-cta { background: var(--sb-smoke); }
.sb-cta .cta-band__title { color: var(--sb-cream); }
.sb-cta .cta-band__sub   { color: var(--sb-ash); }

/* ==========================================================================
   7. Responsive

   Tablet keeps the three-column stage but narrows the panels. Portrait moves
   the copy under the devices entirely — index.js lifts the 3D composition by
   LAYOUT.portrait.lift to make exactly this room, so the two must change
   together.
   ========================================================================== */

/* Below this the scene panels still take a side — the film slides away from
   them and that keeps working — but the CLOSING panel cannot: the laptop and
   phone are real geometry sharing the frame with it. It drops to the bottom,
   and LAYOUT.tablet.finale lifts the devices above it. */
@media (max-width: 1100px) {
  .sb-panels { grid-template-columns: minmax(0, 1fr) minmax(0, .55fr) minmax(0, 1fr); }
  .sb-panel { max-width: 30ch; }
  .sb-panel--final,
  .sb-panel--final[data-side="left"],
  .sb-panel--final[data-side="right"] {
    grid-column: 1 / -1;
    justify-self: start;
    align-self: end;
    max-width: 40ch;
  }
}

@media (max-width: 760px), (orientation: portrait) and (max-width: 900px) {
  .sb-panels {
    grid-template-columns: minmax(0, 1fr);
    align-items: end;
    padding-bottom: var(--s10);
  }
  .sb-panel,
  .sb-panel[data-side="left"],
  .sb-panel[data-side="right"] {
    grid-column: 1;
    justify-self: start;
    max-width: 32ch;
  }
  .sb-hero__title { max-width: none; }
  .sb-shots { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 760px) {
  html:not([data-sb="live"]) .sb-panel { grid-template-columns: minmax(0, 1fr); gap: var(--s6); }
  html:not([data-sb="live"]) .sb-panel:nth-child(even) .sb-panel__copy { order: 0; }
}

/* Reduced motion never reaches live mode, but the flat article still has a
   pulsing dot and CSS transitions of its own to answer for. */
@media (prefers-reduced-motion: reduce) {
  .sb-loading i { animation: none; opacity: .7; }
  .sb-panel { transition: none !important; }
}
