/* ═══════════════════════════════════════════════════════════════════════════
   BRADFORD CARLTON — APP SHELL LAYER
   Panes, states, media surfaces and step lists for the JS-rendered pages.

   Load AFTER office.css, on app pages only:
     /v/  /p/  /p/thank-you  (and later /live/ /replay/)

   These pages are not marketing pages. They render whatever the database hands
   them, they have real failure states, and they need surfaces office.css does
   not carry. Everything here is additive — no overrides of core.

   Deliberately NOT in office.css: none of it appears on the 40 static pages,
   and the core file is already the one every page must load.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Room: a flat page ground for an app page. This is the .scene replacement
      for utility pages — no floor, no cast, and crucially none of the mobile
      floor-clearance padding that .scene carries in office-world.css. ── */
.room { min-height: 100svh; padding: clamp(6.5rem, 12vw, 9rem) 0 4rem; }

/* ── Pane: the generic bordered surface these pages are built from ── */
.pane {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 1.4rem;
}
.pane--flush  { padding: 0; overflow: hidden; }
.pane--scroll { max-height: 340px; overflow-y: auto; }

/* ── State: every terminal condition an app page can land in (not found,
      expired, already watched, still processing). One look, five meanings, so
      a visitor who hits two of them recognises the second. ── */
.pane--state { text-align: center; padding: clamp(3rem, 8vw, 4.5rem) 1.5rem; }
.pane-icon { font-size: 2.8rem; line-height: 1; margin-bottom: 1rem; }
.pane--state h2 { font-size: clamp(1.4rem, 3vw, 1.9rem); }
.pane--state p  { margin: .8rem auto 0; max-width: 44ch; color: var(--cream-dim); font-size: .95rem; }

/* ── Media ── */
.pane--player { background: #000; padding: 0; overflow: hidden; }
.pane--player video { width: 100%; display: block; max-height: 72vh; background: #000; }

/* ── Badge: a small status marker beside a title ("2 views left") ── */
.badge {
  display: inline-block; vertical-align: middle; margin-left: .7rem;
  font-family: 'JetBrains Mono', monospace;
  font-size: 10px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  padding: .28rem .6rem; border-radius: 2px;
  background: rgba(240,169,60,.14); color: var(--amber); border: 1px solid var(--line-hot);
}

/* ── Tabs ── */
.tabs { display: flex; gap: .5rem; margin: 1.6rem 0 .7rem; }

/* ── Transcript: click-to-seek cues, highlighted as the video plays ── */
.transcript { font-size: .9rem; line-height: 1.75; color: var(--cream-dim); }
.transcript .cue { cursor: pointer; padding: 1px 3px; border-radius: 2px; transition: background .15s ease, color .15s ease; }
.transcript .cue:hover { background: rgba(240,169,60,.16); }
.transcript .cue.on { background: rgba(240,169,60,.26); color: var(--cream); }

/* ── Step list: "what happens next" on a thank-you or checkout page ── */
.steps { list-style: none; padding: 0; margin: 0; counter-reset: step; }
.steps li { position: relative; padding-left: 3rem; counter-increment: step; }
.steps li + li { margin-top: 1.5rem; }
.steps li::before {
  content: counter(step); position: absolute; left: 0; top: -.15em;
  width: 2rem; height: 2rem; display: grid; place-content: center;
  font-family: 'JetBrains Mono', monospace; font-size: 12px; font-weight: 700;
  color: var(--ink); background: var(--amber); border-radius: 2px;
}

/* ── Choice buttons: the day and time pickers on /call/, and the answer buttons
      on /bottleneck-finder/ when that page is converted. Replaces the old
      Cornerstone .quiz-option. `selected` is the state class the scripts
      toggle — do not rename it without updating them. ── */
.choice {
  display: block; width: 100%; text-align: left; cursor: pointer;
  font-family: 'Outfit', system-ui, sans-serif; font-size: .98rem; line-height: 1.35;
  color: var(--cream); background: rgba(0,0,0,.22);
  border: 1px solid var(--line); border-radius: 3px;
  padding: .85rem 1rem;
  transition: border-color .18s ease, background .18s ease, transform .18s ease;
}
.choice:hover { border-color: var(--line-hot); background: rgba(0,0,0,.34); }
.choice.selected {
  border-color: var(--amber); background: rgba(240,169,60,.14); color: var(--cream);
  box-shadow: inset 0 0 0 1px var(--amber);
}
.choice b { display: block; font-weight: 600; }
.choice .sub { display: block; font-size: .82rem; color: var(--cream-dim); margin-top: .15rem; }
.choice--time { text-align: center; font-variant-numeric: tabular-nums; padding: .7rem .4rem; }

/* Day column scrolls sideways on narrow screens rather than stacking a dozen
   full-width buttons above the times. */
.daylist { display: grid; gap: .55rem; }
@media (max-width: 700px) {
  .daylist { grid-auto-flow: column; grid-auto-columns: minmax(9.5rem, 1fr); overflow-x: auto; padding-bottom: .5rem; }
}
.timegrid { display: grid; grid-template-columns: repeat(3, 1fr); gap: .55rem; }
@media (max-width: 460px) { .timegrid { grid-template-columns: repeat(2, 1fr); } }

/* Two-column picker: days beside times, stacking under 700px.
   `min-width: 0` on the children is load-bearing, not tidiness. Grid items
   default to min-width:auto, which means they refuse to shrink below their
   content — so the horizontally-scrolling .daylist blew its column out to 795px
   inside a 390px viewport and took the whole page into horizontal scroll. The
   overflow-x:auto above can only do its job once the column is allowed to be
   narrower than its contents. */
.picker { display: grid; grid-template-columns: 2fr 3fr; gap: 1.6rem; }
.picker > * { min-width: 0; }
@media (max-width: 700px) { .picker { grid-template-columns: 1fr; gap: 1.2rem; } }
.picker .times { border-left: 1px solid var(--line); padding-left: 1.6rem; }
@media (max-width: 700px) { .picker .times { border-left: 0; padding-left: 0; border-top: 1px solid var(--line); padding-top: 1.2rem; } }

/* ── Review list: the confirm-your-booking summary ── */
.reviewlist { margin: 0 0 1.8rem; }
.reviewlist > div { display: flex; justify-content: space-between; gap: 1.2rem; padding-bottom: .8rem; border-bottom: 1px solid var(--line); }
.reviewlist > div + div { margin-top: .8rem; }
.reviewlist > div.stacked { display: block; }
.reviewlist dt { color: var(--cream-faint); font-family: 'JetBrains Mono', monospace; font-size: 10.5px; letter-spacing: .11em; text-transform: uppercase; }
.reviewlist dd { margin: 0; font-weight: 600; text-align: right; }
.reviewlist > div.stacked dd { text-align: left; margin-top: .4rem; font-weight: 400; color: var(--cream-dim); }

/* ── Banner: an inline notice above the picker (reschedule context) ── */
.banner { border: 1px solid var(--line-hot); background: linear-gradient(120deg, rgba(240,169,60,.10), rgba(240,169,60,.02)); border-radius: 3px; padding: 1rem 1.2rem; text-align: center; font-size: .95rem; }

/* ── Danger action: cancelling a booking ── */
.btn-danger { background: #B23B3B; color: #fff; box-shadow: 5px 5px 0 #7A2727; }
.btn-danger:hover { transform: translate(-2px,-2px); box-shadow: 7px 7px 0 #7A2727; }

/* ── Minimal footer note for shared-link pages ── */
.sentwith { margin-top: 3rem; text-align: center; font-size: .78rem; color: var(--cream-faint); }
.sentwith a { color: var(--amber); }

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

/* ── Question group: a required multiple-choice question in a booking form.
      Built on .choice (above), which already carries the selected state used by
      the day and time pickers. Added 2026-08-10 for /strategy-call, /call and
      /audit, which collect a bottleneck and a desired outcome so the pre-call
      brief has something concrete to work from.

      <fieldset class="qgroup" data-key="bottleneck"> is the contract the page
      JS reads: it collects data-key -> the selected .choice[data-value]. ── */
.qgroup { border: 0; padding: 0; margin: 0 0 1.5rem; }
.qgroup legend {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 11.5px; letter-spacing: .22em; text-transform: uppercase;
  color: var(--cream-dim); padding: 0; margin-bottom: .75rem;
}
.qopts { display: grid; gap: .55rem; }
@media (min-width: 620px) {
  /* Two across once there is room, but never orphan a lone option on its own
     row: the 5-option bottleneck question would otherwise leave one stranded. */
  .qopts { grid-template-columns: repeat(2, 1fr); }
  .qopts > .choice:last-child:nth-child(odd) { grid-column: 1 / -1; }
}
/* An unanswered required question has to be visible without scrolling back. */
.qgroup.invalid legend { color: var(--amber-hot); }
.qgroup.invalid .choice { border-color: rgba(232,120,90,.5); }

/* ── Tick list: a plain "what happens / what does not" list. The marker is a
      pseudo-element so the text still wraps to a clean left edge. ── */
.tick { list-style: none; margin: 0; padding: 0; }
.tick li {
  position: relative; padding-left: 1.5rem; margin-bottom: .7rem;
  font-size: .95rem; line-height: 1.55; color: var(--cream-dim);
}
.tick li::before {
  content: "\2713"; position: absolute; left: 0; top: 0;
  color: var(--amber); font-weight: 700;
}
.tick--muted li::before { content: "\00d7"; color: var(--cream-faint); }

/* ── Booking-page hero over a .scene.
      .hero-cast is an absolutely-positioned 200px band pinned to the bottom of
      its section (132px under 780px), so hero copy needs matching bottom
      clearance or the sprites render on top of the lede and the buttons. The
      homepage avoids this with .hero-grid reserving a photo column; a
      single-column booking hero has to reserve the space itself.

      Only needed at >=781px: below that, office-scene.csss mobile

/* -- Booking-page hero over a .scene.
      .hero-cast is an absolutely-positioned 200px band pinned to the bottom of
      its section (132px under 780px), so hero copy needs matching bottom
      clearance or the sprites render on top of the lede and the buttons. The
      homepage avoids this with .hero-grid reserving a photo column; a
      single-column booking hero has to reserve the space itself.

      Only needed at >=781px: below that, office-scene.css's mobile
      floor-clearance rules already pad the section. -- */
@media (min-width: 781px) {
  .scene--book > .wrap { padding-bottom: 236px; }
}

/* -- Auto-fitting card row that never strands a lone card beside dead space.
      `auto-fit minmax()` orphans the third of three cards at two-column
      widths. -- */
.fitcards { display: grid; gap: 1px; background: var(--line); border: 1px solid var(--line); }
@media (min-width: 620px)  { .fitcards { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .fitcards { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 620px) and (max-width: 999px) {
  .fitcards > :last-child:nth-child(odd) { grid-column: 1 / -1; }
}

/* -- Cameo vs the world layer.
      office-scene.css carries the world-layer `.sprite { order: 3 }`, which
      exists so a .cast stacks tag / bubble / sprite in the right order. Nothing
      in office.css sets `order` on .cameo > .sprite, so on any page that loads
      BOTH files the cameo portrait silently jumped to the END of the flex row
      and sat on the far right, away from the paragraph it belongs to.
      Measured 2026-08-10: order 3 on /audit, /strategy-call and /call, order 0
      on /work (which does not load office-scene.css).

      This is the collision office.css warns about when it bans .scene/.cast
      outside the homepage. The booking pages deliberately use both layers, so
      the interior pattern has to be restated after them. office-app.css is
      always loaded last, which is what makes this the right home for it. -- */
.cameo > .sprite { order: 0; }

/* -- Sheet: a modal panel for content that must NOT cost the visitor their page.
      Built for /strategy-call, where "How I work" used to link to the homepage.
      A cold visitor part-way through booking who leaves for the homepage meets
      its "Book a call" CTA and books a GENERIC call instead, losing the website,
      LinkedIn and intake answers this page exists to collect.

      Native <dialog> on purpose: showModal() gives focus trapping, Escape to
      close, focus return to the opener, and inert background content for free.
      Re-implementing those by hand is where accessible modals usually go wrong. -- */
.sheet {
  width: min(56rem, 92vw);
  max-height: min(86vh, 60rem);
  padding: 0;
  border: 1px solid var(--line-hot);
  border-radius: 4px;
  background: var(--deep);
  color: var(--cream);
  overflow: hidden;
  /* <dialog> defaults to the UA colour scheme; state ours explicitly. */
  box-shadow: 0 40px 90px rgba(0, 0, 0, .6);
}
.sheet::backdrop { background: rgba(11, 17, 14, .82); }

/* The scroll container is INSIDE the dialog so the sticky header stays put. */
.sheet-body { overflow-y: auto; max-height: inherit; padding: 0 clamp(1.2rem, 4vw, 2.6rem) clamp(1.6rem, 4vw, 2.6rem); }
.sheet-head {
  position: sticky; top: 0; z-index: 2;
  display: flex; align-items: flex-start; justify-content: space-between; gap: 1.5rem;
  padding: clamp(1.4rem, 4vw, 2.2rem) clamp(1.2rem, 4vw, 2.6rem) 1rem;
  background: linear-gradient(var(--deep) 78%, rgba(16, 26, 21, 0));
}
.sheet-close {
  flex-shrink: 0; cursor: pointer;
  width: 2.4rem; height: 2.4rem; line-height: 1;
  font-family: 'JetBrains Mono', ui-monospace, monospace; font-size: 1.1rem;
  color: var(--cream-dim); background: rgba(0, 0, 0, .25);
  border: 1px solid var(--line); border-radius: 3px;
  transition: color .18s ease, border-color .18s ease, background .18s ease;
}
.sheet-close:hover { color: var(--cream); border-color: var(--line-hot); background: rgba(0, 0, 0, .4); }

/* Panels inside a sheet must not lift on hover: there is no room to move, and
   the shadow collides with the scroll container edge. */
.sheet .panel { padding: 1.5rem; }
.sheet .panel:hover { transform: none; box-shadow: none; border-color: var(--line); }
.sheet .panel h3 { font-size: 1.35rem; }
.sheet-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); gap: 1rem; }

.sheet details.panel > summary { cursor: pointer; list-style: none; display: flex; justify-content: space-between; align-items: center; gap: 1rem; }
.sheet details.panel > summary::-webkit-details-marker { display: none; }
.sheet img { max-width: 100%; height: auto; }

/* Body scroll lock while a sheet is open. <dialog> makes the background inert
   but does NOT stop it scrolling underneath. */
html.sheet-open, body.sheet-open { overflow: hidden; }

@media (prefers-reduced-motion: no-preference) {
  .sheet[open] { animation: sheet-in .22s cubic-bezier(.2, 1, .3, 1); }
  @keyframes sheet-in { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } }
}

/* -- Sheet footer: the way back to the booking form must never be below the
      scroll fold. The panels are tall enough that the sheet scrolls on a
      laptop, and a visitor who cannot see a way back is a visitor who reaches
      for the browser Back button -- or the nav. Sticky, not fixed, so it sits
      inside the dialog's own scroll context. -- */
/* [open] is load-bearing here. Writing the selector as a bare .sheet would
   override the user-agent rule that hides a closed dialog, and the entire sheet
   would then render in normal page flow at the bottom of the page for every
   visitor. Caught only by a FULL-PAGE screenshot: every viewport-sized check
   passed, because the stray content sat below the fold. */
.sheet[open] { display: flex; flex-direction: column; }
.sheet-body { flex: 1 1 auto; max-height: none; }
.sheet-foot {
  flex-shrink: 0;
  display: flex; gap: .8rem; flex-wrap: wrap; align-items: center;
  padding: 1rem clamp(1.2rem, 4vw, 2.6rem);
  border-top: 1px solid var(--line);
  background: var(--surface);
}
.sheet-foot .hint { color: var(--cream-faint); font-size: .85rem; margin: 0; }

/* Same orphan rule as .fitcards: at two-column widths the third of three panels
   would sit alone at half width beside dead space. */
.sheet-grid > .panel:last-child:nth-child(odd) { grid-column: 1 / -1; }
