/*
 * Shared polish layer, loaded by every page. Keeps the app looking modern and
 * consistent without touching each page's bespoke layout CSS:
 *   - thin, subtle, theme-aware scrollbars (no chunky "old style" bars)
 *   - smooth scrolling + nicer text rendering
 *   - the nav-links row scrolls horizontally on small screens without a bar
 *   - respects prefers-reduced-motion
 *
 * It relies on the CSS custom properties each page already defines in :root
 * (--accent-violet, --border-subtle, --bg-primary, ...), with safe fallbacks.
 */

/* ---- The body's own typography and surface ----
   Set HERE, in the shared layer, because it is the same on every app page.
   Seventeen pages each declared `body { font-family: var(--font-sans) }` in their
   own inline <style>, and trends.html simply never did — so it rendered in the
   browser's default serif while every other page used Inter, and nothing in the
   source looked wrong. A value repeated seventeen times is a value that will be
   missing the eighteenth.

   Kept to the element selector with no !important so a page can still override it
   deliberately (the document "paper" views do exactly that). */
body {
  font-family: var(--font-sans, 'Inter Tight', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif);
  background: var(--bg-primary);
  color: var(--text-primary);
}

/* ---- Thin, subtle scrollbars (WebKit/Blink) ---- */
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--text-muted, #9ca3af) 45%, transparent);
  border-radius: 8px;
  border: 2px solid transparent;
  background-clip: content-box;
}
*::-webkit-scrollbar-thumb:hover {
  background: color-mix(in srgb, var(--accent-violet, #7c3aed) 70%, transparent);
  background-clip: content-box;
}
*::-webkit-scrollbar-corner { background: transparent; }

/* ---- Firefox ---- */
* {
  scrollbar-width: thin;
  scrollbar-color: color-mix(in srgb, var(--text-muted, #9ca3af) 45%, transparent) transparent;
}

/* ---- Smoothing + rendering ---- */
html { scroll-behavior: smooth; }
body {
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  /* `font-optical-sizing: auto` lets a variable font thin its strokes at small
   * sizes and tighten its spacing at large ones, instead of the browser rendering
   * one compromise for both an 11px badge and a 32px page title.
   *
   * NO `letter-spacing` HERE, and that is the fix for "poor font not readable".
   *
   * The first cut of this put `--tracking-body` (-0.011em) on `body`, so every size
   * in the app inherited negative tracking. On Inter - a wide face - that is a small
   * refinement. On Inter TIGHT, whose entire difference from Inter is already-reduced
   * side bearings, it compounds: at --text-2xs (11px), which this app uses for every
   * badge, stat label and meta line, the letters begin to touch and the word shape
   * stops being recognisable at a glance. It was one optimisation applied twice -
   * narrow the face, then close the spacing on top of it - and the second
   * application is the one that cost legibility for nothing.
   *
   * Negative tracking earns its place at DISPLAY sizes, where the letters are big
   * enough that the space reads as a gap. So it lives on headings only (below), and
   * the smallest UI text gets a little tracking back rather than less. */
  font-optical-sizing: auto;
  /* Kerning on, explicitly. Inspecting the ElevenLabs UI shows
   * `font-feature-settings: "kern"`, and it is worth copying: the pair-kerning
   * table is what stops combinations like "Ta", "Vo" and "Ay" from sitting too
   * loose in headings. Browsers usually enable it for horizontal text anyway,
   * but `text-rendering: optimizeLegibility` above and a variable font in the
   * mix make it worth stating rather than inheriting a default. */
  font-feature-settings: "kern";
}

/* ---- Selection ----
 * A soft lilac wash rather than the browser's default saturated blue, which is
 * the detail that makes selecting text in the ElevenLabs studio feel like part
 * of the product instead of part of the browser. Ink stays --text-primary, so
 * selected text keeps ~16:1 contrast instead of the white-on-blue the default
 * gives (which loses the near-black entirely).
 *
 * The foreground is deliberately NOT var(--text-primary): the lilac wash is a
 * LIGHT surface in both themes, so in dark theme --text-primary would put
 * near-white ink on it and selected text would vanish. --selection-fg is pinned
 * theme-invariant for the same reason --doc-ink is. */
::selection {
  background: var(--accent-lilac, #f5d2fe);
  color: var(--selection-fg, #0f0f10);
}
/* Headings get the tighter display tracking, which is where it belongs: display
   type wants less space than body type. Body text keeps the font's own metrics. */
h1, h2, h3, h4, h5, h6 {
  letter-spacing: var(--tracking-tight, -0.02em);
}
/* ── AND THE SMALLEST TEXT GETS A LITTLE SPACE BACK ─────────────────────────
   A condensed face at 11-12px needs MORE letter space than a normal-width one, not
   less - the opposite of what the body rule above used to do to it. These are the
   uppercase micro-labels and badges this app is full of, where the combination of
   small size, capitals and tight metrics is hardest to read.

   +0.01em, which is a fifth of a pixel at 11px: enough to separate the letters,
   not enough to move any layout. Written as a single rule so there is one place
   that knows which text is "micro" rather than a spacing tweak per component. */
.om-seat-h5, .om-seat-h5-count, .om-stat span, .om-fp-stat span, .om-clock-stat span,
.om-check-state, .om-check-group, .om-check-title, .om-tab-score, .om-issue-n,
.om-issue-by, .om-tag, .om-sev, .om-msg-role, .om-msg-round, .om-refs-count,
.om-fp-round, .om-progress-pill, .om-group-n, .om-group-note, .om-finding-done,
.ow-group-label, .ow-kicker, .ow-verdict-badge, .ow-verdict-unit, .ow-statusline,
.om-strip-title, .om-overall-label, .om-verdict-badge, .om-card-iters,
.crail-group, .crail-sub, .irail-label, .cc-badge, .status-tag, .crow-status,
.facet-label, .portfolio-topic-chip {
  letter-spacing: 0.01em;
}
/* Numbers that sit in a column line up. Scoped to where digits are DATA (stats,
   counts, durations, tables) rather than applied globally, because tabular figures
   in running prose look mechanical. */
.om-stat b, .om-fp-stat b, .ow-stat b, .om-issue-n, .om-tab-score,
.om-pane-score, .om-seat-h5-count, table td, table th {
  font-variant-numeric: tabular-nums;
}

/* ---- Nav links: scroll horizontally on small screens, but hide the bar so it
 *      never looks like a stray scrollbar under the top navigation. ---- */
.nav-links {
  min-width: 0;
  max-width: 100%;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: none;          /* Firefox */
  -ms-overflow-style: none;       /* old Edge */
}
.nav-links > * { flex: 0 0 auto; }
.nav-links::-webkit-scrollbar { display: none; }

/* ---- Accessibility: honour reduced-motion ---- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: 0.001ms !important; transition-duration: 0.001ms !important; }
}

/* ============================================================== * CONTENT-FIRST RESPONSIVE LAYOUT SYSTEM
 * ---------------------------------------------------------------------
 * Implements the content-first, responsive Authoring_UI/Settings_UI shell
 * described in the Course Demo Repository Branding design (Requirement 12,
 * plus Req 13.6 contrast-safe branding tokens). This layer is additive:
 * pages opt in by using the `.app-shell`, `.static-chrome`,
 * `.content-workspace`, `.secondary-panel`, and `.scroll-container`
 * classes. Pages that do not use them keep their existing layout.
 *
 * Design goals mapped to requirements:
 *   - Req 12.1/12.2  Page-level horizontal overflow stays within ~1px of the
 *                    viewport; wide code/tables scroll INSIDE their own
 *                    container, never the page.
 *   - Req 12.3       Desktop: Content_Workspace >= 75% width, >= 70% height.
 *   - Req 12.4       Tablet: Content_Workspace >= 70% width.
 *   - Req 12.5       Mobile: full width with total gutters <= 32px.
 *   - Req 12.6       In-container scroll for overflowing wide content.
 *   - Req 12.7       Reserve async space so workspace edges move <= 2px/500ms.
 *   - Req 12.11      Static_Chrome bounded to <= 20% of viewport area on desktop.
 *   - Req 12.12      Collapsible secondary panels expand the workspace.
 *   - Req 12.13      prefers-reduced-motion disables nonessential animation.
 *   - Req 13.6       Tenant-scoped, contrast-safe branding custom properties.
 * ===================================================================== */

/* ---------------------------------------------------------------------
 * Req 13.6 / Req 7 - Tenant-scoped branding custom properties.
 * Branding is applied by injecting tenant-scoped values for these
 * variables (e.g. on a [data-tenant] scope or inline style on the shell),
 * so a tenant's branding only affects that tenant's rendered pages.
 * Defaults fall back to the product's Default_Branding palette and reuse
 * the existing page tokens (--accent-violet, --text-primary, ...) so
 * unbranded tenants render unchanged. Contrast between --brand-on-primary
 * and --brand-primary must satisfy WCAG 2.2 AA; the Settings_UI contrast
 * gate (Req 7.9/7.10) blocks publication of failing pairs.
 * --------------------------------------------------------------------- */
/* The tenant BRAND alias vocabulary (--brand-primary, --brand-accent, ...)
 * moved to brand-tokens.css: it is brand truth, and brand-tokens.css is the
 * one file EVERY page loads (including the bespoke marketing pages, which
 * do not load this polish layer). Only the layout knobs the shell below
 * actually uses stay here. */
:root {
  /* Layout tuning knobs (overridable per page) */
  --chrome-max-width: 320px;   /* upper bound for a side Static_Chrome rail */
  --workspace-min-width: 0;    /* raised at each breakpoint below */
  --shell-gutter: 16px;        /* mobile total gutters stay <= 32px (2 * 16px) */
  --panel-transition: 200ms ease;
}

/* ---------------------------------------------------------------------
 * Req 12.1 - Keep page-level horizontal overflow within ~1px of the
 * viewport width. Nothing at the document level should scroll sideways;
 * only designated .scroll-container regions may.
 * --------------------------------------------------------------------- */
html, body {
  max-width: 100vw;
  overflow-x: hidden;   /* page never scrolls horizontally (Req 12.1, 12.6) */
}
/* The universal border-box reset belongs HERE, not in fourteen copies.
   theme.css only ever set it on `body`, and every page then declared
   `*, *::before, *::after { box-sizing: border-box }` in its own inline <style> —
   except trends.html, which never did. Its nav rail declared width:68px and
   rendered 80px, because padding was being added to the width instead of counted
   inside it. Twelve pixels wider than every other page, from a rule that was
   present fourteen times and missing once.

   A value repeated per page is a value that will be missing on the next page. */
*, *::before, *::after { box-sizing: border-box; }

/* ---------------------------------------------------------------------
 * The application shell: a CSS grid whose PRIMARY track is the
 * Content_Workspace and whose secondary track is bounded Static_Chrome.
 * Mobile-first (Req 12.5): single column, full width, small symmetric
 * gutters whose total stays <= 32px.
 * --------------------------------------------------------------------- */
.app-shell {
  display: grid;
  grid-template-columns: 1fr;                 /* mobile: single content column */
  grid-template-rows: auto 1fr;               /* chrome header, then workspace */
  gap: var(--shell-gutter);
  width: 100%;
  max-width: 100%;
  min-height: 100vh;
  min-height: 100dvh;                         /* dynamic viewport height where supported */
  padding-inline: var(--shell-gutter);        /* total horizontal gutter = 2 * 16px = 32px (Req 12.5) */
  box-sizing: border-box;
  container-type: inline-size;                /* enable container queries for children */
  container-name: app-shell;
  background: var(--brand-bg);                 /* tenant background (Req 13.6); defaults to product token */
  color: var(--brand-fg);
}

/* Static_Chrome: persistent navigation/header/controls outside the workspace.
 * On mobile it is a top bar; it must map to real function (no decorative-only
 * chrome, per Req 12.9/12.10 which pages enforce by not adding empty panels). */
.static-chrome {
  grid-column: 1;
  min-width: 0;                               /* allow shrink, prevents overflow */
  overflow: hidden;                           /* chrome clips rather than pushes page width */
}

/* Content_Workspace: the primary, content-dominant region (Req 12.3-12.5). */
.content-workspace {
  grid-column: 1;
  min-width: var(--workspace-min-width);
  min-height: 70vh;                           /* height floor toward the desktop >= 70% target (Req 12.3) */
  min-height: 70dvh;
  width: 100%;
  max-width: 100%;
  overflow-wrap: anywhere;                    /* long words wrap instead of overflowing the page */
}

/* ---------------------------------------------------------------------
 * Req 12.6 / 12.2 - In-container scrolling for wide content.
 * Wrap wide code blocks and tables in `.scroll-container` so THEY scroll
 * horizontally, keeping page-level overflow within 1px (Req 12.1).
 * --------------------------------------------------------------------- */
.scroll-container,
.scroll-x {
  max-width: 100%;
  overflow-x: auto;                           /* the block scrolls, not the page */
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
}
/* Wide primitives inside the workspace default to safe wrapping/scrolling. */
.content-workspace pre,
.scroll-container pre { overflow-x: auto; max-width: 100%; }
.content-workspace table,
.scroll-container table { max-width: 100%; }
.content-workspace img,
.content-workspace video,
.content-workspace canvas { max-width: 100%; height: auto; }

/* ---------------------------------------------------------------------
 * Req 12.7 - Layout stability: reserve space for content that loads
 * asynchronously (repository summary, module demo links, quality
 * findings) so the workspace edges move <= 2px within 500ms of arrival.
 * `.async-reserve` holds a min-height until real content replaces it;
 * `.async-media` reserves an aspect-ratio box for images/embeds.
 * --------------------------------------------------------------------- */
.async-reserve {
  min-height: var(--reserve-height, 96px);    /* override per placeholder */
  contain: layout paint;                      /* isolate reflow to this box */
}
.async-media {
  aspect-ratio: var(--reserve-aspect, 16 / 9);
  width: 100%;
  background: color-mix(in srgb, var(--text-muted, #9ca3af) 12%, transparent);
}

/* ---------------------------------------------------------------------
 * Req 12.12 - Collapsible secondary panels. Collapsing a panel hands its
 * track back to the Content_Workspace. Toggle by setting `data-collapsed`
 * on the panel (or adding `.is-collapsed`); a `.panel-toggle` button
 * controls it and exposes state via aria-expanded on the page side.
 * --------------------------------------------------------------------- */
.secondary-panel {
  min-width: 0;
  overflow: hidden;
  transition: max-width var(--panel-transition),
              opacity var(--panel-transition),
              padding var(--panel-transition);
}
.secondary-panel[data-collapsed="true"],
.secondary-panel.is-collapsed {
  max-width: 0;
  padding-inline: 0;
  opacity: 0;
  pointer-events: none;
}
.panel-toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  cursor: pointer;
  background: transparent;
  border: 1px solid var(--border, var(--border-subtle, #e5e7eb));
  border-radius: 8px;
  padding: 0.4rem 0.6rem;
  color: var(--text-primary, #1f2937);
}
.panel-toggle:focus-visible {
  outline: 2px solid var(--brand-primary);    /* visible focus indicator (accessibility) */
  outline-offset: 2px;
}

/* Optional branded chrome header pieces driven by tenant branding tokens. */
.brand-logo {
  display: inline-block;
  height: 28px;
  width: auto;
  background-image: var(--brand-logo);
  background-repeat: no-repeat;
  background-size: contain;
  background-position: left center;
}
.brand-banner {
  width: 100%;
  background-image: var(--brand-banner);
  background-repeat: no-repeat;
  background-size: cover;
  background-position: center;
}

/* =====================================================================
 * Breakpoints. Container queries drive the shell where supported so the
 * layout responds to the shell's own width; media-query fallbacks cover
 * browsers without container-query support. Named viewports from the
 * requirements: Mobile 360x800, Tablet 768x1024, Desktop 1440x900.
 * ===================================================================== */

/* ---- Tablet: >= 768px. Content_Workspace >= 70% width (Req 12.4). ----
 * A single bounded Static_Chrome rail may appear beside the workspace;
 * the workspace track (1fr) stays dominant while chrome is capped. */
@container app-shell (min-width: 768px) {
  .app-shell {
    --shell-gutter: 24px;
    --workspace-min-width: 70cqw;             /* >= 70% of shell width (Req 12.4) */
    grid-template-columns: minmax(70cqw, 1fr) minmax(0, var(--chrome-max-width));
    grid-template-rows: auto 1fr;
  }
  .static-chrome { grid-column: 1 / -1; }     /* top chrome spans full width */
  .content-workspace { grid-column: 1; grid-row: 2; }
  .secondary-panel { grid-column: 2; grid-row: 2; }
}

/* ---- Desktop: >= 1200px (covers the 1440x900 Desktop_Viewport).
 * Content_Workspace >= 75% width and >= 70% height; Static_Chrome <= 20%
 * of viewport area (Req 12.3, 12.11). The chrome rail is capped so the
 * workspace column keeps >= 75% width. */
@container app-shell (min-width: 1200px) {
  .app-shell {
    --shell-gutter: 32px;
    --workspace-min-width: 75cqw;             /* >= 75% of shell width (Req 12.3) */
    --chrome-max-width: 22cqw;                /* side rail capped so workspace stays dominant */
    grid-template-columns: minmax(75cqw, 1fr) minmax(0, var(--chrome-max-width));
  }
  .content-workspace {
    min-height: 70vh;                         /* >= 70% viewport height (Req 12.3) */
    min-height: 70dvh;
  }
  /* Req 12.11 - keep a side Static_Chrome rail bounded to a small share of
   * the viewport so total chrome area stays <= 20%. */
  .static-chrome.chrome-rail {
    max-width: var(--chrome-max-width);
    max-height: 100vh;
  }
}

/* Media-query fallbacks (no container-query support) mirror the floors so
 * the responsive behavior degrades gracefully. */
@media (min-width: 768px) {
  .app-shell { --shell-gutter: 24px; }
  .app-shell.cq-unsupported {
    grid-template-columns: minmax(70%, 1fr) minmax(0, var(--chrome-max-width));
  }
}
@media (min-width: 1200px) {
  .app-shell { --shell-gutter: 32px; }
  .app-shell.cq-unsupported {
    grid-template-columns: minmax(75%, 1fr) minmax(0, 22vw);
  }
  .app-shell.cq-unsupported .content-workspace { min-height: 70vh; }
}

/* =====================================================================
 * Req 13.6 / Req 13.2 - Accessibility: visible keyboard focus + AA contrast
 * ---------------------------------------------------------------------
 * A consistent, visible keyboard-focus indicator for every interactive
 * element inside the shell (Req 13.2), and AA-oriented text/control color
 * defaults driven by the branding tokens (Req 13.6). Contrast between the
 * brand foreground/background pairs is enforced by the Settings_UI
 * contrast gate (Req 7.9/7.10); these defaults keep unbranded pages at
 * product tokens that already meet AA.
 * --------------------------------------------------------------------- */
.app-shell a:focus-visible,
.app-shell button:focus-visible,
.app-shell input:focus-visible,
.app-shell select:focus-visible,
.app-shell textarea:focus-visible,
.app-shell [tabindex]:focus-visible,
.app-shell [role="button"]:focus-visible,
.app-shell summary:focus-visible {
  outline: 2px solid var(--brand-focus, var(--brand-primary));
  outline-offset: 2px;
  border-radius: 4px;                          /* softens the ring on rectangular controls */
}

/* Content_Workspace text/surface defaults inherit the tenant palette so
 * branded pages keep AA-compliant foreground on background (Req 13.6). */
.content-workspace {
  color: var(--brand-fg);
}
.content-workspace a {
  color: var(--brand-primary);
}

/* Non-color status support (Req 13.8): status chips pair color with a text
 * label/icon slot so meaning never depends on color alone. Pages supply the
 * label text; this only provides safe, contrast-aware defaults. */
.status-indicator {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.15rem 0.5rem;
  border-radius: 999px;
  border: 1px solid var(--brand-border);
  color: var(--brand-fg);
  background: color-mix(in srgb, var(--brand-primary) 8%, transparent);
  font-size: 0.85em;
  line-height: 1.4;
}

/* ---------------------------------------------------------------------
 * Req 12.13 - prefers-reduced-motion: disable nonessential animation.
 * (The base reduced-motion rule near the top handles scroll behavior and
 * global animation/transition durations; here we also neutralize the
 * collapsible-panel transition specifically so panel toggling is instant.)
 * --------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .secondary-panel { transition: none !important; }
  .panel-toggle { transition: none !important; }
}

/* ---------------------------------------------------------------------------
 * Loading skeletons — single source of truth.
 *
 * The app renders its data client-side, so regions that wait on a fetch used to
 * paint as empty boxes or pop in late, which reads as a broken page. These
 * primitives give those regions a calm grey placeholder of roughly the right
 * shape until real content replaces them.
 *
 * Usage
 *   <div class="sk sk-card"></div>          one placeholder block
 *   <div class="sk-line"></div>             one line of text
 *   <div class="sk-line w-60"></div>        a shorter line, for ragged text
 *   <div class="sk-stack">…</div>           vertical rhythm between lines
 *   <div data-sk-for="courseGrid">…</div>   removed by skeletons.js when ready
 *
 * Containers whose innerHTML is replaced on render (a grid, a row) need no JS:
 * put the placeholders inside and the render overwrites them.
 * --------------------------------------------------------------------------- */

.sk,
.sk-line,
.sk-avatar,
.sk-pill {
  position: relative;
  overflow: hidden;
  background: var(--border-subtle);
  /* Slightly stronger than the divider colour so placeholders read as content
     being loaded rather than as empty borders. */
  background-image: linear-gradient(
    90deg,
    color-mix(in srgb, var(--border-subtle) 55%, var(--bg-card)) 0%,
    color-mix(in srgb, var(--border-subtle) 95%, var(--bg-card)) 50%,
    color-mix(in srgb, var(--border-subtle) 55%, var(--bg-card)) 100%
  );
  background-size: 200% 100%;
  animation: sk-sweep 1.6s ease-in-out infinite;
  /* Placeholders carry no information; keep them out of the accessibility tree
     and let the live region announce the real content when it arrives. */
  user-select: none;
  pointer-events: none;
}

@keyframes sk-sweep {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

.sk-card {
  height: 132px;
  border-radius: var(--radius-md);
}

.sk-line {
  height: 0.72rem;
  border-radius: var(--radius-sm);
}

.sk-line.tall { height: 1.05rem; }
.sk-line.w-30 { width: 30%; }
.sk-line.w-40 { width: 40%; }
.sk-line.w-60 { width: 60%; }
.sk-line.w-80 { width: 80%; }

.sk-avatar {
  width: 56px;
  height: 56px;
  border-radius: var(--radius-full, 999px);
  flex: none;
}

.sk-pill {
  height: 1.5rem;
  width: 5.5rem;
  border-radius: var(--radius-full, 999px);
}

.sk-stack {
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
}

.sk-row {
  display: flex;
  align-items: center;
  gap: 1.1rem;
}

/* A skeleton shaped like a card that sits in a bordered surface, so the
   placeholder occupies the same box as the card that replaces it. */
.sk-surface {
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  background: var(--bg-card);
  padding: 1.1rem 1.3rem;
}

/* Every page ships <app-topbar> in its markup, but the element is empty until
   topbar.js fills it, so it has no height and the page below it jumps once the
   bar appears. Reserving the height while it is empty removes that shift. */
app-topbar:empty {
  display: block;
  height: var(--app-topbar-h);
  flex: 0 0 auto;
}

@media (prefers-reduced-motion: reduce) {
  .sk,
  .sk-line,
  .sk-avatar,
  .sk-pill {
    animation: none;
    background-image: none;
  }
}
/* =========================================================================
 * A bare `=======` sat here: an unresolved GIT MERGE-CONFLICT MARKER, committed
 * and never noticed. It is not a comment, so the block that followed it was never
 * opened and yet closed with a stray comment terminator. CSS recovers from an
 * invalid selector by consuming up to the next `{...}` block — which meant the
 * first rule after it was swallowed whole. That rule was
 * `html.embedded app-nav, html.embedded app-topbar { display: none !important; }`,
 * so embed mode has not been hiding the app chrome it was written to hide.
 *
 * Found because a new `.app-layout` rule added here silently did nothing: the
 * course shell had `overflow: visible` and `min-height: auto` instead of the
 * scroll-containing flex box, and the content pane stopped scrolling.
 * =========================================================================
 *
 * The course shell — `.app-layout`.
 *
 * Both rails MOUNT INTO this element: course-rail.js inserts `.crail` as its first
 * child and icon-rail.js refuses to build without it. `icon-rail.css`,
 * `outline-workspace.css` and `ui-kit.css` all write rules against it. And it was
 * defined only inside home.html's inline <style>, so no other page could wear the
 * course shell even after loading both scripts — they would find nothing to mount
 * into and return early. That is why the stage editors kept the ordinary app
 * chrome while home.html had the icon rail.
 *
 * Requires a flex-column body with `overflow: hidden`, which is what every course
 * page already declares: `flex: 1` here is what makes the content pane scroll
 * instead of the window.
 * ------------------------------------------------------------------------- */
.app-layout {
  position: relative;
  z-index: 1;
  display: flex;
  gap: 2rem;
  /* Use the FULL real estate (ElevenLabs-style), not a centred column. The old
     1600px cap left large empty gutters on any monitor wider than 1600px — the
     repeated "why is this so narrow / use all the width" report. The course
     editor already ran uncapped; this makes the landing/composer match it.
     Inner document panes (the outline .docx page) keep their own readable
     width, so removing the shell cap widens chrome without hurting readability. */
  max-width: none;
  width: 100%;
  margin: 0;
  padding: 0;
  flex: 1;
  min-height: 0;
  overflow: hidden;
}

@media (max-width: 900px) {
  .app-layout { flex-direction: column; padding: 0.5rem 0.5rem; }
}
@media (max-width: 768px) {
  .app-layout { padding: 0; }
}

/* A stand-alone stage editor's shell.
 *
 * `.app-layout` is a flex ROW because home.html puts the stepper beside the
 * content. A stage editor has no stepper and its content is a vertical stack, so it
 * gets one full-width pane instead. Padding and gap are zeroed because each stage
 * page already carries its own generous padding on the panes inside — adding
 * 2.5rem on top of that is how content ends up pushed under the rail. */
.app-layout.stage-shell {
  padding: 0;
  gap: 0;
  max-width: none;
}

.app-layout > .stage-pane {
  flex: 1;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* =======
 * The page header — ONE definition, for every app page.
 *
 * This did not exist. The `html.embedded` rules below were written against a
 * `.page-header` component the shared layer never defined, so each page invented
 * its own in an inline <style>, and the three stage editors of one workflow ended
 * up rendering their page title at 48px, 32px and 22px with three different
 * eyebrow treatments.
 *
 * A page may still set its own width and padding — the slide studio needs a wider
 * measure than the narration editor. It does not get to pick its own title size,
 * weight or eyebrow, because that is what a user reads as "this is a different
 * app".
 * ------------------------------------------------------------------------- */
.page-header {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-4);
  margin-bottom: var(--space-5);
}

.page-header > h1,
.page-header .page-heading > h1 {
  margin: 0;
  font-size: var(--text-2xl);
  font-weight: var(--weight-display, 700);
  line-height: 1.15;
  letter-spacing: var(--tracking-tight);
  color: var(--text-primary);
}

/* The one-line description under the title. Optional, capped in measure because
   a heading paragraph running the full width of a 1440px viewport is unreadable. */
.page-header > p,
.page-header .page-heading > p {
  max-width: 62ch;
  margin: var(--space-2) 0 0;
  color: var(--text-secondary);
  font-size: var(--text-base);
}

/* The label above the title. `.eyebrow` is the name — `.page-eyebrow` was a second
   name for the same thing on one page. The rule before it is the brand's, and it
   is what makes the label read as a label rather than small bold text. */
.eyebrow {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-1);
  color: var(--accent-violet);
  font-size: var(--text-xs);
  font-weight: var(--weight-strong, 700);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.eyebrow::before {
  content: '';
  width: 18px;
  height: 3px;
  border-radius: var(--radius-full);
  background: var(--gradient-main);
}

/* Actions belong on the title's baseline, and must never squash the title. */
.page-header .header-actions,
.page-header .page-actions {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  gap: var(--space-2);
}

@media (max-width: 640px) {
  .page-header { flex-direction: column; align-items: flex-start; }
  .page-header .header-actions,
  .page-header .page-actions { width: 100%; }
}

/* =======
 * Embed mode (`?embed=1`) — a page rendered as the stage of the Course Studio.
 *
 * components.js adds `html.embedded` and removes <app-nav>/<app-topbar>. What
 * is left to suppress is the page's own identity chrome, which the studio
 * already shows in its top bar: the heading block and the course selector.
 * Action buttons in the header stay, because they are the page's real tools.
 * --------------------------------------------------------------------------- */
html.embedded app-nav,
html.embedded app-topbar { display: none !important; }
html.embedded .page-heading,
html.embedded .page-header > h1,
html.embedded .page-header > p { display: none !important; }
html.embedded .page-header { padding-top: 0; margin-bottom: var(--space-2); }
html.embedded .page-header:not(:has(.header-actions)) { display: none !important; }
/* The studio fixes the course, so the page's own course picker is noise. Its
 * version picker and Load button stay, so a specific version can still be
 * opened from inside the studio. */
html.embedded .course-bar .field:has(#courseName) { display: none !important; }

/* The responsive layer used to live here. It is frontend/responsive.css now,
 * imported by branding.css directly after this file, because it had grown into
 * a component with its own contract: five named breakpoints, the container and
 * action-row ALIAS sets, and the touch-target floor. Two of those needed
 * body-scoped selectors to outrank page-local rules, which is a different
 * cascade strategy from the rest of this file, and mixing the two in one
 * stylesheet is how it stopped being obvious which rule was supposed to win. */
