/* ============================================================
   Overtly design system: components
   v0.1, 28 September 2026. Load after tokens.css.

   Extracted from the local preview, deliberately not copied from it.
   What was left behind, and why:

   - The 12 !important declarations. Every one of them exists because a
     later media query fought an earlier rule for the same selector.
     Specificity fights have cost three fixes in the last week.
   - The viewport breakpoints for the course grid. The brief asks for
     container queries so the same card works beside a sidebar, and it
     is right.
   - The magic numbers: 65px gaps, 214px covers, 35px 50px margins. They
     are on the spacing scale here or they are gone.
   - The third focus green, #277657, which fails on coral at 2.28:1.
   - The prototype's page switcher, proposal banner and demo responses.

   Everything here is a primitive. Navigation, dialogs, accordions and
   notifications stay with the site's existing accessible components,
   restyled through these tokens.
   ============================================================ */

.ov-platform { background: var(--ov-bg); color: var(--ov-text);
  font-family: var(--ov-font); font-size: var(--ov-size-body);
  line-height: var(--ov-leading-body); -webkit-font-smoothing: antialiased; }
.ov-platform *, .ov-platform *::before, .ov-platform *::after { box-sizing: border-box; }

.ov-container { width: min(var(--ov-content-width), 100% - (var(--ov-gutter) * 2));
  margin-inline: auto; }
.ov-reading { max-width: var(--ov-reading-width); line-height: var(--ov-leading-lesson); }

/* ---- Focus. One rule, two rings, never removed. ---- */
.ov-platform :focus-visible {
  outline: var(--ov-focus-width) solid var(--ov-focus);
  outline-offset: var(--ov-focus-offset);
}
.ov-on-forest :focus-visible, .ov-hero--editorial .ov-on-forest :focus-visible {
  outline-color: var(--ov-focus-on-dark);
}
/* A filled button's ring sits outside it, on the page, so it takes the
   page's focus colour. It took the on-dark yellow, which is 1.5:1 on paper. */
.ov-btn:focus-visible { outline: var(--ov-focus-width) solid var(--ov-focus);
  outline-offset: var(--ov-focus-offset); }
.ov-skip { position: fixed; top: -80px; left: var(--ov-space-5); z-index: 99;
  background: var(--ov-action-bg); color: var(--ov-action-text);
  padding: var(--ov-space-3) var(--ov-space-4); border-radius: var(--ov-radius-control); }
.ov-skip:focus { top: var(--ov-space-3); }

/* ---- Type ---- */
.ov-display { font-size: var(--ov-size-display); line-height: var(--ov-leading-tight);
  font-weight: 500; letter-spacing: -0.02em; margin: 0; }
.ov-title   { font-size: var(--ov-size-title); line-height: var(--ov-leading-tight);
  font-weight: 600; letter-spacing: -0.02em; margin: 0; }
.ov-section { font-size: var(--ov-size-section); line-height: 1.25;
  font-weight: 600; margin: 0; }
.ov-lead    { font-size: var(--ov-size-lead); color: var(--ov-text-muted);
  max-width: 52ch; margin: 0; }
.ov-eyebrow { font-size: var(--ov-size-meta); font-weight: 700;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--ov-text-muted); margin: 0; }
.ov-meta    { font-size: var(--ov-size-meta); color: var(--ov-text-muted); }

/* ---- Hero, one component, three variants ---- */
.ov-hero { display: grid; gap: var(--ov-space-8); align-items: center;
  padding-block: var(--ov-space-10) var(--ov-space-9); }
.ov-hero__copy { display: flex; flex-direction: column; gap: var(--ov-space-5); }
.ov-hero__art { margin: 0; }
.ov-hero__art img { display: block; width: 100%; height: auto; }

.ov-hero--editorial { grid-template-columns: 1.08fr 0.92fr; }
.ov-hero--editorial .ov-hero__art img {
  -webkit-mask-image: linear-gradient(to right, transparent, #000 12%, #000 88%, transparent);
          mask-image: linear-gradient(to right, transparent, #000 12%, #000 88%, transparent);
}

/* Ambient: the art is atmosphere behind the copy, and the copy must stay
   readable if the mask or the image never arrives. The scrim is the
   guarantee; the opacity figures are a starting point to verify per image,
   not a promise. */
.ov-hero--ambient { position: relative; grid-template-columns: 1fr;
  padding-block: var(--ov-space-8) var(--ov-space-6); }
.ov-hero--ambient .ov-hero__art { position: absolute; inset: 0; z-index: 0;
  overflow: hidden; pointer-events: none; }
/* Opacity is a variable because the right value is a property of the image,
   not of the component: the comment above says verify per image, and a
   hard-coded number makes that impossible without editing the system. A
   pale watercolour needs roughly double what a dense photograph does. */
.ov-hero--ambient .ov-hero__art img { height: 100%; object-fit: cover;
  object-position: var(--ov-art-focal, 50% 50%);
  opacity: var(--ov-hero-art-opacity, 0.15); }
.ov-hero--ambient .ov-hero__copy { position: relative; z-index: 1;
  background: linear-gradient(to right, var(--ov-paper) 55%, transparent); }
@media (min-width: 721px) {
  .ov-hero--ambient .ov-hero__art img { opacity: var(--ov-hero-art-opacity-wide, 0.25); }
}
.ov-hero--plain { grid-template-columns: 1fr; padding-block: var(--ov-space-8) var(--ov-space-6); }

/* ---- Split hero: copy in a narrow left column, art bleeding off the right
   edge of the viewport. The fourth variant, and the only one that is not
   inside .container-x, because the whole point is that the art has no
   right gutter.

   The gutter is calc((100% - 1280px) / 2 + 2rem), which is .container-x's
   own arithmetic reproduced. 100% and not 100vw: 100vw includes the
   scrollbar, so the 100vw version overflows by its width on every desktop
   browser that reserves one, and that is a horizontal scrollbar on the
   landing page.

   Proportions come from the reference: roughly 40 percent copy, 60 percent
   art, art the full height of the section. 0.75/1.25 rather than a flat
   60/40 because Overtly's headline is much longer than the reference's and
   needs the extra measure to wrap in two lines rather than four.

   The art column is align-self: stretch so the image spans the section
   whatever the copy does, and the section's height is set by the copy or
   by min-height, never by the image's aspect ratio. ---- */
.ov-hero-split {
  --ov-hero-gutter: max(2rem, calc((100% - 1280px) / 2 + 2rem));
  display: grid;
  /* A fixed copy track rather than a fraction. With two fractions the copy
     column keeps growing as the viewport does, and because the left gutter
     grows too (it tracks .container-x so the copy lines up with the tiles
     below), the art's share fell to 53 percent at 1600 when the reference
     wants about 60. A fixed measure gives every spare pixel to the art and
     holds the ratio steady: 59 percent at 1280, 58 at 1440, 57 at 1600.
     It is also the right thing for the text, which does not want to grow
     past a readable measure. */
  grid-template-columns: minmax(0, 32rem) minmax(0, 1fr);
  align-items: center;
  column-gap: var(--ov-space-9);
  padding-left: var(--ov-hero-gutter);
  /* Reduced 29 September. At 1440 the old clamp gave a 605px band holding
     436px of copy, so 164px of empty paper sat between the buttons and the
     tile row. The band is sized by the copy now, with the clamp only as a
     floor and a ceiling. */
  min-height: clamp(400px, 32vw, 520px);
  background: var(--ov-paper);
}
.ov-hero-split__copy {
  display: flex; flex-direction: column; align-items: flex-start;
  gap: var(--ov-space-5);
  padding-block: var(--ov-space-8);
  max-width: 34rem;
}
/* The global .heading-xl is 60px, which is tuned for a headline sitting in
   a half-page column. In a 28rem column it put "Expertise, made practical."
   on three lines where the reference has two, and a three-line headline
   next to a three-line subhead loses the size contrast the composition is
   built on. Scaled to the column instead of widening the column, so the
   art keeps its share. */
.ov-hero-split .heading-xl { font-size: clamp(2.5rem, 1.5rem + 2.6vw, 3.75rem);
  letter-spacing: var(--ov-track-display); line-height: var(--ov-leading-display); }
.ov-hero-split__lede { margin: 0; color: var(--ov-text-muted); }
.ov-hero-split__actions { display: flex; align-items: center; gap: var(--ov-space-6); flex-wrap: wrap; }
/* The secondary action is a text link rather than a second bordered
   button, so the row has one object in it and not two competing ones.
   It keeps a real hit area and a real focus ring: making something look
   like plain text is not a reason to make it behave like plain text. */
.ov-hero-split__link {
  font-weight: 600; color: var(--ov-ink); text-decoration: none;
  border-bottom: 2px solid var(--ov-ink); padding: 2px 0;
}
.ov-hero-split__link:hover { border-bottom-color: transparent; }
.ov-hero-split__link:focus-visible {
  outline: var(--ov-focus-width) solid var(--ov-focus); outline-offset: 4px; border-radius: 2px;
}
/* The image is positioned out of flow rather than sized with height:100%.
   With no definite height on the parent, height:100% on an img falls back
   to auto, so the illustration's own aspect ratio silently became the
   section's height: 763px at 1440 against a 605px min-height. Taking it
   out of flow means the section is sized by the copy and the min-height,
   and the art fills whatever that turns out to be, which is what the
   comment at the top of this component always claimed. */
.ov-hero-split__art { align-self: stretch; overflow: hidden; min-width: 0; position: relative; }
.ov-hero-split__art img {
  position: absolute; inset: 0;
  display: block; width: 100%; height: 100%;
  object-fit: cover; object-position: var(--ov-art-focal, 50% 50%);
  /* Two fades, intersected. Left, because the art meets a column of text
     on that side. Top and bottom, because cover crops the illustration
     and left a hard horizontal edge where the art met the tile row: the
     preview dissolves its hero into the paper at both ends and that is
     most of why the band reads as one page rather than a picture sitting
     on one. An illustration drawn on the page's own ivory needs none of
     this, but the fade costs nothing and means the panel never depends
     on the image having the right background. */
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 14%),
                      linear-gradient(to bottom, transparent 0, #000 11%, #000 82%, transparent 100%);
          mask-image: linear-gradient(to right, transparent 0, #000 14%),
                      linear-gradient(to bottom, transparent 0, #000 11%, #000 82%, transparent 100%);
  -webkit-mask-composite: source-in;
          mask-composite: intersect;
}

@media (max-width: 1199px) {
  /* Stacked: copy takes container gutters on both sides and the art
     becomes a band under it. The band is capped, because an illustration
     with no ceiling in a single column is what pushed everything below
     the fold in the old hero between 768 and 1199. */
  .ov-hero-split { grid-template-columns: 1fr; padding-left: 0; min-height: 0; row-gap: 0; }
  .ov-hero-split__copy { padding-inline: var(--ov-hero-gutter); padding-block: var(--ov-space-9) var(--ov-space-8); max-width: 40rem; }
  /* Stacked, the art is back in flow, so it needs a real height rather
     than an absolutely positioned child with nothing to fill. */
  .ov-hero-split__art { height: 320px; max-height: none; }
  .ov-hero-split__art img {
    -webkit-mask-image: linear-gradient(to bottom, #000 76%, transparent);
            mask-image: linear-gradient(to bottom, #000 76%, transparent);
  }
}
@media (max-width: 767px) {
  .ov-hero-split__art { height: 220px; }
  .ov-hero-split__copy { padding-block: var(--ov-space-8) var(--ov-space-7); }
}

/* ---- The split hero with an illustration rather than a photograph.
   6 October 2026 handoff. The art is a drawn scene with people in it, so it
   is never cropped, never faded and never sits behind text: it keeps its
   own proportions (height: auto, contain), takes the space its width and
   height attributes reserve, and on a narrow screen follows the copy and
   the actions in normal flow. Both columns sit inside the page gutter;
   nothing bleeds off the edge. ---- */
.ov-hero-split--art { grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.05fr);
  padding-inline: var(--ov-hero-gutter); min-height: 0; column-gap: var(--ov-space-8); }
.ov-hero-split--art .ov-hero-split__art { align-self: center; overflow: visible; }
.ov-hero-split--art .ov-hero-split__art img { position: static; width: 100%; height: auto;
  object-fit: contain; -webkit-mask-image: none; mask-image: none; }
@media (max-width: 1199px) {
  .ov-hero-split--art { grid-template-columns: minmax(0, 1fr); padding-inline: 0; }
  .ov-hero-split--art .ov-hero-split__art { height: auto; padding-inline: var(--ov-hero-gutter);
    padding-bottom: var(--ov-space-7); max-width: 44rem; }
  .ov-hero-split--art .ov-hero-split__art img { -webkit-mask-image: none; mask-image: none; }
  .ov-hero-split--art .ov-hero-split__copy { padding-bottom: var(--ov-space-6); }
}
/* On a phone the hero takes the same gutter as every section under it. The
   split hero's own gutter is 2rem; .container-x drops to 1.25rem, then 1.1rem. */
@media (max-width: 767px) { .ov-hero-split--art { --ov-hero-gutter: 1.25rem; } }
@media (max-width: 479px) { .ov-hero-split--art { --ov-hero-gutter: 1.1rem; } }

@media (max-width: 720px) {
  /* The art moves into flow. It never sets the section height, and no
     desktop absolute position is left governing a phone layout. */
  .ov-hero--editorial { grid-template-columns: 1fr; }
  .ov-hero--ambient .ov-hero__art { position: static; }
  .ov-hero--ambient .ov-hero__art img { opacity: 1; height: auto; }
  .ov-hero--ambient .ov-hero__copy { background: none; }
  .ov-hero { padding-block: var(--ov-space-7) var(--ov-space-6); }
}

/* ---- Course grid. Container queries, per the brief: the same card has to
   work beside a sidebar, and the viewport does not know about the sidebar. ---- */
.ov-catalogue { container-type: inline-size; container-name: catalogue; }
.ov-course-grid { display: grid; grid-template-columns: 1fr; gap: var(--ov-space-3); }

/* CORRECTED 28 September, and the correction is worth reading before anyone
   puts the original numbers back.

   As extracted, the tiers were 2 columns from 305px of container and 4 from
   896px. Divide those through: 305px in two columns is a 146px card, and
   896px in four is 212px. No card holds a pricing badge, a review badge, a
   title, a byline, three lines of summary, three meta items and a footer at
   either width. Built as written, the phone case put the card at 171px, the
   review badge overflowed its cover and every meta item took its own line.

   So the thresholds now follow from the card rather than the card being
   asked to survive the thresholds. The floor is 280px, which is the width
   at which the built card holds together, and each tier begins where that
   many cards plus their gaps actually fit:
     2 columns from 2*280 + 16 = 576
     4 columns from 4*280 + 3*16 = 1168
   The brief's 1/2/4 structure is unchanged. Only the numbers move. */
@container catalogue (min-width: 576px) {
  .ov-course-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--ov-space-3); }
}
@container catalogue (min-width: 1168px) {
  .ov-course-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--ov-space-4); }
}
/* A browser without container queries gets the two-column case, which is the
   brief's ordinary mobile treatment and is never wrong, only conservative. */

/* ---- The sparse-catalogue variant, and it is meant to be temporary.
   The grid above reserves a fixed number of tracks, which is right for the
   catalogue Overtly is building toward and wrong today: two listed courses
   in a four-track row leave half the row empty and read as a broken layout
   rather than as a small catalogue.

   auto-fit collapses the empty tracks, and the max on the track keeps the
   two survivors from stretching to half the page each, which is what
   auto-fit does on its own. The card lands at the same width it would have
   had in the full grid, so nothing about the card has to know which variant
   it is in, and the layout becomes the four-up grid by itself as courses
   are published. min(100%, ...) on the floor is the grid-blowout guard: a
   fixed minimum wider than its container overflows the viewport, which is
   the bug the old .course-card min-width:0 rule was written for.

   Remove the modifier once the catalogue fills its rows. ---- */
.ov-course-grid--fill { --ov-card-min: 280px; --ov-card-max: 308px;
  justify-content: start; }
/* Below the two-column tier the modifier does nothing and the base grid's
   single 1fr column stands. Capping the card at 308px on a phone would
   leave a ragged strip of empty page beside it, and there is no row to
   keep tidy when every card is its own row. The threshold matches the
   corrected base grid above, so the two never disagree about when a
   second column appears. */
/* CORRECTED 29 September. The threshold was 576, matching the base grid,
   and that was the wrong number for this rule. auto-fit counts tracks by
   the track's MAXIMUM when the maximum is a fixed length, not by its
   minimum: at a 600px container, floor((600 + 12) / (308 + 12)) is one
   track, so a 600px-wide page showed a single 308px card with 290px of
   empty paper beside it. Reported from a phone, and it read as a broken
   layout rather than as a small catalogue, which is the exact failure
   this modifier exists to prevent.

   The cap may only engage where two capped cards actually fit:
   2 * 308 + 12 = 628. Below that the base grid stands, which is one full
   width column under 576 and two equal columns filling the row from 576
   to 627. Neither leaves a strip of empty page. */
@container catalogue (min-width: 628px) {
  .ov-course-grid--fill { grid-template-columns: repeat(auto-fit,
    minmax(min(100%, var(--ov-card-min)), var(--ov-card-max))); }
}

/* ---- Course card. Neutral since the 6 October 2026 handoff: a white
   surface on the paper, a short accent bar, and every course given the same
   weight. The tinted covers went with their four tones. A tone derived from
   the course id meant nothing, and the loudest thing on a card should not be
   the thing that means nothing.

   No outline and no shadow. The card is separated from the page by its own
   surface, and its one link is the title, stretched over the whole card. ---- */
.ov-card { position: relative; display: flex; flex-direction: column; min-width: 0;
  background: var(--ov-surface); border-radius: var(--ov-radius-card);
  padding: var(--ov-space-5); gap: var(--ov-space-3); }
.ov-card > .ov-accent-bar { margin-bottom: var(--ov-space-2); }
/* The category gives way and wraps; the status keeps one line at the right
   edge. On a very narrow card the top line itself wraps and the status stays
   right-aligned on its own row. */
.ov-card__topline { display: flex; align-items: flex-start; justify-content: space-between;
  flex-wrap: wrap; gap: var(--ov-space-2) var(--ov-space-3); font-size: var(--ov-size-meta); }
.ov-card__cat { flex: 1 1 6ch; min-width: 0; font-weight: 700; letter-spacing: 0.06em;
  text-transform: uppercase; line-height: 1.45; color: var(--ov-text-muted); }
.ov-card__badges { display: inline-flex; flex: none; flex-wrap: nowrap; align-items: center;
  justify-content: flex-end; gap: var(--ov-space-3); margin-left: auto; color: var(--ov-text-muted); }
/* What the course costs and whether it is open, as words. Neither is a
   button, and neither is carried by colour. */
.ov-card__badge { white-space: nowrap; }
.ov-card__badge--status { padding: 1px var(--ov-space-2); border: 1px solid var(--ov-control-border);
  border-radius: var(--ov-radius-pill); color: var(--ov-text); font-weight: 600; }
.ov-card__title { margin: 0; font-size: var(--ov-size-card); line-height: 1.2;
  letter-spacing: var(--ov-track-card); font-weight: 600; }
/* Three lines is a visual limit, not a truncation of the record: the full
   description is on the course page. */
.ov-card__summary { font-size: var(--ov-size-label); line-height: 1.55; color: var(--ov-text-muted);
  margin: 0; display: -webkit-box; -webkit-box-orient: vertical;
  -webkit-line-clamp: 3; line-clamp: 3; overflow: hidden; }
.ov-card__meta { display: flex; flex-wrap: wrap; gap: var(--ov-space-3);
  font-size: var(--ov-size-meta); color: var(--ov-text-muted); margin-top: auto;
  padding-top: var(--ov-space-3); }
.ov-card__foot { display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: var(--ov-space-2); font-size: var(--ov-size-meta); }
/* space-between puts a wrapped last item at the start of its own line. This
   keeps the action at the right edge whether the footer is one line or two. */
.ov-card__foot > :last-child { margin-left: auto; }
/* One navigation target per card, stretched over it. The title carries no
   underline at rest; the hover and the focus ring are the affordance. */
.ov-card__link { color: inherit; text-decoration: none; }
.ov-card__link::after { content: ""; position: absolute; inset: 0; border-radius: inherit; }
.ov-card__link:focus-visible { outline: var(--ov-focus-width) solid var(--ov-focus);
  outline-offset: 3px; border-radius: 2px; }
@media (hover: hover) {
  .ov-card:hover .ov-card__link { text-decoration: underline; text-underline-offset: 0.18em;
    text-decoration-thickness: 1px; }
}
.ov-card__action { position: relative; z-index: 1; }
.ov-card__tags { display: inline-flex; flex-wrap: wrap; gap: var(--ov-space-2); }
.ov-card__byline { font-size: var(--ov-size-meta); color: var(--ov-text-muted); }

/* A card with nothing to click does not pretend otherwise. */
.ov-card--soon { cursor: default; }

/* ---- Controls ---- */
.ov-btn { display: inline-flex; align-items: center; justify-content: center;
  gap: var(--ov-space-2); min-height: var(--ov-target-min);
  padding: var(--ov-space-3) var(--ov-space-6); border: 0;
  border-radius: var(--ov-radius-pill); font: inherit; font-weight: 600;
  background: var(--ov-action-bg); color: var(--ov-action-text);
  cursor: pointer; text-decoration: none;
  transition: background var(--ov-motion-fast) var(--ov-motion-ease); }
.ov-btn:disabled, .ov-btn[aria-disabled="true"] { opacity: 0.55; cursor: default; }
.ov-btn[data-loading="true"] { cursor: progress; }
.ov-btn--secondary { background: transparent; color: var(--ov-text);
  border: 1px solid var(--ov-control-border); }
.ov-link { color: var(--ov-text); text-decoration: underline;
  text-underline-offset: 0.2em; }

.ov-field { display: flex; flex-direction: column; gap: var(--ov-space-2); }
.ov-field__label { font-size: var(--ov-size-label); font-weight: 600; }
.ov-field__help, .ov-field__error { font-size: var(--ov-size-meta); }
.ov-field__help { color: var(--ov-text-muted); }
.ov-field__error { color: var(--ov-ink); font-weight: 600; }
.ov-input, .ov-select {
  min-height: var(--ov-target-min); width: 100%;
  padding: var(--ov-space-3) var(--ov-space-4);
  font: inherit; font-size: var(--ov-size-body);
  background: var(--ov-surface); color: var(--ov-text);
  border: 1px solid var(--ov-control-border);
  border-radius: var(--ov-radius-control); }

.ov-chip { display: inline-flex; align-items: center; min-height: var(--ov-target-min);
  padding: var(--ov-space-2) var(--ov-space-4); font-size: var(--ov-size-label);
  background: transparent; color: var(--ov-text);
  border: 1px solid var(--ov-control-border); border-radius: var(--ov-radius-pill);
  cursor: pointer; }
/* Selected state is carried by aria-pressed, so it is exposed to assistive
   technology and not by colour alone. */
.ov-chip[aria-pressed="true"] { background: var(--ov-action-bg);
  border-color: var(--ov-action-bg); color: var(--ov-action-text); }

.ov-pill { display: inline-flex; align-items: center; padding: 2px var(--ov-space-2);
  font-size: var(--ov-size-meta); border: 1px solid var(--ov-control-border);
  border-radius: var(--ov-radius-pill); white-space: nowrap; }
/* Tones. The system had one neutral pill, so every converted surface kept
   reaching back into styles.css for a coloured one, which is how a design
   system quietly stops being the source of truth.

   Each tone is a background the brand already has, with ink on it, and
   every pairing is asserted by tools/check-design-tokens.mjs rather than
   trusted: ink on mint 8.47, on pale coral 4.87 via coral ink, on yolk
   7.60, on pale 10.75. The border drops where the fill already separates
   the pill from the page.

   A tone is emphasis, never the only carrier of meaning. "Free" is
   readable as the word Free with the colour removed, which is the test. */
.ov-pill--mint  { background: var(--ov-mint);       color: var(--ov-ink); border-color: transparent; }
.ov-pill--coral { background: var(--ov-coral-pale); color: var(--ov-coral-ink); border-color: transparent; }
.ov-pill--yolk  { background: var(--ov-yolk);       color: var(--ov-ink); border-color: transparent; }
.ov-pill--pale  { background: var(--ov-pale);       color: var(--ov-ink); border-color: transparent; }
.ov-pill--ink   { background: var(--ov-ink);        color: var(--ov-text-on-dark); border-color: transparent; }
.ov-pill--ink :focus-visible { outline-color: var(--ov-focus-on-dark); }

/* ---- Progress. The number is in text as well as the bar, because a bar
   alone is not a value anyone can read out. ---- */
.ov-progress { display: flex; flex-direction: column; gap: var(--ov-space-2); }
.ov-progress__label { display: flex; justify-content: space-between;
  gap: var(--ov-space-5); font-size: var(--ov-size-meta); }
.ov-progress__track { height: 5px; border-radius: var(--ov-radius-pill);
  background: rgb(23 59 51 / 0.12); overflow: hidden; }
.ov-progress__fill { height: 100%; background: var(--ov-action-bg); }

/* ---- Reading ---- */
.ov-prose { max-width: var(--ov-reading-width); }
.ov-prose h2 { font-size: var(--ov-size-section); margin: var(--ov-space-7) 0 var(--ov-space-3); }
.ov-prose p { line-height: var(--ov-leading-lesson); margin: 0 0 var(--ov-space-5); }
.ov-prose ul, .ov-prose ol { line-height: var(--ov-leading-lesson); padding-left: var(--ov-space-6); }
.ov-prose li { margin-bottom: var(--ov-space-3); }

/* ---- Skeletons. One announcement for the region, not one per placeholder. ---- */
.ov-skeleton { background: rgb(23 59 51 / 0.07); border-radius: var(--ov-radius-control); }

/* ---- Editorial art that belongs to the page rather than sitting in a box.
   Applied to an existing hero illustration, so it is a treatment rather
   than a component: the image keeps whatever markup and positioning the
   page already has, and only its edges change. Masks are progressive: if
   the browser does not support them the image simply has hard edges, and
   nothing becomes unreadable. ---- */
.ov-art-blend {
  -webkit-mask-image:
    linear-gradient(to right, transparent 0, #000 14%, #000 86%, transparent 100%),
    linear-gradient(to bottom, #000 70%, transparent 100%);
  -webkit-mask-composite: source-in;
          mask-image:
    linear-gradient(to right, transparent 0, #000 14%, #000 86%, transparent 100%),
    linear-gradient(to bottom, #000 70%, transparent 100%);
          mask-composite: intersect;
}
/* On a phone the art sits in normal flow under the copy, where a fade on
   every edge reads as a printing fault rather than a blend. Bottom only. */
@media (max-width: 767px) {
  .ov-art-blend {
    -webkit-mask-image: linear-gradient(to bottom, #000 82%, transparent 100%);
            mask-image: linear-gradient(to bottom, #000 82%, transparent 100%);
  }
}

/* ============================================================
   Course page. Rebuilt 28 September on the composition from
   Orlando's preview, with three changes he asked for:

   1. The enroll tile carries less weight. In the preview it is a
      forest block with a 2rem display headline, which made the
      loudest thing on the page a panel of facts rather than the
      course. It keeps its job, the enroll and continue action,
      and loses the headline and the dark fill.
   2. The learning path keeps the module-tile structure but has to
      survive more than four modules. WASH has nine. So the tiles
      are compact and the grid auto-fills rather than being two
      fixed columns: nine modules become three rows of three
      instead of five rows of tall cards.
   3. Overview, sources and version history move out of a right
      sidebar and into the section at the bottom, closed. That is
      Orlando's preference and it also settles backlog 15.4, where
      Blind Spots' 85 sources rendered open by default.
   ============================================================ */

.ov-breadcrumb { display: flex; align-items: center; gap: var(--ov-space-2);
  font-size: var(--ov-size-meta); color: var(--ov-text-muted); padding-block: var(--ov-space-6); }
.ov-breadcrumb a { color: var(--ov-env-link); font-weight: 600; }

.ov-eyebrow { margin: 0; font-size: var(--ov-size-meta); font-weight: 700;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--ov-ink); }

.ov-course-hero { display: grid; grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
  gap: var(--ov-space-10); align-items: start; padding-bottom: var(--ov-space-9); }
.ov-course-hero__copy { display: flex; flex-direction: column; align-items: flex-start; gap: var(--ov-space-5); }
.ov-course-hero__copy h1 { margin: 0; font-size: clamp(2.25rem, 1.4rem + 2.2vw, 3.5rem);
  line-height: var(--ov-leading-display); letter-spacing: var(--ov-track-display);
  font-weight: var(--ov-title-weight); }
.ov-course-hero__lede { margin: 0; font-size: 1.05rem; line-height: 1.6;
  color: var(--ov-text-muted); max-width: 38rem; }
/* The bar above the author row, and the legend beside the path heading. */
.ov-course-hero__bar { margin: var(--ov-space-3) 0 0; }
.ov-path__aside { display: flex; flex-direction: column; align-items: flex-end; gap: var(--ov-space-2); }
@media (max-width: 1023px) { .ov-path__aside { align-items: flex-start; } }
.ov-author-row { display: flex; align-items: center; gap: var(--ov-space-3); font-size: var(--ov-size-label); }
.ov-author-row small { display: block; color: var(--ov-text-muted); font-size: var(--ov-size-meta); margin-top: 2px; }

/* The lighter enroll tile. Pale rather than forest, no display headline,
   and the facts are a definition list because that is what they are. */
.ov-enroll { background: var(--ov-surface); border-radius: var(--ov-radius-card);
  padding: var(--ov-space-6); position: sticky; top: var(--ov-space-7); }
.ov-enroll > .ov-accent-bar { margin-bottom: var(--ov-space-4); }
.ov-enroll__facts { display: grid; grid-template-columns: 1fr 1fr; gap: var(--ov-space-4);
  margin: var(--ov-space-5) 0; padding: var(--ov-space-5) 0;
  border-block: 1px solid var(--ov-divider); font-size: var(--ov-size-label); }
.ov-enroll__facts dt { font-size: var(--ov-size-meta); color: var(--ov-text-muted); margin-bottom: 4px; }
.ov-enroll__facts dd { margin: 0; font-weight: 600; }
.ov-enroll__note { margin: var(--ov-space-4) 0 0; font-size: var(--ov-size-meta); color: var(--ov-text-muted); }

.ov-section-head { display: flex; justify-content: space-between; align-items: flex-end;
  gap: var(--ov-space-6); margin-bottom: var(--ov-space-6); }
.ov-section-head p { margin: var(--ov-space-3) 0 0; color: var(--ov-text-muted);
  font-size: var(--ov-size-label); max-width: 44rem; }
.ov-section-head h2 { margin: var(--ov-space-3) 0 0; font-size: clamp(1.5rem, 1.1rem + 1vw, 2rem);
  font-weight: var(--ov-title-weight); letter-spacing: var(--ov-track-title); line-height: var(--ov-leading-title); }
.ov-section-head__meta { font-size: var(--ov-size-label); color: var(--ov-text-muted); white-space: nowrap; }

/* The learning path. auto-fill, not two fixed columns: the number of
   modules is a property of the course, not of the design. */
/* ---- The learning path. Two surfaces and they mean one thing each: a
   white module is learning content, a pale environment module ends in a
   hands-on exercise and says so with an APPLY IT label. The surface comes
   from the record's own has_exercise and never from where a module sits in
   the list. It is not progress, selection, access or completion: those keep
   their own words in the foot of the tile.

   The label carries the fact; the surface agrees with it. A module that ends
   in a quiz, or whose record does not say, is white. ---- */
.ov-path { display: grid; gap: var(--ov-space-4);
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 250px), 1fr)); }
.ov-path__item { display: flex; flex-direction: column; min-width: 0;
  padding: var(--ov-space-5); border-radius: var(--ov-radius-card);
  background: var(--ov-surface); color: var(--ov-text); position: relative; }
.ov-path__item[data-practice="true"] { background: var(--ov-env-practice-bg); }
.ov-path__item > .ov-accent-bar { margin-bottom: var(--ov-space-4); }
.ov-path__item :is(h1,h2,h3,h4,strong) { color: inherit; }
.ov-path__top { margin: 0; font-size: var(--ov-size-meta); font-weight: 700; letter-spacing: 0.08em;
  text-transform: uppercase; }
.ov-path__meta { margin: var(--ov-space-2) 0 0; font-size: var(--ov-size-meta); color: var(--ov-text-muted); }
.ov-path__title { margin: var(--ov-space-4) 0 var(--ov-space-5); font-size: 1.125rem;
  line-height: 1.3; font-weight: 600; letter-spacing: var(--ov-track-card); }
.ov-path__foot { display: flex; align-items: center; justify-content: space-between;
  gap: var(--ov-space-3); margin-top: auto; padding-top: var(--ov-space-4); font-size: var(--ov-size-meta); }
.ov-path__action { font-weight: 600; color: var(--ov-env-link); }
.ov-path__link::after { content: ""; position: absolute; inset: 0; border-radius: inherit; }
.ov-path__link { color: inherit; text-decoration: none; }
.ov-path__link:focus-visible { outline: var(--ov-focus-width) solid var(--ov-focus); outline-offset: 3px;
  border-radius: 2px; }
@media (hover: hover) {
  .ov-path__item:hover .ov-path__link { text-decoration: underline; text-underline-offset: 0.18em;
    text-decoration-thickness: 1px; }
}

/* The words that say how a module ends. APPLY IT sits on platform mint,
   which takes ink at 8.6:1. A quiz gets the same shape with no colour. */
.ov-apply-label, .ov-path__kind { align-self: flex-start; display: inline-flex; align-items: center;
  padding: 3px var(--ov-space-3); border-radius: var(--ov-radius-pill);
  font-size: var(--ov-size-meta); font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; }
.ov-apply-label { background: var(--ov-mint); color: var(--ov-ink); }
.ov-path__kind { border: 1px solid var(--ov-control-border); color: var(--ov-text); }

.ov-path__legend { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ov-space-3) var(--ov-space-6);
  margin: 0; font-size: var(--ov-size-meta); color: var(--ov-text-muted); }
.ov-path__legend-item { display: inline-flex; align-items: center; gap: var(--ov-space-3); }
/* A locked module is quiet rather than dimmed: opacity would take its
   text under the contrast floor. It keeps its own word, "Locked". */
.ov-path__item[data-locked="true"] .ov-path__title,
.ov-path__item[data-locked="true"] .ov-path__action { color: var(--ov-text-muted); }

/* ---- The editorial section under the path: what the course is, in full,
   and beside it an illustration where the course has one. The art is a
   scene and never a portrait of an author. It keeps its proportions, sits
   in no frame, and is absent rather than invented for a course without. ---- */
.ov-course-editorial { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--ov-space-9);
  align-items: center; padding-block: var(--ov-space-10) var(--ov-space-7); }
.ov-course-editorial--art { grid-template-columns: minmax(0, 0.9fr) minmax(0, 1fr); }
.ov-course-editorial__art { display: block; width: 100%; height: auto; object-fit: contain;
  border-radius: var(--ov-radius-card); }
.ov-course-editorial__copy { max-width: 44rem; }
.ov-course-editorial h3 { margin: 0 0 var(--ov-space-5); font-size: clamp(1.5rem, 1.15rem + 1.2vw, 2.125rem);
  line-height: var(--ov-leading-title); font-weight: var(--ov-title-weight); letter-spacing: var(--ov-track-title); }
.ov-course-editorial p { margin: 0; font-size: var(--ov-size-body); line-height: 1.6; color: var(--ov-text-muted); }
.ov-course-editorial .ov-related { margin: var(--ov-space-6) 0 0; padding-left: 0; border-left: 0; }
.ov-course-editorial .ov-related a { display: inline-flex; align-items: center; min-height: var(--ov-target-min); }
.ov-course-editorial .ov-related small { margin-top: -6px; }
@media (max-width: 899px) {
  .ov-course-editorial, .ov-course-editorial--art { grid-template-columns: minmax(0, 1fr);
    gap: var(--ov-space-7); padding-block: var(--ov-space-9) var(--ov-space-6); }
}

.ov-course-lower { display: grid; grid-template-columns: minmax(0, 0.9fr) minmax(0, 1fr);
  gap: var(--ov-space-9); padding-block: var(--ov-space-7) var(--ov-space-9); align-items: start; }
.ov-course-lower h3 { margin: 0 0 var(--ov-space-5); font-size: 1.4rem;
  font-weight: var(--ov-title-weight); letter-spacing: var(--ov-track-title); }
.ov-course-lower p { font-size: var(--ov-size-label); line-height: 1.7; color: var(--ov-text-muted); }
.ov-course-lower details { padding: var(--ov-space-1) 0; }
.ov-course-lower details[open] { padding-bottom: var(--ov-space-4); }
.ov-course-lower details + details { border-top: 1px solid var(--ov-divider); }
.ov-course-lower .ovt-review { margin: 0 0 var(--ov-space-5); padding-top: 0; border-top: 0; }
/* Resources reads as the column's heading, at the size of one. */
.ov-course-lower .ov-resources { padding-top: 0; }
.ov-course-lower .ov-resources > summary { font-size: 1.4rem; font-weight: var(--ov-title-weight);
  letter-spacing: var(--ov-track-title); align-items: center; }
.ov-course-lower summary { cursor: pointer; font-weight: 600; font-size: var(--ov-size-label);
  list-style: none; display: flex; justify-content: space-between; align-items: center;
  gap: var(--ov-space-3); min-height: var(--ov-target-min); }
.ov-course-lower summary::-webkit-details-marker { display: none; }
.ov-course-lower summary::after { content: "+"; font-weight: 400; }
.ov-course-lower details[open] summary::after { content: "\2212"; }
.ov-course-lower summary:focus-visible { outline: var(--ov-focus-width) solid var(--ov-focus); outline-offset: 3px; }
/* 85 sources is a scroll box, not a list that owns the page. Backlog 15.4. */
.ov-course-lower .ov-scroll { max-height: 340px; overflow-y: auto;
  overscroll-behavior: contain; margin-top: var(--ov-space-4);
  padding-right: var(--ov-space-3); scrollbar-width: thin;
  /* The last row lands part way down the box. With the rules between rows
     that already reads as "there is more", and this fade says it again in
     the way every scroll region does. Short on purpose: it softens the cut
     row, it does not hide a row anyone has to read, and the same text is
     fully legible one scroll away. */
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 18px), transparent);
          mask-image: linear-gradient(to bottom, #000 calc(100% - 18px), transparent); }

/* ---- The source list ----
   It was a bare <ol>, and the Preflight reset strips list markers and
   padding, so eighty-five titles ran together as one wrapped paragraph
   with nothing to tell one source from the next. Three things fix it and
   each earns its place: a number, because a lesson's citation marker is
   [n] against this list and without the number the marker points at
   nothing findable; a rule between rows, because a wrapped two-line title
   next to another one is otherwise unreadable; and a row that ends part
   way down the scroll box, which now reads as "there is more" rather
   than as broken rendering. */
.ov-source-list { list-style: none; margin: 0; padding: 0; counter-reset: ovsrc; }
.ov-source-list li { counter-increment: ovsrc;
  display: grid; grid-template-columns: 2rem minmax(0, 1fr); gap: var(--ov-space-3);
  padding: var(--ov-space-3) 0; border-top: 1px solid var(--ov-divider);
  font-size: var(--ov-size-label); line-height: 1.4; color: var(--ov-text); }
.ov-source-list li:first-child { border-top: 0; padding-top: 0; }
.ov-source-list li::before { content: counter(ovsrc);
  font-size: var(--ov-size-meta); font-weight: 700; font-variant-numeric: tabular-nums;
  color: var(--ov-text-muted); padding-top: 2px; }

/* ---- Course updates ----
   A version, its date and its note, in that order and visibly separate.
   The note was joined to the version by a <br>, which put a 1.7 line
   height between them and none between one release and the next. */
.ov-version-list { list-style: none; margin: 0; padding: 0; }
.ov-version-list li { display: flex; flex-direction: column; gap: 2px;
  padding: var(--ov-space-4) 0; border-top: 1px solid var(--ov-divider); }
.ov-version-list li:first-child { border-top: 0; padding-top: var(--ov-space-2); }
.ov-version__head { font-size: var(--ov-size-label); color: var(--ov-text); }
.ov-version__head strong { font-weight: 700; }
.ov-version__note { font-size: var(--ov-size-label); line-height: 1.55;
  color: var(--ov-text-muted); }

@media (max-width: 1023px) {
  .ov-course-hero { grid-template-columns: 1fr; gap: var(--ov-space-7); }
  .ov-enroll { position: static; }
  .ov-course-lower { grid-template-columns: 1fr; gap: var(--ov-space-7); }
  .ov-section-head { flex-direction: column; align-items: flex-start; gap: var(--ov-space-3); }
}
@media (max-width: 479px) {
  .ov-enroll__facts { grid-template-columns: 1fr; gap: var(--ov-space-3); }
}

/* ---- Catalogue card, the parts under the title.
   The action says "View course", not "Enroll". The card navigates to the
   course page, where the one start action lives. ---- */
.ov-card__by { font-size: var(--ov-size-meta); color: var(--ov-text-muted); min-width: 0; }
.ov-card__cta { font-size: var(--ov-size-label); font-weight: 600; white-space: nowrap;
  color: var(--ov-env-link); }
.ov-card--soon .ov-card__cta { color: var(--ov-text-muted); }
/* Rules between meta items, not just spacing. Three unseparated figures
   read as one run-on string at 12px. */
.ov-card__meta > span + span { border-left: 1px solid var(--ov-divider);
  padding-left: var(--ov-space-3); }

/* ---- Catalogue controls. Search grows, the filters keep their width, and
   the sort sits at the end. The old toolbar had no sort at all. ---- */
.ov-controls { display: flex; align-items: center; gap: var(--ov-space-4);
  flex-wrap: wrap; margin-bottom: var(--ov-space-4); }
.ov-search { position: relative; flex: 1 1 320px; min-width: 0; max-width: 560px; }
.ov-search > span { position: absolute; left: 14px; top: 50%; transform: translateY(-50%);
  font-size: 1.1rem; color: var(--ov-text-muted); pointer-events: none; }
.ov-search input { width: 100%; background: var(--ov-white);
  border: 1px solid var(--ov-control-border); border-radius: var(--ov-radius-control);
  padding: 13px 14px 13px 40px; font: inherit; font-size: var(--ov-size-label); color: var(--ov-ink); }
.ov-search input:focus-visible { outline: var(--ov-focus-width) solid var(--ov-focus); outline-offset: 2px; }
.ov-filters { display: flex; gap: var(--ov-space-2); flex-wrap: wrap; }
.ov-filter { background: transparent; border: 1px solid var(--ov-control-border);
  border-radius: var(--ov-radius-pill); padding: 10px 15px;
  font: inherit; font-size: var(--ov-size-meta); color: var(--ov-ink); cursor: pointer; }
.ov-filter[aria-pressed="true"] { background: var(--ov-action-bg); border-color: var(--ov-action-bg);
  color: var(--ov-text-on-dark); }
.ov-filter:focus-visible { outline: var(--ov-focus-width) solid var(--ov-focus); outline-offset: 2px; }
.ov-sort { margin-left: auto; display: flex; align-items: center; gap: var(--ov-space-2);
  font-size: var(--ov-size-meta); color: var(--ov-text-muted); }
/* The select sat as a bare underlined native control beside two bordered
   ones, so it read as a different system, and its focus ring landed as a
   hard rectangle around a borderless box. It now takes the same shape as
   .ov-filter, which is its neighbour in the toolbar: same border token,
   same pill, same type.

   appearance: none removes the platform chevron and this draws its own,
   inline so there is no request for it. padding-right clears it, and
   text-overflow keeps a long option from running under it.

   WHAT THIS CANNOT DO: the open option list is drawn by the operating
   system, not by the page, and no stylesheet reaches it on any browser.
   Styling the closed control is the whole of what CSS can change here.
   A fully branded list means replacing the select with a button and an
   ARIA listbox, which costs the native keyboard behaviour and the iOS
   picker, and is a deliberate trade rather than a tidy-up. */
.ov-sort select { font: inherit; font-size: var(--ov-size-meta); color: var(--ov-ink);
  appearance: none; -webkit-appearance: none;
  background-color: transparent;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23173b33' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--ov-space-3) center;
  border: 1px solid var(--ov-control-border); border-radius: var(--ov-radius-pill);
  padding: 7px calc(var(--ov-space-3) + 16px) 7px var(--ov-space-4);
  max-width: 14rem; text-overflow: ellipsis; cursor: pointer; }
.ov-sort select:hover { border-color: var(--ov-ink); }
.ov-sort select:focus-visible { outline: var(--ov-focus-width) solid var(--ov-focus);
  outline-offset: 2px; }
/* Firefox keeps a focus ring inside the control on top of ours. */
.ov-sort select:-moz-focusring { color: transparent; text-shadow: 0 0 0 var(--ov-ink); }
.ov-result-count { margin: 0 0 var(--ov-space-5); font-size: var(--ov-size-meta); color: var(--ov-text-muted); }

/* An empty result is a state with something to say, not a blank grid. */
.ov-empty { grid-column: 1 / -1; padding: var(--ov-space-10) var(--ov-space-6);
  border: 1px dashed var(--ov-control-border); border-radius: var(--ov-radius-panel); text-align: center; }
.ov-empty h2 { margin: 0 0 var(--ov-space-3); font-size: 1.4rem; }
.ov-empty p { margin: 0; color: var(--ov-text-muted); font-size: var(--ov-size-label); }

.ov-catalog-note { display: flex; justify-content: space-between; align-items: baseline;
  gap: var(--ov-space-6); border-top: 1px solid var(--ov-divider);
  margin-top: var(--ov-space-9); padding: var(--ov-space-6) 0 var(--ov-space-9);
  font-size: var(--ov-size-label); }
.ov-catalog-note p { margin: 0; max-width: 40rem; color: var(--ov-text-muted); }

/* ---- The Discover hero. Same ambient idea as the home hero but the art
   starts at 30 percent and bleeds off the right, with a horizontal mask
   rather than a flat opacity, so the copy sits on clean paper and the
   illustration arrives behind it. ---- */
.ov-hero--catalogue { position: relative; isolation: isolate; overflow: hidden;
  min-height: 250px; padding-block: var(--ov-space-8) var(--ov-space-7); }
.ov-hero--catalogue .ov-hero__copy { position: relative; z-index: 2; max-width: 41rem; background: none; }
.ov-hero--catalogue .ov-hero__art { position: absolute; inset: 0 0 0 30%; z-index: -1;
  margin: 0; overflow: hidden; pointer-events: none; }
.ov-hero--catalogue .ov-hero__art img { width: 100%; height: 100%; object-fit: cover;
  object-position: var(--ov-art-focal, 65% center); opacity: var(--ov-hero-art-opacity, 0.55);
  -webkit-mask-image: linear-gradient(90deg, transparent 10%, #000 60%);
          mask-image: linear-gradient(90deg, transparent 10%, #000 60%); }
@media (max-width: 720px) {
  .ov-hero--catalogue { min-height: 0; }
  .ov-hero--catalogue .ov-hero__art { position: static; inset: auto; height: 150px; margin-top: var(--ov-space-6); }
  .ov-hero--catalogue .ov-hero__art img {
    -webkit-mask-image: linear-gradient(to bottom, #000 78%, transparent);
            mask-image: linear-gradient(to bottom, #000 78%, transparent); }
}
@media (max-width: 767px) {
  .ov-sort { margin-left: 0; }
  .ov-search { flex-basis: 100%; max-width: none; }
  .ov-catalog-note { flex-direction: column; gap: var(--ov-space-4); }
}

/* ============================================================
   Lesson page. Rebuilt 29 September on the composition from
   Orlando's preview: the sidebar carries the module as a tile, the
   lesson list as tabs, and the progress bar, so the reading column
   is left with nothing but the lesson.

   Proportions are the preview's, converted to tokens: a 240px
   sticky rail, a 44px gutter, and a reading measure capped near
   735px because that is about 75 characters at this size and the
   old page let lesson prose run the full column width.
   ============================================================ */
.ov-lesson { display: grid; grid-template-columns: 240px minmax(0, 1fr);
  gap: var(--ov-space-9); align-items: start; padding-block: var(--ov-space-7) var(--ov-space-9); }
.ov-lesson__rail { position: sticky; top: var(--ov-space-7); display: flex;
  flex-direction: column; gap: var(--ov-space-5); }
.ov-lesson__back { font-size: var(--ov-size-meta); color: var(--ov-text-muted); }

/* The module tile. Same idiom as the course page's learning path, so a
   learner arriving from that grid sees the tile they clicked. */
.ov-lesson__card { padding: var(--ov-space-5); border-radius: var(--ov-radius-card);
  background: var(--ov-tile-tone, var(--ov-mint)); color: var(--ov-ink); }
.ov-lesson__card :is(h1,h2,h3,h4,strong) { color: inherit; }
.ov-lesson__card .ov-eyebrow { font-size: var(--ov-size-meta); margin-bottom: var(--ov-space-4); }
.ov-lesson__card h2, .ov-lesson__card h3 { margin: 0 0 var(--ov-space-3); font-size: 1.2rem;
  line-height: 1.22; font-weight: var(--ov-title-weight); letter-spacing: var(--ov-track-card); }
.ov-lesson__card p { margin: 0; font-size: var(--ov-size-meta); }

/* Lesson list as tabs rather than a bulleted nav. */
.ov-lesson__nav { display: flex; flex-direction: column; gap: 4px; }
.ov-lesson__nav .lesson-item,
.ov-lesson__nav a { display: flex; align-items: flex-start; gap: var(--ov-space-3);
  padding: var(--ov-space-3) var(--ov-space-3); border-radius: var(--ov-radius-control);
  font-size: var(--ov-size-label); line-height: 1.35; color: var(--ov-ink);
  text-decoration: none; background: transparent; }
.ov-lesson__nav a:hover { background: var(--ov-pale); }
.ov-lesson__nav a.current { background: var(--ov-pale); font-weight: 600; }
.ov-lesson__nav a:focus-visible { outline: var(--ov-focus-width) solid var(--ov-focus); outline-offset: 2px; }
.ov-lesson__nav a > span:last-child { margin-left: auto; color: var(--ov-text-muted);
  font-size: var(--ov-size-meta); white-space: nowrap; }

/* Progress belongs beside the lessons it counts, not floating above the
   article. It was a bare 6px bar with no number, which reads as a rule. */
.ov-lesson__progress { display: flex; flex-direction: column; gap: var(--ov-space-2); }
.ov-lesson__progress .ov-progress__label { display: flex; justify-content: space-between;
  font-size: var(--ov-size-meta); color: var(--ov-text-muted); }
.ov-lesson__progress .progress-bar { height: 5px; border-radius: 5px; }

.ov-lesson__aside-btn { display: flex; align-items: center; justify-content: space-between;
  gap: var(--ov-space-3); width: 100%; padding: var(--ov-space-3) var(--ov-space-4);
  border: 1px solid var(--ov-control-border); border-radius: var(--ov-radius-control);
  background: transparent; font: inherit; font-size: var(--ov-size-meta);
  color: var(--ov-ink); cursor: pointer; text-align: left; }
.ov-lesson__aside-btn:hover { background: var(--ov-pale); }
.ov-lesson__aside-btn:focus-visible { outline: var(--ov-focus-width) solid var(--ov-focus); outline-offset: 2px; }

/* ---- The reading column ---- */
.ov-lesson__toolbar { display: flex; align-items: center; justify-content: space-between;
  gap: var(--ov-space-5); border-bottom: 1px solid var(--ov-divider);
  padding-bottom: var(--ov-space-5); margin-bottom: var(--ov-space-7); }
.ov-lesson__toolbar > span { font-size: var(--ov-size-meta); color: var(--ov-text-muted);
  letter-spacing: 0.06em; text-transform: uppercase; }
/* Depth as a segmented control, not three loose pills. Three pills read as
   three filters you can combine; a segment reads as one choice of three,
   which is what it is. */
.ov-depth { display: inline-flex; align-items: center; gap: 2px;
  background: var(--ov-pale); padding: 4px; border-radius: var(--ov-radius-pill); }
.ov-depth button, .ov-depth .pill { border: 0; background: transparent; cursor: pointer;
  font: inherit; font-size: var(--ov-size-meta); color: var(--ov-ink);
  border-radius: var(--ov-radius-pill); padding: 7px 13px; }
.ov-depth button[aria-pressed="true"], .ov-depth .pill-mint {
  background: var(--ov-forest); color: var(--ov-text-on-dark); }
.ov-depth button:focus-visible { outline: var(--ov-focus-width) solid var(--ov-focus); outline-offset: 2px; }

.ov-lesson__head { margin-bottom: var(--ov-space-6); }
.ov-lesson__head h1 { margin: var(--ov-space-3) 0 var(--ov-space-3);
  font-size: clamp(1.9rem, 1.4rem + 1.4vw, 2.6rem); line-height: var(--ov-leading-title);
  font-weight: var(--ov-title-weight); letter-spacing: var(--ov-track-title); }
.ov-lesson__sub { margin: 0; font-size: var(--ov-size-lead); line-height: 1.5;
  color: var(--ov-text-muted); max-width: 44rem; }
.ov-lesson__body { max-width: 46rem; }

/* ---- The intro video, as a tile.
   It used to be a collapsed grey row that expanded into a bare 16:9
   frame, which is the one element on the page that looked like it came
   from a different product. It is a tile now, like everything else: a
   tone, an eyebrow, a title, and the player inside it. ---- */
.ov-media { border-radius: var(--ov-radius-panel); padding: var(--ov-space-5);
  background: var(--ov-tile-tone, var(--ov-forest)); color: var(--ov-text-on-dark);
  margin-bottom: var(--ov-space-7); }
.ov-media :is(h1,h2,h3,h4,strong) { color: inherit; }
.ov-media .ov-eyebrow { color: inherit; opacity: 0.85; }
.ov-media__head { display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--ov-space-4); margin-bottom: var(--ov-space-4); flex-wrap: wrap; }
.ov-media__title { margin: 0; font-size: 1.15rem; font-weight: var(--ov-title-weight);
  letter-spacing: var(--ov-track-card); }
.ov-media__frame { border-radius: var(--ov-radius-card); overflow: hidden;
  background: rgb(0 0 0 / 0.25); }
.ov-media__frame video, .ov-media__frame img, .ov-media__frame iframe {
  display: block; width: 100%; height: auto; aspect-ratio: 16 / 9; object-fit: cover; }
.ov-media :focus-visible { outline: var(--ov-focus-width) solid var(--ov-focus-on-dark); outline-offset: 2px; }
.ov-media__note { margin: var(--ov-space-3) 0 0; font-size: var(--ov-size-meta); opacity: 0.85; }

.ov-lesson__foot { display: flex; align-items: center; justify-content: space-between;
  gap: var(--ov-space-5); flex-wrap: wrap; border-top: 1px solid var(--ov-divider);
  padding-top: var(--ov-space-6); margin-top: var(--ov-space-8); }
.ov-lesson__foot > span { font-size: var(--ov-size-meta); color: var(--ov-text-muted); }

@media (max-width: 1023px) {
  /* The rail's tile and progress stay: on a phone they are the only thing
     telling a learner which module they are in and how far through. Only
     the lesson tabs collapse, into a disclosure, because a full list above
     the article pushes the lesson itself off the screen. */
  .ov-lesson { grid-template-columns: 1fr; gap: var(--ov-space-6); }
  .ov-lesson__rail { position: static; }
  .ov-lesson__nav { display: none; }
  .ov-lesson__nav.is-open { display: flex; }
  .ov-lesson__toolbar { flex-wrap: wrap; gap: var(--ov-space-3); }
}
@media (min-width: 1024px) { .ov-lesson__nav-toggle { display: none; } }
/* Collapsed and expanded states of the media tile. */
.ov-media__open { display: flex; align-items: center; gap: var(--ov-space-4);
  width: 100%; padding: var(--ov-space-4); border: 1px solid rgb(255 255 255 / 0.22);
  border-radius: var(--ov-radius-card); background: rgb(255 255 255 / 0.06);
  color: inherit; font: inherit; font-size: var(--ov-size-label); cursor: pointer; text-align: left; }
.ov-media__open:hover { background: rgb(255 255 255 / 0.12); }
.ov-media__open .walkthrough-play-icon { font-size: .8rem; }
.ov-media__open .walkthrough-chevron { margin-left: auto; }
.ov-media__foot { display: flex; align-items: center; justify-content: space-between;
  gap: var(--ov-space-4); margin-top: var(--ov-space-3); font-size: var(--ov-size-meta); }
.ov-media__link { background: none; border: 0; padding: 0; cursor: pointer;
  font: inherit; font-size: var(--ov-size-meta); color: inherit;
  text-decoration: underline; text-underline-offset: 3px; }
.ov-media__transcript { margin-top: var(--ov-space-4); padding: var(--ov-space-4);
  border-radius: var(--ov-radius-card); background: rgb(255 255 255 / 0.1);
  font-size: var(--ov-size-meta); line-height: 1.7; max-height: 300px; overflow-y: auto; }
/* The media tile is a dark surface, so every descendant that sets its own
   colour has to be brought back. .walkthrough-collapsed-sub is muted grey
   from styles.css, which is 2.4:1 on forest. Third time this exact trap
   has cost a fix: the forest tile heading, the card byline on a tone, and
   now this. Any tinted or dark surface must re-inherit its text. */
.ov-media__open .walkthrough-collapsed-sub,
.ov-media__open .walkthrough-collapsed-title,
.ov-media__open .walkthrough-chevron,
.ov-media__open .walkthrough-play-icon { color: inherit; }
.ov-media__open .walkthrough-collapsed-text { display: flex; flex-direction: column; gap: 2px; }

/* ============================================================
   DASHBOARD
   Composition taken from the preview's #dashboard, 29 September.
   Four parts: a page head, a two-column band holding the single
   next step beside the counters, then the ordinary catalogue
   sections, then the completed row.

   The point of the band is that it answers one question before
   any list appears: what should I do next. The old dashboard
   opened with three stacked lists and a sidebar of numbers, so
   the answer was somewhere in the first list if it was anywhere.
   ============================================================ */

/* ---- Page head ---- */
.ov-page-head { display: flex; justify-content: space-between; align-items: flex-end;
  gap: var(--ov-space-7); padding: var(--ov-space-9) 0 var(--ov-space-7); }
.ov-page-head h1 { margin: 0 0 var(--ov-space-4);
  font-size: clamp(2.5rem, 1.55rem + 3vw, 3.7rem);
  line-height: var(--ov-leading-display, 1.03);
  font-weight: var(--ov-title-weight, 500);
  letter-spacing: var(--ov-track-display, -0.045em); }
.ov-page-head p:not(.ov-eyebrow) { margin: 0; max-width: 38rem; color: var(--ov-text-muted); }
.ov-page-head .ov-eyebrow { margin: 0 0 var(--ov-space-5); }

/* The second line of the headline. The preview uses #2d8064, which is not
   in Brand System v1.0, and Deep Evergreen was too close to Brand Ink to
   read as a shift at all. The brand already has the colour this wants:
   Deep Accent #176F58, listed as the secondary dark green. 5.86:1 on
   paper, clearly a second green beside Brand Ink's 12:1, and no new value
   introduced. It is also the green the hero wordflip already lands on. */
.ov-accent { color: var(--ov-forest-accent); }

/* A quiet link that sits on the baseline of a heading it is beside. */
.ov-text-link { display: inline-flex; align-items: center; gap: var(--ov-space-3);
  font-size: var(--ov-size-label); color: var(--ov-text); text-decoration: none;
  border-bottom: 1px solid currentColor; padding-bottom: 4px; white-space: nowrap; }
.ov-text-link:hover { border-bottom-color: transparent; }
.ov-text-link:focus-visible { outline: var(--ov-focus-width) solid var(--ov-focus);
  outline-offset: 3px; border-bottom-color: transparent; }

/* ---- The band: next step beside the counters ---- */
.ov-dash { display: grid; grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
  gap: var(--ov-space-5); margin: var(--ov-space-3) 0 var(--ov-space-8); }

.ov-next { display: flex; flex-direction: column; min-width: 0;
  padding: clamp(var(--ov-space-6), 2.4vw, var(--ov-space-7));
  border-radius: var(--ov-radius-panel); min-height: 325px;
  background: var(--ov-next-tone, var(--ov-mint)); color: var(--ov-ink); }
/* The tinted-surface trap again: styles.css colours h1..h4 and strong with
   a bare type selector, which beats inheritance, so a card that sets its
   own colour never reaches its own heading. */
.ov-next :is(h1, h2, h3, h4, h5, h6, strong) { color: inherit; }
.ov-next__title { margin: var(--ov-space-5) 0 var(--ov-space-4); max-width: 22ch;
  font-size: clamp(1.75rem, 1.05rem + 2.2vw, 2.7rem);
  line-height: var(--ov-leading-display, 1.03);
  font-weight: var(--ov-title-weight, 500);
  letter-spacing: var(--ov-track-display, -0.045em); }
.ov-next__text { margin: 0 0 var(--ov-space-6); max-width: 30rem;
  font-size: var(--ov-size-label); line-height: 1.55; }
.ov-next__actions { margin-top: auto; display: flex; align-items: center;
  gap: var(--ov-space-5); flex-wrap: wrap; }
.ov-next__meta { font-size: var(--ov-size-meta); }
.ov-next--forest { --ov-next-tone: var(--ov-forest); color: var(--ov-text-on-dark); }
.ov-next--forest :focus-visible { outline-color: var(--ov-focus-on-dark); }
.ov-next--pale { --ov-next-tone: var(--ov-pale); }

/* ---- Counters ---- */
.ov-metrics { display: grid; grid-template-columns: 1fr 1fr; gap: var(--ov-space-4); }
.ov-metric { display: flex; flex-direction: column; justify-content: space-between;
  gap: var(--ov-space-4); min-width: 0;
  padding: var(--ov-space-6); border: 1px solid var(--ov-divider);
  border-radius: var(--ov-radius-panel); background: var(--ov-surface); }
.ov-metric :is(h1, h2, h3, h4, h5, h6, strong) { color: inherit; }
.ov-metric__num { font-size: clamp(2.25rem, 1.6rem + 1.6vw, 3rem); line-height: 1;
  font-weight: var(--ov-title-weight, 500); letter-spacing: -0.05em; }
.ov-metric__label { margin: var(--ov-space-2) 0 0; font-size: var(--ov-size-label); }
.ov-metric__note { display: block; font-size: var(--ov-size-meta);
  color: var(--ov-text-muted); line-height: 1.5; margin-top: 4px; }

/* The tip runs the full width under the two counters. Ink on Egg Yolk is
   7.60:1, so the note inside it takes ink at reduced weight rather than
   the muted grey, which is 2.9:1 there. */
.ov-metric--tip { grid-column: 1 / -1; background: var(--ov-yolk); border: 0;
  color: var(--ov-ink); gap: var(--ov-space-5); }
.ov-metric--tip .ov-metric__note { color: inherit; }
.ov-metric--tip strong { display: block; font-size: 1.15rem;
  font-weight: var(--ov-title-weight, 500); letter-spacing: var(--ov-track-card, -0.025em);
  margin-bottom: var(--ov-space-2); }
.ov-metric--tip p { margin: 0; max-width: 24rem; font-size: var(--ov-size-label); }

/* ---- Completed, with nothing in it yet ---- */
.ov-done-row { display: flex; align-items: center; gap: var(--ov-space-6);
  padding: var(--ov-space-6) 0; border-top: 1px solid var(--ov-divider); }
.ov-done-row__mark { display: grid; place-items: center; flex: none;
  width: 50px; height: 50px; border-radius: var(--ov-radius-pill);
  border: 1px solid var(--ov-control-border); color: var(--ov-text-muted);
  font-size: 1.25rem; }
.ov-done-row h3 { margin: 0 0 var(--ov-space-1); font-size: 1.15rem;
  font-weight: var(--ov-title-weight, 500); letter-spacing: var(--ov-track-card, -0.025em); }
.ov-done-row p { margin: 0; font-size: var(--ov-size-label); color: var(--ov-text-muted); }

/* ---- A course card that is already being taken ----
   The catalogue card plus a progress strip. Same card everywhere, so a
   course looks the same on Discover as it does here. */
.ov-card__progress { margin-top: 0; }
.ov-card__progress .ov-progress__label { font-size: var(--ov-size-meta);
  color: var(--ov-text-muted); }
.ov-card__progress .ov-progress__label b { color: var(--ov-text); font-weight: 600; }

@media (max-width: 1023px) {
  .ov-dash { grid-template-columns: 1fr; }
  .ov-next { min-height: 280px; }
}
@media (max-width: 720px) {
  .ov-page-head { display: block; padding-top: var(--ov-space-7); }
  .ov-page-head .ov-text-link { margin-top: var(--ov-space-5); }
  .ov-metric { padding: var(--ov-space-5); }
  .ov-done-row { gap: var(--ov-space-5); }
}
@media (max-width: 380px) {
  .ov-metrics { grid-template-columns: 1fr; }
}

/* Vertical rhythm between the dashboard's stacked sections. */
.ov-section-gap { margin-bottom: var(--ov-space-9); }

/* ============================================================
   HOME COMPOSITION
   Composition from the preview's #home, 29 September, reworked for the
   visual handoff of 6 October 2026 (design-resources/overtly/): open
   promise columns, one pale process band, an open closing invitation.
   The copy on the page is unchanged.
   ============================================================ */

/* padding-block, never the `padding: X 0` shorthand. This section also
   carries .container-x, whose horizontal padding is the page gutter, and
   the shorthand set it to 0. On desktop the container's max-width hid it;
   at phone and tablet widths the text ran to both edges of the screen. */
/* A section on the paper. It used to open with a hairline rule; since the
   6 October 2026 handoff a section is separated by space and, where it has
   a heading, by a short accent bar above it. */
.ov-section-rule { padding-block: var(--ov-space-9); }
@media (max-width: 720px) { .ov-section-rule { padding-block: var(--ov-space-7); } }

/* ---- Accent bar. A short rule above a heading or a promise. It is
   decoration and says nothing: no status, no progress, no selection. The
   environment sets its colour; --warm and --coral are occasional warmth on
   learner pages. ---- */
.ov-accent-bar { display: block; width: 2.625rem; height: 4px; border-radius: 2px;
  background: var(--ov-env-accent); margin: 0 0 var(--ov-space-5); }
.ov-accent-bar--warm  { background: var(--ov-yolk); }
.ov-accent-bar--coral { background: var(--ov-coral); }
.ov-accent-bar--quiet { background: var(--ov-divider); }

/* ---- Promises. Open columns of text under an accent bar. They replaced
   the tinted tiles on 6 October 2026: no shell, no fixed height, nothing
   held back behind a button, so each column is as tall as what it says. ---- */
.ov-promises { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--ov-space-8) var(--ov-space-7); }
.ov-promises--three { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.ov-section--promises { padding-top: var(--ov-space-7); padding-bottom: var(--ov-space-10); }
.ov-promise { min-width: 0; }
.ov-promise__index { margin: 0 0 var(--ov-space-3); font-size: var(--ov-size-meta); font-weight: 700;
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--ov-text-muted); }
.ov-promise__title { margin: 0 0 var(--ov-space-3); font-size: clamp(1.125rem, 1.02rem + 0.4vw, 1.375rem);
  line-height: 1.25; font-weight: 600; letter-spacing: var(--ov-track-card); }
.ov-promise__text { margin: 0; font-size: var(--ov-size-label); line-height: 1.55;
  color: var(--ov-text-muted); }
@media (max-width: 1023px) {
  .ov-promises, .ov-promises--three { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 639px) {
  .ov-promises, .ov-promises--three { grid-template-columns: minmax(0, 1fr); gap: var(--ov-space-7); }
  .ov-section--promises { padding-bottom: var(--ov-space-9); }
}

/* ---- Process. One pale band the width of the page, and on it a numbered
   list. Each step stands alone: no rule above it, no thread to the next,
   no connector drawn by a pseudo-element. The circle is the step's number
   and nothing else. It is not a button and it does not mean done.

   The band is outside .container-x so it reaches both edges; the container
   inside it must not paint the page colour back over it. ---- */
.ov-process { background: var(--ov-env-process-bg); padding-block: clamp(2.5rem, 4vw, 3.5rem); }
.ov-process > .ov-platform { background: transparent; }
.ov-process .ov-section-head { margin-bottom: 0; }
.ov-steps { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--ov-space-7); list-style: none; margin: var(--ov-space-7) 0 0; padding: 0; }
.ov-steps--five { grid-template-columns: repeat(5, minmax(0, 1fr)); gap: var(--ov-space-6); }
.ov-step { min-width: 0; }
.ov-step__num { display: inline-flex; align-items: center; justify-content: center;
  inline-size: 2rem; block-size: 2rem; flex: none; border-radius: 50%;
  background: var(--ov-env-step-bg); color: var(--ov-env-step-text);
  font-size: var(--ov-size-label); font-weight: 700; font-variant-numeric: tabular-nums; }
.ov-step h3 { margin: var(--ov-space-4) 0 var(--ov-space-2); font-size: 1.125rem; line-height: 1.3;
  font-weight: 600; letter-spacing: var(--ov-track-card); color: var(--ov-text); }
.ov-step p { margin: 0; font-size: var(--ov-size-label); line-height: 1.55; color: var(--ov-text-muted); }
/* Five across needs room for "Set the voice and depth" on one or two lines;
   under 1200 it has not got it. */
@media (max-width: 1199px) {
  .ov-steps--five { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--ov-space-7); }
}
@media (max-width: 1023px) {
  .ov-steps { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 767px) {
  .ov-steps--five { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 639px) {
  /* One step a row, its number beside its own title and text. */
  .ov-steps, .ov-steps--five { grid-template-columns: minmax(0, 1fr); gap: var(--ov-space-6); }
  .ov-step { display: grid; grid-template-columns: 2rem minmax(0, 1fr); column-gap: var(--ov-space-4);
    align-items: start; }
  .ov-step h3 { margin-top: 3px; }
}

/* ---- Closing invitation. Space, a short accent, a heading and one
   action. It replaced a dark rounded slab on 6 October 2026 and must not
   grow another coloured block in its place. ---- */
.ov-close { display: flex; align-items: center; justify-content: space-between;
  gap: var(--ov-space-7); flex-wrap: wrap; }
.ov-close h2 { margin: 0; font-size: clamp(1.5rem, 1.1rem + 1.3vw, 2rem);
  line-height: var(--ov-leading-title, 1.1);
  font-weight: var(--ov-title-weight, 500); letter-spacing: var(--ov-track-title, -0.035em); }
.ov-close p { margin: var(--ov-space-3) 0 0; font-size: var(--ov-size-label); color: var(--ov-text-muted); }
.ov-close__actions { display: flex; align-items: center; gap: var(--ov-space-6); flex-wrap: wrap; }
@media (max-width: 720px) {
  .ov-close { flex-direction: column; align-items: flex-start; gap: var(--ov-space-6); }
}

/* ============================================================
   CREATOR STUDIO AND ACCOUNT
   Added 29 September 2026, from the preview's #creators and #account.

   Both pages are mostly the same two shapes: a section whose left
   column states the point and whose right column carries the detail,
   and a form. Neither existed in the system, so both pages had been
   carrying their own copy of the furniture in a page <style> block,
   which is how the coral contrast defect in decision 219 survived: a
   page that owns its own palette does not inherit a fix.
   ============================================================ */

/* ---- The point on the left, the detail on the right.
   A fraction pair rather than a fixed measure: unlike the hero, both
   columns here hold text, so they should shrink together. ---- */
.ov-split { display: grid; grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr);
  gap: var(--ov-space-10); align-items: start; }
.ov-split__aside { display: flex; flex-direction: column; gap: var(--ov-space-5); }
.ov-split__aside h2 { margin: 0; font-size: clamp(1.75rem, 1.3rem + 1.4vw, 2.5rem);
  line-height: var(--ov-leading-title); font-weight: var(--ov-title-weight);
  letter-spacing: var(--ov-track-title); }
.ov-split__aside p { margin: 0; color: var(--ov-text-muted); font-size: var(--ov-size-label);
  line-height: 1.6; max-width: 34rem; }

/* ---- Disclosure list. The open and closed state is carried by the
   element, not by the glyph: the marker is generated content and is
   invisible to assistive technology, which reads <summary> itself. ---- */
.ov-disclosures { border-top: 1px solid var(--ov-divider); }
.ov-disclosure { border-bottom: 1px solid var(--ov-divider); }
.ov-disclosure > summary { display: flex; align-items: center; justify-content: space-between;
  gap: var(--ov-space-5); min-height: var(--ov-target-min);
  padding: var(--ov-space-5) 0; cursor: pointer; list-style: none;
  font-size: 1.0625rem; font-weight: 500; letter-spacing: var(--ov-track-card); }
.ov-disclosure > summary::-webkit-details-marker { display: none; }
.ov-disclosure > summary::after { content: "+"; flex: none; font-size: 1.25rem;
  font-weight: 400; line-height: 1; color: var(--ov-text-muted); }
.ov-disclosure[open] > summary::after { content: "\2212"; }
.ov-disclosure > summary:hover { color: var(--ov-forest-accent); }
.ov-disclosure > :not(summary) { margin: 0 0 var(--ov-space-5);
  max-width: 46rem; font-size: var(--ov-size-label); line-height: 1.65;
  color: var(--ov-text-muted); }

/* ---- Forms. .ov-field and .ov-input already exist; this is only the
   stack around them. ---- */
.ov-form { display: flex; flex-direction: column; gap: var(--ov-space-5); }
.ov-form__row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--ov-space-5); }
.ov-form .ov-btn { align-self: flex-start; }
.ov-form textarea.ov-input { min-height: 132px; resize: vertical; line-height: 1.6; }
.ov-input:disabled, .ov-input[readonly] { background: var(--ov-pale); color: var(--ov-text-muted); }

/* The label is the control's accessible name, so the checkbox and its
   text are one target. 16px is the real box, not a styled span, so it
   keeps the platform's own checked and focus states. */
.ov-check { display: flex; align-items: flex-start; gap: var(--ov-space-3);
  font-size: var(--ov-size-label); line-height: 1.5; color: var(--ov-text); cursor: pointer; }
.ov-check input { flex: none; width: 16px; height: 16px; margin-top: 3px;
  accent-color: var(--ov-forest); }
.ov-check .ov-field__help { display: block; margin-top: var(--ov-space-1); }
/* The dashboard card that asks a learner for consents they never answered. */
.ov-consent-prompt { padding: var(--ov-space-6); border: 1px solid var(--ov-divider);
  border-radius: var(--ov-radius-panel); background: var(--ov-surface); max-width: 760px; }
.ov-consent-prompt h2 { margin: 0 0 var(--ov-space-2); font-size: var(--ov-size-card); }
.ov-consent-prompt > p { margin: 0 0 var(--ov-space-4); color: var(--ov-text-muted); }
.ov-consent-prompt .ov-form { display: flex; flex-direction: column; gap: var(--ov-space-3); }
.ov-consent-prompt #consent-prompt-list { display: flex; flex-direction: column; gap: var(--ov-space-3); }
.ov-consent-prompt [hidden] { display: none !important; }
.ov-consent-prompt a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }

/* Form messages. Not colour alone: the error is bold and prefixed by
   the word the copy uses, and both are announced through role. */
.ov-form__error { margin: 0; font-size: var(--ov-size-label); font-weight: 600;
  color: var(--ov-coral-ink); }
.ov-form__ok { margin: 0; font-size: var(--ov-size-label); font-weight: 600;
  color: var(--ov-forest-accent); }
.ov-form__error:empty, .ov-form__ok:empty, .ov-field__help:empty { display: none; }

/* ---- A single held-back caveat. Pale Coral with Coral Ink, 4.87:1.
   Deep Coral on Pale Coral is 2.9:1 and shipped on this page for days,
   which is decision 219. ---- */
.ov-note { display: flex; align-items: flex-start; gap: var(--ov-space-3);
  margin: var(--ov-space-6) 0 0; padding: var(--ov-space-4) var(--ov-space-5);
  border-radius: var(--ov-radius-control);
  background: var(--ov-coral-pale); color: var(--ov-coral-ink);
  font-size: var(--ov-size-label); font-weight: 600; line-height: 1.5; }

/* ---- A plain data table for a page that is not a legal page: fees, build
   times. The wrapper scrolls sideways on a phone and takes keyboard focus
   so the scroll can be reached without a pointer. Numbers align on the
   right so a column of prices can be compared down its length. ---- */
.ov-table-wrap { overflow-x: auto; margin: 0 0 var(--ov-space-5); }
.ov-table-wrap:focus-visible { outline: var(--ov-focus-width) solid var(--ov-focus); outline-offset: var(--ov-focus-offset); }
.ov-table { width: 100%; border-collapse: collapse; font-size: var(--ov-size-label); line-height: 1.5; color: var(--ov-text); }
.ov-table caption { text-align: left; padding: 0 0 var(--ov-space-3); color: var(--ov-text-muted); }
.ov-table th, .ov-table td { text-align: left; vertical-align: top; padding: var(--ov-space-3) var(--ov-space-5) var(--ov-space-3) 0;
  border-bottom: 1px solid var(--ov-divider); }
.ov-table th:last-child, .ov-table td:last-child { padding-right: 0; }
.ov-table thead th { font-weight: 600; border-bottom-color: var(--ov-text-muted); }
.ov-table tbody th { font-weight: 500; }
.ov-table .ov-num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.ov-table thead .ov-num { white-space: normal; }

/* ---- Body copy on the wide side of a split, where a disclosure list
   would hide what the reader came for. ---- */
.ov-prose > * { margin: 0 0 var(--ov-space-5); max-width: 46rem; font-size: var(--ov-size-label); line-height: 1.65; color: var(--ov-text-muted); }
.ov-prose > :last-child { margin-bottom: 0; }
.ov-prose strong { color: var(--ov-text); font-weight: 600; }
/* Underlined, so a link in running text is not told apart by colour alone. */
.ov-prose a, .ov-disclosure > :not(summary) a { color: var(--ov-text); text-decoration: underline; text-underline-offset: 2px; }
.ov-prose ul { padding-left: 1.1rem; list-style: disc; }
.ov-prose li { margin-bottom: var(--ov-space-3); }
.ov-prose .ov-table-wrap { max-width: none; }

/* ---- Closing row: a question on the left, the way out on the right. ---- */
.ov-closing { display: flex; align-items: center; justify-content: space-between;
  gap: var(--ov-space-6); flex-wrap: wrap; }
.ov-closing p { margin: 0; font-size: var(--ov-size-lead); }

/* ---- Account settings. Two columns of sections divided by rules,
   rather than a grid of white cards: the page is a list of settings,
   and boxing each one made five objects where there are five topics. ---- */
.ov-settings { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--ov-space-9); align-items: start; margin-top: var(--ov-space-7); }
.ov-settings__item { border-top: 1px solid var(--ov-divider);
  padding-top: var(--ov-space-7); min-width: 0; }
.ov-settings__item--flush { border-top: 0; padding-top: 0; }
.ov-settings__item--wide { grid-column: 1 / -1; }
.ov-settings__item > h2 { margin: 0 0 var(--ov-space-5);
  font-size: clamp(1.25rem, 1.1rem + 0.5vw, 1.5rem);
  font-weight: var(--ov-title-weight); letter-spacing: var(--ov-track-title); }
.ov-settings__item > p { margin: 0 0 var(--ov-space-5);
  color: var(--ov-text-muted); font-size: var(--ov-size-label); line-height: 1.6; }

/* The one tinted panel on the page, so the identity block reads as the
   thing the rest of the settings hang off. Same headings-inherit rule
   as every other tone-bearing surface: styles.css sets h2 colour as a
   bare type selector, so a tinted surface has to opt back in. */
.ov-panel { border-radius: var(--ov-radius-panel); padding: var(--ov-space-6); }
/* The panel re-points the semantic text tokens for its own ground rather
   than overriding each component that lands on it. Muted Gray, Coral Ink
   and Deep Accent all fail 4.5:1 on mint, and all three appear in here:
   help text under every field, the error line and the saved line. Doing
   it by token means a component added to this panel later inherits the
   fix instead of needing one. All three grounds are asserted in
   tools/check-design-tokens.mjs.

   These are TEXT colours on this surface. Nothing inside the panel may
   use them as a background: on mint that is the inverse pairing and it
   is not checked. */
.ov-panel--mint { background: var(--ov-mint); color: var(--ov-ink);
  --ov-text-muted:    var(--ov-text-muted-on-mint);
  --ov-coral-ink:     var(--ov-coral-ink-on-mint);
  --ov-forest-accent: var(--ov-forest); }
.ov-panel :is(h1, h2, h3, h4, h5, h6, strong) { color: inherit; }
.ov-panel .ov-input { background: var(--ov-white); border-color: var(--ov-control-border); }
/* Restated inside the panel. Both selectors weigh the same, so the panel's
   white was winning on source order and a read-only field looked editable. */
.ov-panel .ov-input:disabled, .ov-panel .ov-input[readonly] { background: var(--ov-pale); }

/* ---- Records: what is not here yet, and when it will be. ---- */
.ov-records { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--ov-space-7); }
.ov-records h3 { margin: var(--ov-space-3) 0 var(--ov-space-2); font-size: var(--ov-size-card);
  font-weight: var(--ov-title-weight); letter-spacing: var(--ov-track-card); }
.ov-records p { margin: 0; color: var(--ov-text-muted); font-size: var(--ov-size-label);
  line-height: 1.6; }

/* ---- Enrolled course rows on the account page ---- */
.ov-enrolled { display: flex; flex-direction: column; gap: var(--ov-space-4); }
.ov-enrolled__row { display: grid; gap: var(--ov-space-3);
  padding: var(--ov-space-5); border: 1px solid var(--ov-divider);
  border-radius: var(--ov-radius-panel); background: var(--ov-surface); }
.ov-enrolled__top { display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--ov-space-4); }
.ov-enrolled__title { font-size: var(--ov-size-card); font-weight: 600;
  letter-spacing: var(--ov-track-card); }
.ov-enrolled__pct { font-weight: 700; white-space: nowrap; font-variant-numeric: tabular-nums; }

@media (max-width: 1023px) {
  /* minmax(0, 1fr), not 1fr: a bare 1fr track grows to its widest child, and
     a table inside the split then pushed the whole page sideways on a phone. */
  .ov-split { grid-template-columns: minmax(0, 1fr); gap: var(--ov-space-7); }
}
@media (max-width: 860px) {
  .ov-settings { grid-template-columns: 1fr; gap: var(--ov-space-7); }
  /* Every item is its own row now, so the first one is the only flush one. */
  .ov-settings__item--flush + .ov-settings__item {
    border-top: 1px solid var(--ov-divider); padding-top: var(--ov-space-7); }
}
@media (max-width: 720px) {
  .ov-records { grid-template-columns: 1fr; }
  .ov-closing { align-items: flex-start; }
}
@media (max-width: 559px) {
  .ov-form__row { grid-template-columns: 1fr; }
  .ov-form .ov-btn { align-self: stretch; }
  .ov-panel { padding: var(--ov-space-5); }
}
/* The cluster on the right of a page head: a quiet link and, where the
   page has one, a secondary action. */
.ov-page-head__actions { display: flex; align-items: center; gap: var(--ov-space-5);
  flex-wrap: wrap; }

/* A utility page's head. The dashboard's head carries a two-line
   headline and is sized for it; "Account" is one word and at 3.7rem it
   was the loudest thing on a page whose job is a list of settings. */
.ov-page-head--utility { align-items: center;
  padding-block: var(--ov-space-8) var(--ov-space-6); }
.ov-page-head--utility h1 { font-size: clamp(1.875rem, 1.6rem + 0.8vw, 2.25rem);
  letter-spacing: var(--ov-track-title); line-height: var(--ov-leading-title);
  margin-bottom: var(--ov-space-3); }


/* ---- Icons ----
   `Overtly.icon(name)` in app.js draws these; the sizing rule lives in
   styles.css, NOT here, and deliberately. Only three of the seven pages
   that use an icon load this sheet, and an <svg> with no width renders
   at 300 by 150. One definition, in the sheet every page loads. Move it
   here when the last page is on the design system. */

/* ---- Course resources ----
   Added 30 September 2026. The course-level deliverables, in the
   disclosure stack between the author and the sources, and the only one
   of the four that opens by default: the other three answer a question,
   this one holds things the reader is meant to take.

   Two states that must not look alike. LOCKED means it exists and
   finishing opens it; PLANNED means it does not exist yet. Locked is a
   pale environment surface, a lock and a sentence; planned is a neutral
   white surface and the words "Coming soon".

   Neither is drawn by dimming the text. A dimmed span is not a disabled
   control and gets no contrast exemption, which is decision 235 and has
   cost three fixes since. Every value here is ink on a brand tint. */
.ov-resource-list { list-style: none; margin: var(--ov-space-4) 0 0; padding: 0;
  display: flex; flex-direction: column; gap: var(--ov-space-3); }
.ov-resource { display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  gap: var(--ov-space-3); align-items: start;
  padding: var(--ov-space-5); border-radius: var(--ov-radius-card); }
.ov-resource--locked  { background: var(--ov-env-practice-bg); }
/* Not built yet: a neutral surface and its own words, "Coming soon". */
.ov-resource--planned { background: var(--ov-surface); }
.ov-resource__mark { display: grid; place-items: center; width: 1.6rem; height: 1.6rem;
  color: var(--ov-ink); }
.ov-resource--planned .ov-resource__mark { color: var(--ov-text-muted); }
.ov-resource__body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ov-resource__title { font-size: var(--ov-size-label); font-weight: 600; color: var(--ov-ink); }
a.ov-resource__title { text-decoration: underline; text-underline-offset: 0.2em; }
/* The resource's one link is its title, stretched over the whole row so the
   target is the row and not a 22px line of text. */
.ov-resource { position: relative; }
a.ov-resource__title::after { content: ""; position: absolute; inset: 0; border-radius: inherit; }
a.ov-resource__title:focus-visible { outline: var(--ov-focus-width) solid var(--ov-focus); outline-offset: 3px;
  border-radius: 2px; }
.ov-resource__summary { font-size: var(--ov-size-meta); line-height: 1.5; color: var(--ov-text); }
/* The gate, stated. It is the sentence that carries the meaning; the
   lock agrees with it and the tint agrees with the lock. */
.ov-resource__note { margin-top: var(--ov-space-2); font-size: var(--ov-size-meta);
  font-weight: 600; color: var(--ov-ink); }
.ov-resource--planned .ov-resource__note { color: var(--ov-text-muted); }
.ov-resource__foot { margin: var(--ov-space-4) 0 0; font-size: var(--ov-size-meta);
  line-height: 1.5; color: var(--ov-text-muted); }
.ov-resource .ov-pill { align-self: start; }

@media (max-width: 559px) {
  /* The pill drops under the body rather than squeezing the title to a
     word a line. */
  .ov-resource { grid-template-columns: auto minmax(0, 1fr); }
  .ov-resource .ov-pill { grid-column: 2; justify-self: start; margin-top: var(--ov-space-2); }
}

/* The creator sign-in line under the Creator Studio hero. Quiet on purpose:
   the page's one filled action is the interest form. */
.ov-creator-door { margin-top: var(--ov-space-2, 8px); }
.ov-creator-door a { color: inherit; font-weight: 600; text-decoration: underline; text-underline-offset: 2px; }
