/*
    SJCreator's own layout. Everything it does not declare — colour, type, spacing, motion — comes
    from the shared library's tokens, so the studio and the product cannot drift apart.

    Deliberately desktop-shaped: editors work at a desk with a wide screen and a lot of rows on it,
    which is the opposite of the phone the child app is designed for.
*/

/*
    The same reset `app.css` has had all along, and the studio did not.

    Without it a control with `width: 100%` and any padding is wider than the box it sits in, by
    exactly its padding and borders. Invisible on a desk — the overflow goes into the margin — and
    six pixels of sideways page scroll at 375px, which moves the sidebar off screen. Found on the
    Story tab's narration fields, but it was never only those: the source library's search box and
    quote textarea have the same shape.
*/
*,
*::before,
*::after {
    box-sizing: border-box;
}

/*
    The studio density layer (P1). The brand does not fork — every colour below is one of the shared
    tokens or a quiet derivative of one — but the *density* does: the shared scale is authored for a
    child's thumb on a phone, and an editor works at a desk for hours. `--sjs-*` is the studio's own
    prefix, guarded by the same DesignTokenTests that caught the five nonexistent `--sj-*` names.

    Two derivatives and why they are not tokens.css's business:

    - `--sjs-bench` is the page ground: the App's canvas (#fff6e9) desaturated. The stories are
      colourful; the studio is the neutral bench they sit on — a cream ground competes with the
      artwork it exists to show. White cards on this bench read as raised without borrowing weight.
    - `--sjs-inset` is the ground a chip or a segmented control sits on. `.creator__badge` used to
      be `background: var(--sj-color-surface)` — white, on containers that are all white, so every
      badge in ten files rendered as bare text with stray padding. A chip must always differ from
      the surface it sits on, and a test now says so.

    The radius scale is the real departure: 6/8/10px against the App's 10/16/24. The blob and the
    pill stay in the child app.
*/
:root {
    --sjs-bench: #f6f2ea;
    --sjs-inset: #f0e9db;

    --sjs-radius-sm: 0.375rem;
    --sjs-radius-md: 0.5rem;
    --sjs-radius-lg: 0.625rem;

    /* Aliases, so the studio reads 15/13/12 where the App reads 17/15/13. */
    --sjs-text-base: var(--sj-text-sm);
    --sjs-text-sm: var(--sj-text-xs);
    --sjs-text-xs: var(--sj-text-2xs);

    /* 36px controls. Primary actions — save, approve, decide — keep --sj-touch-min (44px). */
    --sjs-control: 2.25rem;

    /*
        Radzen, at studio density. `radzen-overrides.css` is shared with the App and stays at the
        App's values; this file loads after it and only the studio links it, so these win here and
        change nothing there. `--rz-input-height` was `--sj-touch-comfortable` — 52px, a child's
        target on every text field an editor types into all day.
    */
    --rz-body-background-color: var(--sjs-bench);
    --rz-border-radius: var(--sjs-radius-lg);
    --rz-input-border-radius: var(--sjs-radius-md);
    --rz-button-border-radius: var(--sjs-radius-md);
    --rz-input-height: var(--sjs-control);
}

/*
    The shell's colour, and deliberately nothing about its layout.

    This class sits on `RadzenLayout`, and it used to declare
    `display: flex; flex-direction: column; min-height: 100vh` — which is what the hand-rolled shell
    needed before CF's Radzen conversion, and which nobody removed afterwards. `.rz-layout` is
    `display: grid` with named areas ("rz-header rz-header" / "rz-sidebar rz-body"), the two
    selectors have identical specificity, and `creator.css` is linked after `standard-base.css`.
    So this one rule won, the grid never applied, and the whole studio stacked vertically: the
    sidebar became a 250x85 band wedged between the header and the content, with `overflow: auto`
    clipping half the menu inside it. That is what "the menu does not work" was — the items were
    there, two of them scrolled out of an 85px box.

    Nothing here may declare `display`, `position`, `height` or `grid-*` again. The layout belongs
    to Radzen; this file supplies the brand (ADR-0013).
*/
.creator {
    background: var(--sjs-bench);
    color: var(--sj-color-ink);
    /*
        15px, not the App's 17. This is also what makes several long-invisible hover states work:
        `.creator__tab:hover` and `.creator__scene:hover` paint `--sj-color-surface`, which on the
        old white ground changed no pixel.
    */
    font-size: var(--sjs-text-base);
}

.creator__bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sj-space-4);
    padding: var(--sj-space-3) var(--sj-space-5);
    border-bottom: 1px solid var(--sj-color-border);
    background: var(--sj-color-surface);
}

.creator__brand {
    font-family: var(--sj-font-display);
    font-size: var(--sj-text-lg);
    font-weight: 600;
    /* Radzen headings carry their own fixed ink, which no theme reaches: after dark the wordmark
       measured 1.01:1 against the header — on the screen and invisible. The token follows the
       theme; Radzen's default does not. */
    color: var(--sj-color-ink);
}

/*
    `.creator__body`, `.creator__nav` and `.creator__nav-link` used to live here — the hand-rolled
    sidebar the Radzen conversion replaced. Not one of them appeared in a `.razor` file any more,
    so they styled nothing; they are gone rather than kept "in case", because dead layout rules
    beside live ones are how `.creator` itself survived long enough to break the grid.

    The focus ring they carried has moved to the Radzen menu link below, which is the control that
    actually takes focus now.
*/

/*
    Spelled out rather than behind a token, because the token this used to name did not exist.

    `outline: var(--sj-focus-ring)` with no such custom property is an invalid declaration, so the
    browser dropped it and the sidebar links had no focus ring at all — invisible in every
    screenshot and only findable by tabbing. This is the same value `app.css` and
    `radzen-overrides.css` already use, which is what makes it a convention.
*/
.creator .rz-navigation-item-link:focus-visible {
    outline: 3px solid var(--sj-color-focus);
    outline-offset: -3px;
}

/*
    No `flex` and no `min-width`: this sits inside `.rz-body`, which is a grid area rather than a
    flex container, so both were inert. Padding is all it was ever really contributing.
*/
.creator__main {
    padding: var(--sj-space-5);
}

.creator__field {
    display: block;
    margin-bottom: var(--sj-space-4);
}

.creator__field > span {
    display: block;
    margin-bottom: var(--sj-space-1);
    font-weight: 600;
}

.creator__field input {
    width: 100%;
    max-width: 24rem;
    padding: var(--sj-space-2) var(--sj-space-3);
    border: 1px solid var(--sj-color-border);
    border-radius: var(--sjs-radius-lg);
    font: inherit;
}

/* The same box as a notice, in the error tone — the two swap for each other at every call site. */
.creator__error {
    display: block;
    width: fit-content;
    padding: var(--sj-space-2) var(--sj-space-3);
    border-radius: var(--sjs-radius-md);
    background: var(--sj-color-danger-soft, var(--sjs-inset));
    color: var(--sj-color-danger);
    font-size: var(--sjs-text-sm);
    font-weight: 600;
}

/*
    The narrow-screen rule for the hand-rolled shell used to live here, restacking `.creator__body`
    and `.creator__nav`. Both classes are gone, so it styled nothing — the third survivor of the
    same conversion, found by grepping this file's own selectors against the markup rather than by
    looking at the screen, which is the only way any of them show up.

    Radzen's sidebar has its own narrow-screen behaviour, and `CreatorLayout` deliberately opts out
    of it with `Responsive="false"` — the reasoning is on that component.
*/

/* ---------------------------------------------------------------- dashboard */

.creator__counts {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sj-space-3);
    margin-bottom: var(--sj-space-5);
}

/* Every count is a link. A number an editor cannot follow is a number they have to act on
   somewhere else, which is where dashboards stop being used. */
.creator__count {
    display: flex;
    flex-direction: column;
    gap: var(--sj-space-1);
    min-width: 9rem;
    padding: var(--sj-space-3) var(--sj-space-4);
    border: 1px solid var(--sj-color-border);
    border-radius: var(--sjs-radius-lg);
    background: var(--sj-color-surface);
    color: inherit;
    text-decoration: none;
}

.creator__count:focus-visible {
    outline: 3px solid var(--sj-color-focus);
    outline-offset: 2px;
}

.creator__count--attention {
    border-color: var(--sj-color-warning, var(--sj-color-border));
}

/*
    Chrome, not content. The number was Fraunces — the storybook voice on a count of jobs — and the
    split is the rule: Fraunces speaks for the story (titles, the words a child reads), Nunito for
    the tool. A count also reads in a column against other counts, hence the tabular figures.
*/
.creator__count-value {
    font-size: var(--sj-text-xl);
    font-weight: var(--sj-weight-bold);
    font-variant-numeric: tabular-nums;
}

.creator__count-label {
    color: var(--sj-color-ink-muted);
}

/* -------------------------------------------------------------------- tables */

.creator__table {
    width: 100%;
    border-collapse: collapse;
    margin-bottom: var(--sj-space-5);
}

.creator__table th,
.creator__table td {
    padding: var(--sj-space-2) var(--sj-space-3);
    border-bottom: 1px solid var(--sj-color-border);
    text-align: left;
    vertical-align: top;
}

.creator__table th {
    color: var(--sj-color-ink-muted);
    font-size: var(--sj-text-sm);
}

/* Tone rather than colour alone: the word is always present, so a status is readable without
   relying on hue (spec 17). The five tones are one language learned once — a person's turn is
   amber, working is teal, blocked is red, done is quiet green, and nothing owed is neutral — and
   every tone pairs a ground with its own ink, because a soft wash under default ink has no
   hierarchy at all. The neutral ground is `--sjs-inset`, never `--sj-color-surface`: white chips
   on white containers is how every badge in ten files rendered as bare text for a whole phase. */
.creator__status,
.creator__failure {
    display: inline-flex;
    align-items: center;
    gap: var(--sj-space-1);
    padding: 0.125rem var(--sj-space-2);
    border-radius: var(--sjs-radius-sm);
    background: var(--sjs-inset);
    color: var(--sj-color-ink-muted);
    font-size: var(--sjs-text-xs);
    font-weight: var(--sj-weight-medium);
}

.creator__status[data-tone="attention"],
.creator__failure[data-tone="budget"] {
    background: var(--sj-color-warning-soft, var(--sjs-inset));
    color: var(--sj-color-warning, var(--sj-color-ink));
}

.creator__status[data-tone="bad"],
.creator__failure[data-tone="permanent"] {
    background: var(--sj-color-danger-soft, var(--sjs-inset));
    color: var(--sj-color-danger, var(--sj-color-ink));
}

/*
    Working is the one state that claims something is happening right now, so it is the one that
    moves — a breathing dot, not a spinner: it says "alive", not "wait". The global reduced-motion
    collapse in tokens.css freezes it at full opacity, which is its finished state.
*/
.creator__status[data-tone="busy"]::before {
    content: "";
    flex: 0 0 auto;
    inline-size: 0.5rem;
    block-size: 0.5rem;
    border-radius: 50%;
    background: var(--sj-color-primary);
    animation: sjs-breathe 1.6s ease-in-out infinite;
}

@keyframes sjs-breathe {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.35; }
}

@media (prefers-reduced-motion: reduce) {
    .creator__status[data-tone="busy"]::before {
        animation: none;
    }
}

.creator__flag,
.creator__badge {
    display: inline-flex;
    align-items: center;
    gap: var(--sj-space-1);
    margin-left: var(--sj-space-2);
    padding: 0.125rem var(--sj-space-2);
    border-radius: var(--sjs-radius-sm);
    background: var(--sjs-inset);
    color: var(--sj-color-ink-muted);
    font-size: var(--sjs-text-xs);
    font-weight: var(--sj-weight-medium);
}

.creator__muted {
    color: var(--sj-color-ink-muted);
}

/* A family's own words in a table cell: wrap rather than stretch the row off the screen. */
.creator__prose {
    max-width: 36rem;
    overflow-wrap: anywhere;
    white-space: pre-line;
}

.creator__actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sj-space-2);
    margin-bottom: var(--sj-space-4);
}

.creator__pipeline,
.creator__outputs,
.creator__escalations {
    margin: 0 0 var(--sj-space-5);
    padding-left: var(--sj-space-5);
}

.creator__pipeline li,
.creator__outputs li,
.creator__escalations li {
    margin-bottom: var(--sj-space-2);
}

.creator__check {
    display: inline-flex;
    align-items: center;
    gap: var(--sj-space-1);
    margin-right: var(--sj-space-3);
}

/* ------------------------------------------------------------- error banner */

/*
    Blazor's error UI, which the framework unhides by setting `display: block` when something
    throws. Without these rules it has no `display: none` to start from and is therefore visible
    on every page — permanently announcing a fault, and unable to announce a real one.

    Found by opening the studio: it said "Something went wrong" on a screen where nothing had.
*/
#blazor-error-ui {
    display: none;
    position: fixed;
    inset-inline: 0;
    bottom: 0;
    z-index: var(--sj-z-dialog);
    padding: var(--sj-space-4) calc(var(--sj-space-6) + 1rem) var(--sj-space-4) var(--sj-space-4);
    background: var(--sj-color-warning-soft);
    color: var(--sj-color-ink);
    box-shadow: var(--sj-shadow-lg);
}

#blazor-error-ui .dismiss {
    position: absolute;
    inset-block-start: var(--sj-space-2);
    inset-inline-end: var(--sj-space-3);
    cursor: pointer;
}

/* ------------------------------------------------------------ source library */

.creator__search {
    flex: 1 1 20rem;
    max-width: 32rem;
    padding: var(--sj-space-2) var(--sj-space-3);
    border: 1px solid var(--sj-color-border);
    border-radius: var(--sjs-radius-lg);
    font: inherit;
}

.creator__field textarea,
.creator__field select {
    width: 100%;
    max-width: 48rem;
    padding: var(--sj-space-2) var(--sj-space-3);
    border: 1px solid var(--sj-color-border);
    border-radius: var(--sjs-radius-lg);
    font: inherit;
}

/* Numbered, because the number is the ranking. An editor reading this list is reading the order
   the Research agent would see, and a bulleted list would hide that it is an order at all. */
.creator__hits {
    margin: 0 0 var(--sj-space-5);
    padding-left: var(--sj-space-5);
}

.creator__hits > li {
    margin-bottom: var(--sj-space-4);
}

.creator__hit-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--sj-space-2);
    margin: 0 0 var(--sj-space-1);
}

/* Source text is quoted, never restyled: what is on screen has to be what is in the chunk, because
   an editor checking a quotation against it is checking characters. */
.creator__chunk {
    margin: 0;
    padding: var(--sj-space-2) var(--sj-space-3);
    border-left: 3px solid var(--sj-color-border);
    background: var(--sj-color-surface);
    white-space: pre-wrap;
}

.creator__verdict {
    padding: var(--sj-space-2) var(--sj-space-3);
    border-radius: var(--sjs-radius-lg);
    background: var(--sjs-inset);
    font-weight: 600;
}

.creator__verdict[data-tone="good"],
.creator__status[data-tone="good"] {
    background: var(--sj-color-success-soft, var(--sjs-inset));
    color: var(--sj-color-success, var(--sj-color-ink));
}

.creator__verdict[data-tone="bad"] {
    background: var(--sj-color-danger-soft, var(--sjs-inset));
    color: var(--sj-color-danger, var(--sj-color-ink));
}

.creator__status[data-tone="waiting"] {
    background: var(--sj-color-warning-soft, var(--sjs-inset));
    color: var(--sj-color-warning, var(--sj-color-ink));
}

/*
    A box, not a colour on a sentence. It used to be `color: success; font-weight: 600` and nothing
    else — a stray green line, used for two different things: the result of an action ("saved as
    version 6") and a standing note ("edit these words on the Scenes tab"). Only the first is good
    news, so only the first is green: every action result in the studio carries `role="status"`
    already, which is the honest selector — the distinction was in the markup all along.
*/
.creator__notice {
    display: block;
    width: fit-content;
    padding: var(--sj-space-2) var(--sj-space-3);
    border-radius: var(--sjs-radius-md);
    background: var(--sjs-inset);
    color: var(--sj-color-ink);
    font-size: var(--sjs-text-sm);
    font-weight: 600;
}

.creator__notice[role="status"] {
    background: var(--sj-color-success-soft, var(--sjs-inset));
    color: var(--sj-color-success, var(--sj-color-ink));
}

.creator__verdict-detail {
    margin-inline-start: var(--sj-space-1);
    font-weight: 400;
}

/* ------------------------------------------------------------- truth contract */

/*
    Three tones, and the red one is not decoration. An unverified quotation is the most serious
    defect this system can produce, so it is rendered as an error everywhere it appears rather
    than as a shade of warning - and the tone is carried on the entry as well as on its badge, so
    an editor scanning the list sees it without reading.
*/
.creator__check[data-tone="good"] {
    color: var(--sj-color-success, var(--sj-color-ink));
}

.creator__check[data-tone="bad"] {
    color: var(--sj-color-danger, var(--sj-color-ink));
    font-weight: 600;
}

.creator__lock,
.creator__escalation-banner {
    margin: 0 0 var(--sj-space-5);
    padding: var(--sj-space-3) var(--sj-space-4);
    border-radius: var(--sjs-radius-lg);
    background: var(--sj-color-surface);
}

.creator__lock[data-tone="bad"] {
    background: var(--sj-color-danger-soft, var(--sj-color-surface));
}

.creator__lock[data-tone="attention"],
.creator__escalation-banner[data-tone="attention"] {
    background: var(--sj-color-warning-soft, var(--sj-color-surface));
}

.creator__lock p,
.creator__escalation-banner p {
    margin: 0 0 var(--sj-space-1);
}

.creator__truth {
    margin: 0 0 var(--sj-space-5);
    padding-left: var(--sj-space-5);
}

.creator__truth > li {
    margin-bottom: var(--sj-space-4);
}

.creator__truth > li p {
    margin: 0 0 var(--sj-space-1);
}

/* The whole entry, not only its badge: the failure has to be visible while scanning. */
.creator__truth > li[data-tone="bad"] {
    padding-left: var(--sj-space-3);
    border-left: 3px solid var(--sj-color-danger, var(--sj-color-border));
}

/*
    Chunk ids and content hashes wrap rather than widening the page.

    Both are long unbroken strings with no space to break at, so without this the lock banner's
    hash alone is 457px inside a 375px viewport and the whole document scrolls sideways - found by
    opening the tab at phone width, where the hash simply ran off the edge and was unreadable.
    The rule has to name the lock as well as the list: covering only one leaves the other doing it.
*/
.creator__truth code,
.creator__lock code {
    overflow-wrap: anywhere;
}

/* ------------------------------------------------------------- production tabs */

.creator__tabs {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sj-space-1);
    margin-bottom: var(--sj-space-5);
    border-bottom: 1px solid var(--sj-color-border);
}

.creator__tab {
    padding: var(--sj-space-2) var(--sj-space-3);
    border-bottom: 3px solid transparent;
    color: var(--sj-color-ink);
    text-decoration: none;
}

.creator__tab:hover {
    background: var(--sj-color-surface);
}

.creator__tab.active {
    border-bottom-color: var(--sj-color-primary);
    font-weight: 600;
}

.creator__tab:focus-visible {
    outline: 3px solid var(--sj-color-focus);
    outline-offset: -3px;
}

/* --------------------------------------------------------------- story tab */

/*
    Scene list beside the selected scene. Collapses to one column below the tablet break rather
    than shrinking both: two 40%-wide columns of prose is worse than scrolling.
*/
.creator__split {
    display: grid;
    grid-template-columns: minmax(16rem, 22rem) 1fr;
    gap: var(--sj-space-5);
    align-items: start;
}

@media (max-width: 60rem) {
    .creator__split {
        grid-template-columns: 1fr;
    }
}

.creator__scene-list {
    margin: 0;
    padding: 0;
    list-style: none;
}

.creator__scene {
    display: block;
    width: 100%;
    padding: var(--sj-space-3);
    border: 1px solid transparent;
    border-radius: var(--sjs-radius-lg);
    background: none;
    font: inherit;
    text-align: start;
    cursor: pointer;
}

.creator__scene:hover {
    background: var(--sj-color-surface);
}

.creator__scene--current {
    border-color: var(--sj-color-primary);
    background: var(--sj-color-primary-soft);
}

.creator__scene:focus-visible {
    outline: 3px solid var(--sj-color-focus);
    outline-offset: 2px;
}

.creator__scene-title {
    display: block;
    font-weight: 600;
}

.creator__story-title {
    margin: 0 0 var(--sj-space-3);
}

.creator__facts {
    display: grid;
    grid-template-columns: minmax(8rem, max-content) 1fr;
    gap: var(--sj-space-1) var(--sj-space-3);
    margin: 0 0 var(--sj-space-4);
}

.creator__fact {
    display: contents;
}

.creator__facts dt {
    font-weight: 600;
}

.creator__facts dd {
    margin: 0;
}

/*
    Scripture and invention must not be tellable apart by colour alone (spec 17), so the difference
    is carried by a border, an indent, and italics — and each line also says which it is in a word.
    A reader who cannot see any of the three still reads the badge.
*/
.creator__dialogue {
    margin: 0 0 var(--sj-space-4);
    padding: 0;
    list-style: none;
}

.creator__dialogue > li {
    margin-bottom: var(--sj-space-3);
    padding-left: var(--sj-space-3);
    border-left: 3px solid var(--sj-color-border);
}

.creator__dialogue > li[data-kind="scripture"] {
    border-left-color: var(--sj-color-primary);
    background: var(--sj-color-surface);
}

.creator__dialogue > li[data-kind="creative"] .creator__line {
    font-style: italic;
}

.creator__speaker {
    margin: 0 0 var(--sj-space-1);
    font-weight: 600;
}

.creator__line {
    margin: 0 0 var(--sj-space-1);
}

.creator__flag[data-tone="attention"] {
    background: var(--sj-color-warning-soft, var(--sjs-inset));
    color: var(--sj-color-warning, var(--sj-color-ink));
}

/* `attention` was the only tone a flag had, so `ok` and `bad` were written on the element,
   matched nothing, and changed no pixel — the same shape as the `good`/`waiting` mismatch
   documented further down. The scripture marker is the one that matters: it is a flag saying a
   line is a quotation rather than the writer's invention, and it has to look different from the
   sentence beside it. It says so in words as well, which is why this was invisible. */
.creator__flag[data-tone="ok"] {
    background: var(--sj-color-success-soft, var(--sjs-inset));
    color: var(--sj-color-success, var(--sj-color-ink));
}

.creator__flag[data-tone="bad"] {
    background: var(--sj-color-danger-soft, var(--sjs-inset));
    color: var(--sj-color-danger, var(--sj-color-ink));
}

/* ---------------------------------------------------------- storyboard tab */

.creator__shots {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
    gap: var(--sj-space-4);
    margin-bottom: var(--sj-space-5);
}

.creator__shot {
    padding: var(--sj-space-3);
    border: 1px solid var(--sj-color-border);
    border-radius: var(--sjs-radius-lg);
}

/* Sized like the picture it is standing in for, so the grid does not reflow when CF-10 fills it. */
.creator__shot-thumb {
    display: flex;
    align-items: center;
    justify-content: center;
    aspect-ratio: 16 / 9;
    margin-bottom: var(--sj-space-2);
    border-radius: var(--sjs-radius-sm);
    background: var(--sj-color-surface);
    color: var(--sj-color-ink-muted);
    font-size: var(--sj-text-sm);
}

.creator__shot-id {
    margin: 0 0 var(--sj-space-2);
}

.creator__targets {
    margin: 0 0 var(--sj-space-3);
    padding-left: var(--sj-space-5);
}

.creator__targets > li {
    margin-bottom: var(--sj-space-1);
}

/*
    A table scrolls inside itself rather than widening the page.

    Found at 375px on the Scenes tab, whose six columns pushed the document to 549px and made the
    whole page scroll sideways — which moves the sidebar and the heading off screen to look at a
    column. `display: block` is what lets a table overflow at all; the row and cell boxes inside keep
    their table layout, so nothing about the grid changes.

    On the table rather than on a wrapper because `creator__table` is used on four screens, and a
    rule that needs a div around it is a rule the fifth one will be written without.
*/
.creator__table {
    display: block;
    max-width: 100%;
    overflow-x: auto;
}

/* ------------------------------------------------------ the Media Studio

   A strip rather than a grid, and it scrolls sideways inside itself. A grid reflows into a column
   on a narrow screen, which is the one thing a continuity strip must not do: the whole point is
   that two drawings of the same person are next to each other, and a column puts them a scroll
   apart. `overflow-x` on the strip keeps the page itself from scrolling sideways, which was the
   defect the missing `box-sizing` reset produced across the whole studio. */
.creator__strip {
    display: flex;
    gap: var(--sj-space-4);
    overflow-x: auto;
    padding-bottom: var(--sj-space-3);
}

.creator__thumb {
    flex: 0 0 auto;
    width: 20rem;
    max-width: 80vw;
    margin: 0;
}

.creator__thumb--small {
    width: 12rem;
}

.creator__thumb img {
    display: block;
    width: 100%;
    height: auto;
    border-radius: var(--sjs-radius-lg);
    /* Generated art has no transparent edge to lean on, so a border is what separates one
       candidate from the next when two have similar palettes. */
    border: 1px solid var(--sj-color-border);
    /* An SVG with a viewBox and no width or height has no intrinsic size, so `height: auto` on one
       that has not loaded yet resolves to zero — every thumbnail was a two-pixel sliver until its
       bytes arrived, and the whole strip jumped as they did. Declaring the ratio gives the box its
       height before there is anything in it. Found by measuring the page, not by looking at it. */
    aspect-ratio: 16 / 10;
    /* Letterboxed rather than stretched. A provider that answers in the wrong shape is already
       flagged by QA; distorting the picture would hide the thing the reviewer needs to see. */
    object-fit: contain;
}

.creator__thumb--square img {
    aspect-ratio: 1 / 1;
}

/* A film gets exactly the box its still would have. The poster is 16:10 and the clip was filmed
   from it, so anything else would make the strip jump at the moment a reviewer pressed play — and
   the two are meant to be compared, which only works if they are the same size. */
.creator__thumb video {
    width: 100%;
    height: auto;
    aspect-ratio: 16 / 10;
    border-radius: var(--sjs-radius-lg);
    border: 1px solid var(--sj-color-border);
    object-fit: contain;
    /* Without this a clip whose poster has not arrived paints as a black rectangle, which reads as
       a picture that generated badly rather than as one that has not loaded. */
    background: var(--sj-color-surface-sunken);
}

/* Same box as a picture, so a candidate with nothing generated yet does not collapse the row and
   make the strip jump as thumbnails arrive. */
.creator__thumb-empty {
    display: flex;
    align-items: center;
    justify-content: center;
    aspect-ratio: 16 / 10;
    border-radius: var(--sjs-radius-lg);
    border: 1px dashed var(--sj-color-border);
    color: var(--sj-color-ink-muted);
}

.creator__thumb figcaption {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sj-space-2);
    align-items: center;
    margin-top: var(--sj-space-2);
    font-size: var(--sj-text-sm);
}

/* Not red. A QA finding is something to look at, not a failure of the studio, and reserving the
   error colour for actual errors is what keeps it meaning something. */
.creator__badge--warn {
    background: var(--sj-color-warning-soft);
    color: var(--sj-color-warning, var(--sj-color-ink));
    font-weight: 600;
}

.creator__scene {
    margin: var(--sj-space-5) 0 var(--sj-space-2);
    font-size: var(--sj-text-md);
}

/* --------------------------------------------- three languages on one row

   The only horizontal scroll in the studio that is a feature. A reviewer is asking whether three
   strings say the same thing, and that question cannot be answered when the languages are stacked.
   The scroll is on this container rather than on the page, so the rest of the screen still behaves
   at phone width. */
.creator__sidebyside {
    overflow-x: auto;
}

.creator__sidebyside .creator__table {
    /* Wide enough that three columns keep readable measures rather than wrapping every word. */
    min-width: 48rem;
}

.creator__sidebyside td {
    /* Long strings are the point of this table; a nowrap here would make every cell a scroll. */
    vertical-align: top;
    min-width: 14rem;
}

/* The substitution mark sits above its text rather than beside it.

   Beside it was the first attempt and it ran the two together — Razor strips leading whitespace
   inside an element, so a space in the markup is a no-op and only CSS can put one there. Above is
   the better answer anyway: the mark is a statement about the whole cell ("these are published
   words, not a translation"), not a label on the first word of it. */
.creator__sidebyside td .creator__badge {
    display: block;
    margin: 0 0 var(--sj-space-1);
    /* Otherwise a one-word badge stretches to the column. */
    width: fit-content;
}

/* A badge before text needs a gap on its right as much as on its left.

   `.creator__badge` carries `margin-left` alone, which assumes a badge always follows something.
   In a table cell it often precedes something instead — a status badge and the timestamp beside
   it, a role badge and a description — and the two ran together with no space at all. Scoped to
   cells rather than changed globally, because two adjacent badges elsewhere would then sit twice
   as far apart as they were drawn to. */
.creator__table td > .creator__badge {
    margin-right: var(--sj-space-2);
}

/* ------------------------------------------------------------ a voice file

   A stacked row rather than an inline one. The player, the words it reads, and any finding about it
   all belong to the same file, and putting them on one line would make the words a caption on a
   transport control instead of the thing being checked. */
.creator__audio {
    display: flex;
    flex-direction: column;
    gap: var(--sj-space-2);
    padding: var(--sj-space-3) 0;
    border-bottom: 1px solid var(--sj-color-border);
}

.creator__audio audio {
    /* Full width so the scrub bar is usable with a thumb; the browser's own control otherwise
       collapses to something only a mouse can hit. */
    width: 100%;
    max-width: 32rem;
}

/* The QA Center. A score is never shown without the bar it was judged against, so the cell holds
   three things and has to stay readable at phone width — hence a grid that wraps rather than a
   table, which would scroll sideways for the one column that matters least. */
.creator__scores {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
    gap: var(--sj-space-3);
    margin: var(--sj-space-4) 0;
}

.creator__score {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--sj-space-2);
    padding: var(--sj-space-3);
    border: 1px solid var(--sj-color-border);
    border-radius: var(--sjs-radius-lg);
}

/* A left bar rather than a red background: the number is the thing being read, and a wash behind
   it makes every failing dimension harder to read than the ones that passed. */
.creator__score--failed {
    border-left: 4px solid var(--sj-color-danger, #b3261e);
}

.creator__score-name {
    flex: 1 1 100%;
    font-weight: 600;
}

.creator__score-value {
    font-size: 1.5rem;
    font-variant-numeric: tabular-nums;
}

.creator__finding {
    padding: var(--sj-space-3);
    border: 1px solid var(--sj-color-border);
    border-radius: var(--sjs-radius-lg);
    margin-bottom: var(--sj-space-3);
}

.creator__finding p {
    margin: 0 0 var(--sj-space-2);
}

.creator__finding p:last-child {
    margin-bottom: 0;
}

/* The preview stage.
   Phone width is the default and the reason is not aesthetic: a story reviewed at desk width has
   been reviewed at a size no child will see it at. Captions wrap differently, a hotspot is a
   different size relative to a fingertip, and a scene that reads as spacious becomes cramped. The
   frame is a real width constraint rather than a scaled-down screenshot, so everything inside it
   lays out exactly as it will on the device. */
.creator__stage {
    margin: var(--sj-space-4) 0;
    border: 1px solid var(--sj-color-border);
    border-radius: var(--sjs-radius-lg);
    /* The story brings its own background; the frame must not tint it. */
    overflow: hidden;
}

.creator__stage--phone {
    width: 100%;
    max-width: 390px;
    margin-inline: auto;
}

.creator__preview-controls {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sj-space-3);
    margin-bottom: var(--sj-space-3);
}

.creator__preview-controls label {
    display: inline-flex;
    align-items: center;
    gap: var(--sj-space-1);
    /* Comfortably tappable: this row is used on a tablet as often as on a desk. */
    min-height: 44px;
}

/* ------------------------------------------------------- the Radzen shell */

/*
    What the studio adds on top of Radzen's layout, and nothing more.

    Radzen owns the header, the sidebar, the menu and the grid; this file's job is to point them at
    Scripture Journey's tokens — which `radzen-overrides.css` already does for the palette — and to
    style the few things that are the studio's own.

    A trap this file set for a whole phase, and the reason every colour below is checked against
    `tokens.css`: five custom properties named here never existed. `--sj-color-text`,
    `--sj-color-text-muted`, `--sj-color-surface-raised`, `--sj-color-surface-muted` and
    `--sj-color-warning-surface` are not tokens, so every declaration using them was invalid and the
    browser dropped it silently. The shell had no text colour, the dashboard tiles had no
    background, and muted text was not muted — none of which raises an error, appears in a diff, or
    shows up in a test. It is the same failure the `--sj-focus-ring` comment above describes, found
    the same way: by grepping every token this file names against the file that defines them.
*/

.creator__lead {
  max-width: 60ch;
  margin-bottom: var(--sj-space-5);
  color: var(--sj-color-ink-muted);
}

.creator__bar-end {
  margin-inline-start: auto;
}

/*
    Sign out has to be legible on the header this studio actually has.

    The button is `ButtonStyle.Light` with `Variant.Text`, which takes its colour from Radzen's
    light palette — correct on Radzen's own header, which is dark. `.creator__bar` repaints the
    header with `--sj-color-surface`, which is white, and nobody revisited the button: it rendered
    at `rgb(233, 234, 237)` on white, a contrast ratio of **1.2:1**. The control was on the screen,
    in the right place, the right size and completely invisible.

    Spelled out rather than swapped for another `ButtonStyle`, because every one of those is a
    palette entry that could be repainted by the same kind of change that caused this. `ink` on
    `surface` is the body pairing this product is built on, so it cannot come apart on its own.

    `.rzi` is the icon, which takes its colour from the same place and was equally invisible.
*/
/*
    Scoped to the bar, not to `creator__bar-end` — and the difference already shipped once. P5
    moved `creator__bar-end` from Sign out to the theme button, and the ink override went with it:
    Sign out was 1.2:1 on white again the same afternoon, the exact defect the paragraph above
    documents. The rule is about every text button on the repainted header, so it names the header.
*/
.creator__bar .rz-button.rz-variant-text,
.creator__bar .rz-button-text,
.creator__bar .rzi {
  color: var(--sj-color-ink);
}

/*
    A queue that reads before it is read.

    The whole row is the target, so an editor on a tablet opens a job with one tap rather than
    aiming at the topic link inside it — which also means the cursor has to say so.
*/
.creator__grid .rz-data-row {
  cursor: pointer;
}

/* A story's name is content, and content speaks Fraunces — the queue reads as a shelf of
   storybooks rather than as rows of records, which is the whole direction in one rule. */
.creator__grid-topic {
  margin: 0;
  font-family: var(--sj-font-display);
  font-size: var(--sj-text-md);
  font-weight: 600;
}

/* Waiting on a person. A left edge rather than a fill, so the row stays readable. */
.creator__grid-row--attention td:first-child {
  box-shadow: inset 3px 0 0 var(--sj-color-warning);
}

/* ------------------------------------------------------------ escalations */

/*
    One decision per card, and each one says which story it is about, how long it has waited, and
    where to go and answer it.

    It was a bare list of paragraphs. Two escalations on two different jobs rendered as the same
    three sentences twice, because the record carries the rule and the detail but not the topic —
    so there was nothing on the screen to tell them apart and nothing to press.
*/
.creator__escalation {
  border-inline-start: 4px solid var(--sj-color-warning);
}

.creator__escalation-kind {
  margin: 0;
}

.creator__escalation-open {
  margin-inline-start: auto;
}

.creator__escalation-detail {
  margin-block: var(--sj-space-2) var(--sj-space-1);
}

/*
    The strip above a list: what is filtered, and the one action that makes a new thing.

    The action goes to the end rather than the start, because the filter is what an editor reads on
    arriving and the action is what they reach for on leaving.
*/
.creator__toolbar {
  margin-bottom: var(--sj-space-4);
}

.creator__toolbar-end {
  margin-inline-start: auto;
}

/* A form is read down a column, so it is not allowed to run the width of a desk monitor. */
.creator__form {
  max-width: 40rem;
}

/* ---------------------------------------------------------------------------
   The workflow: eight steps, in order, with the one that is your turn obvious.

   `ok` and `busy` are added here rather than renamed at the call sites, because
   they are what `StageDisplay.StatusTone` has always returned and the stylesheet
   only ever defined `good` and `waiting`. Every job status has therefore been
   rendering on the default surface — the tone attribute was written, matched
   nothing, and changed no pixel. Two spellings for one idea is how that happened,
   so both are listed rather than one of them being quietly dropped.
   --------------------------------------------------------------------------- */

.creator__status[data-tone="ok"] {
    background: var(--sj-color-success-soft, var(--sjs-inset));
    color: var(--sj-color-success, var(--sj-color-ink));
}

/* Teal, not sky: working is the pipeline's own colour everywhere else in the direction — the
   breathing dot on the dashboard, the current scene's ring — and a second blue for the same
   claim would be a second thing to learn. */
.creator__status[data-tone="busy"] {
    background: var(--sj-color-primary-soft, var(--sjs-inset));
    color: var(--sj-color-primary-hover, var(--sj-color-ink));
}

.creator__badge--warn {
    background: var(--sj-color-warning-soft, var(--sjs-inset));
    color: var(--sj-color-warning, var(--sj-color-ink));
    font-weight: var(--sj-weight-medium);
}

.creator__steps {
    margin: 0 0 var(--sj-space-6);
    padding: 0;
    list-style: none;
}

.creator__step {
    display: flex;
    gap: var(--sj-space-4);
    align-items: flex-start;
    padding: var(--sj-space-4);
    border: 1px solid var(--sj-color-border);
    border-radius: var(--sjs-radius-lg);
    background: var(--sj-color-surface);
}

.creator__step + .creator__step {
    margin-top: var(--sj-space-2);
}

/* The one step that is a person's turn is the one that gets the emphasis. Every
   step drawn equally is a list; one drawn louder is an instruction. */
.creator__step[data-state="attention"] {
    border-color: var(--sj-color-warning, var(--sj-color-border-strong));
    background: var(--sj-color-warning-soft, var(--sj-color-surface));
}

/* Finished steps recede rather than disappear. A checklist that hides what is
   done cannot be used to answer "who approved the artwork". */
.creator__step[data-state="ok"] {
    opacity: 0.72;
}

.creator__step-number {
    flex: 0 0 auto;
    display: grid;
    place-items: center;
    inline-size: 2rem;
    block-size: 2rem;
    border-radius: var(--sj-radius-pill);
    background: var(--sj-color-surface-sunken, var(--sj-color-canvas));
    font-weight: var(--sj-weight-bold);
}

.creator__step-body {
    flex: 1 1 auto;
    min-inline-size: 0;
}

.creator__step-title {
    margin: 0 0 var(--sj-space-1);
    font-weight: var(--sj-weight-medium);
}

.creator__step-body p {
    margin: 0 0 var(--sj-space-1);
}

.creator__step-actions {
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    gap: var(--sj-space-2);
    align-items: stretch;
}

/* 44px on both, because these are the two controls this screen exists to be
   pressed by, and a link styled as a button that is 22px tall is a button on a
   tablet that misses. */
.creator__step-go,
.creator__step-back {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-block-size: var(--sj-touch-min);
    padding: 0 var(--sj-space-4);
    /* Not a pill. The pill is the child app's shape; the studio's buttons are rectangles with
       quiet corners, which is half of what makes the two surfaces read as related tools rather
       than as one product wearing the other's clothes. */
    border-radius: var(--sjs-radius-md);
    font-size: var(--sj-text-sm);
    text-align: center;
    white-space: nowrap;
}

.creator__step-go {
    border: 1px solid transparent;
    background: var(--sj-color-primary);
    color: var(--sj-color-ink-inverse);
    text-decoration: none;
}

.creator__step-back {
    border: 1px solid var(--sj-color-border-strong);
    background: transparent;
    color: var(--sj-color-ink);
    cursor: pointer;
}

/* The strip teaches the order, so the number has to survive the strip wrapping. */
.creator__tab-step {
    display: inline-block;
    margin-inline-end: var(--sj-space-1);
    color: var(--sj-color-ink-subtle, var(--sj-color-ink-muted));
    font-variant-numeric: tabular-nums;
}

/* ---------------------------------------------------------------------------
   Picture or film, per shot.
   --------------------------------------------------------------------------- */

.creator__shot-mode {
    margin: var(--sj-space-3) 0;
    padding: 0;
    border: 0;
}

.creator__shot-mode legend {
    padding: 0;
    font-size: var(--sj-text-sm);
    font-weight: var(--sj-weight-medium);
}

/* The whole option is the target, not the 13px radio inside it. Four options a
   person compares against each other are also four things they have to hit. */
.creator__shot-mode-option {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 0 var(--sj-space-2);
    align-items: baseline;
    min-block-size: var(--sj-touch-min);
    padding: var(--sj-space-1) var(--sj-space-2);
    border: 1px solid transparent;
    border-radius: var(--sjs-radius-sm);
    cursor: pointer;
}

.creator__shot-mode-option[data-selected="True"] {
    border-color: var(--sj-color-primary);
    background: var(--sj-color-primary-soft, var(--sj-color-surface));
}

.creator__shot-mode-name {
    font-weight: var(--sj-weight-medium);
}

.creator__shot-mode-hint {
    grid-column: 2;
    color: var(--sj-color-ink-muted);
    font-size: var(--sj-text-xs);
}

.creator__shot-seconds label {
    display: flex;
    gap: var(--sj-space-2);
    align-items: center;
    font-size: var(--sj-text-sm);
}

.creator__shot-seconds input {
    inline-size: 5rem;
    min-block-size: var(--sj-touch-min);
    padding: 0 var(--sj-space-2);
    border: 1px solid var(--sj-color-border-strong);
    border-radius: var(--sjs-radius-sm);
}

.creator__shot-seconds p {
    margin: var(--sj-space-1) 0 0;
    font-size: var(--sj-text-xs);
}

@media (max-width: 40rem) {
    .creator__step {
        flex-wrap: wrap;
    }

    .creator__step-actions {
        flex-direction: row;
        flex-wrap: wrap;
        inline-size: 100%;
    }
}

/* ---------------------------------------------------------------------------
   The question the pipeline is waiting on.

   Loud on purpose, and at the top of the page. A stopped production used to
   print its escalation as three quiet lines below the fold with no control
   beside it, so the screen answered "what is wrong" and never "what do I do".
   --------------------------------------------------------------------------- */

.creator__decision {
    margin: 0 0 var(--sj-space-6);
    padding: var(--sj-space-5);
    border: 2px solid var(--sj-color-warning);
    border-radius: var(--sjs-radius-lg);
    background: var(--sj-color-warning-soft, var(--sj-color-surface));
}

/* Nunito: "needs your decision" is the tool talking, not the story. */
.creator__decision-title {
    margin: 0 0 var(--sj-space-1);
    font-size: var(--sj-text-lg);
    font-weight: var(--sj-weight-bold);
}

.creator__decision-item + .creator__decision-item {
    margin-top: var(--sj-space-5);
    padding-top: var(--sj-space-5);
    border-top: 1px solid var(--sj-color-border);
}

.creator__decision-item {
    margin-top: var(--sj-space-4);
}

.creator__decision-kind {
    margin: 0 0 var(--sj-space-1);
    font-size: var(--sj-text-md);
    font-weight: 600;
}

.creator__decision-item p {
    margin: 0 0 var(--sj-space-2);
    max-width: 60ch;
}

/* The reason is not optional, so the field is not a footnote. */
.creator__decision textarea {
    inline-size: 100%;
    max-inline-size: 60ch;
    padding: var(--sj-space-2);
    border: 1px solid var(--sj-color-border-strong);
    border-radius: var(--sjs-radius-sm);
    font: inherit;
}

.creator__decision textarea:focus-visible {
    outline: 3px solid var(--sj-color-focus);
    outline-offset: 2px;
}

/* --------------------------------------------------------------- story studio */

/*
    Three panes: the story, the scene, and the scene's detail (SS-1).

    A grid rather than flex because the three have genuinely different jobs and different natural
    widths — a list of pictures, a phone, and a form — and only the middle one should absorb what is
    left over.

    **It collapses at 1100px rather than at a device breakpoint**, because the constraint is the
    390px phone frame plus a readable form beside it plus a rail wide enough for a thumbnail. That
    is about laptop width and has nothing to do with whether the machine is a tablet.

    **Below that the rail becomes a filmstrip rather than a drawer.** A drawer needs state, a
    trigger, a focus trap and a way out; a horizontally scrolling row of the same cards needs none
    of those, keeps every scene one gesture away, and is the pattern every video editor on a small
    screen already uses. The canvas stays where it is and the inspector follows it.
*/
.creator__studio {
    display: flex;
    flex-direction: column;
    gap: var(--sj-space-3);
}

.creator__studio-panes {
    display: grid;
    grid-template-columns: 15rem minmax(0, 1fr) 24rem;
    gap: var(--sj-space-4);
    align-items: start;
}

@media (max-width: 1100px) {
    /*
        `minmax(0, 1fr)` rather than `1fr`, and the difference is the whole narrow layout.
        `1fr` is `minmax(auto, 1fr)`, and `auto` there is the *min-content* width of what is in the
        column — which, once the rail becomes a horizontal filmstrip, is the sum of every card. So
        the column grew to 544px inside a 375px viewport, the page picked up a sideways scrollbar,
        and the second scene card sat off the right edge. `overflow-x: auto` on the strip did not
        save it: a scroll container still reports its content as its minimum unless it is told it may
        be narrower. Found by taking a screenshot; every box measurement said it was fine, because
        the pane was hidden and hidden panes do not give trustworthy geometry.
    */
    .creator__studio-panes {
        grid-template-columns: minmax(0, 1fr);
    }
}

/* ---------------------------------------------------------------- the header */

.creator__studio-header {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--sj-space-3);
    padding-bottom: var(--sj-space-3);
    border-bottom: 1px solid var(--sj-color-border);
}

.creator__studio-header-identity {
    min-width: 0;
}

.creator__studio-back {
    display: inline-block;
    margin-bottom: var(--sj-space-1);
    font-size: var(--sj-text-sm);
}

.creator__studio-title {
    margin: 0;
    font-family: var(--sj-font-display);
    font-size: var(--sj-text-xl);
    line-height: 1.2;
}

.creator__studio-facts {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sj-space-2);
    margin: var(--sj-space-1) 0 0;
    font-size: var(--sj-text-sm);
}

.creator__studio-header-coverage {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sj-space-2);
}

/*
    A count per language rather than a tick and a cross. "Two scenes to go" is an instruction;
    "not done" is not.
*/
.creator__studio-locale {
    display: inline-flex;
    flex-direction: column;
    padding: var(--sj-space-1) var(--sj-space-2);
    border: 1px solid var(--sj-color-border);
    border-radius: var(--sjs-radius-sm);
    font-size: var(--sj-text-sm);
    line-height: 1.3;
}

.creator__studio-locale[data-tone="attention"] {
    border-color: var(--sj-color-warning);
}

.creator__studio-locale-code {
    font-weight: 600;
}

.creator__studio-locale-count {
    color: var(--sj-color-ink-muted);
}

.creator__studio-header-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sj-space-2);
}

/* ------------------------------------------------------------------ the rail */

.creator__studio-rail {
    display: flex;
    flex-direction: column;
    gap: var(--sj-space-2);
    /* The grid item, and it may be narrower than the filmstrip inside it. */
    min-width: 0;
    /*
        Follows the page rather than scrolling in its own box. A scrolling sidebar hides scenes
        behind an edge nobody can see, which is the failure the collapsed studio sidebar had.
    */
    position: sticky;
    top: var(--sj-space-4);
}

.creator__studio-rail-cards {
    display: flex;
    flex-direction: column;
    gap: var(--sj-space-2);
}

@media (max-width: 1100px) {
    .creator__studio-rail {
        position: static;
    }

    /* The filmstrip. Cards keep their width and the row scrolls, so a twelve-scene story is one
       swipe rather than a page of vertical list above the thing being edited.

       `min-width: 0` is what makes the scrolling actually happen here rather than on the page: a
       flex container's automatic minimum is its content, so without it the strip is always exactly
       as wide as its cards and never has anything to scroll. */
    .creator__studio-rail-cards {
        flex-direction: row;
        min-width: 0;
        overflow-x: auto;
        padding-bottom: var(--sj-space-2);
        scroll-snap-type: x proximity;
    }

    .creator__studio-card {
        flex: 0 0 11rem;
        scroll-snap-align: start;
    }
}

/* ------------------------------------------------------------------ the card */

.creator__studio-card {
    display: flex;
    flex-direction: column;
    gap: var(--sj-space-1);
    padding: var(--sj-space-2);
    border: 1px solid var(--sj-color-border);
    border-radius: var(--sjs-radius-lg);
    background: var(--sj-color-surface);
    text-align: left;
    font: inherit;
    color: inherit;
    cursor: pointer;
}

.creator__studio-card:hover {
    background: var(--sj-color-surface-sunken);
}

.creator__studio-card--current {
    border-color: var(--sj-color-primary);
    box-shadow: inset 0 0 0 1px var(--sj-color-primary);
    background: var(--sj-color-surface-sunken);
}

.creator__studio-card:focus-visible {
    outline: 3px solid var(--sj-color-focus);
    outline-offset: 2px;
}

/*
    The frame is drawn whether or not there is a picture in it. Absence is information here — it is
    most of what an editor is working through — so it gets a shape rather than a gap that reads as
    a rendering fault.
*/
.creator__studio-card-frame {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    /* The plate aspect the whole product draws at. */
    aspect-ratio: 16 / 10;
    border-radius: var(--sjs-radius-sm);
    background: var(--sj-color-surface-sunken);
    overflow: hidden;
}

.creator__studio-card-image {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.creator__studio-card-blank {
    font-family: var(--sj-font-display);
    font-size: var(--sj-text-lg);
    color: var(--sj-color-ink-muted);
}

.creator__studio-card-kind {
    position: absolute;
    right: var(--sj-space-1);
    bottom: var(--sj-space-1);
    font-size: var(--sj-text-sm);
}

.creator__studio-card-body {
    display: flex;
    flex-direction: column;
    gap: var(--sj-space-1);
    min-width: 0;
}

.creator__studio-card-title {
    font-weight: 600;
    font-size: var(--sj-text-sm);
    line-height: 1.3;
}

.creator__studio-card-number {
    display: inline-block;
    min-width: 1.5em;
    color: var(--sj-color-ink-muted);
    font-variant-numeric: tabular-nums;
}

.creator__studio-card-marks {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sj-space-1);
    font-size: var(--sj-text-xs);
}

/* ---------------------------------------------------------------- the canvas */

.creator__studio-canvas {
    min-width: 0;
}

.creator__studio-canvas-controls {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sj-space-3);
    margin-bottom: var(--sj-space-2);
}

.creator__studio-locales,
.creator__studio-viewports {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sj-space-1);
}

/*
    Three real widths rather than a scaled screenshot. The frame constrains layout, so everything
    inside lays out exactly as it will on the device — a scaled one would lie about wrapping and
    about how big a hotspot is against a fingertip.
*/
.creator__stage--tablet {
    width: 100%;
    max-width: 768px;
    margin-inline: auto;
}

.creator__stage--desk {
    width: 100%;
}

.creator__studio-gaps {
    margin-top: var(--sj-space-3);
    font-size: var(--sj-text-sm);
}

.creator__studio-gaps summary {
    cursor: pointer;
}

/* -------------------------------------------------------- what is left to do */

/*
    The story's readiness, above the panes.

    A disclosure rather than a permanent list: an editor writing scene four does not need to read
    what is outstanding on scene nine, and a nine-line list pinned above the work would push the
    work off the screen — which is the failure the whole SS-1 layout exists to undo. Closed, it is
    one line that says whether anything is outstanding at all.
*/
.creator__studio-readiness {
    padding: var(--sj-space-2) var(--sj-space-3);
    border: 1px solid var(--sj-color-border);
    border-radius: var(--sjs-radius-lg);
    background: var(--sj-color-surface);
    font-size: var(--sj-text-sm);
}

.creator__studio-readiness summary {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sj-space-2);
    cursor: pointer;
}

/*
    A blocker is a control, so it looks like one.

    The first version drew these as plain text with the sentence beside them, which reads as a
    report — and a report is what this replaced. What makes it a to-do list is that the words are
    the thing you press: it selects the scene and opens the panel that fixes it.
*/
.creator__studio-jump {
    margin-inline-start: var(--sj-space-2);
    padding-block: var(--sj-space-1);
    padding-inline: 0;
    border: 0;
    background: none;
    color: var(--sj-color-primary);
    font: inherit;
    text-align: start;
    text-decoration: underline;
    cursor: pointer;
}

.creator__studio-jump:hover {
    color: var(--sj-color-primary-hover);
}

.creator__studio-jump:focus-visible {
    outline: 2px solid var(--sj-color-primary);
    outline-offset: 2px;
    border-radius: var(--sjs-radius-sm);
}

/* ------------------------------------------------------------- the inspector */

.creator__studio-side {
    display: flex;
    flex-direction: column;
    gap: var(--sj-space-2);
    min-width: 0;
    position: sticky;
    top: var(--sj-space-4);
}

@media (max-width: 1100px) {
    .creator__studio-side {
        position: static;
    }
}

.creator__studio-inspector {
    min-width: 0;
    padding: var(--sj-space-3);
    border: 1px solid var(--sj-color-border);
    border-radius: var(--sjs-radius-lg);
    background: var(--sj-color-surface);
}

/* "Words", "Pictures", "Sound" — the tool naming a panel, so Nunito, at chrome weight. The story
   title in the header keeps Fraunces; that one is the content speaking. */
.creator__studio-inspector-title {
    margin: 0 0 var(--sj-space-1);
    font-size: var(--sjs-text-base);
    font-weight: var(--sj-weight-bold);
    line-height: 1.2;
}

/*
    The panel scrolls in its own box and the rest of the screen does not, because the pane is
    sticky: a hundred-line form would otherwise push the canvas out of view on the way down. It is
    the one scrolling box in this layout and it has a visible edge, which is what the collapsed
    sidebar failure was missing.
*/
.creator__studio-inspector-panel {
    max-height: 60vh;
    overflow-y: auto;
    margin-top: var(--sj-space-2);
}

.creator__studio-save {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: var(--sj-space-2);
}

/*
    The column supplies the rhythm, so nothing inside it brings its own. Without this the save bar's
    own `creator__actions` margin and each status paragraph's default stack up, and the line saying
    another document is dirty ends up a long way from the button it is a warning about.
*/
.creator__studio-save > * {
    margin: 0;
}

/*
    `creator__flag` carries `margin-left` because it is built to sit after a word. As a status line
    of its own that indent reads as a nesting level nothing else on the column has — the sentence
    about the other document looked like it belonged to whatever was above it.
*/
.creator__studio-save .creator__flag {
    margin-left: 0;
}

/*
    Work recovered from a previous visit (SS-3). Across the full width above the panes rather than
    inside one of them: it is a question about the whole document, and an editor has to answer it
    before the scene they are looking at means anything.
*/
.creator__studio-recovery {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    /*
        Adjacent rather than `space-between`. The panel is the full width of the studio, so pushing
        the two buttons to the far edge put them a screen away from the sentence they answer — and
        with no box around either, the whole thing read as a stray line of text rather than as a
        decision. Found by looking at it.
    */
    gap: var(--sj-space-3);
    padding: var(--sj-space-3);
    border: 1px solid var(--sj-color-warning);
    border-radius: var(--sjs-radius-lg);
    background: var(--sj-color-warning-soft, var(--sj-color-surface-sunken));
}

.creator__studio-recovery p {
    margin: 0;
    font-weight: 600;
}

.creator__studio-languages {
    width: 100%;
}

.creator__studio-interaction + .creator__studio-interaction {
    margin-top: var(--sj-space-3);
    padding-top: var(--sj-space-3);
    border-top: 1px solid var(--sj-color-border);
}

/* ------------------------------------------------------ media inside a panel */

.creator__studio-assets {
    display: flex;
    flex-direction: column;
    gap: var(--sj-space-3);
    margin: 0;
    padding: 0;
    list-style: none;
}

.creator__studio-asset {
    display: grid;
    grid-template-columns: 6rem minmax(0, 1fr);
    gap: var(--sj-space-2);
    align-items: start;
}

.creator__studio-asset-frame {
    display: flex;
    align-items: center;
    justify-content: center;
    aspect-ratio: 16 / 10;
    border-radius: var(--sjs-radius-sm);
    background: var(--sj-color-surface-sunken);
    overflow: hidden;
}

.creator__studio-asset-frame img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.creator__studio-asset-facts {
    min-width: 0;
    font-size: var(--sj-text-sm);
}

.creator__studio-asset-facts p {
    margin: 0 0 var(--sj-space-1);
}

/* -------------------------------------------------------- another layer on a scene

   Set apart from the layer rows above it, because it acts on the scene rather than on any one of
   them — the same reason the story's own controls live in the header rather than beside a scene. */

.creator__studio-add-layer {
    margin-top: var(--sj-space-3);
    padding: var(--sj-space-3);
    border: 1px solid var(--sj-color-border);
    border-radius: var(--sjs-radius-lg);
    background: var(--sj-color-surface-sunken);
}

/* -------------------------------------------------------- alternatives (SS-4) */

.creator__studio-alternatives {
    margin-top: var(--sj-space-2);
    font-size: var(--sj-text-sm);
}

.creator__studio-alternatives summary {
    cursor: pointer;
}

.creator__studio-alternative {
    flex: 0 0 8rem;
    margin: 0;
}

.creator__studio-alternative figcaption {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sj-space-1);
    margin-top: var(--sj-space-1);
    font-size: var(--sj-text-xs);
}

/* ------------------------------------------------------------ the pool (SS-4) */

.creator__studio-pool {
    margin-top: var(--sj-space-4);
    padding: var(--sj-space-3);
    border: 1px solid var(--sj-color-border);
    border-radius: var(--sjs-radius-lg);
    background: var(--sj-color-surface-sunken);
}

.creator__studio-pool summary {
    cursor: pointer;
    font-weight: 600;
}

/*
    Its own width rather than the asset row's, because this strip is scanned by eye: an editor
    looking for "the one I generated ten minutes ago" is matching a picture, not reading a key.
*/
.creator__studio-pool-item {
    flex: 0 0 10rem;
    margin: 0;
}

.creator__studio-pool-item figcaption {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sj-space-1);
    margin-top: var(--sj-space-1);
    font-size: var(--sj-text-xs);
}

/*
    The one tab that is not a step. It leads the strip because it is where the work happens; the
    numbered thirteen are where a run is reviewed and a story is shipped.
*/
.creator__tab--lead {
    font-weight: 600;
}

/*
    What a validator objected to (SW-4). Bordered rather than merely coloured, because the list it
    introduces is a list of paths and a reader needs to see where it ends.
*/
.creator__findings {
    margin: var(--sj-space-3) 0;
    padding: var(--sj-space-3);
    border: 1px solid var(--sj-color-danger);
    border-radius: var(--sjs-radius-lg);
    background: var(--sj-color-surface-sunken);
}

.creator__findings-lead {
    margin: 0 0 var(--sj-space-2);
    font-weight: 600;
}

/*
    The document editor, closed by default (SW-4). A `details` rather than a toggle so it is closed
    on arrival without any state to keep, and so a browser's own find-in-page can still open it.
*/
.creator__advanced {
    margin-top: var(--sj-space-5);
    padding: var(--sj-space-3);
    border: 1px dashed var(--sj-color-border);
    border-radius: var(--sjs-radius-lg);
}

.creator__advanced > summary {
    cursor: pointer;
    font-weight: 600;
    /* 44px, like every other target. A summary is a control. */
    min-height: 2.75rem;
    display: flex;
    align-items: center;
}

.creator__code {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: var(--sj-text-xs);
    width: 100%;
    /* The one place in the studio horizontal scrolling is right: wrapping JSON hides its shape. */
    white-space: pre;
    overflow-x: auto;
}

/*
    Path and message are two things and need to read as two. Without this they render as
    "/scenesS05 has no sound design." — the pointer swallowed by the sentence after it.
*/
.creator__findings li {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sj-space-2);
    align-items: baseline;
}

/* -------------------------------------------------- the editing kit (SW-9)

   Seven documents' worth of forms, and the whole visual argument is that a
   language is never hidden behind a switcher: `creator__loc` stacks every
   language the job ships, always, so an empty one is visible rather than a
   click away. One language at a time is how the pt-BR transcription slip hid
   at CF-8.
*/
.creator__loc {
    margin: 0 0 var(--sj-space-5);
    padding: var(--sj-space-3);
    border: 1px solid var(--sj-color-border);
    border-radius: var(--sjs-radius-lg);
}

.creator__loc > legend {
    padding: 0 var(--sj-space-2);
    font-weight: 600;
}

/* Tighter than a standalone field: three of these stack inside one fieldset,
   and the default margin made a single translated sentence look like three
   unrelated questions. */
.creator__loc-row {
    margin-bottom: var(--sj-space-2);
}

.creator__loc-row:last-child {
    margin-bottom: 0;
}

/* A hint belongs under its label and above its control, and it is not a
   heading — so it is lighter and smaller than both, and it wraps. */
.creator__field-hint {
    display: block;
    margin-bottom: var(--sj-space-1);
    font-weight: 400;
    font-size: var(--sj-text-xs);
    max-width: 48rem;
}

.creator__choice {
    margin: 0 0 var(--sj-space-4);
    padding: var(--sj-space-3);
    border: 1px solid var(--sj-color-border);
    border-radius: var(--sjs-radius-lg);
}

.creator__choice > legend {
    padding: 0 var(--sj-space-2);
    font-weight: 600;
}

/* 44px per row, because a radio is a target and a vocabulary is a list of
   them. The label is the target, not the dot. */
.creator__choice-option {
    display: flex;
    gap: var(--sj-space-2);
    align-items: baseline;
    min-height: 2.75rem;
    padding: var(--sj-space-1) 0;
    cursor: pointer;
}

/* For a vocabulary with more than about six values — the fourteen animation
   presets, the eight interaction types. Wrapping rather than a dropdown keeps
   every option visible, which is the whole reason these are radios. */
.creator__choice--compact {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sj-space-3);
}

.creator__choice--compact > legend {
    /* `flex-wrap` treats a legend as a flex item, so without this the first
       option sits on the same line as the field name and reads as one of them. */
    flex-basis: 100%;
}

.creator__choice-name {
    font-weight: 600;
}

.creator__flag-field {
    display: block;
}

.creator__flag-box {
    display: flex;
    gap: var(--sj-space-2);
    align-items: baseline;
    min-height: 2.75rem;
    font-weight: 600;
}

/* One repeated thing — a quiz answer, a sound cue, a layer, a dialogue line.
   Ruled on the left rather than boxed: a page of eight boxes reads as eight
   separate forms, and these are eight rows of one. */
.creator__cue,
.creator__quiz-option,
.creator__dialogue-edit {
    margin: 0 0 var(--sj-space-4);
    padding-left: var(--sj-space-3);
    border-left: 3px solid var(--sj-color-border);
}

/* Colour and a word, never colour alone — the same rule `creator__dialogue`
   already keeps. The badge beside it says which it is out loud; this only
   makes the two kinds tellable apart at a glance. */
.creator__dialogue-edit[data-kind="scripture"] {
    border-left-color: var(--sj-color-primary);
}

.creator__savebar {
    position: sticky;
    bottom: 0;
    z-index: 1;
    padding: var(--sj-space-3) 0;
    /* Opaque, or the form scrolls visibly underneath the one control that
       commits it. */
    background: var(--sj-color-surface);
    border-top: 1px solid var(--sj-color-border);
}

/* ------------------------------------------------------------- the voices (SS-5) */

.creator__studio-voices {
    display: flex;
    flex-direction: column;
    gap: var(--sj-space-3);
    margin: 0;
    padding: 0;
    list-style: none;
}

/*
    A card per language, including the languages nothing has been recorded in — those are the ones
    an editor is looking for, so they get the same box rather than being absent from the list.
*/
.creator__studio-voice {
    padding: var(--sj-space-2);
    border: 1px solid var(--sj-color-border);
    border-radius: var(--sjs-radius-sm);
}

.creator__studio-voice p {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sj-space-1);
    margin: 0 0 var(--sj-space-1);
    font-size: var(--sj-text-sm);
}

/* The browser's own controls, at the width they are given. */
.creator__studio-voice audio {
    width: 100%;
    margin: var(--sj-space-1) 0;
}

/* A cue and the control that removes it, so the two are visibly one thing. */
.creator__cue-group {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--sj-space-1);
}

/* ------------------------------------------------------- the generate dialog (SS-6) */

/*
    In place rather than in a modal. The decision it asks for — which of this key's pictures the
    story should use — is about the picture sitting directly above it, and a modal would cover the
    thing being replaced at exactly the moment somebody is deciding whether to replace it.
*/
.creator__generate {
    /*
        The whole row. `creator__studio-asset` is a grid of a 6rem thumbnail and the facts beside
        it; inside that second column the three shorthand controls wrapped one to a line and the
        prompt became a column of four-word lines. Legible, and nothing anybody would read.
    */
    grid-column: 1 / -1;
    margin-top: var(--sj-space-2);
    padding: var(--sj-space-3);
    border: 1px solid var(--sj-color-primary);
    border-radius: var(--sjs-radius-lg);
    background: var(--sj-color-surface-sunken);
}

.creator__generate-controls {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sj-space-2);
}

/*
    Emphasised and neutral. `creator__notice` was the first attempt and it is the studio's *success*
    colour — a price rendered in green reads as good news, which is not what a number an editor is
    about to be charged is. Danger would be worse: spending money on purpose is not a fault.
*/
.creator__generate-cost {
    margin: 0 0 var(--sj-space-2);
    font-weight: 600;
}

.creator__generate-controls .creator__field {
    flex: 1 1 7rem;
    margin-bottom: var(--sj-space-2);
}

.creator__generate select,
.creator__generate textarea {
    width: 100%;
    padding: var(--sj-space-1) var(--sj-space-2);
    border: 1px solid var(--sj-color-border);
    border-radius: var(--sjs-radius-sm);
    font: inherit;
}

/*
    The prompt as it was built, wrapped rather than scrolled sideways: it is several sentences of
    prose about a picture, and a reader has to be able to see that it already says "late afternoon"
    before adding "make it darker".
*/
.creator__prompt {
    max-height: 10rem;
    overflow-y: auto;
    margin: 0;
    padding: var(--sj-space-2);
    border-radius: var(--sjs-radius-sm);
    background: var(--sj-color-surface);
    font-size: var(--sj-text-xs);
    white-space: pre-wrap;
    word-break: break-word;
}

/* ---------------------------------------------------------------------------
   Elevation (P1). One recipe instead of fourteen hand-made panels: a raised
   card is border + radius + white + shadow-sm, and shadow-sm is the *only*
   shadow ordinary chrome may carry — shadow-md is reserved for the scene
   canvas and for dialogs, so that the one thing a child will actually see
   stays the brightest object on the bench. Wells (the pool, the generate
   dialog, an empty frame) keep the sunken ground and no shadow: a hole does
   not cast one.
   --------------------------------------------------------------------------- */
.creator__count,
.creator__step,
.creator__score,
.creator__finding,
.creator__shot,
.creator__decision,
.creator__studio-inspector,
.creator__studio-readiness {
    box-shadow: var(--sj-shadow-sm);
}

/* ===========================================================================
   P2 — the Story Studio as a real editor.

   Three moves, all of them about weight. The header becomes the story's app
   bar: one white strip holding identity, readiness and the story-wide
   actions. The rail becomes a filmstrip — picture, number, title, and ONE
   mark, because a strip an editor scans is not a place for six chips per
   scene. And the canvas becomes the brightest object on the bench: it is the
   child's actual screen, and it is the only ordinary chrome allowed
   shadow-md.
   =========================================================================== */

.creator__studio-header {
    padding: var(--sj-space-3) var(--sj-space-4);
    border: 1px solid var(--sj-color-border);
    border-radius: var(--sjs-radius-lg);
    background: var(--sj-color-surface);
    box-shadow: var(--sj-shadow-sm);
    align-items: center;
}

.creator__studio-back {
    font-size: var(--sjs-text-sm);
    font-weight: var(--sj-weight-medium);
    color: var(--sj-color-ink-muted);
    text-decoration: none;
}

.creator__studio-back:hover {
    color: var(--sj-color-primary-hover);
    text-decoration: underline;
}

.creator__studio-title {
    font-size: var(--sj-text-lg);
}

/*
    The language chips, worn the way the direction draws them: green when a language is done,
    amber carrying the count when it is not. The count is the instruction; the colour only makes
    it findable.
*/
.creator__studio-locale {
    display: inline-flex;
    align-items: center;
    gap: var(--sj-space-1);
    padding: 0.125rem var(--sj-space-2);
    border-radius: var(--sjs-radius-sm);
    background: var(--sjs-inset);
    font-size: var(--sjs-text-xs);
    font-weight: var(--sj-weight-medium);
    color: var(--sj-color-ink-muted);
}

.creator__studio-locale[data-tone="ok"] {
    background: var(--sj-color-success-soft, var(--sjs-inset));
    color: var(--sj-color-success, var(--sj-color-ink));
}

.creator__studio-locale[data-tone="attention"] {
    background: var(--sj-color-warning-soft, var(--sjs-inset));
    color: var(--sj-color-warning, var(--sj-color-ink));
}

/* ------------------------------------------------------------- the filmstrip */

.creator__studio-card {
    padding: var(--sj-space-1);
    gap: var(--sj-space-1);
}

.creator__studio-card-title {
    font-size: var(--sjs-text-xs);
}

.creator__studio-card--current {
    background: var(--sj-color-primary-soft, var(--sj-color-surface-sunken));
}

/*
    One mark per scene. Red blocks a publish, amber waits on a person — the same split
    `SceneReadiness` makes, and the same tones the readiness segments wear, so the strip and the
    roll-up cannot disagree. The dot is aria-hidden and its sentences are rendered visually hidden
    beside it: colour is never the only carrier.
*/
.creator__studio-card-dot {
    display: inline-block;
    inline-size: 0.625rem;
    block-size: 0.625rem;
    border-radius: 50%;
    background: var(--sj-color-accent);
}

.creator__studio-card-dot[data-tone="bad"] {
    background: var(--sj-color-danger);
}

.creator__studio-card-dot[data-tone="attention"] {
    background: var(--sj-color-accent);
}

.creator__studio-card-marks {
    min-height: 0.875rem;
}

/* -------------------------------------------------- the canvas, brightest */

/*
    Shadow-md is reserved for the child's screen and for dialogs. On the quiet bench this is what
    makes the story the thing the whole room is arranged around.
*/
.creator__studio-canvas .creator__stage {
    border: 0;
    border-radius: var(--sjs-radius-lg);
    box-shadow: var(--sj-shadow-md);
}

/*
    The canvas controls as segmented switches rather than underlined tabs. An underline says
    "another page"; these choose how the same scene is drawn, which is what a segmented control
    has meant everywhere since the first camera app.
*/
.creator__studio-canvas-controls {
    gap: var(--sj-space-2);
}

.creator__studio-locales,
.creator__studio-viewports {
    gap: 0.125rem;
    padding: 0.125rem;
    border-radius: var(--sjs-radius-md);
    background: var(--sjs-inset);
}

.creator__studio-locales .creator__tab,
.creator__studio-viewports .creator__tab {
    padding: 0.25rem var(--sj-space-3);
    border: 0;
    border-radius: var(--sjs-radius-sm);
    background: none;
    font-size: var(--sjs-text-xs);
    font-weight: var(--sj-weight-medium);
    color: var(--sj-color-ink-muted);
    cursor: pointer;
}

.creator__studio-locales .creator__tab.active,
.creator__studio-viewports .creator__tab.active {
    background: var(--sj-color-surface);
    color: var(--sj-color-ink);
    box-shadow: var(--sj-shadow-sm);
}

/* ------------------------------------------------- the inspector's questions */

/*
    Six questions, each wearing its icon. The icon is a CSS ligature rather than markup, so
    `textContent` stays the label alone — a ligature in the markup is how a page-text dump reads
    "imagePictures", and it is exactly how a test (and a screen reader) finds a tab by its name.
    The empty alternative text (`/ ""`) keeps the ligature word out of the accessibility tree.
*/
.creator__panel-tabs {
    display: grid;
    grid-template-columns: repeat(6, minmax(0, 1fr));
    gap: 0.125rem;
    padding: 0.125rem;
    border: 0;
    border-radius: var(--sjs-radius-md);
    background: var(--sjs-inset);
    margin-bottom: var(--sj-space-3);
}

.creator__panel-tabs .creator__tab {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.125rem;
    padding: var(--sj-space-1) 0;
    border: 0;
    border-radius: var(--sjs-radius-sm);
    background: none;
    font-size: 0.6875rem;
    font-weight: var(--sj-weight-medium);
    color: var(--sj-color-ink-muted);
    cursor: pointer;
}

.creator__panel-tabs .creator__tab::before {
    font-family: "Material Symbols";
    font-size: 1.125rem;
    line-height: 1;
}

.creator__panel-tabs .creator__tab[data-panel="content"]::before { content: "edit" / ""; }
.creator__panel-tabs .creator__tab[data-panel="media"]::before { content: "image" / ""; }
.creator__panel-tabs .creator__tab[data-panel="audio"]::before { content: "volume_up" / ""; }
.creator__panel-tabs .creator__tab[data-panel="interaction"]::before { content: "touch_app" / ""; }
.creator__panel-tabs .creator__tab[data-panel="languages"]::before { content: "translate" / ""; }
.creator__panel-tabs .creator__tab[data-panel="qa"]::before { content: "fact_check" / ""; }

.creator__panel-tabs .creator__tab.active {
    background: var(--sj-color-surface);
    color: var(--sj-color-primary-hover);
    box-shadow: var(--sj-shadow-sm);
    font-weight: var(--sj-weight-bold);
}

/* ----------------------------------------------- readiness as a filling bar */

.creator__readiness-track {
    display: inline-flex;
    gap: 0.1875rem;
    margin-inline-end: var(--sj-space-1);
}

.creator__readiness-segment {
    display: inline-block;
    inline-size: 1.5rem;
    block-size: 0.5rem;
    border-radius: 0.25rem;
    background: var(--sjs-inset);
}

.creator__readiness-segment[data-tone="good"] {
    background: var(--sj-color-success);
}

.creator__readiness-segment[data-tone="attention"] {
    background: var(--sj-color-accent);
}

.creator__readiness-segment[data-tone="bad"] {
    background: var(--sj-color-danger);
}

/* The save column reads as the inspector's footer rather than as loose lines. */
.creator__studio-save {
    padding: var(--sj-space-3);
    border: 1px solid var(--sj-color-border);
    border-radius: var(--sjs-radius-lg);
    background: var(--sj-color-surface);
    box-shadow: var(--sj-shadow-sm);
}

/* -------------------------------------------------------- Today's queue (P2) */

/*
    One card per story waiting on a person: title, the question in words, how long it has waited,
    and the one thing to press. The action sits at the card's end at full height, because a queue
    is worked down with one hand on the mouse — the card says why, the button says what.
*/
.creator__queue {
    display: flex;
    flex-direction: column;
    gap: var(--sj-space-2);
    margin-bottom: var(--sj-space-5);
}

.creator__queue-card {
    display: flex;
    align-items: center;
    gap: var(--sj-space-4);
    padding: var(--sj-space-3) var(--sj-space-4);
    border: 1px solid var(--sj-color-border);
    border-radius: var(--sjs-radius-lg);
    background: var(--sj-color-surface);
    box-shadow: var(--sj-shadow-sm);
}

.creator__queue-body {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: var(--sj-space-1);
}

.creator__queue-body p {
    margin: 0;
}

/* A story's name is content, and content speaks Fraunces. */
.creator__queue-title {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sj-space-2);
    font-family: var(--sj-font-display);
    font-size: var(--sj-text-md);
    font-weight: 600;
}

.creator__queue-context {
    color: var(--sj-color-ink-muted);
    max-width: 70ch;
}

.creator__queue-facts {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sj-space-2);
    color: var(--sj-color-ink-subtle);
    font-size: var(--sjs-text-sm);
}

.creator__queue-open {
    flex: 0 0 auto;
}

@media (max-width: 40rem) {
    .creator__queue-card {
        flex-wrap: wrap;
    }
}

/* The three counts recede into a summary strip: the queue is the screen now, and the numbers
   only have to say whether it is worth scrolling. */
.creator__count {
    min-width: 8rem;
    padding: var(--sj-space-2) var(--sj-space-3);
}

.creator__count-value {
    font-size: var(--sj-text-lg);
}

/* ------------------------------------------------------------ the shelf (P2) */

/*
    The library as a bookshelf: cover-first cards, because a library of picture books is recognised
    by its pictures and the covers are already paid for. The grid view stays behind the toggle for
    the visit that is an audit rather than a browse.
*/
.creator__view-toggle {
    display: inline-flex;
    gap: 0.125rem;
    padding: 0.125rem;
    border-radius: var(--sjs-radius-md);
    background: var(--sjs-inset);
    margin-bottom: var(--sj-space-4);
}

.creator__view-toggle .creator__tab {
    padding: 0.25rem var(--sj-space-3);
    border: 0;
    border-radius: var(--sjs-radius-sm);
    background: none;
    font-size: var(--sjs-text-xs);
    font-weight: var(--sj-weight-medium);
    color: var(--sj-color-ink-muted);
    cursor: pointer;
}

.creator__view-toggle .creator__tab.active {
    background: var(--sj-color-surface);
    color: var(--sj-color-ink);
    box-shadow: var(--sj-shadow-sm);
}

.creator__shelf {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr));
    gap: var(--sj-space-4);
    margin-bottom: var(--sj-space-5);
}

.creator__shelf-card {
    display: flex;
    flex-direction: column;
    border: 1px solid var(--sj-color-border);
    border-radius: var(--sjs-radius-lg);
    background: var(--sj-color-surface);
    box-shadow: var(--sj-shadow-sm);
    overflow: hidden;
}

/* The plate aspect the whole product draws at, held whether or not a cover exists, so the shelf
   does not reflow around the two hand-authored stories. */
.creator__shelf-cover {
    display: flex;
    align-items: center;
    justify-content: center;
    aspect-ratio: 16 / 10;
    background: var(--sj-color-surface-sunken);
    overflow: hidden;
}

.creator__shelf-cover img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/* A story with no cover shows its name in the storybook voice — a titled frame, not a hole. */
.creator__shelf-blank {
    padding: var(--sj-space-3);
    font-family: var(--sj-font-display);
    font-size: var(--sj-text-lg);
    color: var(--sj-color-ink-muted);
    text-align: center;
}

.creator__shelf-body {
    display: flex;
    flex-direction: column;
    gap: var(--sj-space-2);
    padding: var(--sj-space-3);
}

.creator__shelf-body p {
    margin: 0;
}

.creator__shelf-title {
    font-family: var(--sj-font-display);
    font-size: var(--sj-text-md);
    font-weight: 600;
    line-height: 1.25;
}

.creator__shelf-facts {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sj-space-2);
    font-size: var(--sjs-text-sm);
    color: var(--sj-color-ink-muted);
}

.creator__shelf-locales {
    color: var(--sj-color-ink-subtle);
    font-size: var(--sjs-text-xs);
    font-weight: var(--sj-weight-medium);
}

.creator__shelf-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sj-space-2);
    margin-top: auto;
}

/*
    A reading width (P3). On a 2560px monitor every prose screen ran edge to edge at two hundred
    characters a line. 90rem holds the Story Studio's three panes comfortably and stops the QA
    findings and the truth contract becoming ribbons; screens whose content is genuinely wide — the
    grids, the strips — already scroll inside their own boxes, so nothing is clipped by this cap.
*/
.creator__main {
    max-width: 90rem;
    margin-inline: auto;
}

/* ---------------------------------------------------- the shell in daylight (P3) */

/*
    The sidebar was Radzen's own dark panel — a block of near-black down the side of a warm, light
    tool, and the heaviest thing on every screen. The direction's shell is quiet: white surfaces on
    the bench, ink for words, teal only where something is active. These are Radzen's published
    variables, so the menu keeps its own behaviour — aria-current, focus, collapse — and only the
    paint changes (ADR-0005: no page carries a raw Radzen styling decision).
*/
:root {
    --rz-sidebar-background-color: var(--sj-color-surface);
    --rz-sidebar-color: var(--sj-color-ink);
    --rz-sidebar-border-inline-end: 1px solid var(--sj-color-border);
    /* The toggle too — its icon kept Radzen's own gray, which after dark measured 1.89:1. */
    --rz-sidebar-toggle-color: var(--sj-color-ink-muted);
    --rz-sidebar-toggle-hover-color: var(--sj-color-ink);
    --rz-sidebar-toggle-hover-background-color: var(--sj-color-surface-sunken);

    /*
        Every word Radzen sets funnels through these four — grid cells, headings, captions,
        disabled text all resolve to them — and they defaulted to Radzen's own gray scale, which
        no theme reaches: after dark a grid cell measured 1.89:1 and a display heading 1.01:1,
        each on the screen and unreadable. Pointed at the ink tokens they follow both themes for
        free, which is also why the dark blocks below need no Radzen entries at all.
    */
    --rz-text-color: var(--sj-color-ink);
    --rz-text-title-color: var(--sj-color-ink);
    --rz-text-secondary-color: var(--sj-color-ink-muted);
    --rz-text-disabled-color: var(--sj-color-ink-subtle);

    /*
        And the gray scale those defaults were drawn from. Radzen derives dozens of surfaces —
        grid header rows, stripes, filter bars, dropdown panels — from `--rz-base-*`, a fixed
        light-gray ladder no theme reaches: after dark the grid's header row kept its near-white
        ground under light ink at 1.11:1. Mapped onto the tokens, every derived surface follows
        the theme, and in daylight the grays stop being gray — they were the one cool-neutral
        family left on a warm page.
    */
    --rz-base-50: var(--sj-color-surface);
    --rz-base-100: var(--sjs-bench);
    --rz-base-200: var(--sjs-inset);
    --rz-base-300: var(--sj-color-border);
    --rz-base-400: var(--sj-color-border-strong);
    --rz-base-500: var(--sj-color-ink-subtle);
    --rz-base-600: var(--sj-color-ink-subtle);
    --rz-base-700: var(--sj-color-ink-muted);
    --rz-base-800: var(--sj-color-ink);
    --rz-base-900: var(--sj-color-ink);
    --rz-base-lighter: var(--sjs-bench);
    --rz-base-light: var(--sjs-inset);
    --rz-base-dark: var(--sj-color-ink-muted);
    --rz-base-darker: var(--sj-color-ink);
    --rz-on-base-lighter: var(--sj-color-ink);
    --rz-on-base-light: var(--sj-color-ink);
    --rz-on-base-dark: var(--sj-color-ink-inverse);
    --rz-on-base-darker: var(--sj-color-ink-inverse);

    /* The one colour in Radzen's grid that is a literal rather than a variable: the stripe rows
       are #f7f8fb in the theme file itself, which after dark left every even row near-white under
       light ink at 1.14:1 — thirty-six findings from one hex. */
    --rz-grid-stripe-background-color: var(--sjs-bench);
    --rz-grid-stripe-odd-background-color: var(--sj-color-surface);

    /*
        The "-lighter" wash and its on-colour, which radzen-overrides never mapped: the pager's
        active page, row hover and the flat alerts all pair them, and the on-colour is the wcag
        theme's own deep blue (#002ea3) — off the palette in daylight and 1.41:1 after dark.
    */
    --rz-primary-lighter: var(--sj-color-primary-soft);
    --rz-on-primary-lighter: var(--sj-color-primary-hover);
    --rz-info-lighter: var(--sj-color-primary-soft);
    --rz-on-info-lighter: var(--sj-color-primary-hover);
    --rz-success-lighter: var(--sj-color-success-soft);
    --rz-on-success-lighter: var(--sj-color-success);
    --rz-warning-lighter: var(--sj-color-warning-soft);
    --rz-on-warning-lighter: var(--sj-color-warning);
    --rz-danger-lighter: var(--sj-color-danger-soft);
    --rz-on-danger-lighter: var(--sj-color-danger);
    --rz-panel-menu-item-background-color: transparent;
    --rz-panel-menu-item-color: var(--sj-color-ink);
    --rz-panel-menu-icon-color: var(--sj-color-ink-muted);
    --rz-panel-menu-item-hover-background-color: var(--sj-color-surface-sunken);
    --rz-panel-menu-item-hover-color: var(--sj-color-ink);
    --rz-panel-menu-item-active-background-color: var(--sj-color-primary-soft);
    --rz-panel-menu-item-active-color: var(--sj-color-primary-hover);
    --rz-panel-menu-item-active-indicator: none;
    --rz-panel-menu-font-weight: var(--sj-weight-medium);
}

/*
    Radzen's filled badges assume their tone colours are light grounds with dark words on them.
    This product's warning and danger tokens are *inks* — #8a5310 is a text colour — so a filled
    warning badge rendered "5d" in black on dark brown at 2.4:1, and read as a dot with dirt in
    it. Every badge joins the chip language instead: soft ground, tone ink, learned once.
*/
.creator .rz-badge-warning {
    background: var(--sj-color-warning-soft);
    color: var(--sj-color-warning);
}

.creator .rz-badge-danger {
    background: var(--sj-color-danger-soft);
    color: var(--sj-color-danger);
}

.creator .rz-badge-info {
    background: var(--sj-color-primary-soft);
    color: var(--sj-color-primary-hover);
}

.creator .rz-badge-success {
    background: var(--sj-color-success-soft);
    color: var(--sj-color-success);
}

.creator .rz-badge-light {
    background: var(--sjs-inset);
    color: var(--sj-color-ink-muted);
}

/* ------------------------------------------------- the studio, arriving (P4) */

/*
    The loading state is the layout it becomes: an app-bar strip, the filmstrip, the canvas and the
    inspector, each a shimmering block in its final place, so the load reads as the story arriving
    rather than as a screen that has not decided what it is. Same three-column arithmetic as
    `.creator__studio-panes`.
*/
.creator__studio-skeleton {
    display: flex;
    flex-direction: column;
    gap: var(--sj-space-3);
}

.creator__studio-skeleton-bar {
    height: 4.25rem;
    border-radius: var(--sjs-radius-lg);
}

.creator__studio-skeleton-panes {
    display: grid;
    grid-template-columns: 164px minmax(0, 1fr) 348px;
    gap: var(--sj-space-4);
    align-items: start;
}

@media (max-width: 1100px) {
    .creator__studio-skeleton-panes {
        grid-template-columns: minmax(0, 1fr);
    }
}

.creator__studio-skeleton-rail {
    display: flex;
    flex-direction: column;
    gap: var(--sj-space-2);
}

.creator__studio-skeleton-card {
    aspect-ratio: 16 / 10;
    border-radius: var(--sjs-radius-md);
}

.creator__studio-skeleton-canvas {
    aspect-ratio: 3 / 2;
    border-radius: var(--sjs-radius-lg);
}

.creator__studio-skeleton-side {
    height: 28rem;
    border-radius: var(--sjs-radius-lg);
}

/* ------------------------------------------------------ the publish moment (P4) */

/*
    The one loud moment. Weeks of pipeline end here, and it used to be the same quiet green line a
    saved sound cue gets. Positioned, because the burst's sparks are absolutely placed and need a
    frame to fly inside; the storybook voice, because the sentence is about the story.
*/
.creator__published {
    position: relative;
    margin: var(--sj-space-3) 0;
    padding: var(--sj-space-5);
    border: 1px solid var(--sj-color-success, var(--sj-color-border));
    border-radius: var(--sjs-radius-lg);
    background: var(--sj-color-success-soft, var(--sj-color-surface));
    text-align: center;
    overflow: hidden;
}

.creator__published-title {
    margin: 0 0 var(--sj-space-1);
    font-family: var(--sj-font-display);
    font-size: var(--sj-text-xl);
    font-weight: 600;
    color: var(--sj-color-success, var(--sj-color-ink));
}

.creator__published-detail {
    margin: 0;
    color: var(--sj-color-ink);
}

/* ===========================================================================
   P5 — the studio after dark.

   The stories are the child's and stay in daylight; the bench around them can
   dim. Because Radzen reads its palette through --rz-* variables that point at
   these tokens, and every studio rule reads tokens, redefining the VALUES
   under a dark scope re-themes the whole tool — no second stylesheet, no
   forked brand. tokens.css itself is untouched: it is the App's, and the App
   is a child's daylight product on purpose.

   Three blocks, and a test that keeps them honest. The system block and the
   explicit block must redefine the same set — an explicit choice has to win
   in both directions — and the light island under `.creator__stage` must
   restore that same set, because the canvas renders the child's actual
   screen: a dark preview would be an editor approving a story no child will
   ever see. `Dark_mode_redefines_no_more_and_no_less` walks all three.
   =========================================================================== */

@media (prefers-color-scheme: dark) {
    :root:not([data-sjs-theme="light"]) {
        color-scheme: dark;

        --sj-color-canvas: #221b16;
        --sj-color-surface: #2b231d;
        --sj-color-surface-sunken: #1a1410;
        --sj-color-ink: #f2e8dc;
        --sj-color-ink-muted: #c7b6a6;
        --sj-color-ink-subtle: #9a8a7b;
        --sj-color-ink-inverse: #241d18;
        --sj-color-border: #43392f;
        --sj-color-border-strong: #5b4d40;
        --sj-color-primary: #23807e;
        --sj-color-primary-hover: #4db6b2;
        --sj-color-primary-soft: #12403f;
        --sj-color-accent: #eaa63c;
        --sj-color-accent-soft: #46340f;
        --sj-color-secondary: #cf7048;
        --sj-color-secondary-soft: #43261a;
        --sj-color-success: #6cc79a;
        --sj-color-success-soft: #1a382c;
        --sj-color-warning: #e3a75f;
        --sj-color-warning-soft: #402e15;
        --sj-color-danger: #e68d82;
        --sj-color-danger-soft: #47211a;
        --sj-color-focus: #4db6b2;
        --sj-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.5);
        --sj-shadow-md: 0 6px 18px rgba(0, 0, 0, 0.55);
        --sj-shadow-lg: 0 18px 40px rgba(0, 0, 0, 0.6);
        --sjs-bench: #1c1613;
        --sjs-inset: #3a3128;
    }
}

:root[data-sjs-theme="dark"] {
    color-scheme: dark;

    --sj-color-canvas: #221b16;
    --sj-color-surface: #2b231d;
    --sj-color-surface-sunken: #1a1410;
    --sj-color-ink: #f2e8dc;
    --sj-color-ink-muted: #c7b6a6;
    --sj-color-ink-subtle: #9a8a7b;
    --sj-color-ink-inverse: #241d18;
    --sj-color-border: #43392f;
    --sj-color-border-strong: #5b4d40;
    --sj-color-primary: #23807e;
    --sj-color-primary-hover: #4db6b2;
    --sj-color-primary-soft: #12403f;
    --sj-color-accent: #eaa63c;
    --sj-color-accent-soft: #46340f;
    --sj-color-secondary: #cf7048;
    --sj-color-secondary-soft: #43261a;
    --sj-color-success: #6cc79a;
    --sj-color-success-soft: #1a382c;
    --sj-color-warning: #e3a75f;
    --sj-color-warning-soft: #402e15;
    --sj-color-danger: #e68d82;
    --sj-color-danger-soft: #47211a;
    --sj-color-focus: #4db6b2;
    --sj-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.5);
    --sj-shadow-md: 0 6px 18px rgba(0, 0, 0, 0.55);
    --sj-shadow-lg: 0 18px 40px rgba(0, 0, 0, 0.6);
    --sjs-bench: #1c1613;
    --sjs-inset: #3a3128;
}

/*
    The light island. The stage frame holds the real StoryPlayer, and the story a child opens is a
    daylight product — an editor approving a dark-tinted preview would be approving a story no
    child will ever see. The values are tokens.css's own, restated because a custom property has no
    "revert to the ancestor before that one".
*/
.creator__stage {
    color-scheme: light;

    --sj-color-canvas: #fff6e9;
    --sj-color-surface: #ffffff;
    --sj-color-surface-sunken: #fbebd5;
    --sj-color-ink: #2c1f17;
    --sj-color-ink-muted: #6b554a;
    --sj-color-ink-subtle: #9a8578;
    --sj-color-ink-inverse: #fff9f0;
    --sj-color-border: #f0dfc6;
    --sj-color-border-strong: #d9c2a2;
    --sj-color-primary: #0e7c7b;
    --sj-color-primary-hover: #0a6260;
    --sj-color-primary-soft: #d7f0ef;
    --sj-color-accent: #e8a02c;
    --sj-color-accent-soft: #fff0d2;
    --sj-color-secondary: #a8431f;
    --sj-color-secondary-soft: #fce3d6;
    --sj-color-success: #2e7d5b;
    --sj-color-success-soft: #dff2e7;
    --sj-color-warning: #8a5310;
    --sj-color-warning-soft: #fdeecd;
    --sj-color-danger: #a83232;
    --sj-color-danger-soft: #fce0de;
    --sj-color-focus: #0e7c7b;
    --sj-shadow-sm: 0 1px 3px rgba(94, 58, 30, 0.08);
    --sj-shadow-md: 0 6px 18px rgba(94, 58, 30, 0.12);
    --sj-shadow-lg: 0 18px 40px rgba(94, 58, 30, 0.16);
    --sjs-bench: #f6f2ea;
    --sjs-inset: #f0e9db;
}
