/*
  Open in place: the panel an item opens into, its soft rise, the open card,
  and a phone's full-screen prototype. "Open in place" in
  specs/redesign.md, with stream.js.

  Values come from Paper, file "Portfolio v3", page "current · redesign":
  row 01 for the rise and rows 03 to 08 for the panels.
  "Quiet xenops", Page 2, "B · Panel from a 2×2 case study", places a panel
  under a case-study block. Mono labels use the site's .micro, 11px, as Paper
  draws them.

  Every class starts with stream- so the panel's names stay apart from the
  page styles in style.css and pages.css.
*/

/* ---- The panel ---------------------------------------------------------- */

.stream-panel {
  /* Row bottom to the top of the rise's box, and panel bottom to the next row. */
  --sp-lead: 20px;
  --sp-trail: 48px;
  --sp-gutter: var(--gutter, 40px);
  --sp-gap: 22px;
  --sp-serif: "EB Garamond", "EB Garamond Fallback", Georgia, serif;
  --sp-text: var(--ink);
  --sp-muted: var(--ink-soft);
  /* A control that can't be pressed, such as a strip's arrow at its end. */
  --sp-disabled: color-mix(in oklch, var(--sp-text) 40%, transparent);
  --sp-rule: color-mix(in oklch, var(--ink) 18%, transparent);
  --sp-image-outline: var(--frame-edge);
  --sp-frame: light-dark(oklch(0.988 0.003 106.4), oklch(0.226 0.002 106.6));
  --sp-mount: light-dark(var(--frame-empty), oklch(0.284 0.004 106.6));

  /* The home grid supplies its full-bleed insets; stream.js measures them
     for nested grids, where a panel starts inside the proof column. */
  grid-column: 1 / -1;
  flex: 0 0 auto;
  box-sizing: border-box;
  width: calc(100% + var(--stream-bleed-start, 0px) + var(--stream-bleed-end, 0px));
  max-width: none;
  margin-inline: calc(-1 * var(--stream-bleed-start, 0px)) 0;
  margin-block:
    calc(var(--sp-lead) - var(--stream-row-gap, 0px))
    calc(var(--sp-trail) - var(--stream-row-gap, 0px));
  color: var(--sp-text);
  font-family: var(--body);
  font-weight: 500;
  text-align: left;
}

/* In the placed grid, from 1024px, the panel takes a row of its own: the
   opener's last row plus one, which stream.js sets as --panel-row, so a panel
   from any card in a block opens under the block's second row. stream.js also
   sets --shift: 1 on every card below it (stream.css). Without --panel-row it
   falls back to auto placement, as in the fixture grid, and the list ignores
   placement altogether. */
@media (width >= 1024px) {
  .stream-grid > .stream-panel { grid-row: var(--panel-row); }
}

.stream-panel[data-tone="band"] {
  --sp-lead: 24px;
  --sp-gap: 24px;
  /* The project's paper, which stays light on its band in both schemes. */
  --sp-text: var(--case-paper);
  --sp-muted: color-mix(in oklch, var(--case-paper) 72%, transparent);
  --sp-rule: color-mix(in oklch, var(--case-paper) 28%, transparent);
  --sp-image-outline: var(--image-outline-on-ink);
}

.stream-panel.is-moving {
  overflow: clip;
}

:where(.stream-panel) *,
:where(.stream-panel) *::before,
:where(.stream-panel) *::after {
  box-sizing: border-box;
}

.stream-panel :where(h2, h3, h4, h5, p, dl, dd, ul, figure) {
  margin: 0;
}

.stream-panel :where(ul) {
  padding: 0;
  list-style: none;
}

/* Zero specificity, so .micro and the component classes win. */
:where(.stream-panel) button {
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  cursor: pointer;
}

.stream-panel :focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 3px;
}

/* Focus lands on the heading when the panel opens. It is not a control, so
   it draws no ring, where the browser's own would otherwise show after a
   key press. */
.stream-panel__title:focus {
  outline: none;
}

.stream-panel img {
  display: block;
  max-width: none;
}

/* ---- The soft rise ------------------------------------------------------ */

/* stream.js draws the path at the element's own width: a diagonal with an
   80px rise under the card that opened the panel. The body overlaps its
   last pixel row, so the band and its edge meet without a seam. */
.stream-panel__rise {
  display: block;
  width: 100%;
  height: 56px;
  margin-bottom: -1px;
  overflow: hidden;
}

.stream-panel__rise path {
  fill: none;
  stroke: var(--ink);
  stroke-width: 1.5;
}

.stream-panel[data-tone="band"] .stream-panel__rise path {
  fill: var(--case-dark);
  stroke: none;
}

/* On a dark page a band's dark ground barely parts from the paper, so its
   edges take a light rule (Jason, 2026-10-08). The rise is a filled shape,
   so a 1.5px shadow cast upward traces only its top diagonal. */
@media (prefers-color-scheme: dark) {
  .stream-panel[data-tone="band"] .stream-panel__rise {
    filter: drop-shadow(0 -1.5px 0 var(--sp-rule));
  }

  .stream-panel[data-tone="band"] .stream-panel__body {
    border-bottom: 1.5px solid var(--sp-rule);
  }
}

/* ---- The body: one column, in reading order ------------------------------ */

.stream-panel__body {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: 32px var(--sp-gutter) 40px;
}

.stream-panel[data-tone="neutral"] .stream-panel__body {
  border-bottom: 1.5px solid var(--ink);
}

.stream-panel[data-tone="band"] .stream-panel__body {
  padding-top: 28px;
  padding-bottom: 48px;
  background: var(--case-dark);
}

.stream-panel__body > * {
  min-width: 0;
  margin-bottom: var(--sp-gap);
}

.stream-panel__body > :last-child {
  margin-bottom: 0;
}

/* Close shows first, but stream.js writes it after the heading, where focus
   lands on open, so Tab reaches Close before the media. The body is a flex
   column, so order moves it; the wide grid places it by area. */
.stream-panel__head {
  order: -1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  min-height: 44px;
  margin-bottom: 12px;
}

.stream-panel__close {
  position: relative;
  color: var(--sp-muted);
  white-space: nowrap;
}

/* A 44px target around a small label, without changing how it looks. */
.stream-panel__close::before {
  position: absolute;
  inset: 50% auto auto 50%;
  width: max(100%, 44px);
  height: 44px;
  content: "";
  transform: translate(-50%, -50%);
}

.stream-panel__close--head {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  justify-content: flex-end;
  min-width: 72px;
  min-height: 44px;
  color: var(--sp-text);
}

.stream-panel__close--head::before {
  display: none;
}

/* Close answers the pointer by size, not colour: its × grows a quarter while
   hovered and gives a little when pressed (Jason, 2026-10-11, who ruled out
   red). The × alone moves, so the button's box and target stay still. */
.stream-panel__close--head .icon {
  transition: scale 150ms var(--ease-out);
}

.stream-panel__close--head:active .icon { scale: 0.96; }

@media (hover: hover) and (pointer: fine) {
  .stream-panel__close--head:hover .icon { scale: 1.25; }
  .stream-panel__close--head:hover:active .icon { scale: 1.15; }
}

@media (prefers-reduced-motion: reduce) {
  .stream-panel__close--head .icon { transition: none; }
}

/* ---- Story type ----------------------------------------------------------- */

.stream-panel__title {
  color: var(--sp-text);
  font: 400 32px / 36px var(--display);
  letter-spacing: -0.015em;
  text-wrap: balance;
}

/* A case study's head, "Beacon FC case study", the byline "Design lead •
   Blueshift 2025" and the premise as the headline. The byline and headline
   are visually hidden since 2026-10-04 (Jason, option A), as the band above
   shows them; the name shows since 2026-10-08, so a case study reached by
   arrowing still says what it is. These rules still lay out the rest for
   any context that shows it. */
.stream-panel__case-head {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.stream-panel__case-name {
  color: var(--sp-text);
  font: 400 32px / 36px var(--display);
  letter-spacing: -0.015em;
  text-wrap: balance;
}

.stream-panel__title--headline {
  font-size: 34px;
  line-height: 38px;
  letter-spacing: -0.02em;
}

.stream-panel__byline {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.stream-panel__byline .micro { color: var(--sp-muted); }

.stream-panel__lede {
  font: 400 21px / 28px var(--sp-serif);
  text-wrap: pretty;
}

.stream-panel__dek {
  color: var(--sp-muted);
  font: 400 22px / 29px var(--sp-serif);
  text-wrap: pretty;
}

.stream-panel__intro {
  color: color-mix(in oklch, var(--sp-text) 86%, transparent);
  font: 400 18px / 26px var(--sp-serif);
  text-wrap: pretty;
}

/* A case study's note leads its story column (Jason, 2026-10-04, option A),
   in the text's full colour, at the lede's size on phones and larger beside
   the strip. */
.stream-panel__intro--lead {
  color: var(--sp-text);
  font-size: 21px;
  line-height: 28px;
}

@media (min-width: 1000px) {
  .stream-panel__intro--lead {
    font-size: 27px;
    line-height: 34px;
  }
}

/* A disguised case study's line on what it stands in for, a step below the
   note it follows. */
.stream-panel__disclosure {
  margin-top: 16px;
  font-size: 16px;
  line-height: 23px;
  color: color-mix(in oklch, var(--sp-text) 72%, transparent);
}

.stream-panel__facts > div {
  display: flex;
  gap: 16px;
  padding: 10px 0;
  border-top: 1px solid var(--sp-rule);
  border-image: linear-gradient(var(--sp-rule) var(--hairline), transparent 0) 1;
}

.stream-panel__facts > div:last-child {
  border-bottom: 1px solid var(--sp-rule);
  border-image: linear-gradient(var(--sp-rule) var(--hairline), transparent 0 calc(100% - var(--hairline)), var(--sp-rule) 0) 1;
}

.stream-panel__facts dt {
  flex: 0 0 96px;
  color: var(--sp-muted);
}

.stream-panel[data-tone="neutral"] .stream-panel__facts dt {
  flex-basis: 112px;
}

.stream-panel[data-tone="band"] .stream-panel__facts {
  padding-top: 8px;
}

.stream-panel__jump {
  color: inherit;
  text-decoration: none;
}

/* A mono line is 13px tall, so the link's hit area grows to 44px, over the
   whole value, without moving the fact rows or the focus ring. It hangs off
   the value rather than the link, since a link that wraps would give it the
   width of its last line only. */
.stream-panel__facts dd { position: relative; }
.stream-panel__jump::before {
  content: "";
  position: absolute;
  inset: -15.5px 0;
}

.stream-panel__credit {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
}

/* A prototype's "Try the prototype ↓" in the strip's caption is a plain
   inline link, without the fact rows' tall hit area. */
.stream-strip__text .stream-panel__jump {
  white-space: nowrap;
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
}
.stream-strip__text .stream-panel__jump::before { content: none; }

.stream-panel__cta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  min-height: 44px;
  /* 2px less on the arrow's side, which reads as more space than text. */
  padding: 14px 14px 14px 16px;
  border: 1px solid var(--ink);
  background: var(--ink);
  color: var(--paper);
  text-decoration: none;
  transition: background-color 120ms var(--ease-fade), color 120ms var(--ease-fade), scale 150ms var(--ease-out);
}

.stream-panel__cta:active { scale: 0.96; }

.stream-panel__cta-arrow {
  color: var(--accent);
}

.stream-panel[data-tone="band"] .stream-panel__cta {
  border-color: var(--case-paper);
  background: var(--case-paper);
  color: var(--case-dark);
}

.stream-panel[data-tone="band"] .stream-panel__cta-arrow {
  color: inherit;
}

@media (hover: hover) and (pointer: fine) {
  .stream-panel__close:hover {
    color: var(--sp-text);
  }

  .stream-panel__jump:hover {
    text-decoration: underline;
    text-underline-offset: 3px;
  }

  .stream-panel__cta:hover {
    background: transparent;
    color: var(--ink);
  }

  .stream-panel[data-tone="band"] .stream-panel__cta:hover {
    background: transparent;
    color: var(--case-paper);
  }
}

/* ---- The case-study strip ------------------------------------------------ */

.stream-strip {
  container-type: inline-size;
}

.stream-strip__track {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 24px;
  /* It runs past the column to the band's right edge, as on the board. */
  margin-right: calc(-1 * var(--sp-gutter));
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
}

.stream-strip__track::-webkit-scrollbar {
  display: none;
}

/* stream.js sizes this so the last frame can reach the left edge too. It
   stretches to the track's height because Firefox leaves an empty box with
   no height out of the scrollable width. */
.stream-strip__track::after {
  flex: 0 0 var(--stream-strip-end, 0px);
  align-self: stretch;
  content: "";
}

.stream-strip__frame {
  position: relative;
  flex: none;
  height: min(427px, 46.8cqi);
  aspect-ratio: 1440 / 1024;
  overflow: hidden;
  background: var(--sp-frame);
  scroll-snap-align: start;
}

/* The image's edge, on a layer above a clip's positioned player. */
.stream-strip__frame::after {
  content: "";
  position: absolute;
  z-index: 1;
  inset: 0;
  outline: 1px solid var(--sp-image-outline);
  outline-offset: -1px;
  pointer-events: none;
}

.stream-strip__frame[data-shape="phone"] {
  aspect-ratio: 390 / 844;
}

/* A clip is never wider than the strip's column, so its Enlarge button, at
   the lower right, stays on screen; stills still run past the edge to show
   that the strip swipes (Jason, 2026-10-16). 100cqi is the strip's width,
   which leaves out the bleed. */
.stream-strip__frame[data-clip] {
  max-height: calc(100cqi * var(--clip-h) / var(--clip-w));
}

/* A frame beside the current one is a way forward: clicking it brings it to
   the front (stream.js), whose contents stay inert until it's current. */
.stream-strip__frame:not([data-current]) {
  cursor: pointer;
}

.stream-strip__frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center top;
}

.stream-strip__frame .stream-player {
  height: 100%;
  aspect-ratio: auto;
}

.stream-strip__empty,
.stream-shot__empty,
.stream-proto__empty,
.stream-player__empty {
  display: block;
  width: 100%;
  height: 100%;
  min-height: 120px;
  background:
    repeating-linear-gradient(135deg, transparent 0 10px, color-mix(in oklch, var(--ink) 12%, transparent) 10px 11px),
    var(--sp-mount);
}

/* Under the track, the lightbox's parts in two lines at every width (Jason,
   2026-10-12; from 2026-10-02 the count and buttons sat at the caption's
   right end on wide screens): the caption in the body face at 15px across
   the whole row, then the "n / total" count and the lightbox's square
   chevron buttons on a line under it, at the left under the current frame.
   The caption reserves three lines; stream.js raises that floor for longer
   captions so the controls do not move as the visitor steps through. */
.stream-strip__bar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 10px 8px;
  margin-top: 14px;
  /* Match the caption cap so controls always wrap onto their own row. */
  max-width: 530px;
}

.stream-strip__text {
  flex: 1 1 100%;
  min-width: 0;
  /* The Measure Rule's 530px: the count and buttons beside it held it to
     that until they moved under it. */
  max-width: 530px;
  min-height: 3lh;
  font-size: 15px;
  line-height: 1.4;
  text-wrap: pretty;
}

.stream-strip__text a {
  color: inherit;
  text-underline-offset: 3px;
}

.stream-strip__count {
  flex: none;
  /* Level with the buttons' middle, as in the lightbox's centred bar. */
  line-height: 44px;
  margin-right: 8px;
  color: var(--sp-muted);
  white-space: nowrap;
}

.stream-strip__button {
  display: grid;
  flex: none;
  place-items: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 1px solid color-mix(in oklch, var(--sp-text) 35%, transparent);
  background: transparent;
  color: var(--sp-text);
  cursor: pointer;
  transition: background-color 120ms var(--ease-fade), scale 150ms var(--ease-out);
}

.stream-strip__button:not([aria-disabled="true"]):active { scale: 0.96; }

.stream-strip__button[hidden] { display: none; }

.stream-strip__button svg {
  width: 20px;
  height: 20px;
}

.stream-strip__button[aria-disabled="true"] {
  border-color: color-mix(in oklch, var(--sp-text) 14%, transparent);
  color: var(--sp-disabled);
  cursor: default;
}

.stream-strip__button:focus-visible {
  outline: 2px solid var(--sp-text);
  outline-offset: 2px;
}

@media (hover: hover) and (pointer: fine) {
  .stream-strip__button:not([aria-disabled="true"]):hover {
    background: color-mix(in oklch, var(--sp-text) 12%, transparent);
  }
}

@media (prefers-reduced-motion: reduce) {
  .stream-strip__button, .stream-panel__cta { transition: none; }
}

/* ---- A single screen ------------------------------------------------------ */

.stream-shot {
  overflow: hidden;
  outline: 1px solid var(--sp-image-outline);
  outline-offset: -1px;
  background: var(--sp-frame);
}

.stream-shot img {
  width: 100%;
  height: auto;
}

/* A landscape or near-landscape shot shows whole. Only a capture taller
   than 1.4 times its width, which stream.js marks data-tall, stops at 650px
   and scrolls, since fitting it whole would shrink its text past reading
   (Jason, 2026-10-02). A phone capture shows whole at 300 by 650, and a
   longer one scrolls. */
.stream-shot[data-tall] {
  max-height: 650px;
  overflow-y: auto;
  scrollbar-width: thin;
}

.stream-shot[data-shape="phone"] {
  width: 300px;
}

/* ---- The project's other phones -------------------------------------------- */
/* Stills of the project's other phones, beside the current one at its size
   (stream.js sets their width and height). They keep their colours, as the
   home page's pictures do, and a clip's carries the card's play badge,
   which goes solid on hover. */

.stream-sibling {
  position: relative;
  display: block;
  flex: none;
  overflow: hidden;
  background: var(--fixed-ink);
}

.stream-sibling[hidden] {
  display: none;
}

.stream-sibling img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top;
}

/* Beside a screen, a frame like the screen's. The row keeps the kind it
   opened with, so a sibling played in place restyles nothing around it. */
.stream-panel__media[data-siblings="screen"] > .stream-sibling {
  border: 1px solid transparent;
  background:
    linear-gradient(var(--sp-rule), var(--sp-rule)) top / 100% var(--hairline) no-repeat border-box,
    linear-gradient(var(--sp-rule), var(--sp-rule)) bottom / 100% var(--hairline) no-repeat border-box,
    linear-gradient(var(--sp-rule), var(--sp-rule)) left / var(--hairline) 100% no-repeat border-box,
    linear-gradient(var(--sp-rule), var(--sp-rule)) right / var(--hairline) 100% no-repeat border-box,
    var(--sp-frame);
}

/* A screen played in a sibling's slot takes the slot's size and shows
   whole, scrolling when it is taller (Jason, 2026-10-03). */
.stream-panel__media[data-siblings] > .stream-shot {
  overflow-y: auto;
  scrollbar-width: thin;
}

.stream-sibling__time {
  position: absolute;
  bottom: 8px;
  left: 8px;
  display: flex;
  align-items: center;
  gap: 6px;
  /* 1px less on the leading icon's side. */
  padding: 3px 6px 3px 5px;
  background: var(--frame-chip, color-mix(in srgb, var(--fixed-ink) 75%, transparent));
  color: var(--fixed-paper);
}

/* The play mark is paper at rest and Archive Red while its still is hovered
   or focused, as on the home page's badges (stream.css). */
.stream-sibling__time::before {
  content: "";
  width: 0.9em;
  height: 0.9em;
  margin-left: -1px;
  background: var(--fixed-paper);
  -webkit-mask: var(--icon-play) center / contain no-repeat;
  mask: var(--icon-play) center / contain no-repeat;
  transition: background-color 120ms var(--ease-fade);
}

@media (hover: hover) and (pointer: fine) {
  .stream-sibling:hover .stream-sibling__time {
    background-color: var(--fixed-ink);
  }

  .stream-sibling:hover .stream-sibling__time::before {
    background-color: var(--site-accent);
  }
}

.stream-sibling:focus-visible .stream-sibling__time::before {
  background-color: var(--site-accent);
}

@media (prefers-reduced-motion: reduce) {
  .stream-sibling__time::before { transition: none; }
}

/* ---- Things to try --------------------------------------------------------- */

.stream-steps__title {
  padding-bottom: 12px;
  color: var(--sp-muted);
  font-weight: 400;
}

.stream-steps__list li {
  padding: 12px 0;
  border-top: 1px solid var(--sp-rule);
  border-image: linear-gradient(var(--sp-rule) var(--hairline), transparent 0) 1;
  font: 500 15px / 21px var(--body);
}

.stream-steps__list li:last-child {
  border-bottom: 1px solid var(--sp-rule);
  border-image: linear-gradient(var(--sp-rule) var(--hairline), transparent 0 calc(100% - var(--hairline)), var(--sp-rule) 0) 1;
}

/* ---- Prototypes ------------------------------------------------------------ */

.stream-proto {
  position: relative;
}

.stream-proto__screen {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  outline: 1px solid var(--sp-image-outline);
  outline-offset: -1px;
  background: var(--sp-frame);
}

/* A snippet's screen takes its component's shape and shows it edge to edge
   (stream.js, hugComponent), and a specimen fills its screen to the edge,
   so neither draws a rule round it (Jason, 2026-10-02). */
.stream-proto__screen:is([data-hug], [data-specimen]) {
  border: 0;
}

.stream-proto__screen[data-hug] {
  aspect-ratio: var(--hug-ratio);
}

.stream-proto__screen img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: left top;
}

.stream-proto__frame {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
  background: var(--sp-frame);
}

/* A mobile build runs in the board's device frame. */
.stream-proto[data-device="phone"] {
  width: 330px;
  padding: 12px;
  border-radius: 48px;
  background: var(--fixed-ink);
}

.stream-proto[data-device="phone"] .stream-proto__screen {
  width: 306px;
  height: 666px;
  aspect-ratio: auto;
  border: 0;
  outline: 0;
  /* Concentric with the frame: its 48px radius less the 12px padding. */
  border-radius: 36px;
}

.stream-proto__badge {
  position: absolute;
  z-index: 1;
  top: -12px;
  left: 36px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  /* 2px less on the live dot's side. */
  padding: 6px 10px 6px 8px;
  background: var(--ink);
  color: var(--paper);
}

.stream-proto[data-device="web"] .stream-proto__badge {
  left: 16px;
}

.stream-proto__dot {
  display: inline-block;
  flex: none;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--site-accent);
}

/* The panel's Live badge draws its dot in the label's colour at rest, and
   Archive Red while the live stage under it is hovered or holds focus, as
   the home page's badges do (Jason, 2026-10-02). The full-screen bar's dot
   stays red: it marks a running prototype, not something to point at. */
.stream-proto__badge .stream-proto__dot {
  background: currentColor;
  transition: background-color 120ms var(--ease-fade);
}

@media (hover: hover) and (pointer: fine) {
  .stream-proto[data-live]:hover .stream-proto__dot {
    background-color: var(--site-accent);
  }
}

.stream-proto[data-live]:focus-within .stream-proto__dot {
  background-color: var(--site-accent);
}

@media (prefers-reduced-motion: reduce) {
  .stream-proto__badge .stream-proto__dot { transition: none; }
}

.stream-proto__note {
  display: flex;
  gap: 10px;
  margin-top: 16px;
  padding: 14px 0;
  border-block: 1px solid var(--sp-rule);
  border-image: linear-gradient(var(--sp-rule) var(--hairline), transparent 0 calc(100% - var(--hairline)), var(--sp-rule) 0) 1;
  line-height: 16px;
}

.stream-proto[data-device="phone"] .stream-proto__note {
  margin: 20px -12px -12px;
  padding: 14px 12px 14px 10px;
  border: 0;
  border-radius: 0 0 48px 48px;
  background: var(--fixed-ink);
  color: var(--fixed-paper);
}

/* ---- Tools ----------------------------------------------------------------- */

.stream-tool {
  position: relative;
  container-type: inline-size;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: var(--sp-frame);
}

/* The edge sits on a layer of its own, since a preview's player is
   positioned and would paint over the frame's own outline. */
.stream-tool::after {
  content: "";
  position: absolute;
  z-index: 1;
  inset: 0;
  outline: 1px solid var(--sp-image-outline);
  outline-offset: -1px;
  pointer-events: none;
}

.stream-tool img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* A tool's preview video keeps its clip's own shape, which stream.js sets on
   the player, so the frame wraps the player instead of holding it at 16:9. */
.stream-tool:has(> .stream-player) {
  aspect-ratio: auto;
}

.stream-tool__tile {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  height: 100%;
  padding: clamp(16px, 4cqi, 36px);
  background: var(--ink);
  color: var(--paper);
}

.stream-tool__tile .micro {
  opacity: 0.6;
}

.stream-tool__name {
  max-width: 12ch;
  font: 400 clamp(26px, 6cqi, 64px) / 0.95 var(--display);
  text-transform: uppercase;
}

/* ---- Video --------------------------------------------------------------- */

.stream-player {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: var(--fixed-ink);
}

.stream-player[data-vertical] {
  width: 330px;
  aspect-ratio: 9 / 16;
}

/* stream.js gives each player its clip's own aspect ratio, so the whole frame
   always shows. contain is the backstop if a size is ever missing. */
.stream-player__video {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.stream-player img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.stream-player[data-missing] {
  background: none;
}

.stream-player__time {
  position: absolute;
  right: 10px;
  bottom: 44px;
  padding: 5px 8px;
  background: var(--fixed-ink);
  color: var(--fixed-paper);
}

.stream-player__note {
  position: absolute;
  inset: auto 0 0;
  padding: 8px 10px;
  background: var(--fixed-ink);
  color: var(--fixed-paper);
}

/* ---- Wide screens: the story beside the stage ----------------------------- */
/*
  The markup is in the phone's reading order. On a wide screen, grid areas
  put the story in a 392px column and the stage beside it. The stage spans
  the story's rows plus a flexible one, so a tall stage never spreads the
  story apart.
*/

@media (min-width: 1000px) {
  .stream-panel__body {
    display: grid;
    grid-template-columns: 392px minmax(0, 1fr);
    grid-template-rows: repeat(6, auto) 1fr auto auto;
    grid-template-areas:
      "head   media"
      "title  media"
      "lede   media"
      "intro  media"
      "facts  media"
      "cta    media"
      ".      media"
      "closer media"
      ".      aside";
    align-items: start;
    column-gap: 56px;
  }

  .stream-panel[data-layout="tall"] .stream-panel__body {
    grid-template-columns: 392px auto minmax(0, 1fr);
    grid-template-rows: repeat(6, auto) 1fr auto;
    grid-template-areas:
      "head   media aside"
      "title  media aside"
      "lede   media aside"
      "intro  media aside"
      "facts  media aside"
      "cta    media aside"
      ".      media aside"
      "closer media aside";
  }

  .stream-panel[data-layout="solo"] .stream-panel__body {
    grid-template-columns: 392px;
  }

  /* With its siblings, a phone's stage takes the rest of the row, and they
     stand in it left to right, the current one first. All of them show, in
     one row that runs off the band's right edge and scrolls sideways, as the
     strip's track does, with its scrollbar hidden the same way; the
     siblings are links, so Tab reaches each and scrolls it in (Jason,
     2026-10-02). The padding keeps a focus ring inside the scroller. */
  .stream-panel[data-layout="tall"] .stream-panel__media[data-siblings] {
    display: flex;
    grid-column: 2 / -1;
    align-items: flex-start;
    gap: 24px;
    min-width: 0;
  }

  .stream-panel[data-layout="tall"] .stream-panel__media[data-siblings-shown] {
    margin-right: calc(-1 * var(--sp-gutter));
    padding: 6px var(--sp-gutter) 6px 6px;
    margin-block: -6px;
    margin-left: -6px;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
  }

  .stream-panel[data-layout="tall"] .stream-panel__media[data-siblings-shown]::-webkit-scrollbar {
    display: none;
  }

  /* Room after the last phone, so a click can slide any one of them to the
     current phone's place at the left, as the strip's track does (stream.js
     fitSiblings sets the width; Jason, 2026-10-03). */
  .stream-panel[data-layout="tall"] .stream-panel__media[data-siblings-shown]::after {
    flex: 0 0 var(--stream-siblings-end, 0px);
    /* Some height, or Firefox leaves it out of the scrollable width, as the strip's. */
    align-self: stretch;
    content: "";
  }

  .stream-panel[data-layout="tall"] .stream-panel__media[data-siblings] > * {
    flex: none;
  }

  .stream-panel__head { grid-area: head; min-height: 0; margin-bottom: var(--sp-gap); }
  .stream-panel__title,
  .stream-panel__case-head { grid-area: title; }
  .stream-panel__lede,
  .stream-panel__dek { grid-area: lede; }
  .stream-panel__media { grid-area: media; margin-bottom: 0; }
  .stream-panel__intro { grid-area: intro; }
  .stream-panel__facts { grid-area: facts; }
  .stream-panel__cta { grid-area: cta; }
  .stream-panel__aside { grid-area: aside; margin-bottom: 0; }

  .stream-panel[data-layout="tall"] .stream-panel__aside {
    margin-left: -8px;
    padding-top: 4px;
  }

  /* A phone prototype stands centred on a matte that takes the stage's whole
     width, and its steps close the story column, as a web demo's do (Jason,
     2026-10-11: the frame sat at the stage's left edge, and Things to try
     was stretched across the room left beside it). The matte is the mount
     the strip's frames stand on; on a case study's band it is the band's own
     colour, a step lighter. */
  .stream-panel[data-layout="tall"] .stream-panel__body:has(> .stream-steps) {
    grid-template-columns: 392px minmax(0, 1fr);
    grid-template-rows: repeat(7, auto) 1fr auto;
    grid-template-areas:
      "head   media"
      "title  media"
      "lede   media"
      "intro  media"
      "facts  media"
      "cta    media"
      "aside  media"
      ".      media"
      "closer media";
  }

  .stream-panel[data-layout="tall"] .stream-panel__body:has(> .stream-steps) > .stream-panel__media {
    display: grid;
    align-self: stretch;
    place-items: center;
    padding: 40px 24px;
    background: var(--sp-mount);
  }

  .stream-panel[data-tone="band"][data-layout="tall"] .stream-panel__body:has(> .stream-steps) > .stream-panel__media {
    background: color-mix(in oklch, var(--case-dark) 86%, var(--fixed-paper));
  }

  .stream-panel[data-layout="tall"] .stream-panel__aside.stream-steps {
    margin: 32px 0 0;
    padding-top: 0;
  }

  /* A web demo's steps close the story column, under the call to action, at
     every width, so the stage keeps the whole row beside the story for the
     demo (Jason, 2026-10-11: a column of their own right of the stage, from
     1200px, narrowed the prototype's viewport by 256px). A snippet and a
     specimen always had them here, and a phone prototype has them here too,
     beside its matte (above). */
  .stream-panel[data-layout="split"] .stream-panel__body:has(> .stream-steps) {
    grid-template-rows: repeat(7, auto) 1fr auto;
    grid-template-areas:
      "head   media"
      "title  media"
      "lede   media"
      "intro  media"
      "facts  media"
      "cta    media"
      "aside  media"
      ".      media"
      "closer media";
  }

  .stream-panel[data-layout="split"] .stream-panel__aside.stream-steps {
    margin: 32px 0 0;
  }

  /* Beside steps, a web prototype's screen trades 16:10 for the window's
     height, less the panel's own margins above and below it, or the story
     column's when that is taller, so its first screens, such as Touchline's
     Training Insights table and buttons, show whole in the narrower stage
     (Jason, 2026-10-02). It stays between 560px and 1000px. */
  .stream-panel[data-layout="split"] .stream-panel__body:has(> .stream-steps) > .stream-panel__media:has(> .stream-proto[data-device="web"][data-live]:not(.stream-live)) {
    align-self: stretch;
  }

  .stream-panel__body:has(> .stream-steps) .stream-proto[data-device="web"][data-live]:not(.stream-live) {
    height: 100%;
  }

  .stream-panel__body:has(> .stream-steps) .stream-proto[data-device="web"][data-live]:not(.stream-live) .stream-proto__screen {
    height: 100%;
    min-height: clamp(560px, 100svh - 144px, 1000px);
    aspect-ratio: auto;
  }

  /* A case study's strip fills the height the story beside it takes, so a
     phone frame stands as tall as the column rather than stopping short of
     it (Jason, 2026-10-02). The old frame height stays as a floor, and a
     680px cap keeps a wide frame from outgrowing the stage. */
  .stream-strip {
    display: flex;
    flex-direction: column;
    align-self: stretch;
  }

  /* A zero basis keeps the frames out of the strip's own height, so the
     story column alone sets it, and the track grows into what is left. */
  .stream-strip__track {
    flex: 1 1 0;
    min-height: min(427px, 46.8cqi);
  }

  .stream-strip__frame {
    height: 100%;
    max-height: 680px;
  }

  .stream-strip__frame[data-clip] {
    max-height: min(680px, 100cqi * var(--clip-h) / var(--clip-w));
  }
}

/* ---- Narrower screens: one column, Close at the top ----------------------- */

@media (max-width: 999px) {
  .stream-panel__head {
    margin-bottom: 6px;
  }

  /* Room under the strip's controls before the story resumes, near the
     28px row 03's phone board leaves. */
  .stream-panel__media {
    margin-block: 4px 12px;
  }

  .stream-panel__aside {
    margin-top: 10px;
  }

  .stream-strip__frame {
    height: min(427px, 58cqi);
  }

  .stream-shot[data-shape="phone"],
  .stream-proto[data-device="phone"],
  .stream-player[data-vertical] {
    align-self: center;
    margin-inline: auto;
  }
}

/* ---- Phones ----------------------------------------------------------------- */

@media (max-width: 599px) {
  .stream-panel {
    --sp-lead: 10px;
    --sp-trail: 28px;
    --sp-gutter: 16px;
    --sp-gap: 18px;
  }

  .stream-panel__body {
    padding: 0 16px 32px;
  }

  .stream-panel[data-tone="band"] .stream-panel__body {
    padding-top: 4px;
    padding-bottom: 32px;
  }

  .stream-panel__head {
    margin-bottom: 0;
  }

  .stream-panel__title,
  .stream-panel__case-name {
    font-size: 24px;
    line-height: 28px;
  }

  .stream-panel__title--headline {
    font-size: 26px;
    line-height: 30px;
  }

  .stream-panel__title + .stream-panel__lede,
  .stream-panel__title + .stream-panel__dek {
    margin-top: -4px;
  }

  .stream-panel__lede,
  .stream-panel__dek {
    font-size: 21px;
    line-height: 27px;
  }

  .stream-panel__cta {
    min-height: 46px;
    padding-block: 15px;
  }

  .stream-panel[data-tone="neutral"] .stream-panel__facts dt {
    flex-basis: 96px;
  }

  /* The strip bleeds off the right edge to show that it swipes. */
  .stream-strip__track {
    gap: 12px;
    margin-inline: -16px;
    padding-inline-start: 16px;
    scroll-padding-inline-start: 16px;
  }

  .stream-strip__frame {
    height: calc((100cqi - 40px) / 1.406);
  }

  .stream-strip__text {
    font-size: 14px;
  }

  .stream-shot[data-shape="phone"] {
    width: 240px;
    max-height: 520px;
  }

  .stream-proto[data-device="phone"] {
    width: min(330px, 100%);
  }

  .stream-proto[data-device="phone"] .stream-proto__screen {
    width: 100%;
    height: auto;
    aspect-ratio: 306 / 666;
  }

  .stream-player[data-vertical] {
    width: min(300px, 100%);
  }

}

/* ---- The open card ---------------------------------------------------------- */
/* A 2px outline in the project colour for a case study, in ink otherwise. It
   sits 3px outside the media, on the paper: inside, a frame's ground paints
   over it. Keyboard focus draws 4px around the whole card, so the two stay
   apart. */

.stream-card__link[aria-expanded="true"] .stream-card__media {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
}

.stream-card[data-case-study] .stream-card__link[aria-expanded="true"] .stream-card__media {
  outline-color: var(--case-accent);
}

/* A card between an open card and its panel, which opens under a block's
   second row, steps back so the rise reads as the open card's. Only its
   picture recedes: the card is still a live link, so its title and meta keep
   full contrast. Focus brings it back, but hover does not: opening scrolls
   the page, which can leave the pointer resting on it. */
/* It steps back in the panel's opening time and returns in its closing
   time, OPEN_MS and CLOSE_MS in stream.js. */
.stream-grid > .stream-card .stream-card__media { transition: opacity 180ms var(--ease-fade); }

.stream-card[data-recede] .stream-card__media { opacity: 0.25; transition-duration: 240ms; }

/* Opening scrolls the page, which can leave the pointer on a receded card, and
   its hover underline would make a faded card read as the live one. */
.stream-card[data-recede] .stream-card__title { text-decoration: none; }

.stream-card[data-recede]:focus-within .stream-card__media { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .stream-grid > .stream-card .stream-card__media { transition: none; }
}

/* A 2×2 takes the same outline as any open card, in its project colour on the
   paper; it underlined its name until Jason turned that down on 2026-10-01.
   A list band runs edge to edge in its colour, so there the outline is the
   band's paper, inside its frame's own margin. */
@media (width < 1024px) {
  .stream-grid > .stream-card[data-type="case-study"] .stream-card__link[aria-expanded="true"] .stream-card__media {
    outline-color: var(--case-paper);
  }
}

/* A 2×2 with a cover hides its media, so the outline goes round the band,
   which its link fills. Focus keeps its own 4px ring. */
@media (width >= 1024px) {
  .stream-grid > .stream-card[data-span="2"]:has(.stream-card__cover) .stream-card__link[aria-expanded="true"]:not(:focus-visible) {
    outline: 2px solid var(--case-accent);
    outline-offset: 3px;
  }
}

/* ---- A mobile prototype, full screen on a phone ----------------------------- */

.stream-fullscreen {
  width: 100%;
  max-width: none;
  height: 100%;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  background: var(--paper);
  color: var(--ink);
}

.stream-fullscreen[open] {
  display: flex;
  flex-direction: column;
}

.stream-fullscreen::backdrop {
  background: var(--ink);
}

.stream-fullscreen__bar {
  display: grid;
  flex: none;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  height: 52px;
  padding: 0 8px 0 4px;
  background: var(--ink);
  color: var(--paper);
}

.stream-fullscreen__close {
  display: inline-flex;
  align-items: center;
  justify-self: start;
  gap: 8px;
  min-width: 44px;
  height: 44px;
  /* 2px less on the x's side. */
  padding: 0 12px 0 10px;
  border: 0;
  background: none;
  color: inherit;
  cursor: pointer;
}

.stream-fullscreen__close > span {
  font-size: 14px;
}

.stream-fullscreen__close:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: -4px;
}

.stream-fullscreen__title {
  margin: 0;
  font-weight: 400;
}

.stream-fullscreen__live {
  display: inline-flex;
  align-items: center;
  justify-self: end;
  gap: 6px;
  margin: 0;
  padding-right: 8px;
}

.stream-fullscreen__frame {
  display: block;
  flex: 1;
  width: 100%;
  border: 0;
  background: var(--sp-frame, var(--frame-screen));
}
