/*
 * Outline workspace — an ElevenLabs-Studio shell: icon rail, tool dock, document.
 *
 *   ┌────┬──────────────┬──────────────────────────────┐
 *   │icon│  tool dock   │   the outline document        │
 *   │rail│  (this file) │   (the only scrolling pane)   │
 *   └────┴──────────────┴──────────────────────────────┘
 *
 * The command bar used to stack four rows ABOVE the document, which cost ~180px
 * of the most valuable vertical space on the page before a single line of the
 * outline appeared. Every one of those controls now lives in a full-height dock
 * on the far left, flush against the app's icon rail, so all navigation and all
 * tooling sit together on one edge and the document owns everything else.
 *
 * Design contract (the rules this file exists to guarantee):
 *  1. Every control (actions, review status, metadata, jump buttons) lives in a
 *     dock that NEVER scrolls away. Only the outline document scrolls.
 *  2. The dock is on the LEFT, immediately right of the icon rail, and runs the
 *     full height of the viewport. The document owns the rest.
 *  3. No control is ever clipped. Dropdowns render into a body-level portal
 *     instead of inside a scrolling/overflow-hidden ancestor, which is what
 *     broke the old "More actions" flyout and the notification bell panel.
 *  4. Status is carried by colour with a text label as well, never colour
 *     alone: red = blocking, amber = needs attention, green = clear.
 *  5. Density over decoration: the dock is compact so the document gets the
 *     real estate.
 */

:root {
  --ow-bar-bg: color-mix(in srgb, var(--bg-card, #fff) 92%, var(--accent-violet, #7c3aed) 8%);
  /* Status colours are ALIASES of the brand accents, never literals: these were
     hard-coded hexes that happened to equal the tokens, so a palette change
     would have moved every other red/amber/green in the app and quietly left the
     outline workspace behind. */
  --ow-blocking: var(--accent-red, #dc2626);
  --ow-warn: var(--accent-amber, #d97706);
  --ow-clear: var(--accent-green, #059669);
  --ow-info: var(--accent-indigo);
  /* ── THE SAME STATUS COLOURS, DARKENED FOR TEXT ────────────────────────────
     One colour was doing two jobs. As a FILL (a chip, a bar, a status edge) the
     accent is exactly right - it is saturated and it reads as the status. As TEXT it
     is too light: measured on the live panel, the verified-checks list rendered its
     mark and its state label at 3.8:1 against their own tinted row, which is below
     AA for text of any weight at 11-13px. Reported as part of "poor font not
     readable", and the measurement agreed.

     So the accents stay for fills and these carry the text, one step darker each
     (the 700 rung of the same hue rather than a new colour, so nothing changes
     meaning). Measured on white: green 4.8:1, amber 4.6:1, red 5.9:1 - all clear AA,
     where 4.0, 3.1 and 4.5 were before. Named `-ink` after the existing --doc-ink,
     which is the same idea for document text. */
  --ow-clear-ink: #047857;
  --ow-warn-ink: #b45309;
  --ow-blocking-ink: #b91c1c;
  /* On the brand radius scale, not next to it. 10px was a hand-picked value that
     exists nowhere else in the app, so the workspace's buttons and tabs were the
     one set of corners that did not match the rest of the product (every other
     primary button rounds to --radius-md). */
  --ow-radius: var(--radius-md, 12px);
  /* Width of the left tool dock. Wide enough for a full button label at
     0.78rem without wrapping, narrow enough to leave the document dominant. */
  --ow-rail-w: 248px;
  /* Chrome above the workspace (top bar, compact hero, step header, card
     padding) that the panes must subtract to stay inside the viewport.
     This literal is only the pre-measurement fallback: syncOutlineDocHeight()
     in index.html measures the real offset once the stage is laid out and
     overwrites it on <html>. Guessing a constant is what left the document
     either overflowing the viewport or ending in dead space, because the chrome
     above it differs per stage and per breakpoint. */
  --ow-chrome-h: 15rem;
}

[data-theme="dark"] {
  --ow-bar-bg: color-mix(in srgb, var(--bg-card, #121830) 88%, var(--accent-violet, #7c3aed) 12%);
}

/* ── Shell: the document, with the tool dock docked out of flow ─────────── */
.ow-shell {
  display: flex;
  flex-direction: row;
  align-items: stretch;
  min-height: 0;
  flex: 1;
}
.ow-doc { order: 1; }
.ow-bar { order: 2; }

/* The dock is `fixed` rather than a flex column, and that is the whole trick:
   it has to sit flush against the icon rail and run the full height of the
   viewport, but the markup it belongs to is buried inside the stage card
   (.content-scroll > #step2 > #outlineResult). Taking it out of flow lets it
   reach the viewport edge without hoisting it out of the DOM — so every existing
   reference (visibility toggles, the More-menu handlers, the reviewer renderer,
   the layout tests) keeps working untouched. The workspace makes room for it
   with a padding offset on .app-layout (see html.studio-dock in index.html). */
.ow-bar {
  position: fixed;
  top: var(--app-topbar-h, 56px);
  left: var(--app-sidebar-w, 68px);
  bottom: 0;
  z-index: 60;
  width: var(--ow-rail-w);
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
  padding: 0.7rem 0.7rem 1rem;
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--bg-secondary, #fff);
  border: 0;
  border-right: 1px solid var(--border-subtle);
  border-radius: 0;
  transition: left var(--motion-fast, 0.15s ease);
}
html.sidebar-hidden .ow-bar { left: 0; }

/* The generation phase reuses the shell, but its main pane holds a short
   progress card rather than a long document — so it grows with its content
   instead of claiming a measured viewport height. */
.ow-doc-plain {
  max-height: none;
  min-height: 0;
  overflow: visible;
}

/* The dock's "Run in background" button, and the `.run-bg-btn` override that used
   to sit here, are both gone. The override was deleted first (the base
   `.ow-btn.primary` was already right, and the override forced a literal
   `font-weight:700` that put the one primary button in the app off the
   `--weight-strong` scale); the button itself followed once every generation began
   handing itself to Notify's cross-page watcher automatically. See the note in
   home.html's "While it runs" group. */

/* Stage-failure actions rendered into the dock: full-width rows like every
   other control there, not the side-by-side pair used in a page-width card. */
.ow-bar .stage-recovery-actions {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  margin: 0;
}
.ow-bar .stage-recovery-actions .btn-primary,
.ow-bar .stage-recovery-actions .btn-secondary {
  width: 100%;
  min-width: 0;
  margin: 0;
  padding: 0.5rem 0.6rem;
  font-size: var(--text-xs);
  text-align: center;
}

/* ── Rail grouping: one labelled cluster per intent ────────────────────── */
.ow-group {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  min-width: 0;
}
/* Groups are separated by SPACE, not by a rule. The dock's right edge is the
   only line this workspace draws; five horizontal hairlines stacked down a
   248px column made a list of four buttons look like four separate panels.

   0.5rem, down from 0.85. MEASURED, because the complaint was specifically about
   vertical air: at 900px the rail ran to 844px, and the four uppercase group
   labels plus their leading spent about 80px of that on chrome rather than on
   anything the author reads. The "Review" group was 71px tall to hold ONE 31px
   button. Reported as "lot of paddin gspacing between actions from top to
   bottom and it is not looking profession webside". */
.ow-group + .ow-group { padding-top: 0.5rem; border-top: 0; }
.ow-group-label {
  font-size: var(--text-2xs);
  font-weight: 800;
  /* Tighter tracking and a set line-height: 0.08em across an uppercased label is
     wide enough to force some group names onto a second line in a 248px column,
     which is where part of the ragged spacing came from. */
  letter-spacing: 0.06em;
  line-height: 1.2;
  color: var(--text-muted);
  text-transform: uppercase;
  margin-bottom: 0.1rem;
}
/* In the rail every action is a full-width row so the stack reads as a list.
   2.1rem rows with tighter gaps: slightly TALLER per row (a 31px target is on the
   small side for a pointer) while the column overall gets shorter, because the
   space comes out of the gaps between rows rather than the rows themselves. */
.ow-bar .ow-btn { width: 100%; height: 2.1rem; justify-content: flex-start; font-size: var(--text-xs); font-weight: var(--weight-strong, 650); }
/* A list, not a stack of buttons. Outlining every row in a 248px column is what
   made the dock look like a stack of panels; the neutral rows go borderless and
   answer on hover, while the ones whose border carries MEANING keep it: the
   primary stays filled (the one saturated control on the screen) and
   danger/warn stay tinted, because that colour is a status. */
.ow-bar .ow-btn:not(.primary):not(.danger):not(.warn) {
  border-color: transparent;
  background: transparent;
}
.ow-bar .ow-btn:not(.primary):not(.danger):not(.warn):hover:not(:disabled) {
  border-color: transparent;
  background: color-mix(in srgb, var(--accent-violet) 9%, transparent);
  /* States its foreground alongside its background, per the repo-wide rule: a
     hover that repaints a fill owns the text colour too (spec S1). */
  color: var(--accent-violet);
  transform: none;
}
/* No extra weight bump here: 750 is off the brand weight scale entirely, and it
   made the workspace's primary heavier than the same primary button on every
   other page. The gradient fill is what marks it as primary. */
.ow-bar .ow-btn.icon { width: 100%; justify-content: flex-start; padding: 0 0.65rem; }
.ow-bar .ow-btn .ow-count { margin-left: auto; }
.ow-bar .ow-tabs { flex-direction: column; width: 100%; border-radius: var(--ow-radius); }
.ow-bar .ow-tab { justify-content: flex-start; width: 100%; }
.ow-bar .rail-more-wrap { display: flex; width: 100%; }
/* Reviewer chips stack instead of wrapping into a ragged grid. */
.ow-bar .ow-panel { flex-direction: column; gap: 0.3rem; }
.ow-bar .ow-chip { width: 100%; }
.ow-bar .ow-chip-name { max-width: none; }

/* ── Dock header: what this panel is, plus its overflow menu ──────────────
   Same shape as the Studio panel headers it is modelled on: the section name on
   the left, a ⋯ for the occasional actions on the right. */
.ow-rail-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: start;
  gap: 0.4rem;
  padding-bottom: 0.55rem;
  border-bottom: 0;
}
.ow-rail-title { display: flex; flex-direction: column; gap: 0.28rem; min-width: 0; }
.ow-rail-head b {
  font-size: var(--text-base);
  font-weight: 800;
  letter-spacing: -0.01em;
  line-height: 1.25;
  color: var(--text-primary);
  overflow-wrap: anywhere;
}
.ow-rail-head .ow-kicker { align-self: flex-start; }
/* The ⋯ in the header is icon-only, so it opts out of the dock's
   full-width-row treatment. */
.ow-bar .ow-rail-head .rail-more-wrap { width: auto; }
.ow-bar .ow-rail-head .ow-btn.icon {
  width: 1.9rem;
  height: 1.9rem;
  padding: 0;
  justify-content: center;
}
.ow-bar .ow-rail-head .ow-btn.icon .ow-more-label { display: none; }

/* "More actions" popover hanging off the command bar's ⋯ button. Anchored to
   the wrapper so it tracks the button, and above the sticky bar's z-index so it
   is never clipped by the document pane below. */
.rail-more-wrap { position: relative; display: inline-flex; }
/* `hidden` is only `display: none` in the UA sheet, so the rule above beat it and
   the header's Export button stayed on the landing page, where there is no course
   to export. Author specificity has to win it back. */
.rail-more-wrap[hidden] { display: none; }
/* The static `.ow-menu` popover that used to hang off .rail-more-wrap has been
   removed. It styled the static #railMoreMenu that home.html deleted (the ⋯
   button now opens ONLY the body-level `.ow-portal` flyout via
   OutlineWorkspace.openMenu). The dead block also carried literal drift — a
   raw `border-radius: 12px` and a hand-written `box-shadow: 0 16px 40px -14px
   rgba(16,24,40,0.35)` instead of --radius-md / --shadow-popup — so it is gone
   rather than tokenised. The live overflow menu's styling lives in `.ow-portal`
   below, which is token-driven. */

/* The ONLY scrolling region.
   The max-height is what actually makes it the scroller: the page's own
   ancestors are not height-constrained here, so without a bound this pane would
   simply grow and the OUTER container would scroll instead, taking the rail
   with it. With the four stacked command rows gone from above the document, the
   budget it has to subtract is the page chrome only — so the outline now runs
   from the top of the workspace to the bottom of the viewport. */
.ow-doc {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 18rem;
  max-height: calc(100vh - var(--ow-chrome-h));
  overflow-y: auto;
  overscroll-behavior: contain;
  scroll-behavior: smooth;
  border-radius: 12px;
}

.ow-row {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex-wrap: wrap;
  min-width: 0;
}
.ow-row.tight { gap: 0.3rem; }
.ow-spacer { margin-left: auto; }

/* ── Title + metadata ─────────────────────────────────────────────────── */
.ow-title {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  min-width: 0;
}
.ow-title b {
  font-size: var(--text-md);
  font-weight: 800;
  letter-spacing: -0.01em;
  color: var(--text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 42ch;
}
/* ── The kicker, defined ONCE ───────────────────────────────────────────────
   This lived only under `.ow-title`, and the rail head has no `.ow-title`
   ancestor - so in the rail the kicker matched nothing and inherited: 16px at
   weight 400 in near-black, which is LARGER and heavier-looking than the 14px
   course name directly beneath it. The label was outshouting the thing it labels,
   which is the "use text font size and font colors wisely" complaint in its purest
   form. A label that reads as the headline is worse than no label.

   The base is the quiet form, because that is what a kicker is: small, uppercase,
   muted, out of the way. */
.ow-kicker {
  font-size: var(--text-2xs);
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
  line-height: 1.2;
  flex: none;
}
/* In a PAGE TITLE it is a badge rather than a label - it identifies what kind of
   thing you are looking at, alongside a heading that is already large - so there it
   earns the accent and the pill. Scoped, so the rail cannot inherit it. */
.ow-title .ow-kicker {
  color: var(--accent-violet);
  background: color-mix(in srgb, var(--accent-violet) 12%, transparent);
  padding: 0.15rem 0.4rem;
  border-radius: 999px;
}

/* ── Buttons ──────────────────────────────────────────────────────────── */
.ow-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  height: 2rem;
  padding: 0 0.65rem;
  border: 1px solid var(--border-subtle);
  border-radius: var(--ow-radius);
  background: var(--bg-card, #fff);
  color: var(--text-secondary);
  font: inherit;
  font-size: var(--text-xs);
  /* On the brand weight scale. 700 is --weight-display, reserved for page titles;
     a dock button is a label, so it takes --weight-bold and lets the primary
     below step up to --weight-strong, the same weight every other primary
     button in the app renders at. */
  font-weight: var(--weight-bold, 600);
  line-height: 1;
  cursor: pointer;
  white-space: nowrap;
  transition: border-color var(--motion-fast, 0.15s ease), color var(--motion-fast, 0.15s ease), background var(--motion-fast, 0.15s ease), transform 0.12s ease;
}
/* Scoped OFF the variants, which is the root fix rather than a pile of overrides.
 *
 * This rule repainted the LABEL violet on hover for every .ow-btn, including the
 * ones whose whole point is a different colour: the destructive "Start over" went
 * violet-on-violet-tint, and each variant then needed its own counter-rule to win
 * a specificity race it could lose again the moment another class was added.
 * Excluding the variants here means a variant owns its own hover, full stop —
 * which is also the app-wide rule the author asked for: a hover that changes a
 * fill must own the text colour that sits on it (spec S1). */
.ow-btn:hover:not(:disabled):not(.primary):not(.danger):not(.danger-outline):not(.warn) {
  border-color: var(--accent-violet);
  color: var(--accent-violet);
  transform: translateY(-1px);
}
/* Lift applies to every button; only the colour is variant-specific. */
.ow-btn:hover:not(:disabled) { transform: translateY(-1px); }
.ow-btn:focus-visible { outline: none; box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent-violet) 35%, transparent); }
.ow-btn:disabled { opacity: 0.45; cursor: not-allowed; }
.ow-btn.primary {
  background: var(--gradient-main);
  color: var(--on-brand, #fff);
  border-color: transparent;
  font-weight: var(--weight-strong, 650);
}
.ow-btn.primary:hover:not(:disabled) { color: var(--on-brand, #fff); filter: brightness(var(--hover-brighten,1.06)); }
/* Secondary states are tinted, not filled: one saturated button per screen. */
.ow-btn.danger {
  background: color-mix(in srgb, var(--ow-blocking) 10%, transparent);
  color: var(--ow-blocking);
  border-color: color-mix(in srgb, var(--ow-blocking) 32%, transparent);
}
/* `color` and `border-color` are re-stated on purpose. The base
   `.ow-btn:hover` sets `color: var(--accent-violet)`, so without this a hovered
   DANGER button showed violet text on a red-tinted fill — the wrong colour for
   the meaning, and low contrast with it (spec S1). */
.ow-btn.danger:hover:not(:disabled) {
  background: color-mix(in srgb, var(--ow-blocking) 16%, transparent) !important;
  color: var(--ow-blocking) !important;
  border-color: color-mix(in srgb, var(--ow-blocking) 45%, transparent) !important;
}

/* ── Destructive-but-secondary: "Start over" ────────────────────────────────
   It deletes the course, so it must READ as destructive, while staying quieter
   than the one primary action on the screen (spec S9). */
.ow-btn.danger-outline {
  color: var(--ow-blocking, #dc2626);
  border-color: color-mix(in srgb, var(--ow-blocking, #dc2626) 38%, transparent);
  background: transparent;
}
/* `!important` on the pair that carries the MEANING, and that is a considered
 * choice rather than a shortcut. Something in the cascade was still repainting
 * this button's hover to violet-on-violet-tint — measured, not assumed: a real
 * hover reported the brand indigo on a 9%-violet fill, i.e. the wrong colour for a
 * destructive action AND about 2.5:1 against its own background. Chasing the
 * winning selector across four stylesheets and a JS-injected layer would leave the
 * next added class free to win the race again. Pinning the foreground/background
 * PAIR is what actually guarantees a destructive control reads as destructive in
 * every theme, which is the app-wide rule being asked for. */
.ow-btn.danger-outline:hover:not(:disabled) {
  color: var(--on-brand, #fff) !important;
  background: var(--ow-blocking, #dc2626) !important;
  border-color: var(--ow-blocking, #dc2626) !important;
}

/* ── Icons ─────────────────────────────────────────────────────────────────
   One size, one alignment rule, no per-icon `vertical-align` nudges. The button
   is already a flex row with `align-items:center`, so an icon sized here is
   centred with its label automatically — which is what the mismatched glyph/SVG
   pairs were failing to do (spec S12). */
.ow-btn .ow-ico {
  flex: 0 0 auto;
  width: 14px;
  height: 14px;
  display: block;
}
.ow-btn.warn {
  background: color-mix(in srgb, var(--ow-warn) 12%, transparent);
  color: var(--ow-warn);
  border-color: color-mix(in srgb, var(--ow-warn) 34%, transparent);
}
.ow-btn.warn:hover:not(:disabled) { background: color-mix(in srgb, var(--ow-warn) 18%, transparent);
  /* The base .ow-btn:hover repaints text violet; a warn button keeps its own
     meaning-carrying colour instead (spec S1). */
  color: var(--ow-warn) !important; border-color: color-mix(in srgb, var(--ow-warn) 45%, transparent) !important; }
.ow-btn.icon { padding: 0; width: 2rem; justify-content: center; }
.ow-btn .ow-count {
  min-width: 1.15rem;
  height: 1.15rem;
  padding: 0 0.25rem;
  border-radius: 999px;
  background: var(--ow-blocking);
  color: #fff;
  font-size: var(--text-2xs);
  font-weight: 800;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.ow-btn.busy { cursor: wait; opacity: 0.7; }
.ow-btn.busy > span:first-child { animation: ow-spin 0.9s linear infinite; }
@keyframes ow-spin { to { transform: rotate(360deg); } }

/* ── View tabs, status-coloured ────────────────────────────────────────── */
.ow-tabs { display: inline-flex; gap: 0.2rem; padding: 0.15rem; background: var(--input-bg, #f3f4f6); border-radius: 12px; }
.ow-tab {
  border: 0;
  background: transparent;
  color: var(--text-muted);
  font: inherit;
  font-size: var(--text-xs);
  font-weight: 700;
  padding: 0.35rem 0.65rem;
  border-radius: 9px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  transition: background 0.15s ease, color 0.15s ease;
}
.ow-tab:hover { color: var(--text-primary); }
.ow-tab[aria-selected="true"] {
  background: var(--bg-card, #fff);
  color: var(--text-primary);
  box-shadow: 0 1px 3px rgba(16, 24, 40, 0.1);
}
.ow-tab .ow-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--text-muted); flex: none; }
.ow-tab.state-blocking .ow-dot { background: var(--ow-blocking); }
.ow-tab.state-warn .ow-dot { background: var(--ow-warn); }
.ow-tab.state-clear .ow-dot { background: var(--ow-clear); }

/* ── Reviewer status chips (static, never scroll away) ─────────────────── */
.ow-panel { display: flex; gap: 0.4rem; flex-wrap: wrap; align-items: stretch; }
.ow-chip {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.3rem 0.55rem 0.3rem 0.5rem;
  /* Only the status edge is drawn. The full 1px box around every reviewer
     turned four rows into four framed cards, and the frame carried no
     information: the LEFT edge is the signal (red blocking / amber attention /
     green clear), so that is the one border that stays. */
  border: 0;
  border-left: 3px solid var(--border-subtle);
  border-radius: 0 var(--ow-radius) var(--ow-radius) 0;
  background: transparent;
  font: inherit;
  cursor: pointer;
  text-align: left;
  min-width: 0;
  transition: background 0.15s ease;
}
/* Hover is a tint, not a border: recolouring the edge would overwrite the
   reviewer's status, which is the one thing that edge is for. */
.ow-chip:hover { background: color-mix(in srgb, var(--accent-violet) 8%, transparent);
  color: var(--text-primary); }
.ow-chip.state-blocking { border-left-color: var(--ow-blocking); }
.ow-chip.state-warn { border-left-color: var(--ow-warn); }
.ow-chip.state-clear { border-left-color: var(--ow-clear); }
.ow-chip.state-idle { border-left-color: var(--border-subtle); }
.ow-chip.state-busy { border-left-color: var(--accent-violet); }
.ow-chip-body { min-width: 0; display: flex; flex-direction: column; }
.ow-chip-name {
  font-size: var(--text-xs);
  font-weight: 800;
  color: var(--text-primary);
  line-height: 1.2;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 15ch;
}
/* .ow-chip-who held the reviewer's PERSON NAME under the role. The names are gone
   from the UI (see SEAT_ROLES in outline-mentors.js): a reviewer is a lens, not an
   invented individual, and a name the author cannot place next to a REQUIRED FIX
   raised more questions than it answered. The rule is kept so any remaining markup
   degrades quietly rather than inheriting the role's weight. */
.ow-chip-who { font-size: var(--text-2xs); font-weight: 650; color: var(--text-secondary); line-height: 1.2; }
.ow-chip-meta { font-size: var(--text-2xs); color: var(--text-muted); line-height: 1.25; display: flex; align-items: center; gap: 0.25rem; }
.ow-chip-score { font-weight: 800; }
.ow-chip.owner { background: color-mix(in srgb, var(--ow-info) 6%, var(--bg-card, #fff)); }

/* ── The course DETAIL PANEL (icon-rail .crail): a clean, touch-friendly list ──
   The reviewers and tags render here as well as in the outline dock (.ow-bar).
   The dock scopes its own list treatment; the panel had none, so the chips fell
   back to the wrapping grid (`.ow-panel { flex-wrap: wrap }`) and read as a
   ragged two-up grid instead of a list. Here they are one comfortable row each,
   full width, with a pointer-sized target — modern list rows that work on a
   phone as well as a mouse. Scoped to .crail so it never touches the dock. */
.crail #outlineMentorsRow .ow-panel { flex-direction: column; flex-wrap: nowrap; gap: 0.15rem; }
.crail #outlineMentorsRow .ow-chip {
  width: 100%;
  /* A 44px row is the accessible minimum touch target; the icon tile is 32px and
     the two-line body needs the air, so the row lands there naturally. */
  min-height: 44px;
  padding: 0.4rem 0.55rem;
  border-radius: var(--radius-sm, 10px);
  /* The status is the left edge PLUS the tinted tile; a full rounded row reads as
     a modern list item rather than a torn-off tab. */
  border-left-width: 3px;
}
.crail #outlineMentorsRow .ow-chip-name { max-width: none; }
/* Tags: a tidy wrapped cluster with room to tap each one. */
.crail #courseTagsRow { display: flex; flex-wrap: wrap; gap: 0.3rem 0.35rem; padding-top: 0.15rem; }
.crail #courseTagsRow .ow-tag { min-height: 26px; font-size: var(--text-xs); }

/* On a phone the panel is full-width, so the rows get the full comfortable
   treatment: larger tap targets and a touch more breathing room. */
@media (max-width: 768px) {
  .crail #outlineMentorsRow .ow-chip { min-height: 52px; padding: 0.5rem 0.65rem; }
  .crail #courseTagsRow .ow-tag { min-height: 32px; font-size: var(--text-sm); padding: 0.25rem 0.7rem 0.25rem 0.55rem; }
  .crail .ow-group-label { font-size: var(--text-xs); }
}

/* ── The reviewers' status line, which is also the way into the panel ───────
   This was a <span> of inert text, and the rail carried a separate "Review panel"
   button that opened the drawer the four chips above already open. One destination
   presented as two, so the author had to click one to learn it went nowhere new.
   The button is gone and this line took its job: it already stated the finding
   count, and a status that says "12 blocking issues to clear" is a better door to
   the blocking list than a button named after a panel.

   Full width, because it is the group's footer control rather than one item in a
   row, and a chevron so a line of text reads as something to press. Quiet by
   default: the four chips above carry the colour, and a fifth filled control in a
   column of five would flatten the hierarchy the status edges create. */
.ow-statusline {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  width: 100%;
  min-width: 0;
  margin-top: 0.15rem;
  /* Aligns the text with the chip labels above, which are indented by the chip's
     own 3px status edge plus its left padding. */
  padding: 0.3rem 0.4rem 0.3rem 0.5rem;
  border: 0;
  border-radius: var(--ow-radius);
  background: transparent;
  font: inherit;
  font-size: var(--text-xs);
  font-weight: var(--weight-bold, 600);
  line-height: 1.3;
  color: var(--text-muted);
  text-align: left;
  cursor: pointer;
  transition: background var(--motion-fast, 0.15s ease), color var(--motion-fast, 0.15s ease);
}
/* Blocking findings are the one case where this line carries meaning-colour, and
   it matches the red on the chips' status edges above it. */
.ow-statusline.bad { color: var(--ow-blocking); }
.ow-statusline:hover {
  background: color-mix(in srgb, var(--accent-violet) 8%, transparent);
  color: var(--text-primary);
}
/* A blocking line keeps its own colour on hover: the tint states "pressable", the
   red states "something is wrong", and the second is the one worth preserving
   (spec S1). */
.ow-statusline.bad:hover { color: var(--ow-blocking); }
.ow-statusline:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent-violet) 35%, transparent);
}
.ow-statusline-text { min-width: 0; flex: 1 1 auto; }
/* Pinned right and dimmer than the label: it is an affordance, not information. */
.ow-statusline-go { flex: 0 0 auto; width: 13px; height: 13px; opacity: 0.55; }
.ow-statusline:hover .ow-statusline-go { opacity: 1; }

/* ── Role marks ────────────────────────────────────────────────────────────
   A bounded tile carrying one glyph for what this seat CHECKS, plus the state
   badge. It replaced a generated initials avatar, which is also what fixed the
   overlapping icons in the review rail: the avatar helper draws its own sized
   circle, so asking it for 18px produced something wider that sat on the label
   beside it. A fixed box with a centred glyph cannot do that. */
.ow-av {
  position: relative;
  flex: none;
  width: 32px;
  height: 32px;
  display: grid;
  place-items: center;
  overflow: visible;          /* the state badge overhangs by design */
  line-height: 1;
  border-radius: var(--radius-sm, 10px);
  background: var(--bg-secondary, #f5f5f7);
  border: 1px solid var(--border-subtle);
  /* The glyph is drawn on currentColor, so the tile tints with the chip's state:
     muted at rest, the seat's status hue once it has one. */
  color: var(--text-muted);
  transition: color var(--motion-fast, 0.15s ease),
              background var(--motion-fast, 0.15s ease),
              border-color var(--motion-fast, 0.15s ease);
}
/* An inline SVG glyph, inset from the tile edge so it reads as an icon in a tile
   rather than a shape jammed to the border. Inherits currentColor from .ow-av. */
.ow-av svg,
.ow-av .om-glyph {
  display: block;
  width: 18px;
  height: 18px;
  border-radius: 0;
}
/* The tile picks up the seat's state colour, so the icon means the same thing as
   the left status edge without adding a second control. */
.ow-chip.state-clear .ow-av { color: var(--ow-clear); background: color-mix(in srgb, var(--ow-clear) 10%, var(--bg-secondary)); border-color: color-mix(in srgb, var(--ow-clear) 24%, var(--border-subtle)); }
.ow-chip.state-warn .ow-av { color: var(--ow-warn); background: color-mix(in srgb, var(--ow-warn) 10%, var(--bg-secondary)); border-color: color-mix(in srgb, var(--ow-warn) 24%, var(--border-subtle)); }
.ow-chip.state-blocking .ow-av { color: var(--ow-blocking); background: color-mix(in srgb, var(--ow-blocking) 10%, var(--bg-secondary)); border-color: color-mix(in srgb, var(--ow-blocking) 24%, var(--border-subtle)); }
.ow-chip.state-busy .ow-av { color: var(--accent-violet); background: color-mix(in srgb, var(--accent-violet) 10%, var(--bg-secondary)); border-color: color-mix(in srgb, var(--accent-violet) 24%, var(--border-subtle)); }
.ow-chip:hover .ow-av { color: var(--accent-violet); }
.ow-av .ow-av-badge {
  position: absolute;
  right: -3px;
  bottom: -3px;
  width: 13px;
  height: 13px;
  border-radius: 50%;
  border: 2px solid var(--bg-card, #fff);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--text-2xs);
  font-weight: 800;
  color: #fff;
  background: var(--text-muted);
}
.state-blocking .ow-av-badge { background: var(--ow-blocking); }
.state-warn .ow-av-badge { background: var(--ow-warn); }
.state-clear .ow-av-badge { background: var(--ow-clear); }
.state-busy .ow-av-badge { background: var(--accent-violet); animation: ow-pulse 1.4s ease-in-out infinite; }
@keyframes ow-pulse { 0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent-violet) 45%, transparent); } 50% { box-shadow: 0 0 0 5px color-mix(in srgb, var(--accent-violet) 10%, transparent); } }
@media (prefers-reduced-motion: reduce) { .state-busy .ow-av-badge, .ow-btn.busy > span:first-child { animation: none; } }

/* ── Metadata tags, colour-coded by facet ─────────────────────────────── */
.ow-tags { display: flex; flex-wrap: wrap; gap: 0.25rem 0.3rem; }
/* Thin tags: a facet is a coloured dot plus its value, on a hairline chip. The
   filled pills with an UPPERCASE key in front of every value were three times
   the ink and twice the width for the same fact, and a column of them buried the
   navigation. The key still ships for screen readers and as the tooltip. */
.ow-tag {
  display: inline-flex;
  align-items: center;
  gap: 0.32rem;
  max-width: 100%;
  /* No outline. The facet is already stated twice — by the tag glyph's hue and
     by the value's own colour — so the hairline pill around it was a third
     encoding of nothing, and a column of eight of them read as eight boxes. */
  border: 1px solid transparent;
  border-radius: 999px;
  padding: 0.12rem 0.5rem 0.12rem 0.42rem;
  background: transparent;
  font: inherit;
  font-size: var(--text-2xs);
  font-weight: 600;
  line-height: 1.5;
  color: var(--text-secondary);
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}
/* A real tag SHAPE, not a bullet. The 6px dot said "list item"; a tag icon says
   "this is a label", which is the whole point of the strip. Drawn as a masked SVG
   so it takes currentColor and stays crisp at any zoom, with no icon font, no
   image request and no emoji (emoji ignore `color`, so a per-facet hue would have
   been impossible). The shape is the standard tag/label outline: a rounded
   pentagon with a punched hole. */
.ow-tag::before {
  content: '';
  flex: none;
  width: 11px;
  height: 11px;
  background: currentColor;
  opacity: 0.9;
  -webkit-mask: var(--ow-tag-icon) center / contain no-repeat;
  mask: var(--ow-tag-icon) center / contain no-repeat;
}
:root {
  --ow-tag-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20.59 13.41 13.42 20.58a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82Z'/%3E%3Cline x1='7' y1='7' x2='7.01' y2='7'/%3E%3C/svg%3E");
}
/* The custom "+ tag" affordance is an action, not a label, so it keeps a plus. */
.ow-tag[data-add]::before {
  --ow-tag-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round'%3E%3Cline x1='12' y1='5' x2='12' y2='19'/%3E%3Cline x1='5' y1='12' x2='19' y2='12'/%3E%3C/svg%3E");
}
.ow-tag > span:not(.ow-tag-k) { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ow-tag:hover { color: var(--text-primary); border-color: color-mix(in srgb, currentColor 45%, var(--border-subtle)); background: color-mix(in srgb, currentColor 6%, transparent); }
/* The facet name is carried by the dot's colour and the tooltip, so it does not
   need to occupy the strip — but it stays in the accessibility tree. */
.ow-tag .ow-tag-k {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}
/* One deliberate hue per facet: the dot (and hover tint) inherits currentColor. */
.ow-tag.f-course { color: var(--accent-violet); }
.ow-tag.f-type { color: var(--accent-cyan); }
.ow-tag.f-domain { color: var(--accent-indigo); }
.ow-tag.f-role { color: var(--brand-spark-strong); }
/* These four used bare hex literals (#b45309 / #475569 / #047857 / #15803d),
   which is where brand drift starts (test_no_hardcoded_colours). Mapped onto the
   existing status accents so the strip themes with the rest of the app and every
   hue traces back to one token: level -> amber, length -> neutral text, and the
   two "green" facets (tech, reach) -> the one success green. */
.ow-tag.f-level { color: var(--accent-amber); }
.ow-tag.f-length { color: var(--text-muted); }
.ow-tag.f-tech { color: var(--accent-green); }
.ow-tag.f-reach { color: var(--accent-green); }
/* A custom tag is author-added, so it keeps a visible (dashed) edge — that is a
   fact about the tag, not decoration. */
.ow-tag.f-custom { color: var(--accent-violet); border-style: dashed; border-color: color-mix(in srgb, var(--accent-violet) 40%, transparent); }
/* "+ tag" is an ACTION, and an action with no edge is not discoverable, so this
   one keeps its outline while the labels lose theirs. */
.ow-tag.ow-tag-add { color: var(--text-muted); border-style: dashed; border-color: var(--border-subtle); font-weight: 700; }
.ow-tag.ow-tag-add::before { display: none; }
.ow-tag .ow-tag-x { flex: none; margin-left: 0.1rem; opacity: 0.55; font-size: var(--text-xs); line-height: 1; }
.ow-tag .ow-tag-x:hover { opacity: 1; }
[data-theme="dark"] .ow-tag.f-custom,
[data-theme="dark"] .ow-tag.ow-tag-add { border-color: color-mix(in srgb, #e5e7eb 30%, transparent); }

/* ── Portal for dropdowns: body-level, so nothing can clip it ─────────── */
.ow-portal {
  position: fixed;
  z-index: 5000;
  min-width: 13rem;
  padding: 0.35rem;
  background: var(--bg-card, #fff);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md, 12px);
  box-shadow: var(--shadow-popup, 0 20px 60px rgba(16, 24, 40, 0.22));
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}
.ow-portal button {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  border: 0;
  background: none;
  padding: 0.45rem 0.55rem;
  border-radius: var(--radius-sm, 8px);
  font: inherit;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text-secondary);
  cursor: pointer;
  text-align: left;
}
.ow-portal button:hover { background: color-mix(in srgb, var(--accent-violet) 9%, transparent); color: var(--accent-violet); }

/* ── Jump-to-top / bottom ─────────────────────────────────────────────────
   The dock is out of flow on the left, so these sit at the document's own
   trailing edge. */
/* Lane 2 of the bottom-right corner stack: directly ABOVE the "Ask AI" FAB, in
   the same right-hand column, using the shared tokens in brand-tokens.css.
   `fixed` rather than `absolute` so it shares the FAB's coordinate space — with
   one measured from the document box and the other from the viewport, the two
   drifted into each other and the purple FAB covered these arrows. */
.ow-jump {
  position: fixed;
  right: var(--fab-inset);
  bottom: var(--corner-lane-2);
  display: flex;
  flex-direction: column;
  align-items: center;              /* centre the circles in the column */
  gap: var(--fab-gap);              /* same rhythm as the corner stack */
  z-index: 1799;
  /* SHARE THE FAB'S VERTICAL AXIS. The FAB is --fab-size wide and sits at
     right:--fab-inset, so its centre is (--fab-inset + --fab-size/2) from the
     edge. These smaller circles used to sit at right:--fab-inset too, so their
     centre landed ~11px inboard of the FAB's and the column read as crooked
     (the reported misalignment). Giving this column the FAB's width and
     centring its children puts all three on one plumb line. */
  width: var(--fab-size);
}
.ow-jump button {
  /* 40px circle: a clear step down from the 56px FAB while staying a
     comfortable target, and it reads as the same family as the FAB. */
  width: 40px;
  height: 40px;
  border-radius: var(--radius-full, 999px);
  border: 1px solid var(--border-subtle);
  background: var(--bg-card, #fff);
  color: var(--text-secondary);
  cursor: pointer;
  box-shadow: var(--shadow-card-hover);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: color var(--motion-fast), border-color var(--motion-fast),
              background var(--motion-fast), transform var(--motion-fast);
}
.ow-jump button svg { width: 18px; height: 18px; display: block; }
.ow-jump button:hover {
  border-color: var(--accent-violet);
  color: var(--accent-violet);
  background: var(--tint-hover);
  transform: translateY(-1px);
}
.ow-jump button:active { transform: translateY(0); }
.ow-jump button:focus-visible { outline: none; box-shadow: var(--focus-ring); }

/* ── Progress strip for the background fix queue ──────────────────────── */
/* The strip's HOST (#outlineFixQueue) is created on demand inside the document
   pane, directly under #reviewStatus, so the running fix pass has ONE full-width
   surface above the document (a refresh that rejoins a server-side pass, S14,
   reports itself here). It borrows the same card treatment as .rev-status — same
   tokens — so the two read as one stacked status block, not two competing bars. */
/* SLIM, NON-BLOCKING STATUS STRIP.
 *
 * Reported: this strip was a full-width CARD that ate the top band and covered
 * the outline while adding little beyond a few values (pass/round, N resolved,
 * rules ratio, timer). It is a STATUS line, not a panel, so it now reads as a
 * thin sticky bar pinned to the top of the document, like a modern app's
 * progress/notification bar: minimal height, quiet surface, the outline visible
 * right underneath it. All the values stay (they wrap on a phone), and the
 * "See each fix" reopen path is preserved. The old tall card treatment is gone. */
.ow-fix-queue {
  position: sticky;
  top: 0;
  z-index: 5;
  /* ── ONE LEFT-ALIGNED CLUSTER, NOT A FULL-WIDTH STRETCH ───────────────────
   *
   * Reported with a screenshot: "the ribbon where you show fixing the panel is
   * so large horizontally ... do we need this big space". The strip spanned the
   * whole document width with a thin bar stretched edge-to-edge and a big empty
   * middle, because the track grew greedily (flex: 1 1 8rem) and the reopen
   * button was pushed to the far right (margin-left: auto). The values are only
   * a few short pills, so the strip is capped to the width they actually need
   * and sits as a single left-aligned cluster. It still spans the full width on
   * a narrow phone (the cap is above the phone breakpoint) so the facts keep
   * their room to wrap. */
  width: fit-content;
  max-width: min(100%, 38rem);
  margin: 0 0 0.5rem;
  padding: 0.3rem 0.6rem;
  border: 1px solid var(--border-subtle);
  border-left: 3px solid var(--accent-violet);
  border-radius: var(--radius-sm, 8px);
  background: color-mix(in srgb, var(--bg-card, #fff) 92%, var(--accent-violet) 4%);
  box-shadow: none;
  backdrop-filter: saturate(1.1);
}
/* ── THE FIX STRIP WRAPS INSTEAD OF OVERFLOWING ─────────────────────────────
 *
 * Reported with a screenshot: on a phone this strip was one nowrap flex row of
 * label + bar + "162 resolved" + "66/80 rules pass" + a cut-off "Re-..." note,
 * running off the right edge. Every child had `white-space: nowrap`, so nothing
 * could reflow and the last items were simply clipped.
 *
 * It is really two tiers: the LABEL and the PROGRESS BAR are the headline and
 * belong on one line; the fact pills (round/pass, resolved, rules, elapsed) are
 * secondary and wrap onto as many lines as they need below. `flex-wrap: wrap`
 * plus a full-width basis on the track pushes the facts to the next line on a
 * narrow strip and keeps everything on one line where there is room, with no
 * device width named.
 */
.ow-queue {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.3rem 0.5rem;
  font-size: var(--text-2xs);
  line-height: 1.3;
  color: var(--text-secondary);
  width: 100%;
  min-width: 0;
}
/* The bar takes the rest of the headline row and forces the facts to wrap below
   it: a high flex-grow with a basis wide enough that a fact pill cannot sit
   beside it on a phone, but that still collapses to share a line on a desktop. */
.ow-queue-track {
  /* A fixed, sensible width - NOT a greedy grow. A grow of 1 stretched the bar
     across the whole strip and made the empty middle. It keeps a healthy basis
     so it still shows meaningful progress, and it may shrink but not expand. */
  flex: 0 1 12rem;
  height: 5px;
  border-radius: 999px;
  background: var(--input-bg, #faf9f7);
  overflow: hidden;
  min-width: 5rem;
}
.ow-queue-fill { height: 100%; width: 0; background: var(--gradient-main); transition: width var(--motion-base, 0.3s ease); }
.ow-queue b { color: var(--text-primary); }

/* ── A fix pass that LOOKS like it is working ───────────────────────────────
   A static "Fixing 0/18" is indistinguishable from a hung one, and a fix pass
   takes minutes. The pulse and the travelling sheen say "running"; the note and
   the elapsed clock say what it is doing and for how long (spec S5, S6). */
.ow-queue-label { display: inline-flex; align-items: center; gap: 0.35rem; white-space: nowrap; font-weight: 700; }
.ow-queue-spark {
  width: 8px; height: 8px; border-radius: 50%; flex: none;
  background: var(--accent-violet);
  animation: ow-queue-pulse 1.1s ease-in-out infinite;
}
@keyframes ow-queue-pulse {
  0%, 100% { opacity: 0.35; transform: scale(0.85); }
  50%      { opacity: 1;    transform: scale(1.15); }
}
/* The bar still reports real progress via its width; the sheen only signals
   liveness, so it never pretends to know a percentage it does not have. */
.ow-queue-fill.indeterminate { position: relative; overflow: hidden; }
.ow-queue-fill.indeterminate::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent 0%, rgba(255,255,255,0.55) 50%, transparent 100%);
  animation: ow-queue-sheen 1.4s linear infinite;
}
@keyframes ow-queue-sheen { from { transform: translateX(-100%); } to { transform: translateX(100%); } }
.ow-queue-time { font-variant-numeric: tabular-nums; color: var(--text-muted); white-space: nowrap; }
/* Which round of how many — the bounded context that turns a bare "0/13" into
   "Round 2 of 3", so a slow pass reads as progress rather than a stall. */
.ow-queue-round {
  font-size: var(--text-2xs); font-weight: 700; white-space: nowrap;
  color: var(--accent-violet);
  background: color-mix(in srgb, var(--accent-violet) 10%, transparent);
  border-radius: var(--radius-full); padding: 0.1rem 0.5rem;
}
/* The finish line: Author Kit rules passing (N/M) — the number that actually
   reaches zero-failing, so the author can see the target close. */
.ow-queue-rules {
  font-size: var(--text-2xs); font-weight: 700; white-space: nowrap;
  font-variant-numeric: tabular-nums;
  color: var(--accent-green);
  background: color-mix(in srgb, var(--accent-green) 10%, transparent);
  border-radius: var(--radius-full); padding: 0.1rem 0.5rem;
}
/* The note ("Applied fixes for 10 finding(s)", "Re-checking the rewritten
   outline...") is a full line of its own, not a pill squeezed onto the fact row
   where it was the thing being clipped to "Re-...". `flex-basis: 100%` drops it to
   its own line; it still ellipsises if a single long message would overflow. */
.ow-queue-note {
  flex: 1 1 100%;
  color: var(--text-muted); min-width: 0; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; max-width: 100%;
}
/* The reopen affordance: while a fix pass runs this strip is the only status
   surface (it hides #reviewStatus), so it carries the way back into the fix
   drawer the author may have closed. A small, tappable secondary that sits at the
   end of the strip and never clips. */
.ow-queue-open {
  /* Sits in the left cluster right after the facts (was `margin-left: auto`,
     which shoved it to the far right edge of a full-width strip and opened the
     empty middle). The strip is now capped and left-aligned, so the button
     simply follows the values. */
  flex: none;
  min-height: 32px;
  padding: 0.25rem 0.7rem;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm, 8px);
  background: var(--bg-card, #fff);
  color: var(--text-secondary);
  font: inherit; font-size: var(--text-xs); font-weight: 700;
  cursor: pointer; white-space: nowrap;
}
.ow-queue-open:hover { background: var(--input-bg, #f3f4f6); color: var(--text-primary); }
.ow-queue-open:focus-visible { outline: 2px solid var(--accent-violet, #7c3aed); outline-offset: 2px; }
@media (pointer: coarse) { .ow-queue-open { min-height: var(--tap-min, 44px); } }
@media (prefers-reduced-motion: reduce) {
  .ow-queue-spark, .ow-queue-fill.indeterminate::after { animation: none; }
}

/* ── Narrow viewports: a 248px dock plus the icon rail would leave the document
      unreadable, so the dock UNDOCKS — back into flow, above the document, as a
      horizontal band. Same DOM, same controls, no clipping — and it is `sticky`
      so it still never scrolls away, which is the one guarantee the layout has to
      keep at every width. */
@media (max-width: 1100px) {
  .ow-shell { flex-direction: column; }
  .ow-doc { order: 2; }
  .ow-bar {
    order: 1;
    position: sticky;
    top: 0;
    left: auto;
    bottom: auto;
    z-index: 40;
    width: 100%;
    align-self: stretch;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    /* Cap the band so a wrapped toolbar can never own more than ~a third of a
       short viewport; it scrolls internally past that. */
    max-height: 34vh;
    overflow-y: auto;
    border: 1px solid var(--border-subtle);
    border-radius: 14px;
    margin-bottom: 0.6rem;
    background: var(--ow-bar-bg);
  }
  .ow-group { flex-direction: row; align-items: center; flex-wrap: wrap; }
  .ow-group + .ow-group { padding-top: 0; border-top: none; }
  .ow-group-label { display: none; }
  .ow-bar .ow-btn, .ow-bar .ow-btn.icon { width: auto; }
  .ow-bar .ow-btn.icon { padding: 0 0.5rem; }
  .ow-bar .ow-tabs { flex-direction: row; width: auto; }
  .ow-bar .rail-more-wrap { width: auto; }
  .ow-bar .ow-panel { flex-direction: row; flex-wrap: wrap; }
  .ow-bar .ow-chip { width: auto; }
  .ow-rail-head {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 0.5rem;
    padding-bottom: 0;
    border-bottom: none;
  }
  .ow-rail-title { flex-direction: row; align-items: baseline; gap: 0.5rem; }
  .ow-rail-head b { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 32ch; }
}

@media (max-width: 900px) {
  .ow-bar { padding: 0.5rem; }
  .ow-chip-name { max-width: 10ch; }

  /* ── GENERATION DOCK: no tall empty band above the progress card ──────────
     During generation the dock band (#outlineProgress .ow-bar) only carries the
     live reviewers row and a Stop button, but the shared band rule reserves up to
     34vh - which on a phone reads as a big empty grey box between the top bar and
     the "Crafting your outline" card ("empty space on top ... is this okay?" - no).
     Scope the generation band to size to its CONTENT: it sits snug under the top
     bar and the progress card follows immediately. The FINISHED-outline dock is
     untouched (it legitimately holds reviewers, tags and actions worth the space). */
  #outlineProgress.ow-shell > .ow-bar {
    max-height: none;
    margin-bottom: 0.4rem;
  }
  /* The reviewers row is shown live in the main pane's context already; in the
     compact generation band keep only the header + Stop so the band is one tidy
     row, not a stacked panel that pushes the progress card down the screen. */
  #outlineProgress.ow-shell > .ow-bar .ow-group-label { display: none; }
}

/* ── The outline verdict: score, whether it clears the bar, and the advice ──
   Replaces a bare "Continue anyway", which asked the author to make the call
   without telling them anything to base it on. */
.ow-verdict {
  /* NO TINT, and no longer in the sidebar.
     This was an amber-washed block of five lines of explanation sitting in the
     left dock among the buttons, and it read as a warning about something the
     author had done: "the left side is more of actions buttons not sure why you
     have the yellow box with text there its very very confusing".

     Two things were wrong with it. It was the only PROSE in a column of
     controls, so it had no business being there; and amber is the colour of a
     problem, while this is a measurement that is amber even on a healthy
     outline (a handful of open judgement calls is normal). It now sits under the
     review status, above the document, as a plain factual card - and the colour
     it carries is a thin left edge, not a wash. */
  border: 1px solid var(--border-subtle);
  border-left: 3px solid var(--text-muted);
  background: var(--bg-card, #fff);
  border-radius: var(--ow-radius);
  padding: 0.55rem 0.7rem;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  margin: 0 0 1rem;
}
.ow-verdict.good { border-left-color: var(--ow-clear); }
.ow-verdict-top { display: flex; align-items: baseline; gap: 0.4rem; flex-wrap: wrap; }
/* The headline is a COUNT, so it needs its unit next to it. "68/80" alone reads
   like a score out of 80; "68/80 Author Kit rules pass" says what was measured. */
.ow-verdict-unit {
  font-size: var(--text-2xs);
  color: var(--text-secondary);
  font-weight: 600;
}
.ow-verdict-top b { font-size: var(--text-md); font-weight: 800; letter-spacing: -0.02em; color: var(--ow-warn); }
.ow-verdict.good .ow-verdict-top b { color: var(--ow-clear); }
.ow-verdict-badge {
  font-size: var(--text-2xs);
  font-weight: 800;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ow-warn);
}
.ow-verdict.good .ow-verdict-badge { color: var(--ow-clear); }
.ow-verdict p { font-size: var(--text-2xs); line-height: 1.5; color: var(--text-secondary); }
.ow-verdict .ow-btn { width: 100%; justify-content: center; }

/* ── The destructive zone, at the foot of the rail ──────────────────────────
   "Start over" deletes the course and everything generated from it, so it does not
   belong in Next step beside the primary action: sitting there it read as an
   ordinary sibling, and it is the one control in the rail you must never press by
   accident on the way to something else.

   SPACE, NOT A HAIRLINE. The first version drew a 1px top border and that was
   wrong twice: the rail has an explicit flat-chrome contract that groups are
   separated by space rather than rules
   (test_the_rail_groups_are_separated_by_space_not_hairlines), and the author's own
   note was "dont put lots of borders paddings". `margin-top: auto` already
   separates it more decisively than any line could - it pushes the button to the
   foot of the column with the whole remaining height as the gap.

   No group LABEL on purpose either: an uppercase "DANGER" heading over a single
   button is the 71px-of-chrome-for-a-31px-action mistake this rail has already been
   through once. */
.ow-group.ow-group-danger {
  margin-top: auto;
  padding-top: 0.35rem;
}

/* ── Collapsible rail groups ────────────────────────────────────────────────
   The rail outgrew a short viewport. Every group except NEXT STEP folds, with a
   caret on its label, so the actions you use stay one click away and the rest
   stops costing scroll. */
.ow-group.collapsible > .ow-group-label {
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 0.3rem;
  user-select: none;
}
.ow-group.collapsible > .ow-group-label::after {
  content: '\203A';
  margin-left: auto;
  font-size: var(--text-sm);
  opacity: 0.6;
  transform: rotate(90deg);
  transition: transform 0.15s ease;
}
.ow-group.collapsible.collapsed > .ow-group-label::after { transform: rotate(0deg); }
.ow-group.collapsible > .ow-group-label:hover { color: var(--text-primary); }
.ow-group.collapsible.collapsed > *:not(.ow-group-label) { display: none; }
@media (prefers-reduced-motion: reduce) {
  .ow-group.collapsible > .ow-group-label::after { transition: none; }
}

/* ── Outline lock ───────────────────────────────────────────────────────────
   Once slides or narration are built from an outline, editing it is never free.
   The lock states that, and unlocking is a deliberate act with a confirm that
   names what would go stale.

   The CONTROL itself no longer lives here. It moved to the course rail
   (.crail-lock[data-lock] in course-rail.js), on the stage it freezes, so it is
   visible from Slides and Narration too and not only while the outline stage
   happens to be open. The
   .ow-lock pill rules that used to sit here were dead once nothing rendered the
   class, so they are gone; only the read-only consequences of the lock — which
   apply to the outline document — remain. */
/* While locked, the document is read-only: the inline editor and its entry
   points step back so an edit cannot happen by accident. */
html.outline-locked #editorMount .ce-edit-toggle,
html.outline-locked .ow-btn[data-ow-edit] { opacity: 0.45; pointer-events: none; }

/* ── The review status, pinned above the outline ────────────────────────────
   THE PROBLEM IT SOLVES. Everything about the review lived inside the drawer,
   and the drawer only opens when the author clicks a reviewer. So an outline
   would land, three reviewers would spend two to four minutes reading it, and
   nothing on screen said so: "u r silently reviewing and unless i click on the
   one of reviewer i cant see panel and dont know ur work is pending".

   It sits at the top of the DOCUMENT because that is where the author is looking
   when the outline arrives, and it is sticky so it stays reachable while they
   scroll. It answers three questions - is anything running, how far along, what
   do I do next - and then shrinks: a clean review is one green line, never a
   permanent banner. */
.rev-status {
  position: sticky;
  top: 0;
  z-index: 5;
  margin: 0 0 1rem;
  padding: 0.7rem 0.9rem;
  border: 1px solid var(--border-subtle);
  border-left: 3px solid var(--text-muted);
  border-radius: var(--radius-md, 10px);
  background: var(--bg-card, #fff);
  box-shadow: var(--shadow-card, 0 1px 3px rgba(16, 24, 40, 0.06));
}
.rev-main { display: flex; align-items: center; gap: 0.65rem; flex-wrap: wrap; }
.rev-text { display: flex; flex-direction: column; min-width: 0; flex: 1 1 18rem; }
.rev-text b { font-size: var(--text-sm); font-weight: 800; color: var(--text-primary); line-height: 1.3; }
.rev-text span { font-size: var(--text-xs); color: var(--text-muted); line-height: 1.45; }
/* ── The running total for the whole course ─────────────────────────────────
   "u must show first round of fixing took x amount of time totla time elasps so far
   on module how much." Read from the server's work ledger, so unlike the elapsed
   clock beside it this survives a reload. Quiet and on its own line: it is context
   for the sentence above, not an instruction, and it is the only line in this strip
   that is true of the course rather than of this moment. */
.rev-spent {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  margin-top: 0.25rem;
  font-size: var(--text-2xs);
  font-weight: var(--weight-bold, 600);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.rev-clock { font-size: var(--text-xs); font-variant-numeric: tabular-nums; color: var(--text-muted); white-space: nowrap; }
.rev-actions { display: flex; gap: 0.4rem; flex: none; margin-left: auto; }

/* The state mark. A fixed circle so a count and a tick occupy the same space and
   the row does not reflow when the review lands. */
.rev-mark {
  flex: none;
  width: 26px; height: 26px;
  display: grid; place-items: center;
  border-radius: 50%;
  font-size: var(--text-xs); font-weight: 800;
  background: var(--input-bg, #faf9f7);
  color: var(--text-secondary);
}
.rev-mark.good { background: var(--ow-clear); color: var(--on-brand, #fff); }
.rev-mark.warn { background: var(--ow-blocking); color: var(--on-brand, #fff); }
.rev-mark.bad  { background: var(--ow-blocking); color: var(--on-brand, #fff); }

.rev-status.phase-clear    { border-left-color: var(--ow-clear); }
/* Rule-clean with opinions outstanding. Green, because the rules are what a
   reviewer can cite and they all pass; the findings are judgement calls. */
.rev-status.phase-ready    { border-left-color: var(--ow-clear); }
.rev-status.phase-blocking { border-left-color: var(--ow-blocking); }
.rev-status.phase-error    { border-left-color: var(--ow-blocking); }
.rev-status.phase-reviewing,
.rev-status.phase-fixing   { border-left-color: var(--accent-violet); }

.rev-btn {
  border: 1px solid var(--border-subtle);
  background: var(--bg-card, #fff);
  color: var(--text-secondary);
  border-radius: var(--radius-sm, 8px);
  padding: 0.35rem 0.6rem;
  font-size: var(--text-xs);
  font-weight: 700;
  cursor: pointer;
  white-space: nowrap;
}
.rev-btn:hover { border-color: var(--accent-violet); color: var(--accent-violet); }
.rev-btn.primary {
  background: var(--gradient-main);
  border-color: transparent;
  color: var(--on-brand, #fff);
}
/* The label must survive the hover, in both themes: a primary button whose text
   turns violet on a violet fill disappears. */
.rev-btn.primary:hover { color: var(--on-brand, #fff); filter: brightness(var(--hover-brighten,1.06)); }

/* "Proceed without fixing" is a DANGER action: it ships an outline the panel
   flagged. It reads as a quiet secondary at rest (it must not shout over "Fix
   everything"), but turns RED on hover/focus so the moment before the click makes
   the trade unmistakable. Red, not the neutral violet the other secondaries use.
   Applied via .rev-btn.danger so only this action changes; the neutral "Read
   them" and the GOOD "Continue to Slides" (a safe proceed) keep the violet hover. */
.rev-btn.danger:hover,
.rev-btn.danger:focus-visible {
  border-color: var(--accent-red, #dc2626);
  color: var(--accent-red, #dc2626);
  background: color-mix(in srgb, var(--accent-red, #dc2626) 8%, var(--bg-card, #fff));
}
.rev-btn.danger:focus-visible { outline: 2px solid var(--accent-red, #dc2626); outline-offset: 2px; }

/* A working spinner, and an INDETERMINATE bar. The review is three reviewers
   running at once and the job reports rounds rather than percent-complete, so any
   percentage would be invented. A moving bar next to a real elapsed clock is
   honest and still reads as progress. */
.rev-spin {
  flex: none;
  width: 18px; height: 18px;
  border-radius: 50%;
  border: 2px solid color-mix(in srgb, var(--accent-violet) 25%, transparent);
  border-top-color: var(--accent-violet);
  animation: rev-spin 0.8s linear infinite;
}
@keyframes rev-spin { to { transform: rotate(360deg); } }
.rev-track {
  display: block;
  margin-top: 0.6rem;
  height: 4px;
  border-radius: 999px;
  background: var(--input-bg, #faf9f7);
  overflow: hidden;
}
.rev-fill {
  display: block;
  height: 100%;
  width: 35%;
  border-radius: 999px;
  background: var(--gradient-main);
  animation: rev-slide 1.5s ease-in-out infinite;
}
@keyframes rev-slide {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(320%); }
}
/* Motion is the signal here, so with motion off the bar becomes a plain filled
   track rather than vanishing: a still bar is better than no indication. */
@media (prefers-reduced-motion: reduce) {
  .rev-spin { animation: none; border-top-color: var(--accent-violet); }
  .rev-fill { animation: none; width: 100%; opacity: 0.5; }
}
@media (max-width: 640px) {
  /* `width: 100%` was not enough, and the reason is `flex: none` on the base rule.
     A `flex: none` item sizes to its CONTENT regardless of the width you give it,
     and with no `flex-wrap` its three buttons stayed on one row: measured at 320px,
     .rev-btn's right edge was 380px, 60px past the viewport, so "Proceed with..."
     was cut in half. Both have to be undone for the row to reflow. */
  .rev-actions {
    margin-left: 0;
    width: 100%;
    flex: 1 1 100%;
    flex-wrap: wrap;
    min-width: 0;
  }
  .rev-btn { flex: 1 1 auto; min-width: 0; text-align: center; }
}

/* ── A gated action LOOKS gated ─────────────────────────────────────────────
   A disabled button is just grey, and grey reads as "broken" or "still loading"
   rather than "not yet". The padlock says which it is, and the title says what
   would open it. Same reasoning as the locked stage icons in icon-rail.css, and
   deliberately the same glyph so the two mean the same thing. */
.ow-btn.is-gated { position: relative; }
.ow-btn.is-gated::after {
  /* A DRAWN padlock (masked SVG), not the emoji \1F512. The comment above always
     claimed this was "the same glyph" as the icon rail, but it was actually the
     OS colour-font emoji: a brown cartoon that ignored the theme, sat off the
     text baseline, and looked different on every platform ("lock icons are not
     looking genuine"). A mask inherits the button's colour and matches the rail
     padlock exactly, so the two locks are now genuinely the same mark. */
  content: '';
  margin-left: auto;
  width: 14px;
  height: 14px;
  flex: none;
  /* Amber = "not yet, needs attention", the same meaning it carries on the rail
     locks and the review status. NOT faded: the lock is the information here. */
  background: var(--accent-amber, #d97706);
  -webkit-mask: var(--ow-lock-mask) center / contain no-repeat;
  mask: var(--ow-lock-mask) center / contain no-repeat;
  opacity: 1;
}
/* One definition of the padlock, shared by every gated control. Same geometry as
   icon-rail.css's --irail-lock-mask: a filled body with a stroked shackle, which
   stays legible at badge size where a fully stroked outline fuses into a blob. */
.ow-shell, :root {
  --ow-lock-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none'%3E%3Cpath d='M7 10V7.5a5 5 0 0 1 10 0V10' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round'/%3E%3Crect x='4.5' y='10' width='15' height='11' rx='2.5' fill='black'/%3E%3C/svg%3E");
}
/* The label still recedes, so the button does not compete with the live primary
   action next to it. */
.ow-btn.is-gated > span,
.ow-btn.is-gated > svg { opacity: 0.5; }

/* The Developers block lived here for one iteration and moved to the icon rail's
   pinned footer (.drail, styled in icon-rail.css). Two things were wrong with it
   in this dock: it sat under the Details metadata rather than in a corner, and as
   a `position: sticky` element inside the dock's own scroll area it overlapped the
   very tags it was meant to sit below - a Content Tag was visible peeking out
   beneath it in the reported screenshot. A sticky footer inside a scrolling flex
   column is the wrong tool; the rail has a real pinned slot. */

/* ── Details: tall, and it must not end mid-tag ─────────────────────────────
   The metadata wrap is the longest thing in the rail (289px measured), so it is
   the group a short viewport cuts through. Bottom padding keeps its last row clear
   of the rail's own edge. */
.ow-bar #courseTagsRow { padding-bottom: 0.35rem; }

/* ── Collapsing the tool dock ───────────────────────────────────────────────
   "side panel i cant collaps hope u r fixint them."

   It could not be collapsed because there was nothing to collapse it with. The
   head held the course name and a "More actions" menu and no fold control, and the
   page's one collapse function (`toggleActionRail`) targets `.action-rail-host` —
   the older rail this one replaced — so it matched no element here and did nothing,
   silently. The "—" beside COURSE that reads like a minimise button is the &mdash;
   placeholder sitting in `#outlineCourseName` until the name loads.

   COLLAPSES TO A STRIP, NOT TO NOTHING. A panel that disappears entirely takes its
   own reopen affordance with it, and the way back then has to be discovered
   somewhere else — the mistake the floating ☰ for the course rail already made.
   2.2rem is enough for the chevron and nothing else. */
.ow-rail-fold {
  flex: 0 0 auto;
  width: 1.65rem;
  height: 1.65rem;
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: var(--radius-sm, 8px);
  background: none;
  color: var(--text-muted);
  cursor: pointer;
  transition: background var(--motion-fast, 0.15s ease), color var(--motion-fast, 0.15s ease);
}
/* A DRAWN chevron via mask, so it takes currentColor and themes correctly — the
   same technique as .om-group-caret and the rail padlocks, and for the same reason
   (an OS emoji arrow ignores the theme and sits off the baseline). */
.ow-rail-fold::before {
  content: '';
  width: 14px;
  height: 14px;
  background: currentColor;
  -webkit-mask: var(--ow-fold-mask) center / contain no-repeat;
  mask: var(--ow-fold-mask) center / contain no-repeat;
  transition: transform var(--motion-fast, 0.15s ease);
}
.ow-shell, :root {
  --ow-fold-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M15 6l-6 6 6 6'/%3E%3C/svg%3E");
}
.ow-rail-fold:hover { background: var(--bg-secondary); color: var(--text-primary); }
.ow-rail-fold:focus-visible { outline: none; box-shadow: var(--focus-ring); }

/* The head is a row, and the fold sits at its right edge. The "More actions" menu
   follows it, so the two controls stay together rather than at opposite ends. */
.ow-rail-head { display: flex; align-items: flex-start; gap: 0.35rem; }
.ow-rail-head .ow-rail-title { flex: 1 1 auto; min-width: 0; }
.ow-rail-head .rail-more-wrap { flex: 0 0 auto; }

/* ── Collapsed: a strip of ICONS, not an empty gutter ────────────────────────
   "when u collapse them then show those icons below script i gues now u r hiding all."

   The first cut hid everything except the chevron, which made collapsing the dock the
   same as losing its actions — so getting to "Continue to Slides" meant expanding the
   panel again, and the collapsed state was only useful for reading.

   The buttons stay, as icon-only squares. They are the dock's OWN buttons: every
   `.ow-btn` in here is already `<svg class="ow-ico"> + <span>label</span>`, so hiding
   the span leaves a 20px icon and the control keeps working, keeps its title, and
   keeps its gated/disabled state. Copying them into the icon rail instead would have
   put one action in two places, which is the duplication this workspace has been
   through before (a floating Run button beside the dock's Run, two Start-overs, a
   "Review panel" button next to four reviewer chips that opened the same panel).

   The strip sits immediately right of the 56px icon rail, so its icons land directly
   beside Course / Outline / Slides / Script — which is where the ask pointed. */
html.ow-rail-off .ow-bar.ow-rail {
  width: 3rem;
  min-width: 3rem;
  padding: 0.4rem 0.35rem;
  gap: 0.3rem;
  align-items: center;
  overflow-x: hidden;
  overflow-y: auto;
}
/* Text goes; controls stay. Group LABELS ("Next step", "Reviewers") are prose that
   needs width, and the reviewer chips and Details metadata are whole panels — those
   are what expanding is for. */
html.ow-rail-off .ow-bar.ow-rail .ow-group-label,
html.ow-rail-off .ow-bar.ow-rail .ow-rail-title,
html.ow-rail-off .ow-bar.ow-rail .ow-more-label,
html.ow-rail-off .ow-bar.ow-rail .ow-statusline-text,
html.ow-rail-off .ow-bar.ow-rail .ow-btn > span:not(.ow-ico),
html.ow-rail-off .ow-bar.ow-rail #outlineMentorsRow,
/* AND the LIVE reviewers row, which is the generating dock's copy. Missing it is
   what left a violet reviewer avatar and a green "80 rules" pill floating in the
   48px strip mid-fix: two elements with their own sized boxes, in a column that has
   room for neither. #outlineMentorsRow (the settled row) was hidden and this one was
   not, so the bug only appeared while a pass was running. */
html.ow-rail-off .ow-bar.ow-rail #outlineMentorsLive,
html.ow-rail-off .ow-bar.ow-rail #outlineFixQueue,
html.ow-rail-off .ow-bar.ow-rail #courseTagsRow,
html.ow-rail-off .ow-bar.ow-rail .ow-panel,
html.ow-rail-off .ow-bar.ow-rail .ow-verdict { display: none; }
/* ── THE HEAD STACKS, or its two controls do not fit ────────────────────────
   Measured in the collapsed strip: the head is a flex ROW holding the fold button
   and the "⋯ More actions" button, and at 48px wide the second one rendered 9px PAST
   the rail's right edge - the stray square outline in the report. Stacked, both fit
   and both stay usable, which is better than dropping More (its entries are not
   reachable any other way while collapsed). */
html.ow-rail-off .ow-rail-head {
  flex-direction: column;
  align-items: center;
  gap: 0.3rem;
}
html.ow-rail-off .ow-bar.ow-rail .rail-more-wrap { width: auto; }
html.ow-rail-off .ow-bar.ow-rail .ow-btn.icon {
  width: 2.15rem;
  min-width: 2.15rem;
  height: 2.15rem;
  padding: 0;
  justify-content: center;
}
/* Square, centred, thumb-sized. `title` is already set on every one of these, so the
   icon keeps its name on hover even though the label is hidden. */
html.ow-rail-off .ow-bar.ow-rail .ow-btn {
  width: 2.15rem;
  min-width: 2.15rem;
  height: 2.15rem;
  padding: 0;
  justify-content: center;
  gap: 0;
}
html.ow-rail-off .ow-bar.ow-rail .ow-group {
  width: 100%;
  align-items: center;
  gap: 0.3rem;
  padding-top: 0;
  border-top: 0;
}
html.ow-rail-off .ow-rail-head { justify-content: center; padding-bottom: 0; border-bottom: 0; }
html.ow-rail-off .ow-rail-fold { margin: 0 auto; }
/* The chevron points the other way, because it now means "bring it back". */
html.ow-rail-off .ow-rail-fold::before { transform: rotate(180deg); }
/* AND THE DOCUMENT TAKES THE SPACE. Collapsing a panel that still owns 248px of
   reserved gutter would move nothing on screen, which is indistinguishable from the
   button being broken — the defect this control was added to fix. The gutter is
   composed from `--irail-dock-w` in icon-rail.css, so zeroing that term is the one
   change that keeps every pane's offset consistent. */
/* Only where the dock IS a left column. Below 1100px it is already a full-width
   horizontal band reserving nothing (see `--irail-dock-w: 0` in icon-rail.css), so
   setting 3rem here made COLLAPSING the dock *add* a 48px gutter — measured going from
   80px to 128px at a 1024px viewport, i.e. the control did the opposite of its job at
   tablet width. Scoped, rather than picking a number that is wrong in one regime. */
@media (min-width: 1101px) {
  html.ow-rail-off.has-irail.studio-dock { --irail-dock-w: 3rem; }
}

/* ═══════════════════════════════════════════════════════════════════════════
 * THE OUTLINE PAGE ON A PHONE (Phase 3)
 * ═══════════════════════════════════════════════════════════════════════════
 *
 * Reported as "this is. outline page so show the outline done show the top two
 * stuff on page keep them as menu option we can alsways open them and see if
 * need ... in mobiel e have less realstate we have to show the stuff which are
 * improtatnt to user primarly we are workigno n outline show the outline".
 *
 * Fair, and measurable: at 390px the score card and the quality card together
 * filled the entire first screen, so the outline - the thing the page is named
 * after - began below the fold. Neither card is wrong; there is simply no room
 * for both of them plus a document on a 390px screen, and the document is what
 * the author came for.
 *
 * WHAT MOVES AND WHAT DOES NOT. `.ow-verdict` is informational: a score, a rule
 * ratio, a list of failing rule ids. Every one of those numbers is now in the
 * review drawer's Overview tab, beside the rounds, the pending list and the time
 * estimate, so hiding it on a phone loses nothing that cannot be reached in one
 * tap. `.quality-summary` STAYS, because it is the only surface carrying the
 * "Proceed without fixing" override, and an escape hatch that exists on desktop
 * and vanishes on a phone is worse than no escape hatch. It is compacted instead:
 * its prose folds away, its buttons remain.
 *
 * `.rev-status` becomes the chip. It already exists, already carries the live
 * phase, already has its own actions, and is already `position: sticky` - so it
 * is the status affordance the report asks for rather than a new one built beside
 * it. On a phone it collapses to a line and stays pinned while the outline scrolls
 * under it.
 */
/* THE TRIGGER IS "NOT ENOUGH ROOM", WHICH IS NOT ALWAYS ABOUT WIDTH.
 *
 * Reported as "in horizontal mode it ssrewed up not good i just see those 2 cards",
 * measured at 850x407 - a phone in landscape.
 *
 * Every rule below was originally gated on `max-width: 768px`, and at 850px wide
 * none of them fired: the page got the full desktop layout inside 407px of height,
 * the two review cards took about 300px of it, and the outline was entirely below
 * the fold. The layout was not broken by the rotation, it was never consulted about
 * it.
 *
 * A short viewport is the same problem as a narrow one - there is not enough room
 * for chrome and a document - so it gets the same answer. `(max-height: 30rem)` is
 * content-derived like the 34rem above it: below roughly 480px of height there is no
 * arrangement where 300px of cards leaves a readable document. It catches every
 * phone in landscape without naming one, and it deliberately does NOT catch a tablet
 * in landscape (iPad is 744-834px tall), which has the room and should keep the full
 * layout.
 */
@media (max-width: 768px), (max-height: 30rem) {

  /* ---- Both cards stand down; the strip and the sheet carry their job ----
   *
   * The first version kept the cards' action rows and hid only their prose, so the
   * override would survive. That left a half-covered purple "Continue to Slides"
   * button peeking out from behind the strip - `.rev-status` is
   * `position: sticky; top: 0` and both cards sit above it in DOM order, so the
   * strip slides straight over them. Visible in the 480px and the landscape
   * screenshots, and unreadable as anything.
   *
   * Safe to remove now, and it was not before: every number on the verdict card is
   * in the sheet's Overview tab beside the rounds, the pending list and the
   * estimate, and "Proceed without fixing" is now a real action IN the sheet
   * (data-action="proceed-anyway" -> onProceedAnyway -> continueAnyway). Nothing is
   * reachable on a desktop and missing on a phone, which is the rule that kept
   * these cards alive through three earlier attempts. */
  /* `!important` because the competitor is an INLINE style, not a stylesheet:
     renderOutlineVerdict and renderOutlineQualitySummary set `.style.display`
     directly as they show and hide these cards, and an inline declaration beats
     any selector. Without it this rule looked correct and did nothing - measured
     at 390px, #outlineVerdict was still 122px tall and still being slid over by
     the sticky strip. This is the case !important exists for. */
  .ow-verdict { display: none !important; }

  /* ---- The quality card stands down, because the strip now carries its job ----
   *
   * It was kept at first specifically to preserve the "Continue to Slides anyway"
   * override. That reasoning held only while the strip was hiding its own actions;
   * now the strip's action row scrolls and keeps "Proceed without fixing", so the
   * override has a home and this card is a second copy of it.
   *
   * It also actively broke the layout: `.rev-status` is `position: sticky; top: 0`
   * and this card sits above it in DOM order, so the strip slid over the top of it
   * and left a half-covered purple button peeking out at the top of the screen -
   * visible in the 480px screenshot and unreadable as anything. */
  .quality-summary { display: none !important; }

  .rev-status .rev-ledger { display: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
 * THE STATUS STRIP SIZES ITSELF, NOT THE DEVICE
 * ═══════════════════════════════════════════════════════════════════════════
 *
 * "see the 7 things fix card looks so jubo and lots of padding spaces here there
 * also make sure u are not hardcoding for 320x480 umust make this owrk in s24
 * ultra, apple latest phone ultra and all mobiles and ipad and things dont
 * hadcodesotuff".
 *
 * Both halves of that are right, and the second is the reason the first kept
 * happening. Two attempts at this strip were driven by VIEWPORT width: the first
 * forced one line and pushed the buttons off the screen, the second let them wrap
 * and produced the jumbo card in the screenshot. Neither could be right everywhere,
 * because the viewport is not what constrains this component.
 *
 * The strip lives inside the document pane, and that pane's width depends on the
 * icon rail, the tool dock and the course panel - none of which the viewport knows
 * about. So the same 480px viewport gives the strip ~430px with the rail hidden and
 * ~180px with a dock open, and one media query cannot be correct for both. Device
 * widths make this worse rather than better: an S24 Ultra reports 480, an iPhone
 * Pro Max about 440, an iPad in split view anything at all, and a folding phone
 * changes mid-session.
 *
 * `container-type: inline-size` asks the only question that has a stable answer:
 * how much room do I actually have. A rule written against that is automatically
 * correct on every device in the list and on a desktop window someone drags narrow,
 * and nothing here names a phone.
 */
.rev-status {
  container-type: inline-size;
  container-name: revstatus;
  /* Fluid, so padding shrinks continuously with the space available instead of
     stepping at a breakpoint. This is the "lots of padding spaces here there" fix:
     the old value was a flat 0.7rem/0.9rem at every size. */
  padding: clamp(0.4rem, 1.6cqi, 0.7rem) clamp(0.5rem, 2.2cqi, 0.9rem);
  margin-bottom: clamp(0.5rem, 2cqi, 1rem);
}

/* ═══════════════════════════════════════════════════════════════════════════
 * COMPACT: A NATIVE ROW, NOT SHRUNKEN DESKTOP BUTTONS
 * ═══════════════════════════════════════════════════════════════════════════
 *
 * "these button fix read process are cutting off u have to make all mobile native
 * style dont keep same buttons etc go deep work indepth for mobiel frienldy".
 *
 * Three attempts at fitting the desktop buttons into this strip each failed in a
 * different way: forced onto one line they ran off the screen, allowed to wrap they
 * made a 162px card, and allowed to scroll they still showed a half-cut "Procee...".
 * All three were the same mistake - treating a phone as a small desktop and looking
 * for the arrangement that squeezes the same controls in.
 *
 * The native answer is that a status row has no buttons. It is one tappable line
 * that says where you stand, and it opens a sheet where the actions are full-width
 * rows with room for their labels. A label in a full-width row cannot be truncated,
 * so the entire class of defect goes away rather than being tuned.
 *
 * `.is-compact` is set by measurement (see watchStripWidth in home.html) so this and
 * the click handler that makes the row tappable cannot disagree about when it
 * applies.
 */
.rev-status.is-compact {
  /* A right-pointing chevron. `--ow-fold-mask` beside it points left (it is the
     dock's collapse control), so this is its own token rather than a reused one
     pointing the wrong way. */
  --ow-chevron-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9 6l6 6-6 6'/%3E%3C/svg%3E");
  display: flex;
  align-items: center;
  gap: clamp(0.4rem, 2cqi, 0.7rem);
  cursor: pointer;
  /* A row you can tap needs to say so, and a chevron is the one affordance every
     phone user already reads. Drawn with a mask so it inherits currentColor and
     themes with the rest of the strip. */
  padding-right: 2rem;
  position: relative;
  -webkit-tap-highlight-color: transparent;
}
.rev-status.is-compact::after {
  content: "";
  position: absolute;
  right: 0.6rem;
  top: 50%;
  width: var(--icon-sm);
  height: var(--icon-sm);
  margin-top: calc(var(--icon-sm) / -2);
  background: currentColor;
  opacity: 0.5;
  -webkit-mask: var(--ow-chevron-mask) center / contain no-repeat;
  mask: var(--ow-chevron-mask) center / contain no-repeat;
  pointer-events: none;
}
.rev-status.is-compact:active { background: var(--bg-secondary); }

/* THE BUTTONS GO. Not shrunk, not scrolled: gone, because the sheet has them. */
.rev-status.is-compact .rev-actions { display: none; }
/* So do the things that only make sense when there is room to read them. */
.rev-status.is-compact .rev-text span,
.rev-status.is-compact .rev-ledger,
.rev-status.is-compact .rev-clock,
.rev-status.is-compact .rev-spent { display: none; }

.rev-status.is-compact .rev-main {
  display: flex;
  align-items: center;
  gap: inherit;
  flex-wrap: nowrap;
  min-width: 0;
  width: 100%;
}
.rev-status.is-compact .rev-text { flex: 1 1 auto; min-width: 0; }
.rev-status.is-compact .rev-text b {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: clamp(var(--text-xs), 3cqi, var(--text-sm));
}
/* The count is a marker, not a headline. At desktop size against text that had
   shrunk, it was most of why this read as jumbo. */
.rev-status.is-compact .rev-badge,
.rev-status.is-compact .rev-mark,
.rev-status.is-compact .rev-count {
  flex: none;
  width: clamp(1.25rem, 5.5cqi, 1.6rem);
  height: clamp(1.25rem, 5.5cqi, 1.6rem);
  font-size: clamp(var(--text-2xs), 3cqi, var(--text-xs));
}

/* The @container revstatus blocks that were here are gone. They decided the same
 * thing as `.is-compact` and could not see viewport HEIGHT, so a phone in
 * landscape (850x407) satisfied the wide branch and got the desktop layout in
 * 407px of space. Two mechanisms deciding one question is how a component ends
 * up in a state neither intended, so the measured class is the only one left. */


/* ═══════════════════════════════════════════════════════════════════════════
 * THE DOCUMENT ITSELF ON A PHONE: nothing cut, nothing oversized
 * ═══════════════════════════════════════════════════════════════════════════
 *
 * Reported against a 320px and a 480px screenshot: "make sure to redce paddings
 * in mobiel mode ... also show outline nicely in the given space dont cut use
 * nice ollokd easy to follow ... i see up down butons larger flogin chat icon
 * they are not looking good".
 *
 * Three distinct defects in one view, and the table was the worst of them.
 */
@media (max-width: 640px) {

  /* ---- 1. THE LOCKED TABLES STACK INSTEAD OF BEING CUT ----------------------
   *
   * responsive.css gives every table `display:block; overflow-x:auto`, so the
   * Course Information table kept its natural width and took a scrollbar. That
   * contains the overflow, which is what that rule is for, but it is the wrong
   * answer for THIS table: at 320px the value column was clipped mid-word
   * ("OpenAI Codex a...", "Software Develo...") and the only way to read a field
   * was to scroll a table sideways inside a document that scrolls vertically.
   * Two axes of scroll in one surface is how content gets missed entirely.
   *
   * A key/value table is the one shape that reflows perfectly: the label goes
   * above its value and the row becomes a block. Nothing is truncated, nothing
   * needs a second gesture, and the reading order is unchanged. This is the
   * standard responsive-table pattern and it applies cleanly because the outline's
   * tables ARE key/value (Course Information, Course Planning) or one-row-per-item
   * (Course Organization, where the cell is prose the reader wants full width for
   * anyway).
   *
   * `display: table` first, undoing responsive.css's block/scroll treatment,
   * because a stacked row must not also be a scroll container. */
  body .preview-box-formatted table,
  body .preview-box-formatted table tbody,
  body .preview-box-formatted table tr {
    display: block;
    width: 100%;
    max-width: 100%;
    overflow-x: visible;
    /* home.html sets `min-width: 620px` on doc tables so they SCROLL rather than
       crush their columns, which is the right call for a table that stays a table.
       Once the rows are stacked it is actively harmful: it held every row card at
       620px inside a 320px viewport, so the cards ran off the right edge and the
       fix looked like it had not worked. Measured at 320px: scrollWidth 620,
       clientWidth 620, viewport 320. */
    min-width: 0;
  }
  body .preview-box-formatted table thead { display: none; }

  /* ── A LIST, NOT SEVEN CARDS ───────────────────────────────────────────────
   *
   * "still stee nlot of vertical padding between cards and horizontal padding
   * aswell make it native look responsive mobile ui like native mobile app look".
   *
   * The first version of this stacking gave every row its own border, radius,
   * background and bottom margin. That is a card, and seven cards down a phone
   * screen means seven borders, seven radii and six gaps - about 90px of the
   * viewport spent on chrome between fields, plus two layers of horizontal padding
   * (the card's and the document's) before any text.
   *
   * No native app draws a key/value screen that way. It draws a LIST: one surface,
   * rows separated by a hairline, the label small above its value, and the divider
   * running to the edge of the container so the eye reads a single column rather
   * than a stack of objects. That is the iOS grouped-list and the Material list, and
   * it is both tighter and calmer.
   */
  body .preview-box-formatted table {
    border: 1px solid var(--doc-border);
    border-radius: var(--radius-md);
    background: var(--bg-card);
    overflow: hidden;                 /* so the first and last rows clip to the radius */
  }
  body .preview-box-formatted table tr {
    border: 0;
    border-top: 1px solid var(--doc-border);
    border-radius: 0;
    margin: 0;                        /* the divider IS the separation */
    padding: clamp(0.4rem, 1.8cqi, 0.6rem) clamp(0.55rem, 2.5cqi, 0.85rem);
    background: none;
  }
  body .preview-box-formatted table tr:first-child { border-top: 0; }
  body .preview-box-formatted table th,
  body .preview-box-formatted table td {
    display: block;
    width: auto;
    border: 0;
    padding: 0;
    /* The whole point of stacking: a value is never clipped. */
    white-space: normal;
    overflow-wrap: break-word;
  }
  /* The first cell of a row is its label, so it reads as one: small, quiet, and
     directly above the value rather than in a tinted box beside it. */
  body .preview-box-formatted table tr > *:first-child {
    font-size: var(--text-2xs);
    font-weight: var(--weight-bold);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    line-height: 1.35;
    color: var(--text-muted) !important;
    /* The markdown renderer tints header cells. In a list the tint is what made each
       row look like a titled card, so it goes. */
    background: none !important;
  }
  body .preview-box-formatted table tr > *:not(:first-child) {
    font-size: var(--text-base);
    line-height: 1.45;
    margin-top: 1px;
  }

  body .preview-box-formatted h1 { margin-top: 0; }

  /* ---- 3. THE CORNER CONTROLS STOP SHOUTING -------------------------------
   * "i see up down butons larger flogin chat icon they are not looking good".
   * Two 34px circles stacked beside a 44px FAB is three round buttons in one
   * corner of a 320px screen, which is more corner than content. The pair comes
   * down to 30px and tightens, and `--corner-jump-h` follows so the lane maths
   * above them stays correct rather than leaving a gap where the old height was. */
  :root { --corner-jump-h: 3.9rem; }
  .ow-jump { gap: 0.2rem; }
  .ow-jump button {
    width: 1.875rem;
    height: 1.875rem;
  }
  .ow-jump button svg { width: 16px; height: 16px; }
}
/* The jump arrows are a real, one-handed control, so under a coarse pointer they
   take the full tap target - a 30px-wide arrow is hard to hit with a thumb while
   scrolling. Width AND height, because the mobile block above set them 30px square;
   width was the half the finding caught (30x44). Keyed on the input device, so a
   narrow desktop window keeps the compact arrows. */
@media (pointer: coarse) {
  .ow-jump button {
    width: var(--tap-min, 44px);
    height: var(--tap-min, 44px);
  }

  /* ---- 4. THE ACTION ROW WRAPS INSTEAD OF RUNNING OFF THE EDGE ------------
   * The screenshot shows "Fix everything | Read them | Proce..." with the third
   * button cut by the viewport. Three labelled buttons cannot share 320px. */
  body .ow-verdict-actions,
  body .quality-summary .gate-override {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
  }
  body .ow-verdict-actions > * { flex: 1 1 auto; min-width: 0; }
}

/* A very small phone gets the buttons stacked outright: at 320px even two
   labelled buttons on a row truncate, and a truncated verb is not a control. */
@media (max-width: 480px) {
  body .ow-verdict-actions { flex-direction: column; align-items: stretch; }
  body .ow-verdict-actions > * { width: 100%; justify-content: center; }
}

/* ═══════════════════════════════════════════════════════════════════════════
 * THE DOCUMENT'S OWN SPACING IS FLUID, AT EVERY SIZE
 * ═══════════════════════════════════════════════════════════════════════════
 *
 * "lots of padding spaces here there ... dont hadcodesotuff ithis is the op to
 * build responsive UI that works in all gadgets and desktop seamlaesy".
 *
 * These were breakpoint step-downs, which is the wrong tool twice over. It picks a
 * device width and is therefore wrong on the next device, and it produces a visible
 * jump: 24px of padding at 641px and 12px at 640px, for a screen one pixel narrower.
 *
 * `cqi` is 1% of the CONTAINER's inline size, so the padding is a proportion of the
 * space the document actually has - not of the window, which on this page is a
 * different number because of the rail and the dock. An S24 Ultra, an iPhone Pro
 * Max, an iPad in split view, a folding phone mid-fold and a desktop window being
 * dragged all get a padding that suits the room, from one declaration, and no
 * device is named.
 *
 * The clamp ceilings are the previous desktop values, so wide layouts are unchanged.
 */
#outlineDocScroll { container-type: inline-size; container-name: outlinedoc; }

body .preview-box-formatted {
  padding: clamp(0.6rem, 3cqi, 2rem);
}
body .preview-box-formatted h2 { margin-top: clamp(0.75rem, 3cqi, 1.5rem); }
body .preview-box-formatted p,
body .preview-box-formatted li { margin-block: clamp(0.35rem, 1.5cqi, 0.75rem); }

/* The row padding used to be set twice - once here and once in the stacking block -
   and the two disagreed. The stacking block owns it now, because a row's padding is
   part of being a list row and means nothing to a table cell. */

/* ═══════════════════════════════════════════════════════════════════════════
 * EDGE TO EDGE: ONE GUTTER, NOT FOUR NESTED ONES
 * ═══════════════════════════════════════════════════════════════════════════
 *
 * "still paddings", after two rounds of reducing them. The reason reducing them kept
 * not working is that no single one was large - there were four, and they add up.
 *
 * Measured at 407px, walking from the text out to the viewport:
 *
 *   #step2.step-card          padding-left   8.0px
 *   .preview-box-formatted    padding-left  11.7px
 *   table.kv-table            border-left    1.0px
 *   tr                        padding-left   9.8px
 *                                          -------
 *                                           30.5px  each side, so 61px of 407 = 15%
 *
 * Every one of those is defensible on a desktop, where the document is a sheet of
 * paper floating on a page and the nesting is what makes it read that way. On a phone
 * there is no page around the paper: the screen IS the paper, and each inset is
 * another 8% of the line length spent on nothing.
 *
 * So the intermediate layers go to zero and the ROW's padding becomes the only
 * horizontal inset - 16px, which is the gutter iOS and Material both use. The table's
 * side borders and radius go with them, which is what lets the hairline dividers run
 * to both edges. That edge-to-edge divider is the single strongest visual cue that a
 * thing is a native list rather than a card someone shrank.
 */
@media (max-width: 768px), (max-height: 30rem) {

  /* The page gutter and the paper padding both stand down; the row owns the inset. */
  body #step2.step-card { padding-inline: 0; }
  body .preview-box-formatted {
    padding-inline: 0;
    /* Tight. This is the gap between the status strip and the first heading, and it
       was carrying desktop's 1.25rem at every size. */
    padding-block: clamp(0.35rem, 1.2cqi, 0.75rem);
    border-radius: 0;
    border-left: 0;
    border-right: 0;

    /* ── ONE SCROLLER, AND THIS IS WHY THE JUMP BUTTONS DID NOT WORK ──────────
     *
     * Reported as "check up and down buttons are not working also". They fired and
     * moved the pane by 42px, then stopped, which reads exactly like a dead control.
     *
     * home.html sets `overflow-x: auto` on this box below 768px, so a wide table
     * could scroll sideways. The side effect is in the CSS spec rather than in the
     * rule: when one axis is `auto` the other cannot remain `visible`, so the
     * `overflow-y: visible` that `.content-scroll .preview-box` carefully sets -
     * with a comment explaining that the pane must be the SINGLE scroll owner -
     * silently computed to `auto` on mobile only.
     *
     * The result was two nested scrollers. Measured at 407px: this box held 24,714px
     * of outline and scrolled it itself, while #outlineDocScroll saw a 744px-tall
     * child and had 43px of range. The buttons scroll the pane, so 43px was all they
     * could do, and the document they were meant to move was scrolling in a box
     * nobody had told them about. On desktop, where overflow-x is not set, the same
     * pane measured 29,034px and the buttons worked.
     *
     * `overflow: visible` on both axes restores the single-scroller contract. Nothing
     * is lost: the horizontal scroll existed for wide tables, and tables now stack
     * into a list on mobile, so there is nothing left to scroll sideways.
     */
    overflow: visible;
    max-height: none;
  }

  /* ── THE DOCUMENT IS ONE WHITE SHEET, NOT WHITE ISLANDS ON GREY ────────────
   *
   * "why did u remove the style of theoutline i dont seee the outline colors
   * matching with doc".
   *
   * The edge-to-edge change zeroed this document's own padding and border, which
   * was right, but it left the paper background behind on the desktop step-card.
   * So on a phone the tables kept their `--bg-card` white while the space between
   * and around them - the headings, the gaps between sections, the page margin -
   * fell through to the app's grey `--bg-secondary`. White list rows floating on
   * grey with grey bands between the sections: patchy, and not what the document
   * looks like anywhere else.
   *
   * A native grouped list sits on ONE surface. So the document pane itself becomes
   * the paper - the white the desktop card provided - and the individual tables
   * stop painting their own fill. One continuous white sheet, headings and rows and
   * dividers all on it, which is both the iOS/Material grouped-list look and the
   * colour match with the Document (docx) view the author was comparing against.
   *
   * `--doc-paper` is the document's own paper token (the same white the docx view
   * renders on), not `--bg-card`, so the two outline views cannot drift apart. */
  body #outlineDocScroll { background: var(--doc-paper, var(--bg-card, #fff)); }

  /* The list spans the screen, so its dividers do too - and it no longer paints its
     own background, because the pane behind it is now the paper. A per-table fill
     here is what created the islands. */
  body .preview-box-formatted table {
    border-left: 0;
    border-right: 0;
    border-radius: 0;
    background: none;
  }

  /* THE ONE GUTTER. 16px is not arbitrary: it is the standard content inset on both
     mobile platforms, and it is what makes a full-bleed list look intentional rather
     than like text that has run out of margin. */
  body .preview-box-formatted table tr { padding-inline: 1rem; }

  /* Everything else in the document lines up with the list rather than sitting in
     its own indent, so the left edge of the page is a single straight line. */
  body .preview-box-formatted > h1,
  body .preview-box-formatted > h2,
  body .preview-box-formatted > h3,
  body .preview-box-formatted > p,
  body .preview-box-formatted > ul,
  body .preview-box-formatted > ol,
  body .preview-box-formatted > blockquote { padding-inline: 1rem; }
  body .preview-box-formatted > ul,
  body .preview-box-formatted > ol { padding-inline-start: 2rem; }

  /* The status strip aligns to the same line. */
  body .rev-status { margin-inline: 1rem; }

  /* ── VERTICAL RHYTHM ──────────────────────────────────────────────────────
   *
   * "still stee nlot of vertical padding between cards", and then "vertial one".
   * Same shape of problem as the horizontal gutters: no single gap was large, and
   * there were four of them stacked between one section and the next.
   *
   *   table margin-bottom      1.25rem   the markdown renderer's default
   *   h2 margin-top            1.5rem    the next section's heading
   *   h2 margin-bottom         0.5rem
   *   table margin-top         0.75rem
   *                          --------
   *                            4.0rem = 64px between "60" and "Course Planning"
   *
   * On a 850px-tall phone that is 7.5% of the screen spent on one seam, and there
   * are five of them in this document. A native list uses the SECTION HEADING as
   * the separator and lets the margins collapse to almost nothing, because the
   * heading is already unambiguous - it is bold, it is larger, and it sits on the
   * page background rather than inside the list.
   */
  body .preview-box-formatted table { margin-block: 0 clamp(0.5rem, 2cqi, 1.25rem); }
  body .preview-box-formatted h2 {
    margin-top: clamp(0.75rem, 3cqi, 1.5rem);
    margin-bottom: clamp(0.2rem, 0.8cqi, 0.5rem);
  }
  body .preview-box-formatted h3 {
    margin-top: clamp(0.5rem, 2cqi, 1rem);
    margin-bottom: clamp(0.15rem, 0.6cqi, 0.4rem);
  }
  /* The document's first heading has the strip directly above it and needs no lead. */
  body .preview-box-formatted > *:first-child { margin-top: 0; }
}

/* ═══════════════════════════════════════════════════════════════════════════
 * THE TOOL RAIL ON A PHONE: ONE ROW, NOT A SECOND COPY OF THE WHOLE PANEL
 * ═══════════════════════════════════════════════════════════════════════════
 *
 * "this is. outline page so show the outline done show the top two stuff on page
 * keep them as menu option we can alsways open them and see if need ... in mobiel
 * e have less realstate we have to show the stuff which are improtatnt to user
 * primarly we are workigno n outline show the outline andshowt he stuatus one
 * small icon where i can click na. see the update".
 *
 * What was actually on screen above the outline, measured at 407px: a band holding
 * the course name, a collapse chevron, "More actions", "Continue to Slides", four
 * reviewer chips with scores, a reviewers status line, ten content tags and
 * "Start over". `.ow-group-label { display: none }` from the 1100px block strips
 * the labels off all of it, so it arrived as an unlabelled jumble, and the sticky
 * status strip painted over its lower half - the "COURSE ▬" sliver with a cut-off
 * purple pill under it in the report.
 *
 * Every one of those things already has a home on a phone:
 *
 *   reviewer chips, scores, status line -> the review SHEET, which IS the panel
 *   content tags, level, length         -> the sheet's Overview tab
 *   the review status                   -> the tappable strip above the document
 *   the course name                     -> the top bar, which now carries it
 *   collapse the rail                   -> meaningless once it is one row
 *
 * So the rail keeps exactly what is NOT reachable elsewhere: the primary action,
 * and the overflow menu. One row, primary action taking the width it needs and the
 * ⋯ button beside it. This is the standard native shape - a bottom-of-screen or
 * top-of-content action bar with an overflow - and it costs about 44px instead of
 * the ~250px the band was taking.
 *
 * NOTHING BECOMES UNREACHABLE. That is the rule these hides have to satisfy, and
 * the reason earlier attempts at this kept the cards alive. "Start over" is the one
 * control with no other home, so it moves INTO the ⋯ menu (see #railStartOver in
 * home.html), which is also a better place for a destructive action than a button
 * sitting in the open next to the primary one.
 */
@media (max-width: 768px), (max-height: 30rem) {

  /* ---- One file decides the rail's position ------------------------------
   *
   * This is a defect independent of how it looks. `outline-workspace.css`'s 1100px
   * block sets `position: sticky; top: 0; z-index: 40; max-height: 34vh` and
   * `icon-rail.css`'s 768px block sets `position: static; max-height: none`, both
   * for the same element at the same viewport. `.ow-bar.ow-rail` (0-2-0) beats
   * `.ow-bar` (0-1-0) so static wins today, but the outcome is decided by which
   * file happens to carry the more specific selector rather than by intent - and a
   * sticky rail with an opaque background and z-index 40 is exactly what would
   * paint over the document. Declared here, in the file that owns the shell, at a
   * specificity that settles it.
   */
  body #outlineCmdBar.ow-bar.ow-rail {
    position: static;
    max-height: none;
    overflow: visible;

    /* Not a card. A card needs a border, a radius and a fill to say "I am a
       separate surface", and this is a single row of controls sitting on the page
       above the document - the same reason the tables became a grouped list rather
       than seven bordered cards. */
    background: none;
    border: 0;
    border-radius: 0;
    padding: clamp(0.3rem, 1.5vw, 0.5rem) 1rem;
    margin-bottom: 0;

    flex-direction: row;
    flex-wrap: nowrap;
    align-items: center;
    gap: 0.5rem;
  }

  /* ---- What goes, and where it already lives -----------------------------
   * `:has()` rather than nth-child so these keep matching if the rail's groups are
   * reordered. Same pattern ui-kit.css already uses to hide the view-tabs group.
   */
  /* Reviewer chips + their status line: the sheet IS this, in full, with tabs. */
  body #outlineCmdBar .ow-group:has(#outlineMentorsRow),
  /* Content tags, level, length: the sheet's Overview tab, beside the rounds and
     the estimate. */
  body #outlineCmdBar .ow-group:has(#courseTagsRow),
  /* The course name: the top bar carries it on mobile now, so this is the second
     copy, and the "—" placeholder in it is what read as a broken minimise button. */
  body #outlineCmdBar .ow-rail-title,
  /* Collapsing a one-row toolbar collapses nothing. */
  body #outlineCmdBar .ow-rail-fold { display: none; }
  /* The .ow-group-danger "Start over" button was removed from the dock entirely;
     Start over now lives only in the ⋯ overflow menu, so there is no longer a
     rule to hide it on mobile. */

  /* ---- The row that is left ----------------------------------------------- */
  body #outlineCmdBar .ow-rail-head {
    display: contents;          /* the head's children join the row directly */
    border-bottom: 0;
    padding-bottom: 0;
  }
  body #outlineCmdBar .ow-group {
    flex: 1 1 auto;
    min-width: 0;
    flex-direction: row;
    align-items: center;
    gap: 0.5rem;
    padding-top: 0;
    border-top: 0;
    /* ── ORDER, and the reason it is not optional ─────────────────────────────
     * `display: contents` on `.ow-rail-head` promotes its children into this row,
     * and they keep their DOM position - which puts the ⋯ wrapper BEFORE the
     * primary action, because the head is the rail's first child. `margin-left:
     * auto` cannot rescue it: the group after it is `flex: 1 1 auto` and absorbs
     * the free space first, so the wrapper settled at x≈24 and its right-anchored
     * menu opened from x=-172 to x=68, i.e. almost entirely off the left edge.
     * Measured, not predicted.
     *
     * An overflow belongs at the end of a toolbar in every native convention, so
     * ordering it explicitly fixes the anchor and the convention at once. */
    order: 1;
  }
  /* The primary action takes the row and states itself fully; a thumb-sized target
     (44px is the floor both iOS and Material set) and no truncation. */
  body #outlineCmdBar .ow-group .ow-btn.primary {
    flex: 1 1 auto;
    min-width: 0;
    min-height: 44px;
    justify-content: center;
  }
  /* The overflow stays a square icon button, pinned to the end of the row. */
  body #outlineCmdBar .rail-more-wrap { flex: none; order: 2; }
  body #outlineCmdBar #railMoreToggle {
    min-height: 44px;
    min-width: 44px;
    justify-content: center;
    padding: 0 0.6rem;
  }
  /* "⋯ More actions" spelled out costs a third of the row for a label the glyph
     already carries in a corner overflow. The button keeps its `title` and
     `aria-label`, so nothing is lost to a screen reader or a hover. */
  body #outlineCmdBar #railMoreToggle .ow-more-label { display: none; }

  /* No rule is needed to keep the flyout on screen: OutlineWorkspace.openMenu
     parents it to <body> and positions it against the anchor, which is what
     TestMoreActionsMenu.test_menu_opens_and_is_not_clipped pins down. The static
     #railMoreMenu that DID need pinning here is gone - it was the second menu the
     same click opened. "Start over" moves into the surviving flyout, added by
     openOutlineMoreMenu only when the rail's own copy is not rendered. */

  /* ── COLLAPSING A ROW IS A NO-OP, SO IT MUST DO NOTHING ────────────────────
   *
   * Reported as "why there are lots of vertial padding", with a screenshot showing
   * an empty band between the top bar and the status strip holding a single small
   * padlock.
   *
   * The band is this rail in its COLLAPSED state, and the collapse is remembered in
   * localStorage under `owRailCollapsed`, so it survives a reload and follows the
   * author onto their phone. `html.ow-rail-off` is a desktop idea: it shrinks the
   * 248px column to a 48px strip and hides every label, which is a sensible way to
   * give the document more width when the rail IS a column. Above, this block has
   * already turned the rail into a single full-width row. Collapsing a row cannot
   * give the document anything - there is no horizontal space to reclaim - so all
   * the collapsed rules did was leave a 48x56 stub with its labels stripped off,
   * showing only the `::after` padlock of the gated Continue button. 56px of height
   * spent on a lock with no word next to it.
   *
   * This is the same trap as `--irail-dock-w`, documented a few hundred lines up:
   * that rule reserved a collapsed column's width below 1100px, where the dock is
   * already a horizontal band, so collapsing it ADDED a 48px gutter and the control
   * did the opposite of its job. The lesson generalises - a collapse rule has to be
   * scoped to the layout where the thing being collapsed is actually a column.
   *
   * So on a phone the collapsed state is neutralised rather than the control being
   * patched. The preference is still stored and still applies on a desktop, which is
   * where it means something; the fold button itself is already hidden here, so
   * there is no control on screen whose state this contradicts.
   *
   * The ID outranks `html.ow-rail-off .ow-bar.ow-rail` (0,3,1) on the a-position, so
   * these win without !important however the two files are ordered.
   */
  html.ow-rail-off #outlineCmdBar.ow-bar.ow-rail {
    width: 100%;
    min-width: 0;
    align-items: center;
    overflow: visible;
  }
  /* The labels come back: "Continue to Slides" is the primary action, and an icon
     plus a padlock with no word beside it is not a button anyone can read. */
  html.ow-rail-off #outlineCmdBar .ow-btn > span:not(.ow-ico),
  html.ow-rail-off #outlineCmdBar .ow-more-label { display: inline; }
  /* ...and the row's one group is laid out as a row again, not centred in a strip. */
  html.ow-rail-off #outlineCmdBar .ow-group {
    width: auto;
    flex-direction: row;
    align-items: center;
  }
  /* The square icon-button sizing the collapsed strip forces would squash the
     primary action into 2.15rem. It is a full-width row here. */
  html.ow-rail-off #outlineCmdBar .ow-group .ow-btn.primary {
    width: auto;
    min-width: 0;
    height: auto;
    padding: 0 0.75rem;
    gap: 0.4rem;
  }
  /* The overflow keeps its own square target, which the collapsed rules already
     size correctly - it is only the label that had to come back. */
  html.ow-rail-off #outlineCmdBar .rail-more-wrap { width: auto; }

  /* ── A DISABLED PRIMARY ACTION DOES NOT GET A ROW ──────────────────────────
   *
   * "whey u need continueto slide utton dispayhed siabled if is not enaled
   * unnecary u r hiding the visiileity of outline remember foxu is outline here".
   *
   * Correct, and it is the same argument this file has already applied twice - to
   * the verdict card and to the quality card. "Continue to Slides" is gated until
   * every Author Kit rule passes, which is exactly the state in the screenshot: 10
   * things to fix, the button greyed with a padlock. It cannot be pressed. It was
   * spending 56px of an 850px screen, above the document, to show the author
   * something they cannot do - while the strip immediately below it already says
   * what is blocking and opens the sheet where the action that DOES work lives.
   *
   * So while it is gated it goes, and the row goes with it. The trigger is the
   * `.is-gated` class the gate itself sets, not a guess about state: the moment the
   * outline passes and the gate lifts, the button and its row come back without
   * anything here having to know why. Nothing is lost in the meantime - a disabled
   * control offers nothing - and "Proceed without fixing" is in the sheet for an
   * author who wants to override.
   */
  body #outlineCmdBar .ow-group .ow-btn.primary.is-gated { display: none; }

  /* With the primary gone the row holds only the ⋯ overflow, and a 56px row for one
   * icon is the same waste in smaller form. Out of flow it reserves NO height and
   * sits on the strip's line instead, so the two stacked bars in the report become
   * one. `#outlineResult` is already `position: relative` (set in
   * _bindOutlineCmdBar so `.ow-jump` can anchor to it), so this has a containing
   * block without adding one.
   *
   * Scoped with `:has()` to the gated state only. When the button is live the bar
   * is a real toolbar again and belongs in flow, where it cannot overlap anything. */
  body #outlineResult:has(.ow-btn.primary.is-gated) #outlineCmdBar {
    position: absolute;
    top: 0;
    right: 0;
    width: auto;
    min-width: 0;
    padding: 0 0.5rem;
    margin: 0;
    z-index: 5;
    background: none;
    border: 0;
  }
  /* ...and the strip stops short of it, so the status text and the overflow read as
     one row rather than the text running underneath the button. */
  body #outlineResult:has(.ow-btn.primary.is-gated) .rev-status { margin-right: 3.5rem; }
}
