/* ==========================================================================
 * Firetech backend admin — TI LMS visual foundation (feature 029).
 *
 * Every rule from the Thought Industries LMS stylesheets that actually
 * matched the LMS homepage markup, merged into one file. Derived from the
 * live LMS CSS bundles named in the section headers below; generated,
 * not hand-written.
 *
 * Cascade order below is the same order the LMS loads them, so later
 * sections legitimately override earlier ones.
 *
 * A rule was included only when every class in its selector was present
 * in the source markup. Selectors in a comma-list that referenced unused
 * classes were pruned, so these rules are narrower than the originals.
 *
 * PRUNED AGAIN since, against THIS application's markup rather than the LMS
 * homepage's: any rule whose selector needs a class no view here renders was
 * removed, along with the @keyframes nothing animated and the Tailwind
 * runtime variables nothing dereferenced. What is left is what this app can
 * actually match. The counts in the section headers below are current.
 * ========================================================================== */

/* ------------------------------------------------------------------------
 * CUSTOM PROPERTIES + BASE TOKENS
 * Not class rules, but the rules below dereference these via var().
 *
 * What TI actually shipped here was three separate :root blocks: a
 * Tailwind/shadcn bundle whose colours were RGB TRIPLETS meant to be wrapped
 * as rgb(var(--x)) and never were -- every harvested rule below that
 * consumed one (--border, --muted, --accessibility_focus) was silently
 * drawing nothing, because `var(--rule-l)` only falls back to the
 * literal when --border is UNDEFINED, not when it's defined-but-invalid --
 * plus a tenant sheet of real hex values and two font-size clamps nothing
 * in this app's markup ever reached.
 *
 * All of it is replaced by the ONE token block this app actually uses: see
 * DESIGN TOKENS, in the hand-written region below. --accent-color,
 * --accent-hover-color, --accent-contrast-color, --secondary-color,
 * --secondary-hover-color and --secondary-contrast-color are still the
 * names the harvested rules below reference (.btn--primary, .btn--secondary,
 * .token-callout, .lesson) -- they're defined there now as aliases onto the
 * real brand tokens, which is how this file already lets hand-written
 * config override harvested rules without touching their selectors.
 * ------------------------------------------------------------------------ */

/* ==========================================================================
 * TI core platform  (86999-72a7b1b05f528fdbc77b.css)
 * 95 rules kept (17 further selectors pruned in a colour cleanup pass: they
 * matched no markup this app renders -- .top-bar .name's anchors, its
 * button/separator variants the app's button_to and plain <li> links never
 * produce, .header--microsite h1, .alert-box's anchor/button children, and
 * the .btn--bare.btn--primary/--secondary combinations no view uses)
 * ========================================================================== */
.alert-box { background-color: #000; border: 0 solid #000; color: #fff; display: block; font-size: .8125rem; font-weight: 400; margin-bottom: 1rem; padding: 1rem; position: relative; -webkit-transition: opacity .3s ease-out; transition: opacity .3s ease-out }
.alert-box.alert { background-color: #ce3532; border-color: #ba2f2c; color: #fff }
.row { margin: 0 auto; max-width: 100%; width: 100% }
.row:after,
.row:before { content: " "; display: table }
.row:after { clear: both }
.row .row { margin: 0 -1rem; max-width: none; width: auto }
.row .row:after,
.row .row:before { content: " "; display: table }
.row .row:after { clear: both }
.column { float: left; padding-left: 1rem; padding-right: 1rem; width: 100% }
@media only screen {
  .column { float: left; padding-left: 1rem; padding-right: 1rem; position: relative }
  .small-12 { width: 100% }
}
@media only screen and (min-width: 48.063em) {
  .column { float: left; padding-left: 1rem; padding-right: 1rem; position: relative }
  .medium-4 { width: 33.3333333333% }
  .medium-6 { width: 50% }
}
@media only screen and (min-width: 64.063em) {
  .column { float: left; padding-left: 1rem; padding-right: 1rem; position: relative }
}
form .row .row { margin: 0 -.5rem }
form .row .row .column { padding: 0 .5rem }
form .row input.column,
form .row textarea.column { padding-left: .5rem }
.top-bar { background: #fff; height: 3rem; line-height: 3rem; margin-bottom: 0; overflow: hidden; position: relative }
.top-bar ul { list-style: none; margin-bottom: 0 }
.top-bar .row { max-width: none }
.top-bar form,
.top-bar input { margin-bottom: 0 }
.top-bar input { font-size: .75rem; height: 1.75rem; padding-bottom: .35rem; padding-top: .35rem }
.top-bar button { font-size: .75rem; margin-bottom: 0; padding-bottom: .4125rem; padding-top: .4125rem }
@media only screen and (max-width: 40em) {
  .top-bar button { position:relative; top: -1px }
}
.top-bar .title-area { margin: 0; position: relative }
.top-bar .name { font-size: 16px; height: 3rem; margin: 0 }
.top-bar .name h1,
.top-bar .name h2,
.top-bar .name h3,
.top-bar .name h4,
.top-bar .name p,
.top-bar .name span { font-size: 1rem; line-height: 3rem; margin: 0 }
.top-bar-section { left: 0; position: relative; -webkit-transition: left .3s ease-out; transition: left .3s ease-out; width: auto }
.top-bar-section ul { display: block; font-size: 16px; height: auto; margin: 0; padding: 0; width: 100% }
.top-bar-section ul li { background: #f6f6f6 }
.top-bar-section ul li>a { color: #969797; display: block; font-family: inherit; font-size: 1rem; font-weight: 400; padding: 12px 0 12px 1rem; text-transform: none; width: 100% }
@media only screen and (min-width: 48.063em) {
  .top-bar { background:#fff; overflow: visible }
  .top-bar:after,
  .top-bar:before { content: " "; display: table }
  .top-bar:after { clear: both }
  .top-bar .title-area { float: left }
  .top-bar button,
  .top-bar input { font-size: .875rem; height: 1.75rem; position: relative; top: .625rem }
  .top-bar-section { left: 0!important; -webkit-transition: none 0 0; transition: none 0 0 }
  .top-bar-section ul { display: inline; height: auto!important; width: auto }
  .top-bar-section ul li { float: left }
}
.container { margin: 0 auto }
@media only screen and (min-width: 64.063em) {
  .container { max-width:90% }
}
@media only screen and (min-width: 90.063em) {
  .column { float: left; padding-left: 1rem; padding-right: 1rem; position: relative }
}
.header { margin-bottom: .5rem; padding: 1rem 1rem 0; position: relative }
.header:after { clear: both; content: ""; display: table }
.header .company__beta-logo { display: inline-block; max-height: 100px; max-width: 300px }
.header .company__beta-logo .company-logo__container img { max-height: 100px }
.header--microsite { margin-bottom: 0; padding: 0 }
@media only screen and (min-width: 48.063em) {
  .header--microsite { padding: 1rem 1rem 0 }
}
.header__inner:after { clear: both; content: ""; display: table }
.btn { -webkit-appearance: none; background-color: #f8fafc; border: 1px solid #d3d4de; border-radius: 2px; color: #333; cursor: pointer; display: inline-block; font-family: sans-serif,Arial,Helvetica; font-size: 14px; font-size: .875rem; font-weight: 400; line-height: normal; margin: 0 0 1rem; padding: .5em 1.25em; position: relative; text-align: center; text-decoration: none; -webkit-transition-duration: .25s; transition-duration: .25s; -webkit-transition-property: background-color,border-color,color,opacity; transition-property: background-color,border-color,color,opacity; -webkit-transition-timing-function: ease-in-out; transition-timing-function: ease-in-out }
.btn:hover { background-color: #fff; border-color: #bdbfc6; color: #333 }
.btn i { padding-right: .25em }
.btn input[type=file] { margin: 0 }
.btn div { line-height: normal }
.btn--primary { background-color: var(--accent-color); border-color: var(--accent-color); color: var(--accent-contrast-color) }
.btn--primary:active,
.btn--primary:focus,
.btn--primary:hover { background-color: var(--accent-hover-color); border-color: var(--accent-hover-color); color: var(--accent-contrast-color) }
.btn--secondary { background-color: var(--secondary-color); border-color: var(--secondary-color); color: var(--secondary-contrast-color) }
.btn--secondary:hover { color: var(--secondary-contrast-color) }
.btn--secondary:active,
.btn--secondary:focus,
.btn--secondary:hover { background-color: var(--secondary-hover-color); border-color: var(--secondary-hover-color) }
.btn--bare { background: none!important; border-color: transparent!important; -webkit-box-shadow: none; box-shadow: none; color: #000 }
.btn--bare:hover { color: #236c96 }
.btn[disabled] { -webkit-box-shadow: none; box-shadow: none; cursor: default; opacity: .25; pointer-events: none }
input[type=submit].btn { border-radius: 5px }
label.row { display: block; margin-bottom: 1em }
label .btn span { font-size: inherit }
.nav:after { clear: both; content: ""; display: table }
.nav ul { list-style: none; margin: 0; padding: 0 }
.nav ul:after { clear: both; content: ""; display: table }
.nav ul a,
.nav ul button { display: block; font-size: 16px; font-size: 1rem; padding: 1rem }
.nav li { float: left }
.nav--top ul { clear: both; float: none; text-align: center; width: 100% }
.nav--top ul a,
.nav--top ul button { font-size: 13px; font-size: .8125rem; text-transform: uppercase }
.nav--top li { display: inline-block; float: none }
@media only screen and (min-width: 48.063em) {
  .nav--top { float:right; margin: 0 }
  .nav--top ul { clear: none; float: right; text-align: left; width: auto }
  .nav--top li { display: block; float: left }
}
.nav--global-links { background-color: #e9e9e9; padding: 5px 5px; margin-right: 3em; margin-left: 3em; }
.nav--global-links>a { color: #4c4c4c; font-size: 12px; font-size: .75rem; padding-left: 2em }
.top-bar { border-bottom: 1px solid #e9e9e9; border-top: 1px solid #e9e9e9; clear: both; line-height: 1.45 }
.top-bar .name a { overflow: hidden; text-indent: 100% }
@media only screen and (min-width: 48.063em) {
  .top-bar { border-bottom:0; border-top: 1px solid #e9e9e9 }
  .top-bar .name { display: none }
}
.top-bar-section ul li { background: #fff; padding: 1px }
.top-bar-section ul li>a { color: #656565 }
.panel { background: #f6f6f6; border-radius: 5px; color: #656565; margin-bottom: 1em; padding: 1rem }
.panel:after { clear: both; content: ""; display: table }
.panel input { margin-bottom: 1rem }
.panel input.btn,
.panel input[type=checkbox] { margin-bottom: 0 }
@media print {
  .panel { border: 1px solid #e9e9e9 }
}
.hero { overflow: hidden; position: relative }
.hero img { width: 100% }
@media only screen and (min-width: 48.063em) {
  .hero .container { padding:0 1rem }
}
.container { background: #fff; -webkit-box-shadow: -2px 0 2px -2px rgba(0,0,0,.25),2px 0 2px -2px rgba(0,0,0,.25); box-shadow: -2px 0 2px -2px rgba(0,0,0,.25),2px 0 2px -2px rgba(0,0,0,.25); height: 100%; position: relative; width: 100% }
.container:after { clear: both; content: ""; display: table }

/* ==========================================================================
 * TI Tailwind build (94166-6fac07f8c88c2500336e.css)
 * 7 rules kept
 * ========================================================================== */
.container { width: 100% }
@media (min-width: 640px) {
  .container { max-width: 640px }
}
@media (min-width: 769px) {
  .container { max-width: 769px }
}
@media (min-width: 1024px) {
  .container { max-width: 1024px }
}
@media (min-width: 1440px) {
  .container { max-width: 1440px }
}
@media (min-width: 1920px) {
  .container { max-width: 1920px }
}
@media (min-width: 2560px) {
  .container { max-width: 2560px }
}

/* ==========================================================================
 * Tenant appearance (b2789d3a-...--c6434f33a8--v2.css)
 * 2 rules kept
 * ========================================================================== */
.top-bar-section ul li>a { font-size: 14px!important }
.btn { border-radius: 20px!important }

/* ==========================================================================
 * Firetech admin — supplemental styles (feature 029)
 *
 * Hand-written, not extracted from TI. Covers admin-specific patterns the
 * vendored TI CSS above has no rules for at all: bare <table>/<dl>, status
 * badges, the API-key token callout, code/log blocks, a danger button
 * variant, and small utility classes already referenced in ERB without a
 * backing rule. Appended last so it can freely build on TI's tokens
 * (--accent-color, --border, etc.) without fighting the cascade above.
 * ========================================================================== */

/* ------------------------------------------------------------------------
 * Base typography / utility classes
 * ------------------------------------------------------------------------ */
.muted { color: var(--muted-l); font-size: .875rem }
.empty { color: var(--muted-l); font-style: italic }

/* ------------------------------------------------------------------------
 * Tables (TI ships no bare-table rules — its own product renders lists via
 * JS components, not <table>)
 * ------------------------------------------------------------------------ */
.table-wrap { overflow-x: auto }
.table-wrap table { border-collapse: collapse; margin-bottom: 1rem; width: 100% }
.table-wrap th,
.table-wrap td { border-bottom: 1px solid var(--rule-l); padding: .5rem .75rem; text-align: left }
.table-wrap th { background-color: var(--paper-2); font-weight: 600 }
.table-wrap tbody tr:hover { background-color: var(--paper-2) }

/* ------------------------------------------------------------------------
 * Definition-list "card" — show-page metadata blocks. Targets the bare
 * element: every <dl> in the app (courses/show, workshops/show,
 * workshop_sessions/show, workshop_bundles/show, ti_course_groups/show,
 * ingestion_runs' _ingestion_run partial) is already a metadata block, so
 * no marker class is needed in the ERB.
 * ------------------------------------------------------------------------ */
dl { background: #fff; border: 1px solid var(--rule-l); border-radius: var(--radius); margin-bottom: 1rem; padding: 1rem 1.25rem }
dl dt { clear: left; color: var(--muted-l); float: left; font-weight: 600; margin-bottom: .5rem; width: 40% }
dl dd { margin-bottom: .5rem; margin-left: 40% }

/* ------------------------------------------------------------------------
 * Status indicators (semantic class names kept as-is in ERB per spec
 * Assumptions — only the backing rule is new)
 * ------------------------------------------------------------------------ */
.status-succeeded,
.api-key-status-active,
.link-status-confirmed { color: var(--success); font-weight: 600 }
.status-failed,
.link-status-rejected,
.link-status-broken { color: var(--danger); font-weight: 600 }
.api-key-status-expired { color: var(--muted-l); font-weight: 600 }

/* ------------------------------------------------------------------------
 * Token callout — newly-issued API key (layered on .panel; see markup)
 * ------------------------------------------------------------------------ */
.token-callout { border: 1px solid var(--accent-color); margin-bottom: 1rem }
.token-callout code { background: #fff; border: 1px solid var(--rule-l); border-radius: 3px; display: inline-block; font-family: Consolas, Menlo, monospace; margin-top: .5rem; padding: .5rem .75rem; word-break: break-all }

/* ------------------------------------------------------------------------
 * Code / raw-output block (ingestion-run failure detail, sync-run logs)
 * ------------------------------------------------------------------------ */
.code-block { background: #24272b; border-radius: 4px; color: #f1f5f9; font-family: Consolas, Menlo, monospace; font-size: .8125rem; overflow-x: auto; padding: 1rem; white-space: pre-wrap; word-break: break-word }

/* ------------------------------------------------------------------------
 * Course content sections (course show page) — visually distinct per US3
 * acceptance scenario 3
 * ------------------------------------------------------------------------ */
.lesson { border-left: 3px solid var(--accent-color); margin-bottom: 1.5rem; padding-left: 1rem }
.content-block { background: var(--paper-2); border-radius: 4px; margin-bottom: .75rem; padding: .75rem 1rem }
.content-block .block-label { color: var(--muted-l); font-size: .8125rem; margin-bottom: .5rem }

/* ------------------------------------------------------------------------
 * TI content-node tree (ti_course_groups/show)
 * ------------------------------------------------------------------------ */
.content-tree { border-left: 1px solid var(--rule-l); margin: 0 0 1rem; padding-left: 1rem }
.content-tree .node-type { background: var(--paper-2); border-radius: 3px; font-size: .75rem; margin-right: .5rem; padding: .125rem .375rem; text-transform: uppercase }

/* ------------------------------------------------------------------------
 * Destructive button variant (TI ships .btn/.btn--primary/.btn--secondary
 * but nothing destructive-flavored)
 * ------------------------------------------------------------------------ */
.btn--danger { background-color: var(--danger); border-color: var(--danger-border); color: #fff }
.btn--danger:active,
.btn--danger:focus,
.btn--danger:hover { background-color: var(--danger-hover); border-color: var(--danger-hover); color: #fff }

/* ==========================================================================
 * FEATURE 031 — PUBLIC STOREFRONT
 *
 * Hand-written, unlike everything above this fence.
 *
 * The region above is generated from the Thought Industries CSS bundles and is
 * regenerable; this section is not, so it is fenced and labelled to keep the
 * two apart. A future re-harvest should replace everything ABOVE this banner
 * and leave this block intact.
 *
 * FR-046 forbids adding a new stylesheet FILE, not new rules, and the
 * application loads exactly one stylesheet — so the storefront's few
 * genuinely-new needs live here. Everything else on the page reuses the
 * inherited vocabulary (.hero, .catalog-item, .panel, .grid, .row,
 * .btn--primary, .muted, .empty).
 * ========================================================================== */

/* ==========================================================================
 * DESIGN TOKENS
 *
 * The ONE palette block for the whole application. Brand colours first, then
 * neutrals, then status colours -- every hand-written rule below draws from
 * these, and so do the harvested TI rules above via the alias names at the
 * bottom of this block (.btn--primary, .btn--secondary, .lesson,
 * .token-callout reference --accent-color / --secondary-color, not the
 * tokens directly, because that's the name a future re-harvest expects to
 * find still defined).
 *
 * --ft-red is Fire Tech's real brand red, #9a2127 -- confirmed against the
 * tenant's own style guide, and NOT the #6f1111 a past pass here had guessed
 * from the tenant appearance sheet's --accent-color (that guess is gone now;
 * nothing references it any more). Every primary action in the application,
 * admin and storefront alike, is this one red.
 *
 * --fa-red is a DIFFERENT red: Fire Alarm, one of the three discipline
 * colours, bright enough to read as a live indicator over dimmed video. It
 * is never used for a button or a link -- only for discipline chips, the
 * hero's live dot and the storefront's accent marks (arrows, numbered list,
 * highlight stroke) that borrow it for visual consistency with "Fire Tech
 * red" even outside a discipline context.
 *
 * --fa-red-glow exists only because the pulse keyframe needs an alpha and a
 * hex token can't carry one.
 *
 * --ft-orange is the tenant's confirmed secondary brand colour (its hover
 * shade, --ft-orange-hover, is the exact value supplied with it -- kept
 * literal rather than computed, since it's a real design decision and not a
 * mechanical darkening of the base).
 *
 * --wb-blue and --gold are the other two discipline colours: Water-Based and
 * Fire Protection.
 * ========================================================================== */
:root {
  /* Brand */
  --ft-red: #9a2127;
  --ft-red-hover: color-mix(in srgb, var(--ft-red) 85%, black);
  --fa-red: #ed1f24;
  --fa-red-glow: rgba(237, 31, 36, .6);
  --wb-blue: #0d477d;
  --gold: #d9a321;
  --ft-orange: #e46d1e;
  --ft-orange-hover: #b95716;

  /* Neutrals -- one warm temperature, used everywhere instead of the mix of
   * cool slates (#e2e8f0, #cbd5e2...) and plain greys (#767676, #969797...)
   * this sheet used to carry. */
  --ink: #1a1614;
  --muted-l: #5a5550;
  --muted-d: #9a938a;
  --paper: #fff;
  --paper-2: #f5f5f5;
  --gray: #dadada;
  --rule-l: rgba(0, 0, 0, .1);
  --rule-d: rgba(255, 255, 255, .12);
  --white: #fff;
  --radius: .625rem;

  /* Status. One danger red application-wide, matching the one TI's own
   * vendored .alert-box.alert already shows in the admin nav -- there was no
   * good reason for the storefront's purchase pages to draw failure in a
   * different red (#a12) from what the admin surface was already stuck
   * with. One success green, replacing a duplicate literal on the admin
   * status labels. */
  --danger: #ce3532;
  --danger-border: #ba2f2c;
  --danger-hover: #a52a28;
  --success: #047857;
  --focus: #2ed0e1;

  /* Aliases the harvested TI rules above dereference by these exact names
   * (see the CUSTOM PROPERTIES note at the top of the file). Redirecting
   * these here is how .btn--primary, .btn--secondary, .token-callout and
   * .lesson pick up the real brand colours without editing a harvested
   * selector. */
  --accent-color: var(--ft-red);
  --accent-hover-color: var(--ft-red-hover);
  --accent-contrast-color: var(--white);
  --secondary-color: var(--ft-orange);
  --secondary-hover-color: var(--ft-orange-hover);
  --secondary-contrast-color: var(--ink);
}

/* --------------------------------------------------------------------------
 * Discipline colours (FR-041: labels, colours and identifiers live in
 * application code — see app/models/discipline.rb).
 *
 * FR-037: colour NEVER carries the meaning alone. Every chip contains its
 * label text, so the discipline survives colour-blindness, greyscale printing
 * and a screen reader. These rules are decoration on top of a working label.
 *
 * Tints are derived from the real discipline tokens with color-mix(), not
 * hand-picked pastels: a past pass here invented nine literals before
 * --fa-red/--wb-blue/--gold existed, and FA's had drifted onto the old
 * legacy red (#6f1111) while FP was picked as green instead of the gold
 * --gold and .cat-fp already agree on. Deriving the tint from the same
 * token the event rows use means the two can't disagree again. */
.discipline--fa { background: color-mix(in srgb, var(--fa-red) 12%, white);
  color: color-mix(in srgb, var(--fa-red) 70%, black);
  border-color: color-mix(in srgb, var(--fa-red) 45%, white) }
.discipline--wb { background: color-mix(in srgb, var(--wb-blue) 12%, white);
  color: color-mix(in srgb, var(--wb-blue) 70%, black);
  border-color: color-mix(in srgb, var(--wb-blue) 45%, white) }
.discipline--fp { background: color-mix(in srgb, var(--gold) 12%, white);
  color: color-mix(in srgb, var(--gold) 70%, black);
  border-color: color-mix(in srgb, var(--gold) 45%, white) }

/* --------------------------------------------------------------------------
 * Filter bar. A plain GET form (FR-017) — no script behind filtering.
 * -------------------------------------------------------------------------- */
.schedule-filters { display: flex; flex-wrap: wrap; gap: 1rem; align-items: flex-end;
  margin: 0 0 1.25rem; padding: 1rem; background: var(--paper-2); border: 1px solid var(--rule-l);
  border-radius: .625rem }
.schedule-filters__field { display: flex; flex-direction: column; gap: .25rem }
.schedule-filters__field label { font-size: .8125rem; font-weight: 600 }
.schedule-filters__actions { display: flex; gap: .5rem; align-items: center }
.schedule__count { font-size: .875rem; color: var(--muted-l); margin: 0 0 1rem }

/* --------------------------------------------------------------------------
 * Run cards and locations.
 * -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
 * FR-038: a visible focus indicator on everything interactive. Applied to the
 * storefront only, so the admin surface keeps whatever the harvested bundle
 * gives it.
 * -------------------------------------------------------------------------- */
.storefront a:focus-visible,
.storefront button:focus-visible,
.storefront select:focus-visible,
.storefront input:focus-visible,
.storefront summary:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px }

/* Visually hidden but present for assistive technology — used for the filter
   form's heading, which contributes to the outline without repeating itself
   on screen. */
.visually-hidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0 }

/* ==========================================================================
 * FEATURE 032 — GLOBAL-LINKS BAR
 *
 * Also hand-written, and inside the same fence as feature 031 above: a future
 * TI re-harvest replaces everything ABOVE that banner and must leave this
 * intact. The bar itself is drawn entirely by the harvested .nav /
 * .nav--global-links vocabulary; what is added here is layout and behaviour.
 * ========================================================================== */

/* THE PAGE GUTTER — which the boxed page observes and the global bar does not.
 *
 * The gutter holds .container off the window edge. It lives on <body> rather
 * than on .container itself because .container relies on `margin: 0 auto` to
 * centre inside its max-width; overwriting those auto margins to inset it would
 * pin the boxed page to the left on any wide screen.
 *
 * The user agent's own <body> margin is replaced rather than kept: it is 8px,
 * it varies between engines, and it is not a designed value.
 *
 * A CUSTOM PROPERTY, not a literal, because the bar below has to cancel this
 * exact value. Written twice as `1rem` the two would silently disagree the
 * first time either was tuned, and the failure is a bar that ALMOST reaches the
 * edge — the kind of thing that survives review. The sheet already leans on
 * custom properties (--font, --accent-color), so this is the local idiom.
 *
 * SCOPED TO .storefront, the public layout's body class. The admin surface
 * renders the same boxed .container and is not asking to change; a global rule
 * would move it too, for no reason connected to this page. */
/* The grey the page sits on is #e9e9e9 — the SAME value the harvested
 * .nav--global-links already paints itself, so the bar needs no rule of its own
 * and the two read as one continuous surface with the white .container floating
 * on it. That shared value is also what makes .container's inherited left/right
 * box-shadows legible; against a white surround they were invisible.
 *
 * Set on <body>, which the CSS background-propagation rules paint across the
 * whole canvas as long as <html> stays transparent — so short pages are grey
 * all the way down rather than grey to the fold and then white. */
body.storefront { --page-gutter: 1rem; margin: 0; padding: 0 var(--page-gutter);
  background-color: #e9e9e9 }

/* The bar is full length: it spans the window edge to edge, ignoring the
 * gutter that insets the boxed page beneath it. Negative side margins pull it
 * back out of the <body> padding by exactly the amount that padding added, so
 * there is one value to change and no way for the two to fall out of step.
 *
 * The links keep their inherited `padding-left: 2em`, so the first label is not
 * flush against the window even though the grey band is.
 *
 * margin-bottom is the space between the bar and .container. The harvested
 * .nav--global-links gives the bar its own `padding: 3px 0` but no outside
 * margin, because on the TI site it butts directly against what follows. */
.storefront {
  margin-bottom: 1rem;
  margin-left: calc(-1 * var(--page-gutter));
  margin-right: calc(-1 * var(--page-gutter))
}

/* Global chrome is not printed. The harvested sheet expressed this on
 * .global-nav__container, the wrapper this bar deliberately no longer uses, so
 * the behaviour is re-attached here rather than silently lost. */
@media print {
  .nav--global-links { display: none }
}

/* The bar's links are not underlined.
 *
 * Everything else about this text already comes from the harvested
 * `.nav--global-links > a` rule — colour #4c4c4c, font-size .75rem,
 * padding-left 2em — so this adds one declaration to that same selector rather
 * than introducing a class. There is no base `a { text-decoration: none }`
 * anywhere in the sheet and no no-underline utility to reuse, which is why the
 * declaration has to be written at all: without it the user agent underlines
 * these four links and the bar does not match its source. */
.nav--global-links > a { text-decoration: none }

/* ==========================================================================
 * FEATURE 032 — PAGE BOX PADDING
 * ========================================================================== */

/* The boxed page holds its contents off its own edges: 1rem at the top and
 * sides, nothing at the foot, which is the TI computed style for .container.
 *
 * THIS IS THE SECOND INSET, NOT THE FIRST. `body.storefront` above already
 * carries a --page-gutter that holds the whole white box off the WINDOW edge.
 * This one is inside that box, between its edge and the header, nav and main
 * that sit in it. The two are independent: the gutter is cancelled by the
 * global-links bar's negative margins so the grey band can run full width,
 * and this padding is not, because nothing inside the box wants to escape it.
 *
 * THE FOOT IS DELIBERATELY 0, as in the TI computed style. What lands at the
 * bottom of the box is <main>, and the storefront sections inside it carry
 * their own trailing margins, so a padding-bottom here would add a second gap
 * under the last one rather than the only gap.
 *
 * IT IS ADDED HERE RATHER THAN EDITED INTO `.container` AT THE TOP OF THE
 * SHEET, which is in the harvested region a re-harvest overwrites. Same
 * specificity, later in the file, so it wins and survives. The Tailwind
 * `.container` rules further down set width and max-width only and do not
 * compete.
 *
 * IT ONLY WORKS BECAUSE OF THE border-box RESET added with the typography
 * below. `.container` is `width: 100%`; under the content-box default this
 * padding would have made it 32px WIDER than its parent and pushed the box
 * shadow off both edges of the window.
 *
 * NOT SCOPED TO .storefront, unlike the body gutter above. The gutter moves
 * the whole box and the admin surface had no reason to move; this is the
 * inside of a box the admin layout now renders too, and content flush against
 * a shadowed white edge is the same mistake on either surface. Scoping it is a
 * one-word change if the admin should stay flush.
 *
 * The header keeps its own `padding: 1rem 1rem 0` from the harvested region,
 * so header content sits 2rem from the box edge -- as it does on TI, where
 * both rules are present. */
.container { padding: 1rem 1rem 0 }

/* ...AND EVERYTHING INSIDE IT SHARES THAT ONE INSET, rather than adding a
 * second of its own.
 *
 * `.header--microsite` is `padding: 0` in the harvested region and then
 * `padding: 1rem 1rem 0` again inside the >= 48.063em block -- the same three
 * values the container now carries. Above that breakpoint the two stacked and
 * put the logo and menus 2rem from the box edge while the page around them sat
 * at 1rem. Media queries carry no specificity of their own, so this later
 * top-level rule wins over the one inside the block.
 *
 * The storefront nav had the same problem and is fixed at its own rule in the
 * feature 031 block above, where it is hand-written and can be edited directly
 * rather than overridden.
 *
 * Only the horizontal and top insets are the container's to give. A child that
 * wants space between ITSELF and its neighbours still owns that, and keeps its
 * own vertical margins. */
.header--microsite { padding: 0 }

/* ==========================================================================
 * FEATURE 032 — MAIN MENU (.mainnav)
 *
 * The header's main menu is NO LONGER the harvested Foundation top bar. It was
 * built out of .top-bar / .top-bar-section / .has-dropdown / .dropdown, whose
 * rules lay the row out with `float: left`, pin the bar to a hard
 * `height: 3rem`, hide submenus with `clip: rect(1px,1px,1px,1px)`, indent
 * every link by a fixed 1rem that a negative margin on the list then had to
 * cancel, and draw the dropdown arrow as an absolutely-positioned border
 * triangle. Six overrides in this sheet existed only to fight those values,
 * and the tenant sheet's `.top-bar-section ul li > a { font-size: 14px
 * !important }` sat on top of all of it.
 *
 * All of that is gone. The markup now carries ONE class name, `.mainnav` on
 * the <nav>, and everything below is addressed by element and relationship --
 * so no harvested rule matches the menu any more and nothing here is an
 * override. Sizes are the gaps and paddings themselves; nothing is a
 * correction to something else.
 *
 * The admin layout still renders .top-bar / .top-bar-section, so those
 * harvested rules stay in the sheet untouched -- they simply no longer reach
 * the storefront.
 * ========================================================================== */

/* THE BAR. One flex line: the links from the left, the button pushed right by
 * `margin-left: auto` rather than by `justify-content: space-between`. Both
 * put it at the right while everything fits on one line; they part company
 * when the row wraps, where space-between would drop the button at the LEFT
 * edge of its new line and an auto margin keeps it right on whatever line it
 * lands on.
 *
 * NO height AND NO line-height. The bar is as tall as the links inside it,
 * which is what lets it grow a second line on a narrow window instead of
 * clipping one. */
.mainnav {
  align-items: center;
  border-bottom: 1px solid var(--rule-l);
  border-top: 1px solid var(--rule-l);
  display: flex;
  flex-wrap: wrap;
  gap: .25rem 1rem;
  padding: .25rem 0
}
.mainnav > ul {
  display: flex;
  flex: 1 1 auto;
  flex-wrap: wrap;
  gap: 0 1.5rem;
  list-style: none;
  margin: 0;
  padding: 0
}
.mainnav > .btn { flex: none; margin: 0 }

/* THE LINKS. Scoped through `li` and NOT written as `.mainnav a`, which would
 * also match the button beside the list: it is an <a> too, and this rule
 * outranks both `.btn`'s padding and `.btn--primary`'s colour, so the button
 * came out flush against its own text in near-invisible grey.
 *
 * The gap between items is the flex `gap` above, not padding, so the first
 * item needs no negative margin to sit flush with the logo. The vertical
 * padding is the whole of the row's height. */
.mainnav li a {
  color: var(--muted-l);
  display: block;
  font-size: .875rem;
  padding: .625rem 0;
  text-decoration: none
}
.mainnav li a:hover,
.mainnav li a:focus { color: var(--ft-red) }

/* THE SUBMENU, SMALL SCREENS FIRST: every submenu is a stack, open and
 * indented whenever its parent list is showing. Nothing here decides
 * WHETHER the top-level list shows -- that is the toggle block below. */
.mainnav ul ul {
  list-style: none;
  margin: 0;
  padding: 0 0 .25rem 1rem
}
.mainnav ul ul a { padding: .25rem 0 }

/* MAIN MENU, SMALL SCREENS: a CSS-only checkbox toggle, not a hamburger
 * driven by script -- FR-047 keeps JavaScript off this page, and an
 * unwired hamburger button is exactly the inert control FR-040 forbids.
 * The checkbox (.visually-hidden in the markup, so it stays in the tab
 * order) and its label live in the erb; only visibility and layout are set
 * here. `.mainnav > ul` -- not `.mainnav ul` -- so this reaches the
 * top-level list only, never the per-item submenus governed above.
 *
 * Collapsed by default below the breakpoint so the hero and event list
 * aren't buried under every discipline sub-link on a phone; the checked
 * state reveals the same list the wide-screen rule always shows. */
@media only screen and (max-width: 48.062em) {
  .mainnav-toggle {
    background: none;
    border: 1px solid var(--rule-l);
    border-radius: .375rem;
    color: var(--muted-l);
    cursor: pointer;
    font-size: .875rem;
    order: -1;
    padding: .5rem .75rem
  }
  .mainnav-toggle-input:focus-visible + .mainnav-toggle { outline: 2px solid var(--ft-red); outline-offset: 2px }
  .mainnav-toggle-input:checked + .mainnav-toggle { color: var(--ft-red) }
  .mainnav > ul { display: none; flex-basis: 100%; order: 1 }
  .mainnav-toggle-input:checked ~ ul { display: flex }
  .mainnav > .btn { order: 2 }
}
@media only screen and (min-width: 48.063em) {
  .mainnav-toggle { display: none }
}

/* THE SUBMENU, WIDE SCREENS: a panel under its own item, shown on hover and on
 * focus. `:focus-within` and not `a:focus + ul` -- focus-within stays true
 * while focus is anywhere inside the item, so a keyboard user tabbing from the
 * trigger INTO the menu keeps it open; the +-sibling form collapses it the
 * moment the trigger blurs and leaves the links focusable but invisible.
 *
 * `display: none` is what closes it, which also takes the links out of the tab
 * order while closed -- so the only way in is through the trigger, and
 * focusing the trigger is itself :focus-within on the <li>.
 *
 * 48.063em is the breakpoint the harvested sheet uses throughout. */
@media only screen and (min-width: 48.063em) {
  .mainnav > ul > li:has(ul) > a:after { content: " \25be" }
  .mainnav ul ul {
    background: var(--paper);
    border: 1px solid var(--rule-l);
    border-radius: .375rem;
    box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .12);
    display: none;
    left: 0;
    min-width: 11rem;
    padding: .25rem 0;
    position: absolute;
    top: 100%;
    z-index: 99
  }
  .mainnav li:hover > ul,
  .mainnav li:focus-within > ul { display: block }
  .mainnav ul ul a { padding: .5rem 1rem; white-space: nowrap }
}

/* ==========================================================================
 * FEATURE 032 — EVENT LIST
 *
 * Replaces the grid of cards feature 031 rendered the schedule with. The class
 * names are the supplied ones; the discipline colours are the real tokens.
 * ========================================================================== */

/* Where every schedule link lands. How far below the top of the window an anchor stops
 * is the one site-wide scroll-padding-top on <html> (see "Everything directly in <main>"),
 * so the count line above the results stays visible and the page does not look as though
 * it has been cropped. Smooth scrolling to it is set in the MOTION block below,
 * only for a visitor who has not asked for reduced motion. (It used to be left off
 * so a same-page click and a full navigation would behave alike; nearly every menu
 * link is now a same-page anchor, so the glide is what a visitor expects.) */

.event-list { display: flex; flex-direction: column; gap: 1rem; margin: 0 0 1.5rem }

/* A row is a four-column grid on a wide screen and a stack on a narrow one.
 * The date and side columns are fixed because their content is bounded -- a
 * date and a price -- while the body takes whatever is left. minmax(0, 1fr)
 * rather than 1fr: a grid track defaults to min-content floor, and a long
 * unbroken course name would otherwise push the row wider than its container. */
.event { background: var(--paper); border: 1px solid var(--rule-l);
  border-radius: .625rem; display: grid; gap: 1.25rem; overflow: hidden;
  padding: 1.25rem }
@media only screen and (min-width: 48.063em) {
  .event { align-items: start;
    grid-template-columns: 5.5rem 9rem minmax(0, 1fr) 11rem }
}

/* The left edge is the discipline, and it is the ONLY thing carrying colour on
 * the row. FR-037 requires that colour never be the sole signal, which it is
 * not: every row also names its discipline in .event-meta. */
.event { border-left: 4px solid var(--gray) }
.event.cat-fa { border-left-color: var(--fa-red) }
.event.cat-wb { border-left-color: var(--wb-blue) }
.event.cat-fp { border-left-color: var(--gold) }

.event-date { text-align: center }
.event-date .mo { color: var(--muted-l); font-size: .75rem; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase }
.event-date .day { font-size: 2.25rem; font-weight: 700; line-height: 1 }
.event-date .range { color: var(--muted-l); font-size: .75rem; margin-top: .25rem }

/* The plate the course code sits on, where the supplied markup had a
 * photograph. Sized by aspect-ratio rather than a height so it keeps its shape
 * at every column width, and tinted per discipline so the row reads at a
 * glance. Feature 045 put the photograph back as an OPTIONAL layer underneath
 * (see below) that covers the plate when it loads: the plate and its course
 * code are the fallback, so the icon never has to be there for the row to work. */
.event-thumb { align-items: center; aspect-ratio: 4 / 3; background: var(--paper-2);
  border-radius: .5rem; display: flex; justify-content: center; overflow: hidden;
  position: relative }
/* The workshop icon is a LAYER ABOVE .cat-tag. Absolutely positioned, so it
 * paints over the in-flow pill and takes no part in sizing the box. A loaded
 * icon hides the course code; a missing, slow or 404ing one paints nothing
 * (its alt is empty), so the plate and the code show through with no layout
 * shift and no script to detect the failure. `contain`, not `cover`: the whole
 * image is shown, letterboxed on the plate, rather than cropped to the box.
 * The fixed aspect-ratio above is what reserves the space either way. */
.event-thumb img { height: 100%; inset: 0; object-fit: contain; position: absolute;
  width: 100% }
.cat-tag { border-radius: 999px; color: var(--white); font-size: .75rem;
  font-weight: 700; letter-spacing: .06em; padding: .3rem .7rem;
  text-transform: uppercase }
.cat-fa .cat-tag { background: var(--fa-red) }
.cat-wb .cat-tag { background: var(--wb-blue) }
.cat-fp .cat-tag { background: var(--gold); color: var(--ink) }

.event-meta { align-items: center; color: var(--muted-l); display: flex;
  flex-wrap: wrap; font-size: .8125rem; gap: .5rem }
/* The separator is drawn, not typed: a literal "·" between spans is read out
 * by a screen reader as "middle dot" on every row. */
.event-meta .dot { background: currentColor; border-radius: 50%; height: 3px;
  opacity: .55; width: 3px }

.event-title { font-size: 1.125rem; margin: .5rem 0 .35rem }
.event-sub { color: var(--muted-l); margin: 0 0 .75rem }

.event-side { display: flex; flex-direction: column; gap: .5rem }
.event-price { margin: 0 }
.event-price__was { color: var(--muted-l); margin-right: .35rem }
.event-price__amount { font-size: 1.5rem }
.event-price__per-seat { color: var(--muted-l); font-size: .8125rem }
.event-price__tax-note { display: block; font-size: .75rem }
.event-deadline { font-size: .75rem; margin: 0 }

.event-enroll { align-items: center; background: var(--ft-red); border-radius: 999px;
  color: var(--white); display: inline-flex; font-size: .8125rem;
  font-weight: 700; gap: .4rem; justify-content: center; padding: .55rem 1rem;
  text-decoration: none }
.event-enroll:active,
.event-enroll:focus,
.event-enroll:hover { background: var(--ft-red-hover); color: var(--white) }

/* Wireframe integration: See Details, the quiet sibling of Enroll Now -- same shape, outlined,
 * so the primary action stays the only filled button on the row. */
.event-learn, .event-bundle { align-items: center; border: 1px solid var(--ft-red); border-radius: 999px;
  color: var(--ft-red); display: inline-flex; font-size: .8125rem; font-weight: 700;
  justify-content: center; padding: .5rem 1rem; text-decoration: none }
.event-learn:active,
.event-learn:focus,
.event-learn:hover { background: var(--ft-red); color: var(--white) }

/* Bundle it! is gold, filled, with dark text (white on gold fails contrast). */
.event-bundle { background: var(--gold); border-color: var(--gold); color: #1a1614 }
.event-bundle:active,
.event-bundle:focus,
.event-bundle:hover { background: color-mix(in srgb, var(--gold) 82%, black); border-color: color-mix(in srgb, var(--gold) 82%, black); color: #1a1614 }

/* ==========================================================================
 * FEATURE 032 — PAGINATION
 * ========================================================================== */

.pagination { align-items: center; display: flex; flex-wrap: wrap; gap: .5rem;
  justify-content: center; margin: 0 0 2rem }
.pagination__pages { display: flex; flex-wrap: wrap; gap: .25rem; list-style: none;
  margin: 0; padding: 0 }
.pagination__page,
.pagination__step { border: 1px solid var(--rule-l); border-radius: .375rem;
  display: block; min-width: 2.25rem; padding: .4rem .6rem; text-align: center;
  text-decoration: none }
/* The current page is a <span>, so this styles a non-link. currentColor for the
 * border keeps the two in step whatever the fill becomes. */
.pagination__page.is-current { background: var(--ft-red); border-color: currentColor;
  color: var(--white); font-weight: 700 }
a.pagination__page:hover,
a.pagination__step:hover { background: var(--paper-2) }

/* ==========================================================================
 * FEATURE 032 — VIDEO HERO
 *
 * NOT HUNG OFF `.hero`. That class is in the harvested region, so a rule on it
 * reaches whatever else adopts it later; every rule here is addressed to an
 * inner class name that only this hero has. (It once had to share `.hero` with
 * the copy hero below it, which has since been removed.) The two things
 * `.hero` does provide -- `position: relative` and `overflow: hidden` -- are
 * already in the harvested rule and are what the absolute positioning below
 * hangs on, so nothing needs restating.
 * ========================================================================== */

/* The hero's live indicator draws its dot and glow from --fa-red /
 * --fa-red-glow -- Fire Alarm's discipline red, not the brand red -- because
 * it's the brighter of the two and is what stays visible over dimmed video.
 * Every token used in this section (--fa-red, --fa-red-glow, --ft-red,
 * --ink, --paper, --paper-2...) is defined once, in the DESIGN TOKENS block
 * near the top of this fence; nothing below re-declares them.
 *
 * The video breaks out to the edges of the boxed page. `.container` insets its
 * children by 1rem; a hero that stops short of the box edge reads as a picture
 * in the page rather than the top of it. The negative margins cancel exactly
 * that padding -- the same trick the global-links bar uses against the body
 * gutter, and the same reason it is written as one value here.
 *
 * The height is what gives the whole hero its size: `.hero` itself has none,
 * and the overlay and content are absolutely positioned and contribute
 * nothing. clamp() keeps it from swallowing a short laptop viewport or
 * collapsing on a phone. */
.hero-media { background: #000; height: clamp(18rem, 40vh, 24rem); margin: 0 -1rem; overflow: hidden;
  position: relative }
/* The banner photo fills the box and is cropped to it. object-position keeps the equipment in
 * the upper middle on a wide screen and the middle of the picture on a phone, where the
 * box is nearly as tall as it is wide. */
.hero-banner { height: 100%; inset: 0; object-fit: cover; object-position: 50% 35%;
  position: absolute; width: 100% }

/* Dark at the bottom, clear at the top, and darker on the left where the headline sits: the
 * content sits low and to the left, and this is what keeps white text legible over whatever
 * photo is behind it without dimming the whole picture. pointer-events: none so the overlay
 * cannot swallow a click meant for the buttons beneath it. */
.hero-overlay { background: linear-gradient(to bottom, rgba(0,0,0,.15) 0%,
  rgba(0,0,0,.35) 45%, rgba(0,0,0,.78) 100%),
  linear-gradient(to right, rgba(0,0,0,.5) 0%, rgba(0,0,0,.2) 55%, rgba(0,0,0,0) 85%);
  inset: 0; pointer-events: none; position: absolute }

/* Content is inset by the container's own 1rem, NOT by the video's break-out,
 * so the copy lines up with the header above it while the picture runs wider. */
/* NO `gap` HERE ANY MORE. The tag now carries a `margin-bottom: 24px` of its
 * own, and a gap does not replace a margin -- it adds to it, which would have
 * made that supplied 24px silently 44px. Spacing between the three rows is
 * therefore each row's own margin, and the supplied number is the number. */
.hero-content { color: #fff; display: flex; flex-direction: column;
  inset: 0; justify-content: flex-end; padding: 2rem 1rem; position: absolute }

.small-caps { font-size: .6875rem; font-weight: 700; letter-spacing: .12em;
  text-transform: uppercase }

/* `align-self: flex-start` IS LOAD-BEARING, and it is the one declaration here
 * that was not in the supplied CSS. .hero-content is a column flex container,
 * so its children stretch to full width by default. That was invisible while
 * the tag was bare text; the moment it takes a background and a border it
 * becomes a chip, and without this it would be a chip the full width of the
 * hero. Shrinking to its contents is what makes it read as a pill.
 *
 * The padding and radius were not supplied either. A border with no padding
 * sits hard against the text, so they are chosen -- adjust freely; nothing
 * else depends on them.
 *
 * `backdrop-filter` is what makes the 6%-white fill read as frosted glass over
 * the video rather than as a flat grey wash. Where it is unsupported the chip
 * degrades to exactly that flat wash, which is legible, just less pretty. */
.hero-tag { align-items: center; align-self: flex-start;
  backdrop-filter: blur(8px); background-color: rgba(255, 255, 255, .06);
  border: 1px solid rgba(255, 255, 255, .22); border-radius: 999px;
  display: flex; gap: .5rem; margin-bottom: 24px; padding: .5rem .875rem }

.hero-tag .live { animation: pulse 1.6s ease-in-out infinite;
  background: var(--fa-red); border-radius: 999px; flex: none; height: 7px;
  width: 7px }

/* The keyframes were not supplied, only the animation shorthand naming them.
 *
 * TWO PROPERTIES, NOT ONE. An expanding ring alone is what this was, and it
 * reads as a halo rather than as a light: the dot itself never changed, so
 * nothing appeared to flash. Dimming the dot on the same beat is what makes it
 * look like something switching on and off, and the ring is then the glow
 * around it rather than the whole effect.
 *
 * Only `opacity` and `box-shadow` animate -- neither affects layout, so the
 * chip beside it never reflows on any frame. The ring ends on `transparent`
 * rather than a zero-alpha literal, so it fades out in whatever colour
 * --fa-red-glow happens to be. */
@keyframes pulse {
  0%, 100% { box-shadow: 0 0 0 0 var(--fa-red-glow); opacity: 1 }
  50% { box-shadow: 0 0 0 .4rem transparent; opacity: .45 }
}

/* Stopped rather than never started: the animation above is declared exactly as
 * supplied, and this turns it off only for a reader who has asked for less
 * motion. (The hero used to autoplay a video; it is a still photo now.) */
@media (prefers-reduced-motion: reduce) {
  .hero-tag .live { animation: none }
}

/* clamp() rather than a breakpoint: the headline is the one element here big
 * enough that a fixed size is wrong at both ends of the range. */
/* Wireframe integration: 52px at most, as in the wireframe, so the headline, lede and stats
 * fit the shorter hero. */
.hero-headline { font-size: clamp(2rem, 4.5vw, 3.25rem); font-weight: 700;
  letter-spacing: -.02em; line-height: 1.05; margin: 0 0 1.5rem }

/* THE SUPPLIED RULE IS ON THE <em>, NOT ON THE WHOLE HEADLINE, and that is a
 * reading of the request rather than a transcription of it. Three things in
 * the supplied declaration only make sense for the inner word: `font-style:
 * italic` would otherwise italicise "Fire Tech" as well; `padding: 0 6px` is
 * the bleed a highlighter needs either side of a WORD; and the gradient's band
 * sits between 65% and 95% of the element's height, which is a marker stroke
 * through one line of text and would be a stripe across the whole block if it
 * spanned both lines. Move the selector up to `.hero-headline` if the stripe
 * across both lines is what was wanted. .highlight-red is the same stroke for
 * a phrase outside the hero (storefront/_problem). */
.hero-headline em,
.highlight-red { background: linear-gradient(180deg, transparent 0%,
  transparent 65%, var(--ft-red) 65%, var(--ft-red) 95%, transparent 95%);
  font-style: italic; font-weight: 700; padding: 0 6px }

.hero-foot { align-items: flex-end; display: flex; flex-wrap: wrap; gap: 1.5rem;
  justify-content: space-between }

/* THE BUTTONS ARE `.btn` FIRST. The markup pairs the harvested base class with
 * TI's single-dash `.btn-primary` / `.btn-ghost`, which are NOT this
 * application's `.btn--primary`. What the base brings that a hero does not
 * want is the 1rem bottom margin it uses in forms; the rest -- the pill
 * radius, the padding, the transition -- is wanted and left alone. */
/* Wireframe integration: the lede under the headline, at the hero's own 82% white. */
/* On a phone the lede wraps to five lines and the two buttons stack, so the hero
 * needs more height than the laptop's 24rem or the headline is pushed off the top. */
@media only screen and (max-width: 48em) {
  .hero-media { height: 33rem }
}

.hero-lede { color: rgba(255, 255, 255, .82); font-size: 1.0625rem;
  line-height: 1.55; margin: 0 0 1.5rem; max-width: 38rem }
.hero-cta-row { display: flex; flex-wrap: wrap; gap: .75rem }
.hero-cta-row .btn { align-items: center; display: inline-flex; gap: .5rem;
  margin: 0 }

/* Transparent over the video, so its border and label are the only things
 * drawn. The hover fill is white at low alpha rather than a colour, so it
 * works over any frame. */
.hero-cta-row .btn-ghost { background-color: transparent;
  border-color: rgba(255, 255, 255, .65); color: #fff }
.hero-cta-row .btn-ghost:active,
.hero-cta-row .btn-ghost:focus,
.hero-cta-row .btn-ghost:hover { background-color: rgba(255, 255, 255, .14);
  border-color: #fff; color: #fff }

/* ==========================================================================
 * FEATURE 089 — DISCIPLINE PAGE HERO
 *
 * storefront/disciplines/show. Reuses the home page's .hero / .hero-media /
 * .hero-overlay / .hero-content / .hero-headline / .hero-lede above wholesale
 * rather than a second full-bleed mechanism: `.hero` as a direct child of
 * <main> is exactly what `main > :not(.hero)` (FEATURE 070, below) already
 * exempts from the content gutter, so nesting the discipline hero there is
 * what makes it full-bleed. Nothing new to break out of the column.
 *
 * .hero-lede here wraps the discipline's own intro partial (_fa/_wb/_fp,
 * still a plain <p>, unchanged) instead of replacing it, so that partial
 * stays the one place a discipline's words are edited (FR-004).
 * ========================================================================== */
/* image_slot (storefront_helper.rb) always wraps a photo in <figure class="media
 * media--banner">, which carries its OWN box model (a 21/6 aspect-ratio, rounded
 * corners, a top margin) for every other caller of size: :banner (the Enroll
 * Now and Bundle It! popups, FR-011, untouched: they are not inside .hero-media).
 * Here the figure needs to fill .hero-media's own box instead, the same way
 * .hero-banner does on the home page -- so this is scoped to that one
 * ancestor, same trick as .popup__dialog > .media--banner above. */
.hero-media > .media--banner,
.hero-media > .media--placeholder { aspect-ratio: auto; border-radius: 0;
  height: 100%; inset: 0; margin: 0; position: absolute; width: 100% }

.discipline-hero .hero-lede p { margin: 0 }

/* Three fixed badges, identical on every discipline page (FR-003): every
 * workshop's registration already includes these (.includes__item below), so
 * nothing here is a new, discipline-specific claim. Same frosted-glass pill
 * look as .hero-tag above, laid out as a wrapping row instead of one chip. */
.hero-badges { display: flex; flex-wrap: wrap; gap: .5rem; list-style: none;
  margin: 0 0 1.25rem; padding: 0 }
.hero-badges li { backdrop-filter: blur(8px); background-color: rgba(255, 255, 255, .06);
  border: 1px solid rgba(255, 255, 255, .3); border-radius: 999px; color: #fff;
  font-size: .6875rem; font-weight: 700; letter-spacing: .08em; padding: .4rem .75rem;
  text-transform: uppercase }

/* ==========================================================================
 * FEATURE 032 — TYPOGRAPHY (Montserrat)
 *
 * The harvested :root above has declared `--font: Montserrat` since feature
 * 029, but NOTHING EVER CONSUMED IT and no font was ever loaded, so every page
 * rendered in the user agent's default sans. On the TI site the family is
 * inherited from the document root, which is why it shows up in the computed
 * style of a nav link rather than on any rule of its own -- so it is applied
 * here at the root too, not on the bar.
 *
 * SELF-HOSTED, NOT fonts.googleapis.com. The public layout deliberately makes
 * no third-party request (see the og:image reasoning in
 * app/views/layouts/public.html.erb, which rejected an external URL because
 * its failure would be silent); a webfont link would reintroduce exactly that,
 * and on the one surface whose spec is careful about what it discloses to
 * whom.
 *
 * One VARIABLE face per subset, axis 100..900, so a single file serves both
 * the 400 of body copy and the 700 the event rows and the hero already ask
 * for -- no faux-bold synthesis, and fewer bytes than two static weights. Cyrillic and Vietnamese subsets are deliberately
 * not shipped: <html lang="en">, and unicode-range means latin-ext is only
 * fetched if a page actually contains those glyphs.
 * ========================================================================== */

/* latin */
@font-face {
  font-family: Montserrat;
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/assets/montserrat-latin-9fa5e944.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* latin-ext */
@font-face {
  font-family: Montserrat;
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/assets/montserrat-latin-ext-9d7ec31b.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* A FALLBACK STACK, where the TI computed style carried a bare `var(--font)`.
 * `font-display: swap` means text paints in the fallback before the woff2
 * arrives, so the stack is what visitors read on a cold cache rather than a
 * theoretical safety net -- a bare var() would drop them to an unpredictable
 * UA default for that moment.
 *
 * THE SECOND ARGUMENT TO var() IS LOAD-BEARING, and this is the subtle half of
 * the rule. `var(--font)` with no fallback is INVALID AT COMPUTED-VALUE TIME
 * the moment --font goes missing -- a re-harvest that replaces the :root block
 * above, a renamed token -- and an invalid font-family does not quietly fall
 * through to the next name in the list. The whole declaration is thrown away
 * and the property inherits from <html>, landing on the user agent default and
 * stepping straight over the "Helvetica Neue", Helvetica, Arial, sans-serif
 * stack written right beside it. `var(--font, Montserrat)` keeps that stack
 * reachable, so the worst case is Helvetica rather than Times. */
body { font-family: var(--font, Montserrat), "Helvetica Neue", Helvetica, Arial, sans-serif }

/* TEXT INFLATION OFF, at the root, which is where the TI computed style has it
 * and where normalize.css has put it for a decade. Mobile WebKit's default is
 * `auto`: it silently enlarges text in a narrow column so a page designed for
 * a desktop reads without zooming. That heuristic is helpful on an unstyled
 * page and harmful here, where the type sizes are deliberate and a nav row
 * scaled up on its own would break the header layout it belongs to.
 *
 * On `html` rather than on any one component: it is an inherited property, so
 * one declaration covers every element, and the alternative -- repeating it on
 * each element whose size happens to matter -- guarantees the next one added
 * is forgotten. The -webkit- prefix is the form Safari actually reads;
 * unprefixed is the standard the rest implement. */
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100% }

/* BORDER-BOX SIZING, which the TI computed style has and the harvest lost.
 *
 * This is not a preference. The whole grid above this fence is Foundation, and
 * Foundation is written against border-box: `.columns` is `width: 100%` with
 * `padding: 0 1rem`, and the 32 percentage widths that go with it all assume
 * the padding is drawn INSIDE the declared width. Under the content-box
 * default every one of those columns is its stated width plus 2rem, which is
 * how a grid that adds up to 100% ends up wrapping. The reset that made it
 * work is in Foundation's normalize layer, which is not part of what was
 * harvested into this file.
 *
 * WHAT IT ACTUALLY CHANGES TODAY: almost nothing, which is why it is safe to
 * add now rather than during a layout pass. box-sizing only matters on a box
 * with BOTH an explicit width or height AND padding or a border, and no such
 * box is currently rendered -- `.columns` appears in no view in this
 * repository, `.container` sets width but no padding, and every hand-written
 * component (.event, .schedule-filters) is auto-width
 * or a stretched flex item, where the used size is identical either way.
 *
 * IT IS LOAD-BEARING NOW, which it was not when first added. `.container` is
 * `width: 100%` and has since been given `padding: 1rem 1rem 0`; without this
 * reset that box is 32px wider than its parent and its side shadows are pushed
 * off both edges of the window.
 *
 * ::before and ::after are included because Foundation's clearfixes are
 * generated content that participates in layout. */
*,
::before,
::after { box-sizing: border-box }

/* FORM CONTROLS DO NOT INHERIT TYPE, which is why the page was only mostly
 * Montserrat. `font-family` is an inherited property everywhere EXCEPT
 * <button>, <input>, <select>, <textarea> and <optgroup>: user agents give
 * those their own platform UI font, and the rule on `body` above never reaches
 * them. On the storefront that was the three <select>s and the date <input> in
 * storefront/_filters.html.erb; on the admin surface it is every form on every
 * page. This is the standard normalize.css / preflight remedy, and `inherit`
 * rather than a restated stack means it tracks `body` forever.
 *
 * font-size is deliberately NOT included. It is the other half of what those
 * elements refuse to inherit, but resizing every control in the application is
 * a visual change nobody asked for; this rule changes the typeface only. */
button,
input,
optgroup,
select,
textarea { font-family: inherit }

/* .btn IS THE ONE RULE THAT OPTS OUT ON PURPOSE, and it is overridden here
 * rather than edited in place: the declaration lives at `.btn` in the harvested
 * region, which a future TI re-harvest overwrites wholesale. Same specificity,
 * later in the sheet, so this wins and survives.
 *
 * What it overrides is `font-family: sans-serif,Arial,Helvetica` -- note the
 * order, which is TI's own and is almost certainly a mistake: the generic
 * family comes FIRST, so it always matches and Arial and Helvetica are never
 * consulted at all. Every button in the application was therefore drawn in
 * whatever the browser calls "sans-serif", next to Montserrat body copy.
 *
 * This does mean buttons no longer match the live TI site. That is the
 * intent -- one typeface across the page -- and it is the only rule in this
 * cleanup that is a deliberate divergence rather than a repair. */
.btn { font-family: inherit }

/* THE REST OF THE font-family DECLARATIONS IN THIS SHEET, and why none of them
 * needed touching:
 *
 *   meta.foundation-mq-*, meta.foundation-data-attribute-namespace
 *     -- not typography at all. Foundation smuggles its media-query strings
 *        through font-family so its JavaScript can read them back off a hidden
 *        <meta>. That script is never loaded and no such element is
 *        rendered, so these are inert either way.
 *   .top-bar-section ul li > a { font-family: inherit }
 *     -- already correct. This now reaches the ADMIN navigation only; the
 *        storefront's menu is .mainnav and matches no harvested rule.
 *   .token-callout code, .code-block { font-family: Consolas, Menlo, monospace }
 *     -- intentional. Monospace is the point; API keys and raw log output are
 *        read character by character.
 *   @font-face { font-family: Montserrat }
 *     -- the face declarations themselves, above. */

/* ==========================================================================
 * FEATURE 032 — HEADER LINK APPEARANCE
 * ========================================================================== */
/* The header's "My Dashboard" link carries no link colour and no underline in
 * any state. That is the whole of it -- every other value in the live TI
 * computed style already arrives on its own, and is listed below so a reader
 * can tell "already correct" from "forgotten".
 *
 * WHY ANYTHING IS NEEDED. The harvested sheet reaches this anchor through
 * `.nav ul a` and `.nav--top ul a`, which set size, padding and case but never
 * colour or decoration -- so it fell through to the user agent's own link
 * defaults and rendered blue, purple once visited, and underlined throughout.
 *
 * `color: inherit` rather than a literal #000. TI's black is its <body>
 * default showing through, not a colour anyone chose for this link, and
 * `inherit` reproduces that same mechanism instead of freezing today's result.
 * ONE SELECTOR COVERS EVERY STATE: the blue, the visited purple and the active
 * red all come from the user agent's own sheet, and an author declaration
 * outranks it by cascade origin whatever the state -- listing :link, :visited,
 * :hover, :focus and :active separately would be five ways to say this.
 *
 * `text-decoration-color` needs no declaration: it is `currentcolor`, so it
 * follows the colour above at no cost. -style: solid and -thickness: auto are
 * the initial values, and moot under `text-decoration-line: none` anyway.
 *
 * ALREADY TRUE, DELIBERATELY NOT RESTATED:
 *
 *   font-size: 13px        -- `.nav--top ul a` in the harvested region.
 *   text-transform: uppercase -- same rule. TI uppercases this row too.
 *   display: block, padding: 16px -- `.nav ul a` (padding is its 1rem).
 *   list-style: none       -- `.nav ul`, inherited past the <li>.
 *   text-align: left       -- the >= 48.063em block, where the row floats right.
 *   font-family: Montserrat, font-weight: 400, font-style: normal,
 *   background-color: transparent, cursor: pointer
 *                          -- the `body` rule and user-agent defaults.
 *   box-sizing: border-box, text-size-adjust: 100%
 *                          -- the two root rules added with the typography.
 *   line-height: 18.85px   -- NOT a declaration anywhere, in this sheet or
 *                             TI's. It is `normal` resolved through
 *                             Montserrat's own metrics, which run 1.45x: the
 *                             same ratio produces 23.2px at the global bar's
 *                             16px and 17.4px at its 12px links. An explicit
 *                             1.45 here would compute identically and then
 *                             quietly stop matching the day the family
 *                             changes, so it is left alone.
 *
 * Scoped through .header so this reaches the header's nav only -- .nav--top is
 * a generic harvested class and nothing else asked to change. The focus ring
 * is untouched: `.storefront a:focus-visible` above draws it with an outline,
 * not with colour or decoration, so FR-038 still holds. */
.header .nav--top ul a { color: inherit; text-decoration: none }

/* ==========================================================================
 * ADMIN TOP BAR — SMALL SCREENS
 *
 * The harvested `.top-bar { height: 3rem; overflow: hidden }` clips its whole
 * menu below 48.063em, because on TI it is the Foundation hamburger that
 * expands it. That script is not loaded anywhere in this application, so
 * without this the admin menu is simply UNREACHABLE on a phone. Letting the
 * bar grow and the items stack keeps every link available with no script.
 *
 * The storefront header no longer renders .top-bar at all -- see the .mainnav
 * block above -- so this reaches the admin layout only.
 * ========================================================================== */
@media only screen and (max-width: 48.062em) {
  .top-bar { height: auto; line-height: 1.45; overflow: visible }
}

/* ==========================================================================
   FEATURE 033 — cart, checkout and orders
   --------------------------------------------------------------------------
   Appended as a delimited section rather than added as a new stylesheet file:
   FR-070 forbids a new file, following the constraint feature 029 set and
   feature 031 already worked within.

   Deliberately small. The purchase pages reuse the vocabulary already here --
   .container, .btn, .btn--primary, .btn--secondary, .muted, .visually-hidden
   -- and this section only supplies what a transactional page needs and a
   marketing page never did: a form layout, a line table, and the two states a
   cart line can be in.
   ========================================================================== */

.purchase { max-width: 46rem; margin: 0 auto; padding: 2rem 1rem 4rem; }
.purchase__breadcrumb { margin-bottom: 1rem; font-size: 0.9rem; }
.purchase__title { margin: 0 0 1.5rem; }

.purchase__facts { display: grid; grid-template-columns: auto 1fr; gap: 0.35rem 1.25rem; margin: 0 0 2rem; }
.purchase__facts dt { font-weight: 600; }
.purchase__facts dd { margin: 0; }

.purchase__form fieldset { border: 1px solid var(--gray); border-radius: 4px; margin: 0 0 1.5rem; padding: 1.25rem; }
.purchase__form legend { font-weight: 600; padding: 0 0.5rem; }

.field { margin-bottom: 1rem; }
.field label { display: block; font-weight: 600; margin-bottom: 0.25rem; }
.field input[type="text"], .field input[type="email"], .field input[type="tel"], .field input[type="number"],
.field select {
  width: 100%; max-width: 26rem; padding: 0.5rem; border: 1px solid #bbb; border-radius: 3px;
}
/* A checkbox and its label read as one line, not as a stacked field. */
.field--check { display: flex; align-items: center; gap: 0.5rem; }
.field--check label { display: inline; font-weight: 400; margin: 0; }
/* Feature 067: the photograph release's wording, tucked under its box. In
   normal flow, so opening it pushes the seat's later fields down. */
.purchase__terms { margin: -0.5rem 0 1rem; font-size: 0.875rem; }
.purchase__terms summary { cursor: pointer; }
.purchase__terms p { margin: 0.5rem 0 0; }

.purchase__seat { border-top: 1px solid var(--rule-l); padding-top: 1rem; margin-top: 1rem; }
.purchase__seat:first-of-type { border-top: 0; margin-top: 0; padding-top: 0; }
.purchase__seat h3 { margin: 0 0 0.75rem; font-size: 1rem; }

.purchase__lines { width: 100%; border-collapse: collapse; margin-bottom: 1.5rem; }
.purchase__lines th, .purchase__lines td { text-align: left; padding: 0.75rem 0.5rem; border-bottom: 1px solid var(--rule-l); vertical-align: top; }
.purchase__lines th[scope="row"] span { display: block; font-weight: 400; }
.purchase__quantity { width: 5rem; }
.purchase__quantity-form { display: flex; gap: 0.5rem; align-items: center; }

/* FR-009: a line that cannot be bought is dimmed but never hidden, and its
   reason is stated in text rather than by the dimming alone. */
.purchase__line--unavailable { background: #fdf6f6; }
.purchase__unavailable { display: block; color: var(--danger); margin-top: 0.35rem; }

.purchase__bundle { margin: 0 0 1rem; }
.purchase__total { font-size: 1.15rem; margin: 1rem 0; }
.purchase__reference { font-size: 1.05rem; }
.purchase__actions { display: flex; flex-wrap: wrap; gap: 0.75rem; align-items: center; margin-top: 1.5rem; }

.purchase__notice { border: 1px solid var(--gray); border-left: 4px solid var(--danger); border-radius: 4px; padding: 1rem 1.25rem; margin-bottom: 1.5rem; }
.purchase__notice h2 { margin-top: 0; font-size: 1.05rem; }

/* The header's cart control, restored by this feature. .badge never had
   a rule, which is one reason feature 031 dropped the control rather than
   shipping it unstyled. */
.cart-link__count { font-variant-numeric: tabular-nums; }

/* Feature 033: seats remaining on a run card. Text-first — the count is
   in the markup and this only styles it, because FR-016 forbids conveying
   availability by colour or position alone. */
.event-seats { margin: 0.35rem 0; font-size: 0.9rem; }
.event-seats--none { color: var(--danger); }

/* Feature 033 (FR-047b): an unresolved payment must not read as another
   routine sync problem. Position does most of the work — it sits above
   everything else in "Needs attention" — and this makes the difference visible
   without relying on position alone. */
.unresolved-payments__heading { color: var(--danger); }
.unresolved-payments { width: 100%; border-collapse: collapse; margin-bottom: 1.5rem; border-left: 4px solid var(--danger); }
.unresolved-payments th, .unresolved-payments td { text-align: left; padding: 0.5rem; border-bottom: 1px solid var(--rule-l); }
/* Feature 033 (FR-048a): over-capacity on a staff run view. */
.over-capacity { color: var(--danger); display: block; }

/* ==========================================================================
 * FEATURE 070 — HOME PAGE SECTIONS
 *
 * Shared by every narrative section below the schedule (storefront/_problem,
 * _plan, _outcomes, _teams). 071 (locations) and 072 (videos) apply
 * .page-section to their own sections as it stands, so it carries nothing
 * specific to any one of them. The eyebrow reuses .small-caps and the call to
 * action reuses .btn .btn--primary; the rest renders on plain element styles
 * until a design pass restyles it.
 *
 * The header is not sticky, so the breathing room above a section when a menu link
 * jumps to it comes from the one site-wide scroll-padding-top on <html>.
 * ========================================================================== */
.page-section { border-top: 1px solid var(--rule-l); padding: 2rem 0 }

/* One column on a phone; two equal columns from the sheet's usual breakpoint.
 * minmax(0, 1fr) rather than 1fr so a long word cannot widen one column and
 * push the page into a sideways scroll. */
.page-section__columns { display: grid; gap: 1.5rem }

@media only screen and (min-width: 48.063em) {
  .page-section__columns { grid-template-columns: repeat(2, minmax(0, 1fr)) }
}

/* A black band (storefront/_problem). Text turns white to stay readable. */
.page-section--dark { background: #000; border-top: 0; color: #fff }

/* A numbered list on a dark section (storefront/_problem). Each number sits in
 * the item's left padding, so every line of the text keeps to its own column
 * beside it; the numbers are drawn with a counter rather than the list's own
 * markers so the rule lines run the full width, under the numbers too. The
 * item's <strong> is its heading, and the text after it the description. */
.numbered-list { counter-reset: item; list-style: none; margin: 0; padding: 0 }
.numbered-list li { border-top: 1px solid var(--rule-d); color: var(--muted-d);
  counter-increment: item; padding: 1.5rem 0 1.5rem 2.5rem; position: relative }
.numbered-list li:last-child { border-bottom: 1px solid var(--rule-d) }
.numbered-list li::before { color: var(--fa-red); content: counter(item);
  font-weight: 700; left: 0; position: absolute; top: 1.5rem }
.numbered-list strong { color: #fff; display: block; margin-bottom: .25rem }

/* Cards in a row (storefront/_plan): one column on a phone, three equal
 * columns from the sheet's usual breakpoint. A grid stretches every card to
 * the row's height, so a card with less text keeps the same size and simply
 * has more white below it. Margins inside a card are zeroed and replaced with
 * one small gap, so its label, heading and text read as a single unit. */
.card-grid { display: grid; gap: 1.5rem }

@media only screen and (min-width: 48.063em) {
  .card-grid { grid-template-columns: repeat(3, minmax(0, 1fr)) }
}

.card { background: var(--paper); border: 1px solid var(--gray); padding: 2rem }
.card > * { margin: 0 }
.card > * + * { margin-top: .5rem }
.card--dark { background: #000; border-color: #000; color: #fff }

/* A list whose every item starts with a mark instead of a bullet
 * (storefront/_outcomes): a cross for what goes wrong, an arrow for what goes
 * right. The mark sits in the item's left padding so the text keeps to its own
 * column, and a dashed rule runs between items but not above the first or
 * below the last. Each content has a second, `/ ""` form so a screen reader
 * does not read out "times" or "right arrow"; a browser that cannot parse it
 * keeps the first. */
.mark-list { list-style: none; margin: 0; padding: 0 }
.mark-list li { padding: .75rem 0 .75rem 1.5rem; position: relative }
.mark-list li + li { border-top: 1px dashed var(--rule-l) }
.mark-list li::before { left: 0; position: absolute }
.mark-list--cross li::before { color: var(--muted-l); content: "\00d7"; content: "\00d7" / "" }
.mark-list--arrow li::before { color: var(--fa-red); content: "\2192"; content: "\2192" / "" }
.card--dark .mark-list li + li,
.page-section--dark .mark-list li + li { border-top-color: var(--rule-d) }

/* ==========================================================================
 * FEATURE 071 — LOCATIONS
 *
 * The Locations section (storefront/_locations) is a .page-section like the
 * others; these rules are its cards. A card is a picture panel beside its
 * details: stacked on a phone, side by side from the sheet's usual breakpoint.
 * The list fits as many 28rem cards to a row as there is room for -- two on a
 * laptop -- and min() lets one card shrink below that on a phone rather than
 * push the page sideways.
 * ========================================================================== */
.location-list { display: grid; gap: 1.5rem;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 28rem), 1fr)) }
.location { background: var(--paper); border: 1px solid var(--gray); display: grid }

@media only screen and (min-width: 48.063em) {
  .location { grid-template-columns: 14rem minmax(0, 1fr) }
}

/* The gradient is the placeholder a venue without an image shows; a real
 * image covers it. The ::after darkens the panel's foot so the white city
 * name stays readable over any photograph. */
.location__media { background: linear-gradient(135deg, var(--ft-red), #000);
  min-height: 12rem; overflow: hidden; position: relative }
.location__media img { height: 100%; inset: 0; object-fit: cover; position: absolute; width: 100% }
.location__media::after { background: linear-gradient(to top, rgba(0, 0, 0, .6), transparent 60%);
  content: ""; inset: 0; position: absolute }
.location__place { bottom: 1rem; color: #fff; left: 1.25rem; margin: 0;
  position: absolute; z-index: 1 }
.location__place .small-caps { display: block; margin-top: .25rem }

/* Margins inside the details are zeroed and replaced with small gaps, as in
 * .card. */
.location__body { padding: 1.5rem }
.location__body > * { margin: 0 }
.location__body > * + * { margin-top: .35rem }
.location__name { font-size: 1.125rem; font-weight: 700 }
.location__body > p:last-child { margin-top: .75rem }
.location__more { text-decoration: none }
.location__more::after { content: " \2192"; content: " \2192" / "" }

/* ==========================================================================
 * EVENT VIDEOS
 *
 * The videos section (storefront/_videos): a grid of cards, each a thumbnail that becomes a
 * YouTube player on click. The thumbnail and the player share one 16:9 box.
 * ========================================================================== */
.video-grid { display: grid; gap: 1.5rem 1.25rem; grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
  list-style: none; margin: 1.5rem 0 0; padding: 0 }
.video-card { margin: 0 }
.video-card__play { color: var(--ink); display: block; text-decoration: none }
.video-card__thumb, .video-card__player { aspect-ratio: 16 / 9; background: #000; border: 0; border-radius: .5rem;
  display: block; object-fit: cover; width: 100% }
.video-card__play { position: relative }
.video-card__mark { aspect-ratio: 16 / 9; display: grid; inset: 0 0 auto; place-items: center; position: absolute; width: 100% }
.video-card__mark::before { background: var(--ft-red); border-radius: 50%; content: ""; height: 3.25rem; width: 3.25rem;
  grid-area: 1 / 1; box-shadow: 0 .25rem 1rem rgba(0, 0, 0, .35) }
.video-card__mark::after { border-color: transparent transparent transparent #fff; border-style: solid;
  border-width: .6rem 0 .6rem 1rem; content: ""; grid-area: 1 / 1; margin-left: .25rem }
.video-card__play:hover .video-card__mark::before, .video-card__play:focus-visible .video-card__mark::before {
  background: var(--ft-red-hover) }
.video-card__title { font-size: 1rem; font-weight: 700; line-height: 1.3; margin: .6rem 0 0 }
.video-dialog { background: #000; border: 0; border-radius: .75rem; box-shadow: 0 24px 60px rgba(0, 0, 0, .5); max-width: none;
  overflow: visible; padding: 0; width: min(64rem, 92vw) }
.video-dialog::backdrop { background: rgba(26, 22, 20, .78) }
.video-dialog__player { aspect-ratio: 16 / 9; border: 0; border-radius: .75rem; display: block; width: 100% }
.video-dialog__close { background: none; border: 0; color: #fff; cursor: pointer; font-size: 2rem; line-height: 1; padding: .25rem .5rem;
  position: absolute; right: 0; top: -2.75rem }

/* ==========================================================================
 * FEATURE 073 — WAITLIST FORM
 *
 * The iframe on /waitlist. Monday's default embed size and shadow, with the
 * width made fluid so the frame narrows on a phone instead of overflowing.
 * ========================================================================== */
.waitlist-frame { width: 100%; max-width: 650px; height: 500px; border: 0; box-shadow: 5px 5px 56px 0 rgba(0, 0, 0, .25); }

/* ==========================================================================
 * WIREFRAME INTEGRATION — WHAT EVERY REGISTRATION INCLUDES
 *
 * The strip under a discipline's schedule (storefront/disciplines/_includes).
 * Chips, not a bulleted list, so it reads as a row of takeaways; they wrap on a
 * phone.
 * ========================================================================== */
.includes__list { display: flex; flex-wrap: wrap; gap: .5rem; list-style: none; margin: .75rem 0 0; padding: 0 }
.includes__item { background: var(--paper-2); border: 1px solid rgba(0, 0, 0, .12);
  border-radius: 999px; font-size: .8125rem; font-weight: 600; padding: .35rem .8rem }

/* ==========================================================================
 * WIREFRAME INTEGRATION — HOME PAGE, FOLLOWING THE WIREFRAME
 *
 * Explore our workshops (storefront/_explore): three cards, one per discipline,
 * each topped with that discipline's own colour. The colour is decoration only;
 * every card also carries the discipline's label.
 * ========================================================================== */
.explore__card { color: inherit; display: block; text-decoration: none }
.explore__card:hover, .explore__card:focus { border-color: currentColor }
.explore__card--fa { border-top: 4px solid var(--fa-red) }
.explore__card--wb { border-top: 4px solid var(--wb-blue) }
.explore__card--fp { border-top: 4px solid var(--gold) }

/* The stakes band's testimonial: a rule down its left edge, as in the wireframe. */
.testimonial { align-self: center; border-left: 4px solid var(--fa-red); margin: 0; padding: .4rem 0 .4rem 1.5rem; text-align: center }
.testimonial__quote { font-size: 1.5rem; font-weight: 700; line-height: 1.35; margin: 0 }

/* A red band (storefront/_teams), white text. */
.page-section--red { background: var(--ft-red); border-top: 0; color: #fff }
/* The brand-red button would vanish on the brand-red band, so it is outlined. */
.page-section--red .btn--primary { background: transparent; border: 1px solid #fff; color: #fff }
.page-section--red .btn--primary:hover, .page-section--red .btn--primary:focus { background: #fff; color: var(--ft-red) }

/* ==========================================================================
 * WIREFRAME INTEGRATION — THE WIREFRAME'S FRAME, SPACING AND TYPE
 *
 * WHY THIS BLOCK EXISTS. The storefront's frame was harvested from the Thought
 * Industries site: a grey page with a boxed 1440px white "card" and 1rem gutters.
 * Each section then picked its own mix of utility classes, so headings were 24px
 * in one section and 40px in the next, vertical padding jumped between 0, 2rem
 * and 5rem, and paragraphs ran 1360px wide on a line-height of "normal". The
 * wireframe has none of that: edge-to-edge white, full-width colour bands, one
 * centred 1180px column, and one scale. This block is that scale, in one place;
 * it sits after the harvested rules and wins over them by specificity or order.
 *
 * THE COLUMN. --content-gutter is the space each side of the content. A % in it
 * resolves where it is USED, as a padding on a full-width element, so a band
 * keeps its background edge to edge while its content stays 1100px wide
 * (1180 less 40px each side, the wireframe's .wrap) -- and never closer than
 * 1rem to a phone's edge.
 * ========================================================================== */
body.storefront { --page-gutter: 0px; --content-gutter: max(1rem, calc((100% - 1180px) / 2 + 40px));
  background-color: #fff; padding: 0 }
.container { box-shadow: none; max-width: none; padding: 0 }

/* WHERE AN ANCHOR STOPS. Every link that jumps to an id on a page (the menu, Register Now,
 * the Explore cards, "See dates", Browse, a popup's way back) lands with the section's
 * heading about 2rem below the top of the window: close enough to read as the top of the
 * page's next thing, with room above it, and never cropped.
 *   - html: the 2rem gap itself. A heading that is the target (a discipline's "Upcoming
 *     workshops") lands exactly there.
 *   - .band: a section has 3.5rem of padding above its heading, so the section lands that
 *     much higher and its heading ends up at the same 2rem.
 *   - .schedule__results: the target of every Enroll Now and every filter link is the
 *     results, which sit BELOW the schedule's heading and filters; pulling it down by their
 *     height keeps "Upcoming workshops" and the filters in view above the count instead of
 *     scrolled off. The filters stack on a phone, so there it is taller.
 * Not motion, so all of it applies whatever the visitor's motion setting. */
html { scroll-padding-top: 2rem }
.band { scroll-margin-top: -3.5rem }
.schedule__results { scroll-margin-top: 18.25rem }
@media only screen and (min-width: 48.063em) {
  .schedule__results { scroll-margin-top: 8.75rem }
}

/* Everything directly in <main> except the hero sits on the column: the home
 * page's sections, a discipline page's heading and intro, the cart. */
main > :not(.hero) { padding-inline: var(--content-gutter) }
.hero-media { margin: 0 }
.hero-content { padding-inline: var(--content-gutter) }
.header__inner, .header .nav--top { padding-inline: var(--content-gutter) }
.nav--global-links { margin: 0; padding-inline: var(--content-gutter) }
footer.site-footer { padding-inline: var(--content-gutter) }

/* A band: one full-width section, 56px above and below (the wireframe's). */
.band { padding-block: 3.5rem }
.schedule { padding-block: 3.5rem }

/* THE TYPE SCALE, from the wireframe: a 30px section heading, with the schedule's
 * at 32px and Explore's at 22px as drawn; a 12px tracked eyebrow; body copy on a
 * 1.55 line; a measure that stops a paragraph running the width of the screen. */
.band h2, .schedule > h2 { font-weight: 800; letter-spacing: -.02em; line-height: 1.15; margin: .5rem 0 0 }
.band h2 { font-size: 1.875rem }
.schedule > h2 { font-size: 2rem }
#explore h2 { font-size: 1.375rem; margin: 0 0 1.25rem }
.band h3 { letter-spacing: -.01em; margin: 0 }
.band .small-caps { font-size: .75rem; font-weight: 700; letter-spacing: .14em; margin: 0 }
.band p, .band li { line-height: 1.55 }
.band p { margin: .75rem 0 0; max-width: 46rem }
.band p.small-caps { max-width: none }
/* The TI button carries a 1rem bottom margin that left a stray gap under every
 * button; a paragraph holding one gets its space above instead. */
.band .btn { margin: 0 }
.band p:has(> .btn) { margin-top: 1.5rem }

/* A discipline page's heading, as the wireframe's hub: 34px, 800. */
main > h1 { font-size: 2.125rem; font-weight: 800; letter-spacing: -.02em; line-height: 1.1;
  margin: 0; padding-top: 2.25rem }

/* ==========================================================================
 * WIREFRAME INTEGRATION — THE ENROLL NOW POPUP, AND THE CHECKOUT PAGES
 *
 * storefront/cart_lines/new: the wireframe's popup, drawn as a page. A dimmed
 * schedule sits behind (.popup__backdrop, decorative and inert), a scrim over it,
 * and the dialog on top: details left, "Your order" right. It needs no script.
 * On the home and discipline pages the same section.popup__dialog is also drawn
 * inside the run-details <dialog> (feature 088), so only a page popup's section
 * (.popup > .popup__dialog) is the fixed, centred box; in the <dialog> it is a
 * plain block and the <dialog> is the box.
 * ========================================================================== */
.btn--outline { background: #fff; border-color: var(--ink); color: var(--ink) }
.btn--outline:hover, .btn--outline:focus { background: var(--ink); color: #fff }

/* A MODAL OVER THE WHOLE PAGE. The scrim is fixed, so it dims the header, the nav and the
 * footer as well as the schedule behind, as the wireframe's does; the dialog is fixed and
 * centred, with its own scroll when it is taller than the window, so Add to Cart is
 * always in view. The page behind does not scroll. (display: flow-root so a margin can
 * never collapse out of this box and push the dimmed layer down, which left a bright
 * band above the dialog.) */
html:has(.popup), html:has(dialog[open]) { overflow: hidden }
main > .popup { display: flow-root; padding-inline: 0; position: relative; min-height: 100vh }
.popup__backdrop { inset: 0; overflow: hidden; padding: 2.25rem var(--content-gutter); position: absolute }
.popup__backdrop h2 { font-size: 2rem; font-weight: 800; letter-spacing: -.02em; margin: 0 0 1.25rem }
.popup__scrim { background: rgba(26, 22, 20, .62); inset: 0; position: fixed; z-index: 1 }
.popup__dialog { background: #fff }
.popup > .popup__dialog { border-radius: 1rem; box-shadow: 0 24px 60px rgba(0, 0, 0, .35);
  left: 50%; max-height: calc(100vh - 2rem); max-width: 900px; overflow-y: auto; position: fixed;
  top: 50%; transform: translate(-50%, -50%); width: calc(100% - 2rem); z-index: 2 }
.popup__head { align-items: flex-start; border-bottom: 1px solid var(--rule-l); display: flex;
  gap: 1rem; justify-content: space-between; padding: 1.625rem 1.875rem 1.125rem }
.popup__head .eyebrow { color: var(--ft-red); font-size: .6875rem; font-weight: 700; letter-spacing: .12em;
  margin: 0; text-transform: uppercase }
.popup__head h1 { font-size: 1.625rem; font-weight: 800; letter-spacing: -.02em; line-height: 1.15; margin: .375rem 0 0 }
.popup__close { color: var(--muted-l); font-size: 1.375rem; line-height: 1; text-decoration: none }
.popup__body { display: grid }
@media only screen and (min-width: 48.063em) {
  .popup__body { grid-template-columns: 1.25fr 1fr }
  .popup__details { border-right: 1px solid var(--rule-l) }
}
.popup__details, .popup__order { padding: 1.375rem 1.875rem 1.625rem }
.popup__order { background: var(--paper-2); display: flex; flex-direction: column }
.popup__h { color: var(--ink); display: block; font-size: .6875rem; font-weight: 800; letter-spacing: .08em;
  margin: 1rem 0 .25rem; text-transform: uppercase }
.popup__h:first-child { margin-top: 0 }
.popup__details p, .popup__details li, .popup__order p { font-size: .8125rem; line-height: 1.55; margin: 0 }
.popup__details p + p { margin-top: .25rem }
.popup__details ul { margin: .375rem 0 0; padding-left: 1.125rem }
.popup__details li { color: var(--muted-l); margin-bottom: .2rem }
.popup__more { color: var(--ft-red); font-weight: 700 }
.popup__price { margin-top: .375rem !important }
.popup__price strong { font-size: 1.875rem }
.popup__price span { color: var(--muted-l) }
.popup__tax { color: var(--muted-d); font-size: .6875rem !important }
.popup__quantity { width: 100% }

/* The small popups (storefront/schedule_calls, calendar_downloads): one column, narrower than the
 * Enroll Now dialog, as a page popup or in the run-details <dialog>. The Monday form is framed at a
 * height that fits it without a second scrollbar on most screens; the dialog scrolls when the
 * window is shorter. */
.popup > .popup__dialog--form, .run-details-dialog:has(.popup__dialog--form) { max-width: 36rem }
.popup__form { padding: 1.375rem 1.875rem 1.75rem }
.popup__form > p { font-size: .9375rem; line-height: 1.55; margin: 0 0 1rem }
.popup__frame { border: 0; display: block; height: 36rem; width: 100% }
.popup__emailform { display: flex; flex-direction: column; gap: .5rem }
.popup__label { font-size: .8125rem; font-weight: 700 }
.popup__input { border: 1px solid var(--ink); border-radius: .375rem; font-size: 1rem; padding: .625rem .75rem;
  width: 100% }
.popup__emailform .btn { align-self: flex-start; margin: .75rem 0 0 }
.popup__error { color: var(--ft-red); font-size: .875rem; font-weight: 700; margin: 0 !important }
.popup__order .btn { margin-top: .75rem; width: 100% }
/* Bundle it! in the Enroll popup: outlined in the brand red, under the primary button. */
.btn--bundle { background: #fff; border-color: var(--ft-red); color: var(--ft-red) }
.btn--bundle:hover, .btn--bundle:focus { background: var(--ft-red); color: #fff }
.popup__bundle { margin-top: 1rem !important }
.popup__order .field { margin-top: .75rem }

/* The bundle popup: the two sessions under "What you get", and the struck-through
 * list price with the bundle price beside it. */
.popup__part { background: #fff; border: 1px solid var(--rule-l); border-radius: .625rem;
  margin-top: .625rem; padding: .875rem 1rem }
.popup__part .eyebrow { color: var(--ft-red); font-size: .6875rem; font-weight: 700; letter-spacing: .12em;
  margin: 0; text-transform: uppercase }
.popup__part-row { display: flex; gap: .625rem; justify-content: space-between; margin-top: .25rem !important }
.popup__part-row strong { font-size: .9375rem }
.popup__was { color: var(--muted-d); font-size: 1rem; margin-right: .5rem }
.popup__save { color: var(--ft-red); font-weight: 700 }

/* "Bundle & save" on a discipline page: a boxed card, price and button on the right. */
.bundle-card { align-items: center; background: #fff7f6; border: 2px solid var(--ft-red); border-radius: .625rem;
  display: flex; flex-wrap: wrap; gap: 1.25rem; justify-content: space-between; margin-top: .75rem; padding: 1.25rem 1.5rem }
.bundle-card__body { max-width: 38rem }
.bundle-card__tag { background: var(--ft-red); border-radius: .25rem; color: #fff; display: inline-block;
  font-size: .625rem; font-weight: 700; letter-spacing: .08em; margin-bottom: .375rem; padding: .1875rem .5rem;
  text-transform: uppercase }
.bundle-card h3 { font-size: 1.125rem; margin: 0 }
.bundle-card p { margin: .375rem 0 0 }
.bundle-card__price { text-align: right }
.bundle-card__price s { color: var(--muted-d); margin-right: .5rem }
.bundle-card__price strong { font-size: 1.5rem }
.bundle-card__save { color: var(--ft-red); font-size: .6875rem; font-weight: 700 }
.bundle-card__price .btn { margin-top: .5rem }

/* THE CHECKOUT PAGES (cart, details, and the seat picker's form) -- the wireframe's.
 * Their markup already matches it; what differed was the frame and the controls. A
 * 900px column, pill buttons with the secondary one in orange, rounded fieldsets
 * and inputs, and the cart table ruled in ink under its header. */
main > .purchase { margin: 0 auto; max-width: 900px; padding: 2rem 2.5rem 3.5rem }
.purchase__title { font-size: 2rem; font-weight: 800; letter-spacing: -.02em; margin: 0 0 1.75rem }
.purchase__breadcrumb a { color: var(--ft-red); text-decoration: underline }
.purchase .btn, .popup__dialog .btn, .band .btn { border-radius: 999px; font-size: .875rem; font-weight: 700;
  padding: .75rem 1.375rem }
.purchase .btn--secondary { background: var(--ft-orange); border-color: var(--ft-orange); color: #fff }
.purchase .btn--secondary:hover, .purchase .btn--secondary:focus { background: var(--ft-orange-hover); border-color: var(--ft-orange-hover) }
.purchase__lines th[scope="col"] { border-bottom: 2px solid var(--ink); font-size: .875rem; padding-bottom: .625rem }
.purchase__lines td, .purchase__lines th[scope="row"] { padding: 1.125rem .5rem; vertical-align: middle }
.purchase__lines .btn { font-size: .75rem; margin: 0; padding: .5rem 1rem }
.purchase__form fieldset { border-color: var(--rule-l); border-radius: .625rem; padding: 1.5rem }
.purchase__form legend { font-size: .9375rem; font-weight: 700; padding: 0 .625rem }
.purchase__form label { display: block; font-size: .875rem; font-weight: 700; margin-bottom: .375rem }
.purchase__form input:not([type="checkbox"]), .purchase__form select { border: 1px solid var(--rule-l);
  border-radius: .375rem; font: inherit; font-size: .875rem; padding: .6875rem .875rem; width: 100% }
.purchase__form input[type="checkbox"] + label, .purchase__form label:has(+ input[type="checkbox"]) { display: inline }
.purchase__form .field { margin-bottom: 1rem }
.purchase__form .field input[type="text"], .purchase__form .field input[type="email"],
.purchase__form .field input[type="tel"], .purchase__form .field input[type="number"],
.purchase__form .field select { max-width: none }
.purchase__form .field select[name*="shirt_size"] { max-width: 16rem }
@media only screen and (max-width: 48em) {
  main > .purchase { padding-inline: 1rem }
  /* A four-column table cannot fit a phone: each cart line becomes a card, the
   * workshop, then its seats, amount and Remove, one under another. */
  .purchase__lines thead { display: none }
  .purchase__lines, .purchase__lines tbody, .purchase__lines tr,
  .purchase__lines th[scope="row"], .purchase__lines td { display: block }
  .purchase__lines tr { border-bottom: 1px solid var(--rule-l); padding: 1rem 0 }
  .purchase__lines td, .purchase__lines th[scope="row"] { border: 0; padding: .375rem 0 }
}
.purchase__actions { gap: 1rem }
.purchase__actions > a:not(.btn) { color: var(--ft-red); font-size: .875rem; text-decoration: underline }

/* ==========================================================================
 * FEATURE 084 — RUN DETAILS, AS A PAGE AND IN A MODAL
 *
 * storefront/runs/show is the page; its .run-details article is also what the
 * run-details controller moves into the schedule's <dialog>
 * (storefront/_run_details_dialog), so .run-details is styled to read the same
 * in both. No design was supplied: the existing type, colours and pills.
 *
 * The dialog has no padding of its own -- .run-details-dialog__inner carries it
 * -- so a click whose target is the <dialog> itself can only be on the backdrop.
 * The UA's dialog:modal max-height and overflow keep a long run inside a phone's
 * screen, scrolling within the dialog.
 *
 * Feature 088: the dialog also holds a popup's section.popup__dialog (Enroll
 * Now, Bundle it!, Schedule a call). The popup keeps its own look and its own ×,
 * so the inner padding goes and the dialog's own × is hidden while it does.
 * ========================================================================== */
main > .run-page { margin: 0 auto; max-width: 900px; padding: 2rem 2.5rem 3.5rem }
@media only screen and (max-width: 48em) {
  main > .run-page { padding-inline: 1rem }
}
.run-page__back a { color: var(--ft-red); font-size: .875rem; text-decoration: underline }

.run-details__eyebrow { color: var(--ft-red); font-size: .6875rem; font-weight: 700; letter-spacing: .12em;
  margin: 0; text-transform: uppercase }
.run-details h1 { font-size: 1.625rem; font-weight: 800; letter-spacing: -.02em; line-height: 1.15; margin: .375rem 0 .75rem }
.run-details__icon { height: 6rem; margin: 0 0 .75rem; width: 8rem }
.run-details__icon img { height: 100%; object-fit: contain; width: 100% }
.run-details__when { font-weight: 700; margin: 0 }
.run-details__where { color: var(--muted-l); font-size: .875rem; margin: .25rem 0 0 }
.run-details__description { margin: 1rem 0 0 }
.run-details__list h2 { color: var(--ink); font-size: .6875rem; font-weight: 800; letter-spacing: .08em;
  margin: 1.25rem 0 .375rem; text-transform: uppercase }
.run-details__list ul { margin: 0; padding-left: 1.125rem }
.run-details__list li { margin-bottom: .375rem }
.run-details__detail { color: var(--muted-l); display: block; font-size: .875rem }
.run-details__action { margin: 1.5rem 0 0 }

.run-details-dialog { background: #fff; border: 0; border-radius: 1rem; box-shadow: 0 24px 60px rgba(0, 0, 0, .35);
  max-width: 900px; padding: 0; width: calc(100% - 2rem) }
.run-details-dialog::backdrop { background: rgba(26, 22, 20, .62) }
.run-details-dialog__inner { padding: 1.625rem 1.875rem; position: relative }
.run-details-dialog__inner > .popup__close { background: none; border: 0; cursor: pointer; padding: .25rem .5rem;
  position: absolute; right: 1rem; top: 1rem }
.run-details-dialog__inner:has(.popup__dialog) { padding: 0 }
.run-details-dialog__inner:has(.popup__dialog) > .popup__close { display: none }
.run-details-dialog .run-details { padding-right: 2rem }

/* ==========================================================================
 * WIREFRAME INTEGRATION — MOTION
 *
 * CSS only: no script animates anything. EVERY moving declaration sits inside a `prefers-reduced-motion:
 * no-preference` block, so a visitor who has asked for less motion gets none of it
 * (test/unit/storefront_motion_css_test.rb holds that line). What it adds:
 *   - smooth scrolling to an anchor (the menu, Register Now, the Explore cards);
 *   - a simple lift on hover for buttons and cards, on devices that can hover;
 *   - a fade-up as a card or heading scrolls into view, where the browser supports
 *     scroll-driven animation (others just see the page, unanimated);
 *   - a short cross-fade between pages, where it is supported, which is also what
 *     makes the popups open smoothly.
 * ========================================================================== */
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth }

  /* A page change fades instead of cutting. The opt-in itself (@view-transition) is in the
   * layout, so a page can decline it; this is only how long the fade takes. */
  ::view-transition-old(root), ::view-transition-new(root) { animation-duration: .25s }

  /* Buttons: the colour change eases, and on hover the button lifts a hair. */
  .btn, .event-enroll, .event-learn, .event-bundle {
    transition: background-color .2s ease, border-color .2s ease, color .2s ease,
      box-shadow .2s ease, transform .2s ease }
  .event, .explore__card, .card, .bundle-card, .location, .testimonial {
    transition: box-shadow .2s ease, transform .2s ease, border-color .2s ease }

  @media (hover: hover) {
    .btn:hover, .event-enroll:hover, .event-learn:hover, .event-bundle:hover {
      box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .18); transform: translateY(-1px) }
    .btn:active, .event-enroll:active, .event-learn:active, .event-bundle:active {
      box-shadow: none; transform: translateY(0) }
    .event:hover, .explore__card:hover, .bundle-card:hover {
      box-shadow: 0 .5rem 1.5rem rgba(0, 0, 0, .12); transform: translateY(-2px) }
  }

  /* Fade up as it enters the viewport. `backwards`, not `both`: the finished
   * animation must stop applying, or its last frame would pin `transform` and
   * stop the hover lift above. */
  @keyframes reveal { from { opacity: 0; transform: translateY(1.25rem) } to { opacity: 1; transform: none } }
  @supports (animation-timeline: view()) {
    /* Not on a page that sets body.no-reveal (the discipline pages). */
    body:not(.no-reveal) main > section :is(h2, .card, .explore__card, .event, .location, .bundle-card, .testimonial) {
      animation: reveal linear backwards; animation-timeline: view(); animation-range: entry 0% entry 60% }
  }
}

/* The FAQ: a ruled list of <details>, the question as the summary. */
.faq { margin-top: 1.25rem; max-width: 46rem }
.faq__item { border-top: 1px solid var(--rule-l) }
.faq__item:last-child { border-bottom: 1px solid var(--rule-l) }
.faq__item summary { cursor: pointer; font-weight: 700; padding: 1rem 0 }
.faq__item p { margin: 0 0 1rem }

/* ==========================================================================
 * IMAGE SLOTS (storefront_helper#image_slot)
 *
 * A photo, at one of a few fixed shapes, or on staging a labelled grey box where one
 * will go. The shape is fixed by aspect-ratio, so a slot never changes the page's
 * layout when its photo loads, and an absent photo on the live site draws nothing.
 * ========================================================================== */
.media { margin: 0; overflow: hidden }
.media img { display: block; height: 100%; object-fit: cover; width: 100% }
.media--banner { aspect-ratio: 21 / 6; border-radius: .625rem; margin-top: 1.5rem }
.media--card { aspect-ratio: 16 / 9; border-radius: .375rem }
.media--thumb { aspect-ratio: 4 / 3; border-radius: .375rem; flex: none; width: 4.5rem }
.media--placeholder { align-items: center; background: repeating-linear-gradient(135deg, #eee, #eee .875rem, #e2e2e2 .875rem, #e2e2e2 1.75rem);
  border: 1px dashed var(--muted-d); color: var(--muted-l); display: flex; font-size: .6875rem; font-weight: 700;
  justify-content: center; letter-spacing: .06em; padding: .5rem; text-align: center; text-transform: uppercase }
/* A photo across the top of a card sits flush with its edges, under the card's coloured top border. */
.card > .media:first-child { border-radius: 0; margin: -2rem -2rem 1rem }
.page-section--red .media { margin-bottom: 1rem }
/* In a popup: the banner spans the dialog; the workshop's picture tops the Enroll Now details
 * column; the workshop's image sits beside its name. */
.popup__dialog > .media--banner { aspect-ratio: 5 / 1; border-radius: 0; margin: 0 }
.popup__details > .media { aspect-ratio: 16 / 7; margin-bottom: 1rem }
.popup__head .media--thumb { margin-right: .25rem }
.popup__head { align-items: center }
.popup__head > div:not(.media) { flex: 1 }
.popup__part .media--thumb { float: right; margin-left: .75rem }
.purchase__lines th[scope="row"] .media--thumb { float: left; margin-right: .75rem }

/* ==========================================================================
 * SITE FOOTER
 *
 * storefront/_footer: a .page-section--dark band at the foot of every public
 * page. The grid fits as many 12rem columns to a row as there is room for --
 * the logo and three link columns in one row on a laptop, two rows of two on a
 * tablet, one column on a phone -- so there is no media query; the gap is
 * .gap-large. Links take the band's white rather than the browser's blue, and
 * show their underline only on hover or focus.
 * ========================================================================== */
.site-footer__grid { display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr)) }
.site-footer a { color: inherit; text-decoration: none }
.site-footer a:focus,
.site-footer a:hover { text-decoration: underline }
.site-footer h3 { margin: 0 0 1rem }
.site-footer__list { font-size: .875rem; list-style: none; margin: 0; padding: 0 }
.site-footer__list li + li { margin-top: .5rem }
.site-footer__bottom { border-top: 1px solid var(--rule-d); font-size: .875rem;
  margin: 3rem 0 0; padding-top: 1.5rem }

/* ==========================================================================
 * UTILITIES
 *
 * LAST IN THE FILE ON PURPOSE, and new ones belong here. A utility is one
 * class doing one job, and it has to win when it is put on an element that
 * also carries a component class -- `.padding-y-large` on a `.page-section`,
 * say. Two single-class rules tie on specificity, so the later one wins, and
 * last is what makes that always the utility.
 *
 * .muted and .empty are older and stay in the admin section: moved here they
 * would start beating .event-deadline and .event-price__tax-note, which
 * shrink them to .75rem on purpose.
 * ========================================================================== */

/* ------------------------------------------------------------------------
 * Typography
 * ------------------------------------------------------------------------ */
.text-large { font-size: clamp(1.75rem, 5vw, 2.5rem) }
/* A line length for an intro sentence: short enough that it wraps rather
 * than running the full width of a wide screen. */
.text-narrow { max-width: 36rem }

/* ------------------------------------------------------------------------
 * Colors
 * ------------------------------------------------------------------------ */
.text-red { color: var(--fa-red) }
/* The muted grey made for a dark background; on white it is too faint. */
.text-muted-d { color: var(--muted-d) }
/* The same, for a light background. */
.text-muted-l { color: var(--muted-l) }
/* A barely-grey section background. Text keeps its contrast: --muted-l
 * drops only from 7.4:1 to 6.8:1 on it. */
.bg-light { background: var(--paper-2) }

/* ------------------------------------------------------------------------
 * Spacing
 * ------------------------------------------------------------------------ */
.padding-y-large { padding-bottom: clamp(3rem, 8vw, 5rem); padding-top: clamp(3rem, 8vw, 5rem) }
/* The space between a grid or flex container's children: about 60px on a
 * laptop, never under 1.5rem on a phone. */
.gap-large { gap: clamp(1.5rem, 4.4vw, 4rem) }
/* After .padding-y-large on purpose, so it can trim just the bottom of it. */
.padding-bottom-small { padding-bottom: 2rem }
/* No space at all, for children that are meant to touch. */
.gap-none { gap: 0 }
/* Keeps content off a section's side edges, which matters most on a coloured
 * background, and lines sections up when every one carries it. */
.padding-x-small { padding-left: 1rem; padding-right: 1rem }

/* ==========================================================================
 * LOCATIONS MAP (storefront/_locations, Storefront::LocationMap)
 *
 * The US map is a static drawing, with the states we run workshops in filled gold over it
 * (an inline <svg> the same shape as the map, so each state lines up). The pins are links laid
 * over both at a percentage across and down, so everything scales together and needs no
 * script. The dot is 14px with a 44px touch target around it. Hovering or focusing a pin
 * (:hover, :focus-within) pops up the card of each venue in that city. The pop-up opens to
 * the side of the pin that has room (--left in the east, --up in the south) and has a
 * transparent margin on the pin's side, so the pointer can cross from the dot to the card
 * without it closing. On a phone it is a sheet along the bottom of the screen instead.
 * ========================================================================== */
.locations-map { margin: 1.5rem 0 .5rem; max-width: 62rem; position: relative }
.locations-map__img { display: block; height: auto; width: 100% }
.locations-map__states { height: 100%; inset: 0; pointer-events: none; position: absolute; width: 100% }
.locations-map__states a { cursor: pointer; pointer-events: auto }
.locations-map__states a:hover path, .locations-map__states a:focus path { fill-opacity: 1 }
.locations-map__states path { fill: var(--gold); fill-opacity: .7; stroke: #fff; stroke-width: 1;
  vector-effect: non-scaling-stroke }
.locations-map__hint { align-items: center; display: flex; font-size: .875rem; gap: .5rem; margin: 0 0 2rem }
.locations-map__swatch { background: var(--gold); border-radius: 2px; display: inline-block;
  height: .75rem; opacity: .7; width: 1.25rem }
.locations-map__pins { list-style: none; margin: 0; padding: 0 }
.locations-map__pin { margin: 0; position: absolute }
.locations-map__pin:hover, .locations-map__pin:focus-within { z-index: 5 }
.locations-map__link { display: block; height: 14px; margin: -7px 0 0 -7px; position: relative; width: 14px }
.locations-map__link::before { content: ""; inset: -15px; position: absolute }
.locations-map__dot { background: var(--ft-red); border: 2px solid #fff; border-radius: 50%;
  box-shadow: 0 0 0 1px var(--ft-red); display: block; height: 14px; width: 14px }
.locations-map__pin:hover .locations-map__dot, .locations-map__pin:focus-within .locations-map__dot {
  box-shadow: 0 0 0 5px rgba(154, 33, 39, .25) }

.locations-map__popup { display: flex; flex-direction: column; gap: .5rem; left: 0; padding: 0 0 0 1.25rem;
  position: absolute; top: -1rem; visibility: hidden; width: 21rem }
.locations-map__pin--left .locations-map__popup { left: auto; padding: 0 1.25rem 0 0; right: 0 }
.locations-map__pin--up .locations-map__popup { bottom: -1rem; top: auto }
.locations-map__pin:hover .locations-map__popup, .locations-map__pin:focus-within .locations-map__popup {
  visibility: visible }
.locations-map__popup .location { box-shadow: 0 .5rem 1.5rem rgba(0, 0, 0, .22); grid-template-columns: 1fr }
.locations-map__popup .location__media { min-height: 6.5rem }
.locations-map__popup .location__body { padding: 1rem 1.25rem }
@media only screen and (max-width: 48em) {
  .locations-map__popup, .locations-map__pin--left .locations-map__popup {
    bottom: 1rem; left: 1rem; max-height: calc(100vh - 2rem); overflow-y: auto; padding: 0;
    position: fixed; right: 1rem; top: auto; width: auto }
}

/* ==========================================================================
 * DISCIPLINE PAGE -- CHOOSE YOUR WORKSHOP, PRIVATE GROUPS, CATALOG
 *
 * storefront/disciplines/_choose and _private_groups. These sections are .page-section.band
 * like the home page's, so their frame, headings and spacing come from there; this is only
 * the "Who it's for" disclosure and the student quotes.
 * ========================================================================== */
.choose__grid { margin-top: 1rem }

/* Feature 089: a lone card (Sprinkler today) gets a fixed, readable width
 * instead of stretching across a grid track sized for three. Feature 092
 * widens it from 26rem: once the quote sits beside the workshop instead of
 * under it, the card needs room for both columns. */
.choose__grid--single { grid-template-columns: minmax(0, 42rem) }

/* Feature 093: a card left alone in the final row of the three-column default
 * (5 or 7 workshops, say) gets a fixed, readable, centered width instead of
 * sitting at column 1's normal third with two empty columns beside it.
 * :nth-child(3n+1) matches a last child at the start of a row -- the 1st,
 * 4th, 7th... card; :not(--single, --four) excludes the dedicated layouts
 * just above/below, which don't hit this problem in the first place. */
.choose__grid:not(.choose__grid--single):not(.choose__grid--four) .choose__card:last-child:nth-child(3n+1) {
  grid-column: 1 / -1; margin-inline: auto; max-width: 26rem;
}

/* Feature 094: four workshops (Fire Pumps today) get their own modifier
 * rather than the three-column default stranding the fourth card in its own
 * row (which feature 093, above, exists to tidy up for OTHER counts -- four
 * has its own answer instead). Feature 095: a 2x2 grid reads better than
 * four narrow columns -- one column on a phone, two from the sheet's usual
 * tablet breakpoint up, at every wider width too. */
.choose__grid--four { grid-template-columns: minmax(0, 1fr) }
@media only screen and (min-width: 48.063em) {
  .choose__grid--four { grid-template-columns: repeat(2, minmax(0, 1fr)) }
}

/* Feature 089: a top-edge accent by position, independent of recommends,
 * "Who it's for" or quotes -- Fire Pumps has three cards and none of the
 * first two, Sprinkler has one card and none of the three, and every card
 * still reads as deliberate. Brand red/orange/ink only, never the discipline
 * colours (--fa-red/--wb-blue/--gold are reserved for discipline identity
 * elsewhere, FR-037, not a generic accent here). */
.choose__card { border-top: 3px solid var(--gray) }
.choose__card:nth-child(1) { border-top-color: var(--ft-red) }
.choose__card:nth-child(2) { border-top-color: var(--ft-orange) }
.choose__card:nth-child(3) { border-top-color: var(--ink) }

/* Feature 092: .choose__main/.choose__quotes group a card's content (see
 * _choose.html.erb) so a lone card can lay them out side by side below; the
 * spacing .card > * used to give their children directly is restated here
 * one level deeper, since those elements are no longer direct children of
 * .card. */
.choose__main > *, .choose__quotes > * { margin: 0 }
.choose__main > * + *, .choose__quotes > * + * { margin-top: .5rem }

.choose__details summary { cursor: pointer; font-weight: 600 }
.choose__details[open] summary { margin-bottom: .5rem }
.choose__dates { font-weight: 600 }

/* Feature 092: the description is the card's primary copy; the quote is
 * supporting material, not the headline -- the reverse of .muted's usual
 * (de-emphasized) sense, hence its own class rather than that shared one. */
.choose__summary { color: var(--ink); font-size: 1rem }

.choose__quote { border-left: 3px solid var(--fa-red); font-size: .8125rem; margin: 1rem 0 0; padding: .2rem 0 .2rem 1rem }
.choose__quote p { font-style: italic; margin: 0 }
.choose__quote footer { font-size: .75rem; margin-top: .35rem }
.choose__quote cite { font-style: normal }

/* Feature 092: a lone card (Sprinkler today) lays its quote(s) beside the
 * workshop instead of under it -- there's width to spare once the grid isn't
 * sharing a row with other cards, and a quote stacked under one short
 * paragraph left most of that width empty. :has() keeps a quote-less single
 * card (none exist today) a plain stack, no empty second column. */
.choose__grid--single .choose__card:has(.choose__quote) { display: flex; flex-wrap: wrap; gap: 1.5rem 2rem }
.choose__grid--single .choose__card:has(.choose__quote) .choose__main { flex: 1 1 16rem }
.choose__grid--single .choose__card:has(.choose__quote) .choose__quotes { flex: 1 1 14rem; margin-top: 0 }

/* ==========================================================================
 * COOKIE CONSENT BANNER (storefront/_consent_banner)
 *
 * A bar along the bottom of the window, shown by public/storefront/analytics.js only until the
 * visitor chooses. [hidden] has to win over the flex display, or it would never hide.
 * ========================================================================== */
.consent { align-items: center; background: #fff; border-top: 1px solid var(--gray);
  box-shadow: 0 -.5rem 1.5rem rgba(0, 0, 0, .12); display: flex; flex-wrap: wrap; gap: 1rem;
  inset: auto 0 0 0; justify-content: space-between; padding: 1rem var(--content-gutter);
  position: fixed; z-index: 60 }
.consent[hidden] { display: none }
.consent__text { font-size: .875rem; line-height: 1.5; margin: 0; max-width: 46rem }
.consent__actions { display: flex; gap: .75rem }
.consent .btn { margin: 0 }
.site-footer__cookies { background: none; border: 0; color: inherit; cursor: pointer; font: inherit;
  padding: 0; text-decoration: underline }

/* ==========================================================================
 * GOLD ACCENT -- the wireframe's palette
 *
 * The wireframe's accent is gold (#d9a321, --gold). Gold is the colour of the marker stroke
 * under a key phrase, of the small label and the numbers on a DARK band, of the
 * testimonial's rule and of the active video tab. On a LIGHT section the small label stays
 * the brand red, as the wireframe has it: gold on white is only 2.3:1, too faint to read,
 * while gold on the dark bands is 9:1 (test/unit/storefront_gold_contrast_test.rb holds
 * both). Buttons stay the brand red. Every DISCIPLINE colour (Fire Alarm red, Sprinkler
 * blue, Fire Pumps gold) is left alone: those exist to tell the three apart, and turning
 * Fire Alarm gold would make it the same colour as Fire Pumps.
 *
 * The dark bands are the wireframe's ink (#1a1614), not pure black. Last in the file, so
 * these win over the utilities above by order.
 * ========================================================================== */
.page-section--dark { background: var(--ink) }

/* The marker stroke: gold under the hero's emphasis and under each section's key phrase. */
.hero-headline em,
.highlight-red { background-image: linear-gradient(180deg, transparent 0%,
  transparent 65%, var(--gold) 65%, var(--gold) 95%, transparent 95%) }

/* The small-caps label: brand red on a light section, gold on a dark one. */
.small-caps.text-red { color: var(--ft-red) }
.page-section--dark .small-caps.text-red, .card--dark .small-caps.text-red { color: var(--gold) }

.testimonial { border-left-color: var(--gold) }

/* WHY IT MATTERS: the dark band's three columns. Each is a rule, a gold numeral, a short
 * bold line in small capitals, and a sentence; one column on a phone. */
.why__label { font-size: .75rem; font-weight: 700; letter-spacing: .14em; margin: 0; text-transform: uppercase }
.why { display: grid; gap: 2rem; list-style: none; margin: 1.25rem 0 0; padding: 0 }
@media only screen and (min-width: 48.063em) {
  .why { grid-template-columns: repeat(3, minmax(0, 1fr)) }
}
.why li { border-top: 1px solid var(--rule-d); color: rgba(255, 255, 255, .72); font-size: .875rem;
  line-height: 1.5; padding-top: 1rem }
.why__num { color: var(--gold); display: block; font-size: 1.25rem; font-weight: 800; margin-bottom: .5rem }
.why strong { color: #fff; display: block; font-size: .75rem; letter-spacing: .06em; margin-bottom: .25rem;
  text-transform: uppercase }

/* THE BUTTON STRIP between sections: a white band ruled above and below, buttons centred. */
.cta-strip { align-items: center; border-bottom: 1px solid var(--rule-l); border-top: 1px solid var(--rule-l);
  display: flex; flex-wrap: wrap; gap: .875rem; justify-content: center; padding-block: 1.375rem }
.cta-strip .btn { margin: 0 }

/* WHAT'S AT STAKE: one card in two cells, grey then white. */
.outcomes__card { display: grid; margin-top: 1.75rem; overflow: hidden; padding: 0 }
@media only screen and (min-width: 48.063em) {
  .outcomes__card { grid-template-columns: 1fr 1fr }
}
.outcomes__cell { padding: 1.75rem 1.5rem }
.outcomes__cell--without { background: var(--paper-2) }
.outcomes__cell h3 { font-size: 1.125rem; margin: 0 0 1rem }
.outcomes__cell .mark-list li { color: var(--muted-l); font-size: .875rem; line-height: 1.5 }
.outcomes__cell .mark-list li::before { color: var(--muted-l) }
.outcomes__cell p { margin: 1.25rem 0 0 }
.outcomes__cell .btn { margin: 0 }
