/* Icon rail + detail panel — the two-column navigation pattern.
 *
 * Problem it solves: the course editor stacked TWO always-open navigation
 * columns before any content. Measured on a 1024px viewport that was 415px, over
 * 40% of the window, spent on chrome the author reads once and then ignores —
 * while the outline itself, the thing being authored, got the remainder.
 *
 * The pattern here is the standard for dense creative tools: a permanent
 * icon-only rail that costs 56px, and a detail panel that opens on demand. Total
 * resting cost drops from 415px to 56px, and the panel is one click away.
 *
 * Implemented as progressive enhancement over the existing .crail markup rather
 * than a rewrite: course-rail.js keeps owning its content, this owns the shell.
 * Everything is driven by two classes on <html> so there is a single source of
 * truth for the layout state and no JS-computed inline widths to drift.
 *
 *   html.has-irail            the rail is active (course editor only)
 *   html.irail-open       a detail panel is showing
 *   html[data-irail=key]  which panel
 *
 * All values come from brand tokens. No literal colours, no literal font sizes.
 */

/* Every box here is border-box.
 *
 * Without it the 1px border-right sits OUTSIDE the declared width, so the rail
 * occupied 57px while .app-layout reserved 56px and the border overlapped the
 * content by exactly one pixel — the kind of seam that looks like a rendering
 * artefact and gets chased for an hour. The panels have the same border and the
 * same trap. */
.irail,
.irail *,
.irail-panel,
.irail-panel *,
html.has-irail .crail {
    box-sizing: border-box;
}

:root {
    --irail-w: 56px;
    /* Narrows on tighter desktops instead of holding one width and squeezing the
       content. The old rail did this and losing it was a regression: at 1024px a
       fixed 268px panel leaves the outline noticeably cramped. clamp() keeps it
       between a readable floor and a sensible ceiling with no media query and no
       JS measurement. */
    --irail-panel-w: clamp(216px, 22vw, 288px);
    /* One transition duration for the whole component, so the rail, the panel
       and the content edge move as one object instead of three. */
    --irail-ease: 160ms cubic-bezier(0.4, 0, 0.2, 1);
}

/* ── The rail ─────────────────────────────────────────────────────────── */

.irail {
    position: fixed;
    top: var(--topbar-h, 48px);
    bottom: 0;
    left: 0;
    z-index: 60;
    width: var(--irail-w);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    padding: 8px 0;
    background: var(--bg-card);
    border-right: 1px solid var(--border-subtle);
    /* Never a scrollbar on a rail this narrow: a 6px scrollbar next to a 20px
       icon looks like damage. Overflow is handled by keeping the set small. */
    overflow: visible;
}

/* ONE vertical rule. When a stage docks its own tool panel (html.studio-dock —
   the outline workspace's .ow-bar) the left of the window is icon rail, then
   panel, then dock, and giving each of them an edge drew three parallel lines
   before any content: the eye reads that as three competing sidebars. The
   INNERMOST edge is the only one that means anything, because it is the one that
   separates chrome from the document. So the rail's own edge stands down while a
   dock is on screen, and comes back when the content pane starts right after it.
   Same rule the course panel follows in course-rail.js. */
html.studio-dock .irail { border-right: 0; }

.irail-btn {
    position: relative;
    width: 44px;
    /* Taller than it is wide so the label has room; ElevenLabs-style stacked
       icon + micro-label. */
    min-height: 48px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    padding: 4px 2px;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--text-muted);
    font-family: var(--font-sans);
    font-size: var(--text-2xs);
    font-weight: 600;
    line-height: 1.1;
    letter-spacing: -0.01em;
    cursor: pointer;
    transition: background var(--irail-ease), color var(--irail-ease);
    -webkit-tap-highlight-color: transparent;
}

/* Inline SVG on a fixed 20px box, so every icon occupies identical space and the
   column lines up. The rail used platform EMOJI, which ignore the theme (they
   render in the OS colour font, so a muted label sat beside a full-colour cartoon
   and dark mode kept the light palette), have different metrics per glyph and per
   platform, and cannot take a state - which is why lock state had to be signalled
   by fading the whole button. currentColor fixes all three at once. */
.irail-btn .irail-ico {
    display: grid;
    place-items: center;
    width: var(--icon-md);
    height: var(--icon-md);
    flex: none;
    line-height: 1;
}
.irail-btn .irail-ico svg {
    width: var(--icon-md);
    height: var(--icon-md);
    display: block;
}
.irail-btn:not(.is-locked):not([aria-current="page"]):not([aria-current="true"]) .irail-ico {
    /* Resting icons sit a step back from their label: the label is what is read,
       the icon is what is recognised. */
    opacity: 0.75;
}
.irail-btn[aria-current="page"] .irail-ico,
.irail-btn[aria-current="true"] .irail-ico { opacity: 1; }
.irail-btn .irail-ico-legacy {
    font-size: var(--text-md);
    line-height: 1;
    /* Emoji glyphs sit high in their box; nudge so icon and label read centred. */
    transform: translateY(1px);
}

.irail-btn .irail-label {
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* `color` is set alongside `background` on purpose: a hover that repaints the
   fill and leaves the foreground inherited is how a label dissolves into its
   own button (spec S1). */
.irail-btn:hover:not(:disabled) {
    background: var(--bg-secondary);
    color: var(--text-primary);
}

/* ── A locked stage LOOKS locked ────────────────────────────────────────────
   Not `disabled`: the button must stay hoverable and clickable so it can
   explain the lock. Muted + a padlock says "not yet" without pretending the
   control is missing (spec S3). */
.irail-btn.is-locked {
    /* MUTE THE LABEL, NOT THE LOCK.
       This was `opacity: 0.45` on the whole button, which faded the padlock along
       with everything else: the badge ended up at 0.45 x 0.9, about 40% opacity, on
       a 10px glyph. Reported as "lock icons not much visible strong", and the cause
       was that the one element carrying the information was the one being hidden.
       The label recedes via colour; the lock stays at full strength. */
    color: var(--text-muted);
    cursor: not-allowed;
}
/* `.irail-btn.is-locked > * { opacity: 0.55 }` USED TO BE HERE, and it undid the
   very fix the comment above describes. The button already recedes via `color`;
   multiplying that by 0.55 put the icon and the label at roughly half of the
   lowest-emphasis text token, under any reasonable contrast floor. Reported again
   as "the left lock icons are not visible" after the first attempt at this, because
   the first attempt moved the padlock out of the fade and left its siblings in it -
   so the whole button was still a grey smudge and the badge had nothing legible to
   sit against. Colour is the entire mechanism now, and nothing here is faded. */
/* A DRAWN padlock, not the emoji.
   `\1F512` rendered in the OS colour font: a small brown cartoon that ignored the
   theme, could not take the muted colour of the button it belonged to, and looked
   different on every platform - reported as "the lock icons are not looking
   professional". A masked SVG inherits currentColor, so the badge is the same
   stroke language as the icon it sits on and themes correctly. */
/* The padlock itself, drawn as a mask so it takes the text colour. Two elements
   because ::after already owns the chip; this rides on top of it. */
/* SIZE PINNED WITH `nav.irail` IN FRONT, and all four insets stated.
   Measured on the live page at 1440px: this pseudo-element was rendering 44x48px -
   the button's whole padding box - with the amber background and the padlock mask
   scaled up to fill it. That is the "one circle oragen one showing up" and the
   "stage and slides are override by icon badly" in the reports: a giant amber blob
   sitting on top of the Slides and Script icons.

   The cause is that `.irail-btn` has TWO `::before` rules - this padlock and the
   active-marker bar below - and CSS merges declarations from every matching rule
   into one pseudo-element. Two attempts at fixing that by moving the badge's offsets
   treated the instance; the badge is a real child element now (.irail-lock, below),
   which cannot be merged into by anything. */
.irail {
    /* Declared once on the rail so both pseudo-elements share one definition.
       A SOLID (filled) padlock, not a stroked outline. At a 10px render a 2.4px
       stroke fused its own shackle and body into an amber blob; a filled body
       with a stroked shackle keeps the two shapes distinct at badge size, which
       is what reads as a genuine lock. viewBox 0 0 24 24: body is a filled
       rounded rect, shackle a 2.5px arc sitting on top of it. */
    --irail-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 LOCK BADGE, as a real element ────────────────────────────────────────
   Reported three times, most recently as "when we colaps circles are stil visible":
   two hollow amber rings floating just outside the rail beside exactly the two locked
   stages, revealed once the collapsed tool dock stopped covering that strip.

   The badge used to be `.irail-btn.is-locked::before` + `::after`. But
   `.irail-btn::after` is the hover TOOLTIP, and CSS merges declarations from EVERY
   rule matching one pseudo-element - so the chip inherited the tooltip's
   `left: calc(100% + 8px)`, `padding: 4px 8px` and `transform: translateY(-50%)`,
   rendering a 14px circle as a 30x22 box eight pixels to the right of the rail. Its
   own `opacity: 1` then overrode the tooltip's `opacity: 0`, so it never hid. Two
   rules, each correct alone, describing one box.

   It cost the tooltip too: a locked button's `::after` carried `content: ''`, so the
   one button that most needs to explain itself had no hover text at all.

   A child element cannot be merged into, which ends the class of bug rather than
   moving the badge again. Created by syncStageLocks() in icon-rail.js.

   Position: the ICON's top-right corner. The button is a 48px column of
   icon-then-label, so its bottom ~12px is the word - a bottom-anchored badge sits
   through "Slides" and "Script", which is the "overlapping text cutting lock icon on
   slides scripts" report and what test_the_rail_lock_badge_cannot_sit_on_the_label
   guards against. */
.irail-lock {
    position: absolute;
    top: 2px;
    right: 5px;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    display: grid;
    place-items: center;
    /* A solid chip so the glyph stays legible against whatever icon sits behind it,
       ringed in the same amber as the glyph so chip and padlock read as one mark
       rather than an amber glyph floating on a neutral dot. No literal hex: this file
       carries brand TOKENS only (test_no_hardcoded_colours). */
    background: var(--bg-card);
    box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent-amber) 55%, transparent);
    /* FULL opacity, and not subject to the label's fade: the lock IS the information
       on a locked button, and fading it with everything else is what made it nearly
       invisible in the first place. */
    opacity: 1;
    pointer-events: none;
    z-index: 2;
}
/* The padlock, masked so it takes a token colour. AMBER: this app's "not yet, needs
   attention" colour, the same meaning .crail-lock.open carries. Drawn in
   --text-secondary it was a grey glyph on a grey chip beside a grey label - present,
   and invisible in practice. */
.irail-lock::before {
    content: '';
    width: 9px;
    height: 9px;
    background: var(--accent-amber);
    -webkit-mask: var(--irail-lock-mask) center / contain no-repeat;
    mask: var(--irail-lock-mask) center / contain no-repeat;
}

.irail-btn.is-locked:hover {
    /* Both properties, so the muted label cannot land on a light fill. */
    background: var(--bg-secondary);
    color: var(--text-secondary);
}

/* Selected: either this button's panel is open ("true") or this is the stage you
   are on ("page"). Both read as "you are here", so both get the treatment. */
.irail-btn[aria-current="true"],
.irail-btn[aria-current="page"] {
    background: color-mix(in srgb, var(--accent-violet) 12%, transparent);
    color: var(--accent-violet);
}

/* The active marker is a bar on the rail edge, not a border on the button:
   a border would shift the icon by a pixel every time selection changed. */
.irail-btn[aria-current="true"]::before,
.irail-btn[aria-current="page"]::before {
    content: "";
    position: absolute;
    left: -8px;
    top: 50%;
    width: 3px;
    height: 20px;
    transform: translateY(-50%);
    border-radius: 0 3px 3px 0;
    background: var(--accent-violet);
}

.irail-btn:disabled {
    opacity: 0.38;
    cursor: not-allowed;
}

.irail-btn:focus-visible {
    outline: 2px solid var(--accent-violet);
    outline-offset: 2px;
}

.irail-spacer { flex: 1 1 auto; }

.irail-sep {
    flex: none;
    width: 24px;
    height: 1px;
    margin: 4px 0;
    background: var(--border-subtle);
}

/* Hover tooltip. Pure CSS: a JS tooltip on a nav rail is a memory leak waiting
   to happen, and title= is too slow to be useful. */
.irail-btn::after {
    content: attr(data-tip);
    position: absolute;
    left: calc(100% + 8px);
    top: 50%;
    transform: translateY(-50%);
    padding: 4px 8px;
    border-radius: 6px;
    background: var(--text-primary);
    color: var(--bg-card);
    font-size: var(--text-2xs);
    font-weight: 600;
    white-space: nowrap;
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--irail-ease);
    z-index: 5;
}

.irail-btn:hover::after { opacity: 1; }
/* Suppressed while a panel is open: the panel already names the section, so the
   tooltip would sit on top of its own heading.
   SCOPED OFF THE LOCKED BUTTONS, which is a real bug fix and not a tidy-up. The
   tooltip and the lock badge shared `::after`, so this rule was blanking the PADLOCK
   as well: open the Course panel and every locked stage silently lost its lock.
   Half of "the lock icons are not visible" was exactly this - they were not faint,
   they were gone.

   THE EXCEPTION IS GONE because its reason is. The padlock is its own element
   (.irail-lock) now, so blanking the tooltip cannot touch it, and this rule no longer
   needs to carve locked buttons out. That carve-out was itself a symptom: it existed
   only to work around two things sharing one pseudo-element. A locked button still
   explains itself on hover through `title`, which syncStageLocks() sets alongside
   data-tip. */
html.irail-open .irail-btn::after { content: none; }

/* On the Home page at step 1 (no course open), the icon rail has nothing useful
   to show — all its buttons are course navigation (Outline, Slides, Script, Review)
   and there is no course to navigate. Showing it wastes 56px of width for a column
   of icons that cannot do anything. Hide it until a course opens. */
html.at-step-1 nav.irail {
    display: none;
}
html.at-step-1.has-irail .app-layout {
    padding-left: 0 !important;
}

/* ── Layout: the rail owns the left edge ──────────────────────────────── */

html.has-irail body { overflow-x: hidden; }

html.has-irail .app-layout {
    padding-left: var(--irail-w);
}

/* Animate only AFTER the first paint.
 *
 * The rail is mounted by JS, so `has-irail` lands after the stylesheet is
 * already live. With an unconditional transition the layout animated from 0 to
 * 56px on every single page load — a visible lurch, and it made geometry
 * unmeasurable because any read within 160ms caught a value mid-flight.
 * `has-irail-ready` is added on the frame after mount, so the resting state
 * appears instantly and only user-driven changes animate. */
html.has-irail-ready .app-layout,
html.has-irail-ready .crail,
html.has-irail-ready .irail-panel,
html.has-irail-ready .ow-bar {
    transition: padding-left var(--irail-ease), transform var(--irail-ease),
                visibility var(--irail-ease), left var(--irail-ease);
}

html.has-irail.irail-open .app-layout {
    padding-left: calc(var(--irail-w) + var(--irail-panel-w));
}

/* The existing course rail becomes the detail panel. Collapsed to zero width by
   default rather than display:none, so course-rail.js can keep measuring and
   rendering into it without ever seeing a detached node. */
html.has-irail .crail {
    position: fixed;
    top: var(--topbar-h, 48px);
    bottom: 0;
    left: var(--irail-w);
    z-index: 55;
    width: var(--irail-panel-w);
    border-right: 1px solid var(--border-subtle);
    background: var(--bg-card);
    transform: translateX(-100%);
    visibility: hidden;
}

html.has-irail.irail-open[data-irail="course"] .crail {
    transform: translateX(0);
    visibility: visible;
}

/* The stepper is duplicated by the rail's stage buttons, so it goes away
   entirely in this mode instead of being a third column. */
html.has-irail .stepper { display: none; }

/* The old floating ☰ re-open toggle is meaningless once the rail owns collapsing:
   the 56px icon rail is always present and its Course button reopens the panel. */
html.has-irail .crail-toggle { display: none !important; }

/* The panel's own « button IS the collapse affordance in this mode.
 *
 * It used to be hidden here on the grounds that "the rail owns collapsing" — but
 * that left the open panel with NO visible way to close it: only the Course icon
 * or Escape did, neither of which is discoverable, which is what "I should be able
 * to collapse this side panel easily" reported. course-rail.js now points this
 * button at IconRail.close(), so it folds the panel back into the 56px rail. Keep
 * it visible and match the generic .irail-panel-close chrome so every rail panel
 * closes the same way from the same corner. */
html.has-irail .crail-collapse {
    margin-left: auto;
    width: 24px;
    height: 24px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 0;
    border-radius: 6px;
    background: transparent;
    color: var(--text-muted);
    font-size: var(--text-sm);
    cursor: pointer;
    transition: background var(--irail-ease), color var(--irail-ease);
}
html.has-irail .crail-collapse:hover { background: var(--bg-secondary); color: var(--text-primary); }
html.has-irail .crail-collapse:focus-visible { outline: 2px solid var(--accent-violet); outline-offset: 1px; }

/* Anything the old rail pinned to --crail-w has to follow the new geometry. */
/* .course-editor is in the selector to outrank course-rail.js's own
   `html.course-editor .ow-bar { left: var(--crail-w) }`. That rule is injected at
   runtime, so at equal specificity it wins the tie and the dock stayed pinned at
   the OLD 270px rail width — 214px right of the icon rail it is supposed to sit
   flush against, and straight over the document. */
html.has-irail.course-editor .ow-bar,
html.has-irail .ow-bar { left: var(--irail-w); }

/* Collapsing the course PANEL must not move a dock under the icon rail.
   course-rail.js carries `html.course-editor.crail-off .ow-bar { left: 0 }` from
   before the icon rail existed, when collapsing the 270px rail genuinely did free
   the window's left edge. The rail now occupies 0-56px permanently, so left:0 put
   the dock UNDERNEATH it: the author collapsed the panel and the dock's text was
   clipped by the rail sitting on top of it — which is what "collapse button not
   working" actually looked like, because the click did that instead of anything
   useful. The floor is the rail's right edge, collapsed or not. */
html.has-irail.crail-off .ow-bar,
html.has-irail.course-editor.crail-off .ow-bar { left: var(--irail-w); }
html.has-irail.irail-open.course-editor .ow-bar,
html.has-irail.irail-open .ow-bar { left: calc(var(--irail-w) + var(--irail-panel-w)); }

/* …and so does the space the document leaves for it.
   course-rail.js offsets .content-area by the dock's width alone, which was
   right when the course rail was an in-flow column that had already pushed the
   content across. The rail is `fixed` now, so the content pane starts at x=0 and
   that offset left the dock sitting ON TOP of the first 246px of the outline —
   the document was underneath the panel, not beside it. The offset has to
   account for every fixed pane to its left: icon rail, the open detail panel,
   then the dock, then a gutter.
   The selector carries .course-editor as well, purely to outrank the
   equally-specific rule in course-rail.js, which is injected at runtime and
   would otherwise win the tie. */
/* ── The content pane's left offset, computed once ───────────────────────────
   Everything to the left of the document is `position: fixed`, so the pane has to
   reserve room for it or the chrome paints over the content. This used to be
   several rules in two files, each aware of ONE pane: course-rail.js offset for
   the tool dock, icon-rail.css offset for the rail, and the base case offset for
   neither. On the outline stage they happened to add up; on the slides stage there
   is no dock, so nothing accounted for the 56px rail and the content started at
   24px underneath it — the clipped text in the report.

   Now each pane contributes a term, defaulting to zero, and ONE rule sums them. A
   new fixed pane adds a variable instead of another competing rule, and a stage
   that lacks a pane simply contributes 0 rather than falling into a different
   rule. */
html.has-irail {
    --irail-panel-open-w: 0px;   /* the sliding course panel, when open */
    --irail-dock-w: 0px;         /* a stage's own tool dock, when docked */
}
html.has-irail.irail-open { --irail-panel-open-w: var(--irail-panel-w); }
html.has-irail.studio-dock { --irail-dock-w: var(--ow-rail-w, 248px); }

/* ── A GUTTER IS ONLY OWED TO A PANE THAT IS ACTUALLY A COLUMN ───────────────
 *
 * Reported as "i see few thing cuttin when i open up" with a screenshot showing a
 * wide empty band down the left of the outline and the document squeezed into what
 * was left.
 *
 * Measured at 1024x900 on the outline stage: `.content-area` had
 * `padding-left: 328px` (56 rail + 248 dock + 24 gutter) and the document therefore
 * got 640px of a 1024px viewport - a third of the screen held open, and the text
 * inside the remaining third wrapping and truncating because of it.
 *
 * The 248px was reserved for the outline tool dock. But `outline-workspace.css`
 * already turns that dock into a full-width HORIZONTAL band at `max-width: 1100px`
 * (`.ow-bar { order: 1; position: sticky; width: 100% }`) - it stops being a left
 * column entirely. So below 1100px the arithmetic above was reserving a column's
 * width for something that had become a row. Two rules in two files, each correct
 * about its own half, describing different layouts.
 *
 * The term goes to zero at the same breakpoint where the dock stops being a column.
 * Same reasoning as `test_the_dock_reserves_no_gutter_for_an_off_canvas_rail`
 * applied to the other pane: reserve space for what is beside the document, and
 * nothing for what is above it. */
@media (max-width: 1100px) {
    html.has-irail.studio-dock { --irail-dock-w: 0px; }
}

/* ── ONE OVERLAY AT A TIME on anything narrower than a laptop ────────────────
 *
 * "these side left side panel is not looking on mobile app." The screenshot shows
 * three chrome columns at once - the 56px icon rail, then the ~270px course tree,
 * then the outline dock pushed half off the right edge with its counts cut in half.
 * Each panel opens independently and none of them knows the others exist, so on a
 * narrow viewport they simply queue up left-to-right until the last one runs out of
 * screen.
 *
 * Below 1100px the course panel overlays the document instead of sitting beside it,
 * and the document stops reserving anything for it: an overlay is temporary, and
 * reserving width for a temporary thing is what produced the dead band above. The
 * panel gets a real usable width rather than the 216px it was squeezed to, because
 * a narrow screen is where the module titles need the most room, not the least. */
@media (max-width: 1100px) {
    html.has-irail { --irail-panel-open-w: 0px; }
    html.has-irail.irail-open { --irail-panel-open-w: 0px; }
    /* WIDENED VIA THE TOKEN, not by overriding the width on two selectors.
     * `--irail-panel-w` is the single source of the panel's width - `.crail`,
     * `.irail-panel` and the `.ow-bar` offset all read it - so moving the token is
     * the only change that cannot leave one of them disagreeing with the others.
     * The base value is `clamp(216px, 22vw, 288px)`, i.e. it gets NARROWER as the
     * viewport shrinks, which is backwards for an overlay: a narrow screen is where
     * a module title needs the most room, not the least. As an overlay it costs the
     * document nothing, so it can afford to be wide. */
    html.has-irail { --irail-panel-w: min(88vw, 320px); }
    html.has-irail.irail-open .crail,
    html.has-irail .irail-panel {
        box-shadow: 8px 0 28px rgb(0 0 0 / 0.16);
    }
    /* The dock is a band above the document at this width, so it must not be
     * pushed across for a panel that is floating over the top of it either. */
    html.has-irail.irail-open .ow-bar,
    html.has-irail.irail-open.course-editor .ow-bar { left: var(--irail-w); }
}

/* `.content-area` is home.html's document pane. `.stage-pane` is the same role on a
   stand-alone stage editor, which has no stepper and therefore no .content-area — so
   it takes the identical composed offset rather than a second rule of its own. That
   is the whole point of composing it: a new pane adds a NAME here, not arithmetic
   somewhere else.

   SCOPED to .course-editor: on the Home page at step 1 (the composer/templates),
   no course is open and the content should be CENTRED, not pushed right by 80px
   to make room for a rail that is not showing anything useful yet. */
html.has-irail.course-editor .content-area,
html.has-irail.course-editor .app-layout > .stage-pane {
    padding-left: calc(
        var(--irail-w) + var(--irail-panel-open-w) + var(--irail-dock-w) + 1.5rem);
}

/* ── Generic detail panel (reviewers, modules, and future sections) ───── */

.irail-panel {
    position: fixed;
    top: var(--topbar-h, 48px);
    bottom: 0;
    left: var(--irail-w);
    z-index: 55;
    width: var(--irail-panel-w);
    display: flex;
    flex-direction: column;
    background: var(--bg-card);
    border-right: 1px solid var(--border-subtle);
    transform: translateX(-100%);
    visibility: hidden;
}

html.irail-open .irail-panel.is-active {
    transform: translateX(0);
    visibility: visible;
}

.irail-panel-head {
    flex: none;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 12px;
    border-bottom: 1px solid var(--border-subtle);
}

.irail-panel-title {
    font-size: var(--text-sm);
    font-weight: 700;
    color: var(--text-primary);
    letter-spacing: -0.01em;
}

.irail-panel-close {
    margin-left: auto;
    width: 24px;
    height: 24px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 0;
    border-radius: 6px;
    background: transparent;
    color: var(--text-muted);
    font-size: var(--text-sm);
    cursor: pointer;
    transition: background var(--irail-ease), color var(--irail-ease);
}

.irail-panel-close:hover { background: var(--bg-secondary); color: var(--text-primary); }
.irail-panel-close:focus-visible { outline: 2px solid var(--accent-violet); outline-offset: 1px; }

.irail-panel-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 10px 12px 16px;
}

/* ── Narrow viewports: the panel overlays instead of pushing ──────────── */

@media (max-width: 900px) {
    html.has-irail.irail-open .app-layout { padding-left: var(--irail-w); }
    html.has-irail.irail-open .ow-bar { left: var(--irail-w); }
    .irail-panel,
    html.has-irail .crail { box-shadow: 8px 0 24px rgb(0 0 0 / 0.12); }
}

@media (max-width: 640px) {
    :root { --irail-panel-w: calc(100vw - var(--irail-w)); }
    .irail-btn .irail-label { display: none; }
    .irail-btn { min-height: 40px; }
}

/* On mobile the icon rail is off-canvas by default, overlaid when opened.
 *
 * The desktop design docks the rail permanently at 56px, which is fine on a 1440px
 * display. On a 390px phone that is 14% of the viewport occupied by chrome the
 * author cannot tap without precision — and the screenshot report showed exactly
 * this: the rail sitting on top of content with labels partially visible.
 *
 * The rail is accessible via a swipe or the hamburger; it should not eat the
 * viewport at rest. The panel (course contents, reviewers) is full-width on mobile,
 * which is what `--irail-panel-w: calc(100vw - var(--irail-w))` above was already
 * doing for the panel — but the rail itself was never hidden.
 */
@media (max-width: 768px) {
    /* ── A REAL DRAWER, not a 56px sliver of unlabelled icons ────────────────
     *
     * Reported as "this side menu is not shoing up on mobile". The mechanism was
     * in fact working - the hamburger toggles `irail-mobile-open` and the rail
     * slid in - but what slid in was the DESKTOP rail: 56px wide, with
     * `.irail-btn .irail-label { display: none }` from the 560px block above
     * still in force. So opening it produced a narrow strip of four unlabelled
     * glyphs overlaying the page. Technically visible, effectively absent, and
     * indistinguishable from a rendering fault - which is exactly how it was
     * reported.
     *
     * A phone drawer is wide and it is labelled. 78vw capped at 264px leaves the
     * page edge visible behind it, so it reads as a temporary overlay you dismiss
     * rather than a new screen you have navigated to. */
    nav.irail {
        transform: translateX(-100%);
        z-index: 200;
        /* A fixed position so it overlays content when open. */
        position: fixed;
        top: var(--topbar-h, 48px);
        bottom: 0;
        left: 0;
        width: min(78vw, 264px);
        padding: 8px 8px 12px;
        gap: 2px;
        overflow-y: auto;
        box-shadow: none;
    }
    /* Opened by a class on <html>, toggled by the hamburger or a swipe. */
    html.irail-mobile-open nav.irail {
        transform: translateX(0);
        box-shadow: 6px 0 20px rgb(0 0 0 / 0.15);
    }
    /* Full-width rows: icon then label, left aligned, thumb-sized. This is the
     * shape every native drawer uses, and it is why the labels can come back. */
    .irail-btn {
        width: 100%;
        min-height: 48px;
        flex-direction: row;
        justify-content: flex-start;
        gap: 12px;
        padding: 0 12px;
        border-radius: 10px;
    }
    /* THE LABELS COME BACK. Overriding the 560px rule above, which exists for a
     * docked 44px rail and is wrong for a drawer. This block is later in the file
     * at equal specificity, so it wins without !important. */
    .irail-btn .irail-label {
        display: block;
        max-width: none;
        font-size: var(--text-sm);
        letter-spacing: 0;
    }
    .irail-btn .irail-ico { width: var(--icon-lg); height: var(--icon-lg); }
    .irail-btn .irail-ico svg { width: var(--icon-lg); height: var(--icon-lg); }
    .irail-sep { width: 100%; margin: 6px 0; }
    /* The active marker sits at left:-8px for a docked rail, which is off-canvas
     * in a drawer. Inside the row instead. */
    .irail-btn[aria-current="true"]::before,
    .irail-btn[aria-current="page"]::before { left: 0; }
    /* No hover tooltips on a touch drawer: the label is right there, and a tooltip
     * on tap would cover the row it describes. No longer scoped off the locked
     * buttons - the padlock has its own element now, so `::after` is the tooltip on
     * every button and suppressing it here is unambiguous. */
    .irail-btn::after { content: none; }
    /* A drawer row is a full-width line of icon-then-label, so the badge rides the
     * icon's lower-right rather than the button's far corner ~200px away. */
    .irail-lock { top: 50%; left: 24px; right: auto; margin-top: -2px; }
    /* Content takes the full width — no left offset for the rail. */
    html.has-irail .app-layout,
    html.has-irail.irail-open .app-layout {
        padding-left: 0 !important;
    }
    html.has-irail .content-area,
    html.has-irail .app-layout > .stage-pane {
        padding-left: 0 !important;
    }
    /* AND NEITHER DOES THE DOCK. The desktop rules pin `.ow-bar` to
     * `left: var(--irail-w)` because the rail is docked there and permanently
     * visible. On a phone the rail is off-canvas, so that 56px was reserved for
     * something not on screen: a dead gutter down the left of every stage, and
     * 56px is 14% of a 390px viewport. The dock and the document both start at
     * the edge, and the drawer overlays them when it opens - which is the whole
     * point of an overlay. */
    html.has-irail .ow-bar,
    html.has-irail.course-editor .ow-bar,
    html.has-irail.crail-off .ow-bar,
    html.has-irail.course-editor.crail-off .ow-bar,
    html.has-irail.irail-open .ow-bar,
    html.has-irail.irail-open.course-editor .ow-bar {
        left: 0;
        width: 100%;
    }
    /* The dock stacks ABOVE the document on a phone rather than beside it: two
     * columns in 390px gives neither one enough room to be read. */
    .ow-shell { flex-direction: column; }
    .ow-bar.ow-rail {
        position: static;
        width: 100%;
        max-height: none;
        border-right: 0;
        border-bottom: 1px solid var(--border-subtle);
    }
    /* The panel goes full-screen on mobile, no rail beside it. */
    html.has-irail .crail,
    .irail-panel {
        left: 0;
        width: 100vw;
    }
    /* A scrim behind the open rail/panel. Added by JS. */
    .irail-scrim {
        display: none;
        position: fixed;
        inset: 0;
        z-index: 199;
        background: rgb(0 0 0 / 0.3);
    }
    html.irail-mobile-open .irail-scrim,
    html.has-irail.irail-open .irail-scrim {
        display: block;
    }
}

/* Respect a user who has asked the OS for less motion. */
@media (prefers-reduced-motion: reduce) {
    .irail-btn,
    .irail-panel,
    html.has-irail .crail,
    html.has-irail .app-layout,
    html.has-irail .ow-bar,
    html.has-irail-ready .app-layout,
    html.has-irail-ready .crail,
    html.has-irail-ready .irail-panel,
    html.has-irail-ready .ow-bar { transition: none !important; }
}

/* ── Reclaim the top of the editor ────────────────────────────────────────
 *
 * Measured on a live page at 1280x860 before this block, from the bottom of the
 * 56px top bar down to the first line of actual content:
 *
 *   .app-layout        padding-top  16px
 *   section.hero-compact            41px   "Course Authoring Studio" + tagline
 *   .step-card         padding-top  19px
 *   -----------------------------------
 *                                   76px  of chrome above the content
 *
 * The hero repeats what the top bar already says, and the two paddings stack
 * with the card's own. Total is now 8px. Everything here is scoped to
 * html.has-irail so only the editor shell is affected — the landing page keeps
 * its hero.
 */

html.has-irail .app-layout { padding-top: 0; }

/* The hero duplicates the top bar's own title, so in the editor it is pure
   vertical cost. Removed rather than shrunk: a 41px band that says what the
   chrome above it already said is not worth any pixels. */
html.has-irail .hero-compact { display: none; }

/* The card supplies the only top gap that remains, and it is a small one. */
html.has-irail .step-card { padding-top: 8px; }

/* The scroller must not add a second one under the card's. */
html.has-irail .content-scroll { padding-top: 0; }

/* When the action bar is present it becomes the first thing under the top bar,
   so it carries the separation instead of a dedicated spacer. */
html.has-irail .top-actionbar:not(:empty) {
    padding-top: 6px;
    padding-bottom: 6px;
}

/* ── The pinned footer entry: Developers ────────────────────────────────────
   Sits after .irail-spacer, so it is held in the extreme bottom-left corner
   whatever the workflow above it contains, in the same column as Course /
   Outline / Slides / Script. It rests COLLAPSED like every other rail entry and
   opens its panel on click, which is the whole ask: a permanent fixture in the
   corner that expands when you want it.

   A hairline above it separates "where you are in the course" from "everything
   technical", which are different kinds of destination. */
.irail-btn-foot {
    border-radius: 0;
    color: var(--text-muted);
}
/* The hairline and the auto margin belong to the FIRST footer entry only.
   There are two now - the settings gear and the account avatar - and leaving
   these on the shared class drew a second rule between them and pushed them
   apart, because `margin-top:auto` on the second child claims the free space
   the .irail-spacer had already claimed. The corner is one group: one rule
   above it, the two entries stacked tight underneath. */
.irail-btn-foot:not(.irail-account) {
    margin-top: auto;
    border-top: 1px solid var(--border-subtle);
}
.irail-btn-foot:hover { color: var(--accent-violet); }

/* ── The account avatar ─────────────────────────────────────────────────────
   Initials in a filled circle, the way every studio app marks the signed-in
   user, in the rail's last slot. The fill comes from the user's own
   --accent-* token (set inline by icon-rail.js from /api/auth/me's accent_var),
   so it is a brand accent rather than a colour invented in CSS.

   --on-brand for the text, not white: the accent set includes amber and green,
   and --on-brand is the token that is contrast-checked against branded fills. */
.irail-account { text-decoration: none; }
.irail-avatar {
    width: 26px;
    height: 26px;
    border-radius: var(--radius-full, 999px);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: var(--text-2xs);
    font-weight: var(--weight-bold, 600);
    line-height: 1;
    letter-spacing: 0;
    color: var(--on-brand);
    background: var(--accent-indigo);
    /* Initials are DATA in a fixed-width circle, so they must not inherit the
       body's negative tracking or a two-letter pair sits off-centre. */
    font-variant-numeric: tabular-nums;
    flex: none;
}
/* Signed out: a neutral ring rather than a coloured circle with nothing in it. */
.irail-avatar.anon {
    background: transparent;
    border: 1.5px dashed var(--border-subtle);
    color: var(--text-muted);
}

/* ── The Developers panel ───────────────────────────────────────────────────
   Built by icon-rail.js (nothing else owns it) and given the standard panel
   chrome by wrapPanels(), so it opens, closes and remembers its state exactly
   like the Course panel rather than being a special case. */
.drail { display: flex; flex-direction: column; }
.drail-body { display: flex; flex-direction: column; gap: 0.15rem; padding: 0.2rem 0; }
.drail-link {
    display: flex;
    flex-direction: column;
    gap: 0.05rem;
    padding: 0.4rem 0.5rem;
    border-radius: var(--radius-sm, 8px);
    text-decoration: none;
    color: var(--text-secondary);
}
.drail-link b {
    font-size: var(--text-xs);
    font-weight: 750;
    color: var(--text-primary);
    line-height: 1.3;
}
/* The hint is the point of the panel: an author who does not already know what
   "Versions" holds learns it here rather than by clicking and finding out. */
.drail-link span {
    font-size: var(--text-2xs);
    color: var(--text-muted);
    line-height: 1.35;
}
.drail-link:hover {
    /* Background AND foreground together, per the repo-wide hover rule (spec S1). */
    background: color-mix(in srgb, var(--accent-violet) 9%, transparent);
}
.drail-link:hover b,
.drail-link:hover span { color: var(--accent-violet); }
.drail-foot {
    margin: 0.5rem 0 0;
    padding-top: 0.5rem;
    border-top: 1px solid var(--border-subtle);
    font-size: var(--text-2xs);
    line-height: 1.45;
    color: var(--text-muted);
}
