/* ============================================================================
   talks.css — styles unique to /talks-and-fireside.

   Loads alongside site-pages.css, which supplies the shared sh- components
   (section rails, cards, schedule rows, pricing plans). Only the pieces this
   page introduces live here, all prefixed st-.
   ========================================================================== */

/* --- Audience cards -------------------------------------------------------
   Same shell as the homepage's join cards, but the copy sits at the bottom
   with the title at the top and artwork behind. */

.st-audience {
  justify-content: space-between;
  min-height: 30rem;
}

.st-audience__title {
  margin: 0;
  font-family: Mesotrial, Verdana, sans-serif;
  font-weight: 400;
  font-size: clamp(1.75rem, 2.2vw, 2.5rem);
  line-height: 1.05;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  color: var(--_colors---3);
}

/* text="small" resolves to a line-height of 0.9 — below its own font-size —
   which sets the card copy solid. The [text="small"] rule lives in a later
   inline <style>, so the class alone loses the tie; qualify it with the card
   to win on specificity and restore the site's normal body rhythm. */
.st-audience .st-audience__copy {
  margin: 0;
  max-width: 36ch;
  line-height: 1.4;
  /* #f7f7f7 on the dark card artwork. The paragraphs previously carried
     text-color="7" (#737776), whose rule is !important, which both overrode
     this line and read as poor contrast on the artwork. Attribute removed so
     this stays the only place the colour is set. */
  color: var(--_colors---5);
}

/* The live canvas backgrounds (card-canvas.js). It sits in the same layer as
   the ::before gradient below and paints after it, so the gradient stays
   visible until — and only if — the canvas fails to mount. */
.sh-join__canvas {
  position: absolute;
  inset: 0;
  z-index: -1;
  display: block;
  width: 100%;
  height: 100%;
}

/* Card copy has to clear the canvas, which the gradient (a pseudo-element)
   never needed. */
.has-canvas .st-audience__title,
.has-canvas .st-audience__copy {
  position: relative;
  z-index: 1;
}

/* The gradients these replaced were dark and even, so white copy sat on them
   safely. A live canvas is neither — the ASCII field in particular runs bright
   lime right where the copy is. This scrim darkens the top and bottom bands
   where the title and paragraph sit and leaves the middle of the artwork
   alone. It is ::after, so it paints above the canvas but still below the copy. */
.has-canvas::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: linear-gradient(
    180deg,
    rgba(0, 0, 0, 0.72) 0%,
    rgba(0, 0, 0, 0.34) 26%,
    rgba(0, 0, 0, 0.18) 48%,
    rgba(0, 0, 0, 0.55) 78%,
    rgba(0, 0, 0, 0.82) 100%
  );
}

/* Stand-ins for the Figma image fills — same hue and direction as the
   artwork, swapped for the real exports when they land. */
.st-audience--founders::before {
  background:
    radial-gradient(90% 70% at 30% 70%, rgba(218, 250, 156, 0.22), transparent 62%),
    #131313;
}

.st-audience--makers::before {
  background:
    radial-gradient(110% 90% at 55% 55%, rgba(126, 58, 142, 0.75), transparent 65%),
    #1a0f1e;
}

.st-audience--hiring::before {
  background:
    linear-gradient(90deg, #04121c 0%, #04121c 55%, rgba(56, 189, 248, 0.55) 78%, #04121c 100%),
    #04121c;
}

/* --- Speakers: heading, intro and line-up placement -----------------------
   The heading comes first in the markup so mobile stacks it directly under the
   section rule. That leaves three grid items — heading (right columns), intro
   (left columns), line-up (right columns) — and auto-placement drops the
   heading and the intro into row 1 with the line-up in row 2. The intro column
   is the tall one (it carries the pinned portrait), so it has to span both rows
   or it would set row 1's height and push the line-up down below itself. */
@media (min-width: 992px) {
  .st-speakers__intro { grid-row: 1 / span 2; }
}

/* --- Featured speaker portrait -------------------------------------------- */

.st-feature { margin: 2rem 0 0; }

/* Desktop: every portrait sits in the same 1:1 box, stacked on top of each
   other, and only .is-active is visible. animations.js pins the figure and
   swaps .is-active as you hover the line-up — same cross-fade as the
   homepage's Application Process visual. */
.st-feature__stack {
  position: relative;
  width: 100%;
  max-width: 22.5rem;
  aspect-ratio: 9 / 10;
  background: #000;
}

.st-feature__img {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: #000;
  opacity: 0;
  transition: opacity 500ms ease;
}

.st-feature__img.is-active { opacity: 1; }

/* speaker-morph.js draws the portrait as particles on a canvas and takes the
   <img>s out of the picture. It only adds .is-particles once the canvas has
   something to show, so this never leaves an empty box: without JS, on mobile,
   or under reduced motion the cross-fade above stays in charge. */
.st-feature__canvas {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
}

.st-feature__stack.is-particles .st-feature__img { opacity: 0; }

/* The hovered name reads as the active one, so it's clear which portrait
   you're looking at. */
@media (min-width: 992px) {
  .st-lineup__row { transition: opacity 300ms ease; }
  .st-lineup:hover .st-lineup__row { opacity: 0.45; }
  .st-lineup:hover .st-lineup__row:hover { opacity: 1; }

  /* The names live in the right-hand grid columns, but hovering anywhere
     across the page at a row's height should activate it — same trick as the
     homepage's .ap-step-block::before. The invisible pseudo-element widens the
     hit area leftwards across the viewport; it sits on the row so the row's
     own padding is part of the target, leaving no dead gaps between names. */
  .st-lineup__row::before {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    right: 0;
    left: -100vw;
  }

  /* The pinned portrait is fixed, so it paints over the left half of those
     hit areas. It's decorative, so let the pointer straight through — the
     access-pass button above it stays interactive. */
  .st-feature { pointer-events: none; }
}

/* Those hit areas run past the left edge of the page; clip so they can't
   create a horizontal scrollbar. */
.st-speakers { overflow-x: clip; }

@media (prefers-reduced-motion: reduce) {
  .st-feature__img,
  .st-lineup__row { transition: none; }
}

/* --- Line-up: load more (phone only) ---------------------------------------
   Twelve rows each carrying their own portrait is a long scroll on a phone, so
   the list opens at four. Desktop shows everything: the portrait pinned beside
   it swaps as you move down the list, which needs the whole list present.

   It is the site's .button-nav2, so it picks up the shared border, radius,
   padding, masked hover and hover blip for free — only the fill differs, light
   grey where the others are lime. The button element itself stays transparent
   and .button-bg carries the colour, which is how the others are built.

   Hidden by default and only shown while the list is collapsed, so it
   disappears once used and never appears on desktop or without JS. */
.st-lineup__more {
  /* a <button> inherits neither, and .button-nav2 assumes it has both */
  font: inherit;
  color: var(--_colors---4);
}

/* Hidden unless the list is collapsed. Qualified up to three classes so it
   beats `.button-nav2.w-variant-… { display: flex }`, which is what makes the
   shared button full-width on mobile and would otherwise keep this one on
   screen after it has been used. */
.st-lineup__more.button-nav2.inline-flex { display: none; }

@media (max-width: 991px) {
  .st-lineup.is-collapsed .st-lineup__row:nth-child(n + 5) { display: none; }

  .st-lineup.is-collapsed ~ .st-lineup__more.button-nav2 {
    display: flex;
    justify-content: center;
    width: 100%;
    /* !important to beat the blanket
       `.button-nav2 { border: 1px solid #DAFA9C !important }` in this page's own
       <style> block, which strokes every nav button lime. Same reason as
       .hero-actions__secondary in site-pages.css. */
    border-color: var(--_colors---5) !important;
  }

  .st-lineup.is-collapsed ~ .st-lineup__more.button-nav2 .button-bg { background: var(--_colors---5); }
}

/* Mobile: the figure goes entirely. There is nothing for it to do once each
   line-up row carries its own portrait — no pinning, no hover to swap it, and
   leaving it in would mean showing all eleven faces twice. Its <img>s are
   never in a viewport, so they are never downloaded either. */
@media (max-width: 991px) {
  .st-feature { display: none; }
}

/* --- Portrait caption -----------------------------------------------------
   One caption per speaker, swapping with the portrait. All eleven are stacked
   in the same grid cell so the block is as tall as the longest of them and
   never resizes as you move down the list — a caption that grew or shrank
   would shift the pinned figure under the cursor. */
.st-feature__caption {
  display: grid;
  margin-top: 0.75rem;
  max-width: 22.5rem;
  text-align: left;
}

/* text="small" sets the leading solid at 0.9, which is a display setting, and
   these captions run to five lines. The site puts body copy back on 1.4 via
   p[text="small"], but this is a <figcaption> so that rule never reaches it.
   Qualified with .st-feature to outrank the bare [text="small"], which lives
   in a later inline <style> and would otherwise win the tie on source order —
   the same trap as the audience-card copy above. */
.st-feature .st-feature__caption { line-height: 1.4; }

.st-feature__cap {
  grid-area: 1 / 1;
  opacity: 0;
  transition: opacity 300ms ease;
}

.st-feature__cap.is-active { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .st-feature__cap { transition: none; }
}

/* The caption is the short bio; the full one lives in the speaker modal. Without
   a cue there is nothing to say the modal exists — the line-up rows open it, but
   that is only discoverable by clicking a name. Sits under the caption and opens
   whoever the figure is currently showing. */
.st-feature__more {
  justify-self: start;
  margin-top: 0.75rem;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 0.25em;
  opacity: 0.7;
  transition: opacity 200ms ease;
}

.st-feature__more:hover,
.st-feature__more:focus-visible { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .st-feature__more { transition: none; }
}

/* --- Speaker line-up ------------------------------------------------------
   Name left, organisation right, hairline between each. */

.st-lineup {
  margin: 0;
  padding: 0;
  list-style: none;
  border-top: 1px solid rgba(0, 0, 0, 0.14);
}

.st-lineup__row {
  position: relative; /* anchors the full-width hover target (::before) */
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1.5rem;
  padding: 1.5rem 0;
  border-bottom: 1px solid rgba(0, 0, 0, 0.14);
}

/* Name and role are wrapped together so they can stack on mobile. On desktop
   the wrapper dissolves and the name goes back to being a direct flex child of
   the row, exactly as before. */
.st-lineup__text { display: contents; }

.st-lineup__name {
  font-family: Laygrotesk, Arial, sans-serif;
  font-size: clamp(1.25rem, 1.9vw, 1.875rem);
  line-height: 1.1;
  letter-spacing: -0.01em;
}

/* Desktop carries the role in the modal and the logo in the row's right-hand
   slot, so printing it here as well would just crowd the list. */
.st-lineup__role { display: none; }

/* Rows open the speaker's modal. */
.st-lineup__row { cursor: pointer; }

.st-lineup__row:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 4px;
}

/* The real client logos, which this slot was always waiting for. They ship at
   a uniform 40px viewBox height in the site's own grey (--_colors---7), so on
   this light section they need no recolouring — only a height to sit on. */
.st-lineup__org {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  font-family: Laygrotesk, Arial, sans-serif;
  font-size: 1rem;
  font-weight: 500;
  color: var(--_colors---7);
  /* Fixed to the tallest logo the optical-balance rules below can produce, and
     centred rather than baseline-aligned. The row aligns on the baseline, and an
     image has none — it sits its bottom edge on the text baseline and grows
     upward, so a 26px mark pushed its row 3px past the rest and broke the
     rhythm of the hairline-separated rows. Centring takes the logo out of that
     calculation entirely. */
  height: 2rem;
  align-self: center;
}

.st-lineup__logo {
  display: block;
  width: auto;
  height: calc(1.5rem * var(--logo-scale, 1));
}

/* Optical balance. Locking every mark to one height only works if they share
   a shape and a weight, and these share neither: aspect runs 1.25 to 6.2, and
   ink coverage — the share of the box actually filled — runs 11% for Virgin
   Active's script to 43% for Kloset Klub's heavy sans. Sizing on the box alone
   left Framer looking small, because its mark is an icon, a gap and a word, so
   most of its box is air.

   The scale below is the geometric mean of two normalisations: equal box area,
   and equal ink. Box alone under-sizes airy marks; ink alone sends Virgin
   Active to 52px. The mean lands them together, clamped 15-32px so nothing
   towers or thins to a hairline. One variable drives the desktop column and
   the mobile card badge, so the two can never drift apart. Keyed on filename,
   so swapping a logo carries its sizing with it. */
.st-lineup__logo[src$="flo-state.svg"],
.st-lineup__badge[src$="flo-state.svg"],
.spk-modal__brand-logo[src$="flo-state.svg"] { --logo-scale: 0.75; }
.st-lineup__logo[src$="kloset-club.svg"],
.st-lineup__badge[src$="kloset-club.svg"],
.spk-modal__brand-logo[src$="kloset-club.svg"] { --logo-scale: 0.75; }
.st-lineup__logo[src$="takealot.svg"],
.st-lineup__badge[src$="takealot.svg"],
.spk-modal__brand-logo[src$="takealot.svg"] { --logo-scale: 0.8; }
.st-lineup__logo[src$="canal-plus.svg"],
.st-lineup__badge[src$="canal-plus.svg"],
.spk-modal__brand-logo[src$="canal-plus.svg"] { --logo-scale: 0.9; }
.st-lineup__logo[src$="sealand.svg"],
.st-lineup__badge[src$="sealand.svg"],
.spk-modal__brand-logo[src$="sealand.svg"] { --logo-scale: 0.9; }
.st-lineup__logo[src$="faro.svg"],
.st-lineup__badge[src$="faro.svg"],
.spk-modal__brand-logo[src$="faro.svg"] { --logo-scale: 1.0; }
.st-lineup__logo[src$="yoco.svg"],
.st-lineup__badge[src$="yoco.svg"],
.spk-modal__brand-logo[src$="yoco.svg"] { --logo-scale: 1.0; }
.st-lineup__logo[src$="framer.svg"],
.st-lineup__badge[src$="framer.svg"],
.spk-modal__brand-logo[src$="framer.svg"] { --logo-scale: 1.62; }
.st-lineup__logo[src$="ringer.svg"],
.st-lineup__badge[src$="ringer.svg"],
.spk-modal__brand-logo[src$="ringer.svg"] { --logo-scale: 0.85; }
.st-lineup__logo[src$="makereign.svg"],
.st-lineup__badge[src$="makereign.svg"],
.spk-modal__brand-logo[src$="makereign.svg"] { --logo-scale: 0.9; }
.st-lineup__logo[src$="heavy-chef.svg"],
.st-lineup__badge[src$="heavy-chef.svg"],
.spk-modal__brand-logo[src$="heavy-chef.svg"] { --logo-scale: 1.4; }
.st-lineup__logo[src$="virgin-active.svg"],
.st-lineup__badge[src$="virgin-active.svg"],
.spk-modal__brand-logo[src$="virgin-active.svg"] { --logo-scale: 1.92; }

.st-lineup__org sup { font-size: 0.6em; vertical-align: super; }

/* The per-row portrait is a mobile-only affordance: on desktop the pinned
   .st-feature carries the picture and the row is just a name. Hidden here, so
   its lazy <img> never enters a viewport and never downloads. */
.st-lineup__media { display: none; }

/* --- Mobile line-up: one card per speaker -------------------------------
   Desktop reads as a list because the portrait lives beside it and swaps on
   hover. Neither of those exists on a phone, so the row carries its own
   picture and the three pieces stack: portrait with the organisation set into
   its bottom-left corner, then the name, then the meta line. */
@media (max-width: 991px) {
  .st-lineup { border-top: 0; }

  .st-lineup__row {
    display: block;
    padding: 0 0 2.5rem;
    border-bottom: 0;
  }

  .st-lineup__media {
    position: relative;
    display: block;
    margin-bottom: 1rem;
    aspect-ratio: 1 / 1;
    overflow: hidden;
    background: #000;
  }

  .st-lineup__img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
  }

  /* The client logo, set into the corner of the portrait. The SVGs ship in the
     site's grey, which is right for the light desktop slot but disappears on
     a dark photo — brightness(0) invert(1) drives whatever is in the file to
     pure white, whether it is grey, black or a brand colour. */
  .st-lineup__badge {
    position: absolute;
    left: 1.25rem;
    bottom: 1.25rem;
    display: block;
    width: auto;
    height: calc(2.1rem * var(--logo-scale, 1));
    filter: brightness(0) invert(1);
  }

  /* The desktop slot's copy of the same logo has no place in the card. */
  .st-lineup__logo { display: none; }

  .st-lineup__name {
    display: block;
    font-size: 1.5rem;
    line-height: 1.15;
  }

  .st-lineup__text { display: block; }

  /* The meta line under the name, reading "ROLE | COMPANY". */
  .st-lineup__role {
    display: block;
    margin-top: 0.35rem;
    font-family: Geistmono, Arial, sans-serif;
    font-size: 0.8125rem;
    line-height: 1.4;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--_colors---7);
  }

  /* The company is already set into the corner of the portrait above, and the
     role line names it again, so the logo slot has nothing left to do here. */
  .st-lineup__org { display: none; }
}

/* --- Schedule rail: middle label -------------------------------------------
   Long for an eyebrow: in the three columns the other rails use it wrapped to two
   lines on narrower desktops, which pushed the rule below it out of step with
   every other section. It spans to column 11 and does not wrap, so it holds one
   line and the rail keeps its single-line height at every desktop width. */
@media (min-width: 992px) {
  #schedule .sh-rail__mid [text="eyebrow"] { white-space: nowrap; }
}

/* --- Schedule header CTA ---------------------------------------------------
   The pass button sits in the last two grid columns, which is narrower than
   "Get your Attendee Pass - Free" at 16px with its 30px side padding. The button
   carries overflow: hidden for the label's hover slide, so a too-narrow button
   clips the text instead of letting it spill — the label lost its last word.

   Hugging the label fixes it without touching the grid: the wrapper keeps the
   button right-aligned and it extends left into the empty column beside the
   heading, which ends nine columns over. Desktop only — below 992 this button is
   hidden and the full-width one below the rows takes over. */
@media (min-width: 992px) {
  /* flex: none as well as the width — the wrapper is only as wide as the two
     columns, and with the default shrink factor the button was compressed
     straight back to it. */
  #schedule .layout > .flex > .button-nav2 {
    flex: none;
    width: max-content;
  }
}

/* --- Sticky day tabs (desktop) ---------------------------------------------
   The tab column is short and the rows beside it are long, so the tabs scroll
   out of view well before the day does. Sticking them keeps the day you are
   reading — and the way to switch — on screen for the whole section.

   The grid stretches the tab column to the full row height, which is what gives
   the sticky element its travel. Desktop only: on a phone the tabs sit above the
   rows in normal flow and there is no room to spare at the top. */
@media (min-width: 992px) {
  #schedule .sh-tabs {
    position: sticky;
    top: 40px;
  }
}

/* --- Session schedule ------------------------------------------------------
   The tabbed speaker programme, built to the artboards (Figma 10621-4639
   desktop, 10621-4738 mobile).

   Both days live in the page and the tabs swap which panel shows, so the two
   day lists occupy the same space instead of stacking. Session rows are light
   cards; breaks stay plain, so the day reads as blocks of sessions with the
   gaps between them visible rather than as one unbroken table.

   Desktop puts the discipline and format under the time; mobile drops that line
   entirely and pairs the speaker names with their faces on one row.

   Scoped to .st-sessions so the homepage's hackathon schedule is untouched. */

.st-sessions [hidden] { display: none; }

/* --- Day tabs -------------------------------------------------------------
   Stacked beside the rows on desktop, side by side above them on mobile. */
.st-sessions .sh-tabs {
  display: flex;
  flex-direction: column;
  gap: 20px;
  max-width: 242px;
}

.st-sessions .sh-tab {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  margin: 0;
  /* The board sets 40px sides, but "Saturday, 19 September" plus the marker is
     wider than the 242px pill allows at that padding and wrapped to two lines,
     leaving the two tabs different heights. 24px keeps both on one line. */
  padding: 20px 24px;
  border: 0;
  border-radius: 4px;
  background: var(--_colors---5);
  color: var(--_colors---4);
  font-family: inherit;
  font-size: 16px;
  line-height: 1.4;
  text-align: center;
  white-space: nowrap;
  cursor: pointer;
  transition: background 240ms ease, color 240ms ease;
}

.st-sessions .sh-tab.is-active {
  background: var(--_colors---4);
  color: var(--_colors---3);
}

.st-sessions .sh-tab:focus-visible {
  outline: 2px solid var(--_colors---8);
  outline-offset: 2px;
}

/* The lime marker belongs to the selected day only. It holds its 5px of space
   on the others so the labels do not shift as you switch. */
.st-sessions .sh-tab__dot {
  flex: 0 0 5px;
  width: 5px;
  height: 5px;
  background: var(--_colors---8);
  opacity: 0;
}

.st-sessions .sh-tab.is-active .sh-tab__dot { opacity: 1; }

/* --- Rows ---------------------------------------------------------------- */
.st-sessions .sh-day {
  height: auto;
  padding: 0;
  border-radius: 0;
  background: none;
}

.st-sessions .sh-day__list {
  gap: 16px;
  border: 0;
}

/* Session rows are cards; the break override below strips this back. */
.st-sessions .sh-slot {
  --row-pad: 32px;
  display: flex;
  align-items: flex-start;
  gap: 16px;
  padding: var(--row-pad);
  border-radius: 4px;
  background: var(--_colors---5);
  line-height: 1.4;
}

/* 413 of the board's 1309pt of row content, with the remainder to the right. */
/* 30/33, matching .st-lineup__name in Meet the speakers rather than the board's
   32/32 — the two sit on the same page and read as one type size. */
.st-sessions .sh-slot__time {
  flex: 0 0 413px;
  min-width: 0;
  font-size: 30px;
  line-height: 1.1;
  /* Without this the row inherits a positive tracking from its ancestor, which
     is the wrong direction at this size. -0.01em is what .st-lineup__name uses. */
  letter-spacing: -0.01em;
  color: var(--_colors---4);
}

/* Discipline, session number, format and length on one line. Desktop only —
   see the phone block at the bottom. */
.st-sessions .sh-slot__fmt {
  display: block;
  margin-top: 12px;
  font-size: 18px;
  line-height: 1.4;
  /* It sits inside .sh-slot__time, so it would otherwise inherit that element's
     tightened tracking. Secondary copy across the page runs at 0.32px — the same
     as the speaker line beside it and .st-lineup__role. */
  letter-spacing: 0.32px;
  color: var(--_colors---7);
}

.st-sessions .sh-slot__main {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.st-sessions .sh-slot__name {
  font-size: 30px;
  line-height: 1.1;
  letter-spacing: -0.01em;
  color: var(--_colors---4);
}

/* Names on the left, faces pushed to the right edge of the row. */
.st-sessions .sh-slot__foot {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
}

.st-sessions .sh-slot__by {
  min-width: 0;
  font-size: 18px;
  line-height: 1.4;
  color: var(--_colors---7);
}

/* --- Speaker faces --------------------------------------------------------
   Overlapped, first on top. The board steps them 22px across a 48px circle, so
   each after the first is pulled back by the difference. */
.st-sessions .sh-slot__faces {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
}

/* The board pins the group to the top-right of the row, level with the time and
   the session title — not to the names line it sits next to in the markup. It is
   taken out of flow to get there, so the names reserve room for the widest group
   (five faces) rather than measuring each row. */
@media (min-width: 768px) {
  .st-sessions .sh-slot { position: relative; }

  .st-sessions .sh-slot__faces {
    position: absolute;
    top: var(--row-pad);
    right: var(--row-pad);
  }

  /* The group is top-aligned with the session title, so the title is what it
     would run into — the whole text column is indented, not just the names.
     A group is 48px for the first face and 22px for each after it; the reserve
     is that plus the row's 16px gap. The base value covers the widest group
     (five faces) so a browser without :has() still never overlaps, and each
     :has() rule below narrows it to what the row actually carries. */
  .st-sessions .sh-slot__main { padding-right: 152px; }

  .st-sessions .sh-slot:has(.sh-face:nth-child(1)) .sh-slot__main { padding-right: 64px; }
  .st-sessions .sh-slot:has(.sh-face:nth-child(2)) .sh-slot__main { padding-right: 86px; }
  .st-sessions .sh-slot:has(.sh-face:nth-child(3)) .sh-slot__main { padding-right: 108px; }
  .st-sessions .sh-slot:has(.sh-face:nth-child(4)) .sh-slot__main { padding-right: 130px; }
  .st-sessions .sh-slot:has(.sh-face:nth-child(5)) .sh-slot__main { padding-right: 152px; }
}

.st-sessions .sh-face {
  width: 48px;
  height: 48px;
  /* A 3px ring in the row colour, so overlapping faces read as separate circles
     rather than one shape. box-sizing is border-box site-wide, so the footprint
     stays 48px and the 22px step below is unaffected. */
  border: 3px solid var(--_colors---5);
  border-radius: 50%;
  object-fit: cover;
  background: #d9d9d9;
}

.st-sessions .sh-face + .sh-face { margin-left: -26px; }

/* The faces overlap, so each one normally covers a slice of the one before it.
   Hovering lifts whichever you are pointing at clear of its neighbours, which
   is what makes a partly covered face readable. Pointer devices only: it needs
   position to take a z-index, and there is no hover to leave on a touch screen. */
@media (hover: hover) and (min-width: 768px) {
  .st-sessions .sh-face:hover {
    position: relative;
    z-index: 1;
  }
}

/* A face opens that speaker's bio, but only where there is one to open: the
   schedule also bills the judges and the live-build crew, who have no line-up
   row. Those stay inert, so the pointer never promises a panel that will not
   come. The lift already exists above; this adds the pointer and a ring on
   focus, since these are reachable by keyboard now. */
.st-sessions .sh-face.is-linked { cursor: pointer; }

.st-sessions .sh-face.is-linked:focus-visible {
  outline: 2px solid var(--_colors---2);
  outline-offset: 2px;
  position: relative;
  z-index: 2;
}

/* --- Breaks ---------------------------------------------------------------
   No card, no format line, everything grey: a break is a gap between sessions,
   not one of them. */
.st-sessions .sh-slot--break {
  padding: 0 32px 24px;
  background: none;
}

/* A break that follows a row sits straight under a card and needs the same air
   above it as below. The first row of a day has nothing above it to clear. */
.st-sessions .sh-slot--break:not(:first-child) { padding-top: 24px; }

.st-sessions .sh-slot--break .sh-slot__time,
.st-sessions .sh-slot--break .sh-slot__name { color: var(--_colors---7); }

/* --- Tablet ---------------------------------------------------------------
   The board has no view between 1920 and a phone. The time column is 413pt of
   1309 there; below about 1200 that leaves the session title too little, so the
   column narrows and the type steps down. */
@media (max-width: 1199px) and (min-width: 768px) {
  .st-sessions .sh-slot__time { flex-basis: 260px; font-size: 26px; }
  .st-sessions .sh-slot__name { font-size: 26px; }
  .st-sessions .sh-slot__fmt,
  .st-sessions .sh-slot__by { font-size: 16px; }
  .st-sessions .sh-slot { --row-pad: 24px; }
  .st-sessions .sh-slot--break { padding: 0 24px 20px; }
  .st-sessions .sh-slot--break:not(:first-child) { padding-top: 20px; }
  .st-sessions .sh-tab { padding: 16px 24px; }
}

/* --- Phone ---------------------------------------------------------------
   Rows lose the card and become hairline-separated, the discipline line goes
   entirely, and the names share a line with the faces. */
@media (max-width: 767px) {
  .st-sessions .sh-tabs {
    flex-direction: row;
    gap: 12px;
    max-width: none;
  }

  .st-sessions .sh-tab {
    flex: 1 1 0;
    min-width: 0;
    padding: 12px 24px;
    border-radius: 2px;
    font-size: 14px;
  }

  /* No room for it beside two labels, and the selected tab is already black. */
  .st-sessions .sh-tab__dot { display: none; }

  .st-sessions .sh-day__list { gap: 0; }

  .st-sessions .sh-slot {
    flex-direction: column;
    /* The base rule sets align-items: flex-start, which is the cross axis once
       this is a column — it shrink-wrapped every child to its own content, so
       .sh-slot__main ended as wide as its title and the faces aligned to that
       edge instead of the page margin, by a different amount on every row. */
    align-items: stretch;
    gap: 16px;
    padding: 24px 0;
    border-radius: 0;
    border-top: 1px solid #a9a9a980;
    background: none;
  }

  /* The rule separates one row from the row above, so the first row of a day has
     nothing to separate from. Applied by position rather than by kind: a break
     needs the line above it just as much as a session does. */
  .st-sessions .sh-slot:first-child { border-top: 0; }

  .st-sessions .sh-slot__time {
    flex: 0 0 auto;
    font-size: 18px;
    line-height: 1.1;
    color: var(--_colors---7);
  }

  /* Dropped on purpose: the discipline and format do not survive this width. */
  .st-sessions .sh-slot__fmt { display: none; }

  .st-sessions .sh-slot__name { font-size: 22px; }

  .st-sessions .sh-slot__foot { align-items: center; }

  .st-sessions .sh-slot__by { font-size: 14px; }

  /* No card at this width — the faces sit on the section, so the ring does too.
     2px rather than the desktop 3px: the circle is a third smaller here, so the
     same weight would eat the photo and read much heavier than it does there. */
  .st-sessions .sh-face {
    width: 32px;
    height: 32px;
    border-width: 2px;
    border-color: var(--_colors---3);
  }
  .st-sessions .sh-face + .sh-face { margin-left: -13px; }

  /* No rule above a break — it is a gap, not a row. The padding-top set further
     up still applies, so a break below the first row keeps its air and the one
     that opens a day does not. */
  .st-sessions .sh-slot--break {
    padding-right: 0;
    padding-bottom: 24px;
    padding-left: 0;
  }

  .st-sessions .sh-slot--break .sh-slot__name { color: var(--_colors---4); }
}
