/* app/editor/ui.css
   The editor screen's own stylesheet — token-only, moved back out of an inline <style> block.
   The editor's CSP (app/editor/ui.html) is `style-src 'self'`, with no `unsafe-inline`: an inline
   <style> block is refused by the browser, not merely discouraged. Every other governed screen may
   keep its CSS inline because its CSP allows it; this screen may not, so this file is that screen's
   one same-origin stylesheet, linked after the shared sheet (app/shared/storyme-tokens.css) and
   checked by app/shared/editor-screens-consume-tokens.test.mjs exactly as an inline <style> block
   would be. Every colour, type size and spacing value below resolves against a custom property the
   shared sheet declares; nothing here mints a literal, a local custom property, an @import or an
   @font-face. */
    html { color-scheme:light; }
    /* The publish tray can occupy up to70vh. Native focus/scrollIntoView must use
       the unobscured viewport, so centring Save never puts its real hit target
       behind the fixed tray. This changes scroll alignment, not flow or tray size. */
    html:has(#live-publish-panel:not([hidden])) { scroll-padding-block-end:70vh; }
    * { box-sizing:border-box; }
    /* BODY_SIZE 16 and LINE_HEIGHT 1.5, TEXT_RESIZE 200: every size on this screen is a var() into
       the shared sheet, content remains scrollable, and no region reads under 16px. */
    body { margin:0; min-height:100vh; background:var(--color-page-background); color:var(--color-primary-text);
      font-family:var(--font-family-display); font-size:var(--type-body-16-size);
      font-weight:var(--type-body-16-weight); line-height:1.5; }
    button, input, textarea { font-family:inherit; color:inherit; }
    a:focus-visible, button:focus-visible, input:focus-visible, textarea:focus-visible,
    summary:focus-visible, [tabindex]:focus-visible {
      outline:3px solid var(--color-state-hover); outline-offset:2px; }

    .skip-link { position:fixed; z-index:20; inset-block-start:var(--space-8); inset-inline-start:var(--space-8);
      inline-size:168px; min-block-size:var(--space-48); padding:var(--space-8) var(--space-16); border-radius:var(--space-8);
      background:var(--color-state-hover); color:var(--color-surface-primary); font-weight:var(--type-button-1-bold-weight);
      transform:translateY(-180%); }
    .skip-link:focus { transform:translateY(0); }

    .studio-shell { width:min(1600px, calc(100% - var(--space-32))); margin-inline:auto;
      padding-block:var(--space-24) var(--space-48); }

    /* HEADER — hamburger (decorative, no drawer exists yet) · brand · product-area tabs
       (Author / Design / Publish, Flow 3) · language · email · sign in · the "Changes saved" chip,
       which is the existing #docState element restyled in place: no new state, no fake control. */
    .studio-header { display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap;
      gap:var(--space-16); min-width:0; padding:var(--space-8) 0 var(--space-16); }
    .hamburger { display:inline-grid; place-items:center; min-width:var(--space-32); min-height:var(--space-32);
      border-radius:var(--space-8); color:var(--color-secondary-text); font-size:var(--type-h5-x-btn-size); }
    .brand-lockup, .header-actions, .panel-heading, .canvas-toolbar, .toolbar { display:flex; align-items:center; }
    .brand-lockup { gap:var(--space-12); min-width:max-content; }
    .brand-mark { display:grid; place-items:center; min-width:var(--space-32); min-height:var(--space-32);
      border-radius:var(--space-8); background:var(--color-dark-theme-surface); color:var(--color-surface-primary);
      font-size:var(--type-h5-x-btn-size); font-weight:var(--type-button-1-bold-weight); }
    .eyebrow, .kicker { margin:0 0 var(--space-4); color:var(--color-secondary-text);
      font-size:var(--type-body-16-size); font-weight:var(--type-button-1-bold-weight); letter-spacing:.08em; text-transform:uppercase; }
    h1, h2, h3, p { overflow-wrap:anywhere; }
    h1 { margin:0; font-size:var(--type-h3-size); font-weight:var(--type-h3-weight); line-height:1.15; }
    h2 { margin:0; font-size:var(--type-h4-size); font-weight:var(--type-h4-weight); line-height:1.2; }
    h3 { margin:0 0 var(--space-8); font-size:var(--type-h5-x-btn-size); font-weight:var(--type-h5-x-btn-weight); }

    .header-modes { display:flex; flex-direction:column; gap:var(--space-4); align-items:flex-start; min-width:0; }
    /* c367 r15 (C, measured at 1440): the header row spaced its three groups apart, so the modes moved whenever the
       title's, the deferral note's (max-width in ch) or the actions' WIDTH followed the face (AR 515→496px, EN title
       181→186px). In the wide header the modes now follow the title, whose width is held in both languages and faces
       (measured 181–199px), and the actions take the far edge — no group's position depends on a word's width. */
    #studio-title { inline-size:208px; white-space:nowrap; }
    @media (min-width:761px) {
      .studio-header { justify-content:flex-start; }
      .studio-header > .header-actions { margin-inline-start:auto; }
    }
    .header-actions { justify-content:flex-end; gap:var(--space-8); min-width:0; flex-wrap:wrap; }
    .email-field { display:flex; align-items:center; gap:var(--space-8); color:var(--color-secondary-text); font-size:var(--type-body-16-size); }
    #email-label { flex:0 0 152px; }
    .email-field input { width:min(220px, 24vw); }

    input, textarea { width:100%; min-width:0; border:1px solid var(--color-control-border); border-radius:var(--space-8);
      background:var(--color-surface-primary); color:var(--color-primary-text); padding:var(--space-8) var(--space-12);
      font-size:var(--type-body-16-size); font-weight:var(--type-body-16-weight); }
    input:hover { border-color:var(--color-secondary-text); }
    input:disabled, textarea:disabled, button:disabled { cursor:not-allowed; opacity:.5; }
    textarea { resize:vertical; line-height:1.5; }

    /* BUTTONS. Secondary (outlined) is the default; Primary (filled) is create/save; quiet/tertiary
       are text-only. TARGET_SIZE 24 as a min-height/min-width of 48, through the spacing scale. */
    button, input { min-height:var(--space-48); }
    button { min-width:var(--space-48); border-radius:var(--space-8); padding:var(--space-8) var(--space-16);
      cursor:pointer; font-size:var(--type-button-1-bold-size); font-weight:var(--type-button-1-bold-weight);
      color:var(--color-state-pressed); background:var(--color-surface-primary); border:1px solid var(--color-secondary-accent); }
    button:hover:not(:disabled) { border-color:var(--color-state-hover); background:var(--color-secondary-accent-light); }
    button:active:not(:disabled) { border-color:var(--color-state-pressed); background:var(--color-state-pressed-light); }
    .button.primary { color:var(--color-surface-primary); background:var(--color-state-hover); border-color:var(--color-state-hover); }
    .button.primary:hover:not(:disabled), .button.primary:active:not(:disabled) { background:var(--color-state-pressed); border-color:var(--color-state-pressed); }
    .button.quiet { color:var(--color-secondary-text); background:transparent; border-color:transparent; }
    .button.quiet:hover:not(:disabled) { border-color:var(--color-control-border); background:transparent; }

    .segmented { display:inline-flex; padding:var(--space-4); border:1px solid var(--color-border);
      border-radius:var(--space-8); background:var(--color-page-background); }
    .segmented button { min-width:var(--space-32); border-color:transparent; border-radius:var(--space-4);
      background:transparent; font-size:var(--type-body-16-size); font-weight:var(--type-button-1-bold-weight); }
    .segmented button[aria-pressed="true"] { background:var(--color-surface-primary); color:var(--color-state-pressed); border-color:var(--color-secondary-accent); }

    .session-state { inline-size:150px; max-width:150px; color:var(--color-status-active-on-light); font-size:var(--type-body-16-size); overflow-wrap:anywhere; }
    /* The signed-in sentence owns one line before sign-in as well as after it. Its fixed width holds
       its place; the line reserve keeps the phone header from growing when #who gains text. */
    .session-state { min-block-size:1lh; }
    /* c367 r15 (A+B, measured on every cell): the box is a fixed 150px wide, so no hidden copy is needed to hold its
       width — the old zero-height copy overflowed its own block (scroll 48 vs 24) and read as a clip. Its line is tall
       enough for the Arabic face's content area (27px measured), so the sentence never overflows its own box. */
    .session-state { line-height:1.75; }
    /* c367: THE HEADER KEEPS ITS GEOMETRY WHATEVER ITS WORDS SAY. The signed-in word (#who), the saved chip
       (#docState) and the sign-in label are the only header text that changes after the first paint, and each
       change arrives with an ANSWER (sign-in, Save, Reload, Undo). None of them may wrap: a word that wraps is a row
       that grows, and a chip that narrows is a row that re-wraps (measured on main at AR 390: the narrower
       «قصة محفوظة» let the chip join the sign-in row, the sign-in button shrank to its 90px basis and wrapped
       its label; the c361 r5 review measured #signin moving 143.2 px in AR and 44.4 px in EN on Save at 390).
       Widths may still change; heights and line breaks may not. c340's width reserve above stays whole (the ::after
       holds the signed-in sentence's width before the answer), and so does its max-width:150px — "Signed in" and
       «تم تسجيل الدخول» each fit it on one line; its overflow-wrap:anywhere is inert under nowrap. */
    .session-state { white-space:nowrap; }
    #signin { white-space:nowrap; }
    /* HarfBuzz measurement of the actual AR/EN labels: the largest of the vendored face and
       its local fallback, at 16px, plus 42px for padding, borders and rounding. The labels still
       paint in the real faces; their late swap no longer changes intrinsic control widths or flex rows. */
    #header-mode-tabs button { flex:0 0 104px; inline-size:104px; white-space:nowrap; }
    #language-controls button { flex:0 0 72px; inline-size:72px; }
    #signin { inline-size:152px; }

    /* THE "CHANGES SAVED" CHIP — #docState, moved into the header and restyled here only. The text
       and the data-state attribute are still written by the same updateDocumentState() in ui.mjs;
       nothing here fakes a saved state the document has not reached. */
    #docState { display:inline-flex; align-items:center; gap:var(--space-4); padding:var(--space-4) var(--space-16);
      border-radius:var(--space-80); border:1px solid var(--color-border); background:var(--color-page-background);
      color:var(--color-secondary-text); font-size:var(--type-body-16-size); font-weight:var(--type-button-1-bold-weight); margin:0;
      white-space:nowrap; inline-size:200px; }
    #docState[data-state="saved"] { border-color:var(--color-status-active-on-light); background:var(--color-success-light); color:var(--color-status-active-on-light); }
    #docState[data-state="dirty"] { border-color:var(--color-highlight); background:var(--color-secondary-accent-light); color:var(--color-state-pressed); }

    .deferred-note { margin:0; padding:var(--space-8) var(--space-16); border:1px solid var(--color-border);
      border-radius:var(--space-8); background:var(--color-page-background); color:var(--color-secondary-text);
      font-size:var(--type-body-16-size); max-width:80ch; }

    .status-strip { display:flex; justify-content:space-between; flex-wrap:wrap; gap:var(--space-16);
      margin-block-end:var(--space-16); padding:var(--space-8) var(--space-16); border:1px solid var(--color-border);
      border-radius:var(--space-8); background:var(--color-surface-primary); color:var(--color-secondary-text);
      font-size:var(--type-body-16-size); }
    /* c367: an empty status keeps its line, so the first sentence an answer writes never pushes the toolbar down.
       Nothing more is reserved: c365's shared cell below is kept whole (no blank band at any width); a sentence that
       wrapped would grow the strip, and the stillness guard judges the strip's height at every answer, AR and EN,
       from 320 to 1440 px. */
    .status-strip p { margin:0; min-block-size:1lh; }
    /* c365 r8/r9: the status sentence and the waiting notice share ONE grid cell at every width, and both are laid out
       at all times: only which one is seen switches. The cell is the taller of the two whether or not a request waits,
       so a request waiting its turn (a Restore pressed while another runs) never changes the box's height at any width,
       in either language, and moves nothing (qualification c365q1; at 320 px the old English words wrapped). The
       notice's words are one line at 320 px in Arabic and English, so the box keeps one line, with no blank band.
       While nothing waits the notice is hidden from the eye and the accessibility tree (visibility), not removed from
       layout; while a request waits, #gates is hidden only to the eye (opacity): it stays the live region, so an
       outcome written meanwhile is still announced. The notice is still switched only by its hidden attribute. */
    .status-strip { display:grid; grid-template-columns:minmax(0, 1fr); }
    .status-strip > p { grid-area:1 / 1; }
    .status-strip > .request-waiting[hidden] { display:block; visibility:hidden; }
    .status-strip:has(> .request-waiting:not([hidden])) > #gates { opacity:0; }
    .status-strip [data-state="ready"] { color:var(--color-status-active-on-light); }
    .status-strip [data-state="error"] { color:var(--color-state-pressed); }

    /* TOOLBAR CARD (Flow 3): Export · Preview · Story DNA · settings. Only Preview is live — it is
       the same #preview button and the same client.preview() call; the rest are disabled controls,
       never a fake handler, with the deferral declared beside them in both languages. */
    .toolbar { justify-content:space-between; flex-wrap:wrap; gap:var(--space-12); margin-block-end:var(--space-16);
      padding:var(--space-12) var(--space-16); border:1px solid var(--color-border); border-radius:var(--space-8);
      background:var(--color-surface-primary); }
    .toolbar-controls { display:flex; flex-wrap:wrap; gap:var(--space-8); align-items:center; }
    .toolbar-controls button, .story-map-controls button, .placement-controls button {
      flex:0 0 auto; white-space:nowrap; }
    #toolbar-export { inline-size:96px; }
    #preview { inline-size:152px; }
    #toolbar-story-dna { inline-size:200px; }
    #toolbar-reading-profile { inline-size:168px; }
    #toolbar-keyboard { inline-size:144px; }
    #toolbar-interactions { inline-size:216px; }
    #toolbar-launch-template { inline-size:176px; }
    #toolbar-scroll-motion { inline-size:152px; }
    #toolbar-table-chart { inline-size:168px; }
    #story-versions { inline-size:160px; }
    #undo, #redo { inline-size:88px; }
    #add-section { inline-size:144px; }
    #add-beat { inline-size:128px; }
    #move-beat-earlier { inline-size:144px; }
    #move-beat-later { inline-size:136px; }
    #placement-rulers { inline-size:152px; }
    #placement-line-up-start { inline-size:160px; }
    #placement-line-up-middle { inline-size:176px; }
    #placement-line-up-end { inline-size:160px; }
    #placement-even-spacing { inline-size:184px; }
    /* B7: Undo and Redo sit in the toolbar, above the author fields, so no adopted words can move them. Their two
       reasons are their accessible descriptions, read from hidden paragraphs (and the pointer's tooltip), so they take
       no space and clip nothing; the one status line has a FIXED block of
       two lines from the first paint, in both languages, and scrolls inside itself when a sentence is longer, so an
       answer never grows it and no control after it moves. */
    .toolbar [aria-disabled="true"] { cursor:not-allowed; opacity:.5; }
    .undo-lines { flex-basis:100%; min-width:0; }
    .undo-lines .undo-status { block-size:2lh; margin:0; overflow-y:auto; overflow-wrap:anywhere; color:var(--color-secondary-text); }

    .studio-grid { display:grid; grid-template-columns:minmax(220px, .8fr) minmax(420px, 2.1fr) minmax(300px, 1.1fr);
      gap:var(--space-16); min-width:0; align-items:start; }

    .panel, .canvas-column, .response-drawer { border:1px solid var(--color-border); border-radius:var(--space-8);
      background:var(--color-surface-primary); }
    .panel { min-width:0; padding:var(--space-16); }
    .panel-heading, .canvas-toolbar { justify-content:space-between; gap:var(--space-8); }
    .count, .selection-chip, .badge { display:inline-flex; align-items:center; min-height:var(--space-24);
      padding-inline:var(--space-8); border:1px solid var(--color-border); border-radius:var(--space-80);
      color:var(--color-secondary-text); font-size:var(--type-body-16-size); }

    .story-map-counts { display:flex; flex-wrap:wrap; gap:var(--space-8); margin-block-start:var(--space-12); }
    .story-map-search { margin-block-start:var(--space-12); display:grid; gap:var(--space-4); }
    .story-map-controls { display:flex; flex-wrap:wrap; gap:var(--space-8); margin-block-start:var(--space-12); }
    .story-map-controls-deferred { margin-block-start:var(--space-8); }
    /* c367 r15 (C, measured): a deferral note keeps two lines in every face — it wrapped to two lines on the fallback and
       to one on the real face (66→42px at EN 1440 and AR 320), moving every row below it when the faces landed. */
    .deferred-note { min-block-size:calc(2lh + 2 * var(--space-8) + 2px); }
    /* The Story Map's position announcement (aria-live). Empty until an author adds or moves a beat. */
    /* A9 × B7 (contract A): the beat line keeps two lines of room from the first paint. A saved story that replaces the
       one on screen (an Undo, a Redo, a restore, a Reload) withdraws its sentence, and the line must not collapse and
       lift the Arrange beats controls under the author's pointer (measured: 32 px on the first c369r4 seat run). */
    .beat-status { margin:var(--space-8) 0 0; color:var(--color-secondary-text); font-size:var(--type-body-16-size); max-width:80ch;
      min-height:calc(2 * 1.5em); line-height:1.5; }

    /* c367: the outline is a box of one size. Its rows follow the document (a beat, its content, its picture each
       take a row), so a Reload, a Restore or an Undo that adopts a document with more or fewer rows would move
       every control below the outline (on a phone, the whole inspector). Four rows show; more scroll inside. */
    #outline-nav { block-size:12rem; overflow-y:auto; overscroll-behavior:contain; overflow-anchor:none; }
    .outline-list { display:grid; gap:var(--space-8); margin:var(--space-16) 0 0; padding:0; list-style:none; }
    .outline-button { display:grid; grid-template-columns:24px minmax(0,1fr); gap:var(--space-8); align-items:center;
      width:100%; padding:var(--space-8); border-radius:var(--space-8); border-color:var(--color-border);
      background:var(--color-page-background); color:var(--color-secondary-text); text-align:start; }
    .outline-button[aria-current="true"] { border-color:var(--color-secondary-accent); color:var(--color-primary-text); background:var(--color-secondary-accent-light); }
    .outline-index { display:grid; place-items:center; min-width:24px; min-height:24px; border-radius:var(--space-4);
      background:var(--color-surface-primary); color:var(--color-state-pressed); font-size:var(--type-body-16-size); }
    .outline-label { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

    /* A9 ARRANGE BEATS. The buttons sit above the tree and keep one label each; the tree has a fixed height and
       scrolls inside itself, and the status line keeps two lines, so no change, fold or answer moves the panel. */
    .layers-panel { margin-block-start:var(--space-16); padding-block-start:var(--space-12); border-block-start:1px solid var(--color-border); }
    .layers-panel h3 { margin:0; font-size:var(--type-body-16-size); }
    .layers-toolbar { display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); gap:var(--space-4); margin-block-start:var(--space-8); }
    .layers-toolbar .button { min-width:0; padding-inline:var(--space-8); overflow-wrap:anywhere; }
    /* c367 r16s (C, measured on c369's layers): a two-line label's button took its face's own line (normal), so the
       grid row followed the face when it landed (EN 1440 "Group with next" 66→60px, lifting every control above it at
       EN 320). The row now counts the page's line in both faces. */
    .layers-toolbar .button { line-height:1.5; }
    /* c367 r18 (measured on the Linux CI image): 'تجميع مع التالية' fits one line in the metric-matched fallback and two in
       the served Arabic face at 320px (48→66), so every button of the row keeps its two lines from the first paint (66px:
       two 1.5 lines, the 8px padding above and below and the 1px border), and the face landing changes no row. */
    .layers-toolbar .button { min-block-size:calc(2lh + 2 * var(--space-8) + 2px); }
    .layers-toolbar [aria-pressed="true"] { border-color:var(--color-secondary-accent); background:var(--color-secondary-accent-light); color:var(--color-primary-text); }
    /* The tree is a scrolling region of one extent: its rows scroll inside it and are never clipped. */
    .layers-tree { position:relative; min-height:12rem; max-height:12rem; overflow-y:auto; overflow-anchor:none; margin:var(--space-8) 0 0; padding:var(--space-4); list-style:none;
      border:1px solid var(--color-border); border-radius:var(--space-8); }
    .layer-row { display:grid; grid-template-columns:1.25rem minmax(0, 1fr) auto; align-items:center; gap:var(--space-4);
      min-width:var(--space-48); min-height:var(--space-48); padding-inline:var(--space-4); border-radius:var(--space-4); cursor:pointer; }
    .layer-row[aria-level="2"] { padding-inline-start:var(--space-24); }
    .layer-row[hidden] { display:none; }
    .layer-row[aria-selected="true"] { background:var(--color-secondary-accent-light); color:var(--color-primary-text); }
    .layer-row:focus-visible { outline:3px solid var(--color-state-hover); outline-offset:-3px; }
    .layer-row[data-hidden="true"] .layer-name { opacity:.55; }
    .layer-name { overflow-wrap:anywhere; }
    .layer-flags { color:var(--color-secondary-text); font-size:var(--type-body-16-size); white-space:nowrap; }
    .layers-status { min-height:3em; margin:var(--space-8) 0 0; color:var(--color-secondary-text); font-size:var(--type-body-16-size); line-height:1.5; }
    .canvas-card[data-layer-hidden="true"] { opacity:.55; }
    /* Read by assistive technology, drawn nowhere: paint containment keeps its words inside its one-pixel box, so it
       never widens the page (c369 r2, M6). */
    .visually-hidden { position:absolute; width:1px; margin:0; padding:0; clip-path:inset(50%); white-space:nowrap; border:0; contain:paint; }

    .canvas-column { display:flex; flex-direction:column; min-width:0; padding:var(--space-16); }
    .mode-controls button { min-width:70px; }
    #mode-design { inline-size:104px; flex:0 0 104px; }
    #mode-preview { inline-size:112px; flex:0 0 112px; }
    .viewport-frame { min-height:560px; margin-block-start:var(--space-12); border:1px solid var(--color-border);
      border-radius:var(--space-8); overflow:hidden; background:var(--color-page-background); }
    .viewport-ruler { display:flex; justify-content:space-between; padding:var(--space-8) var(--space-12);
      border-block-end:1px solid var(--color-border); color:var(--color-secondary-text);
      font-family:var(--font-family-mono); font-size:var(--type-body-16-size); direction:ltr; }
    /* c367: the canvas is the viewport its ruler names — a frame of one size that scrolls its content, never a
       column that grows with it. Adopted words that wrap differently, a third beat, or the preview replacing
       the design view change what is inside the frame, not where the outline, the inspector and the activity
       drawer below it sit (the design view already scrolled sideways for a wide measure, c335 r5). */
    /* c367 r16t (KIMI VERDICT-ESCALATE-c367-canvas-wheel): the canvas keeps its own fixed box and scroll, but its wheel chains to the page at its end — the shipped scroll-motion preview is driven by the page's scroll. */
    .canvas { display:grid; align-content:start; gap:var(--space-16); block-size:520px;
      padding:clamp(var(--space-16), 4vw, var(--space-48)); overflow:auto; overflow-anchor:none; }
    .empty-state { place-self:center; max-width:32ch; margin:calc(var(--space-80) * 2) auto 0; color:var(--color-secondary-text); text-align:center; }
    .canvas-story-title { width:min(100%, 680px); margin:0 auto calc(-1 * var(--space-4)); color:var(--color-primary-text);
      font-size:var(--type-h4-size); font-weight:var(--type-h4-weight); line-height:1.2; unicode-bidi:plaintext; }
    .canvas-card { position:relative; display:grid; gap:var(--space-12); width:min(100%, 680px); min-height:168px;
      margin-inline:auto; padding:clamp(var(--space-16), 4vw, var(--space-32)); border-radius:var(--space-8);
      background:var(--color-surface-primary); border:1px solid var(--color-border); color:var(--color-primary-text);
      text-align:start; }
    .canvas-card:not(.preview-card)::before { content:attr(data-index); position:absolute; inset-block-start:var(--space-12);
      inset-inline-end:var(--space-12); color:var(--color-secondary-text); font-family:var(--font-family-mono); font-size:var(--type-body-16-size); }
    .canvas-card[aria-current="true"] { border-color:var(--color-secondary-accent); }
    .canvas-card h3, .preview-card h4 { max-width:22ch; margin:0; font-size:var(--type-h4-size); font-weight:var(--type-h4-weight); line-height:1.05; }
    .canvas-card p { max-width:54ch; margin:0; color:var(--color-secondary-text); line-height:1.6; font-size:var(--type-body-16-size); }
    .canvas-card .card-kind { color:var(--color-state-pressed); font-size:var(--type-body-16-size); font-weight:var(--type-button-1-bold-weight); letter-spacing:.08em; text-transform:uppercase; }
    /* B3 (c372 r4): the preview card is the reader's column, where blocks follow one another at their own spacing. Its
       rows start at the top instead of sharing the card's spare height, so a block that wraps (a narrowed beat) grows
       itself and moves nothing else, as in the reader. */
    .preview-card { background:var(--color-secondary-accent-light); align-content:start; }
    /* The preview draws the reader's column (c335 r5): the card's content box is the reading's column, drawn by the
       one reading rule (the author's measure, or the reader's 42rem). On a desktop window the canvas makes room for
       it, and scrolls rather than squeeze a wider measure. */
    /* c367: the columns follow the AUTHOR'S PRESS (data-layout, written only by the three mode presses in ui.mjs
       setLayout), never the answer (data-render-source, what the canvas draws): keyed on the answer, the grid
       re-laid out when the snapshot arrived and moved the outline column, the mode buttons under the pointer, the
       inspector tabs and the beat fields (measured at 1440). The padding inside the frame stays with what is drawn. */
    @media (min-width:1081px) {
      .studio-grid:has(#studio-canvas[data-layout="preview"]) {
        grid-template-columns:minmax(200px, .6fr) minmax(420px, 2.4fr) minmax(280px, 1fr); }
      #studio-canvas[data-render-source="canonical-runtime-preview"] { padding:var(--space-16); }
    }
    /* On a narrower window the reader's own page narrows too: the card follows its canvas, up to the column. */
    @media (max-width:1080px) {
      .preview-card { width:auto !important; }
    }
    .preview-card [data-reading-profile-notice] { color:var(--color-state-pressed); background:var(--color-surface-primary); }
    .narrative-imagery { display:grid; gap:var(--space-8); margin:0; }
    .narrative-imagery img { width:min(100%, 34rem); aspect-ratio:3 / 2; object-fit:cover; border-radius:var(--space-8); }
    .narrative-imagery figcaption { color:var(--color-secondary-text); font-size:var(--type-body-16-size); }
    /* One class for every author-facing picture: the beat picture, the preview card, and the launch thumbnail. */
    .author-picture { max-width:100%; width:fit-content; min-width:0; justify-self:start; align-self:start; }

    /* B3: placing beats. The guides, the ghost and the rulers are overlays in the canvas frame that take no room and
       take no pointer, and the spoken status is visually hidden, so showing or hiding any of them moves nothing. */
    .placement-controls { display:flex; flex-wrap:wrap; gap:var(--space-8); margin-block-start:var(--space-12); }
    .viewport-frame { position:relative; }
    .placement-guides, .placement-rulers { position:absolute; inset:0; pointer-events:none; }
    .placement-guide { position:absolute; inset-block:0; inline-size:0; border-inline-start:1px dashed var(--color-secondary-accent); }
    .placement-ghost { position:absolute; inset-block:0; border:1px dashed var(--color-secondary-accent); opacity:.6; }
    .placement-ruler-mark { position:absolute; inset-block-start:0; translate:-50% 0; padding:0 var(--space-4);
      color:var(--color-secondary-text); font-family:var(--font-family-mono); font-size:var(--type-body-16-size); background:var(--color-page-background); }
    .placement-status { position:absolute; inline-size:1px; block-size:1px; margin:0; overflow:clip; clip-path:inset(50%); white-space:nowrap; }
    .canvas-card[data-beat] { touch-action:pan-y; }
    .placement-grip { position:absolute; inset-block:var(--space-12); inset-inline-end:0; inline-size:var(--space-8);
      cursor:ew-resize; border-inline-end:3px solid var(--color-border); }
    #studio-canvas[data-placement-dragging] .canvas-card { cursor:grabbing; }
    /* c367: each note under the canvas keeps its lines (one; two on a phone), so the preview's sentence replacing
       the design's, or the scroll-motion note arriving, never moves what sits below the canvas. */
    .canvas-note { margin:var(--space-12) 0 0; color:var(--color-secondary-text); font-size:var(--type-body-16-size); min-block-size:1lh; }

    .author-fields { display:grid; gap:var(--space-12); }
    .author-status { white-space:pre-wrap; overflow-wrap:anywhere; }
    /* The generated beat fieldsets (ui.mjs renderAuthorFields) sit in this wrapper so the screen can
       replace them as one ordered region; display:contents keeps each fieldset a direct item of the
       .author-fields grid, exactly as the two static fieldsets were. */
    .beat-fields { display:contents; }
    .author-fields label { display:grid; gap:var(--space-4); color:var(--color-secondary-text); font-size:var(--type-body-16-size); font-weight:var(--type-button-1-bold-weight); }
    #rich-text-role, #rich-text-direction { max-inline-size:100%; min-inline-size:0; min-block-size:var(--space-24);
      font-family:inherit; font-size:var(--type-body-16-size); }
    /* c390 r19 (measured: a direction change to rtl grew it 24→48 and moved Create, Save, Reload below): the panel's status line
       keeps two lines from the first paint, so its longer sentence never moves a control. */
    #rich-text-status { min-block-size:2lh; }
    /* The file input stays the tab stop. It is clipped, never removed from the tab order. Focus shows on the label. */
    .author-fields label.picture-choose { position:relative; display:inline-grid; place-items:center; justify-self:start;
      width:100%; max-width:100%; min-width:0;
      min-height:var(--space-48); padding:var(--space-8) var(--space-16);
      border:1px solid var(--color-secondary-accent); border-radius:var(--space-8); background:var(--color-surface-primary);
      color:var(--color-state-pressed); font-size:var(--type-button-1-bold-size); font-weight:var(--type-button-1-bold-weight);
      cursor:pointer; }
    .author-fields label.picture-choose:focus-within { outline:3px solid var(--color-state-hover); outline-offset:2px; }
    .author-fields label.picture-choose:has(input:disabled) { cursor:not-allowed; opacity:.5; }
    .picture-chosen { overflow-wrap:anywhere; }
    .author-fields .picture-file { position:absolute; inset:0; width:100%; min-width:0; margin:0; padding:0; border:0;
      opacity:0; clip:rect(0, 0, 0, 0); clip-path:inset(50%); white-space:nowrap; }
    .author-fields fieldset { display:grid; gap:var(--space-8); min-width:0; margin:0; padding:var(--space-12);
      border:1px solid var(--color-border); border-radius:var(--space-8); }
    .author-fields legend { padding-inline:var(--space-4); color:var(--color-state-pressed); font-size:var(--type-body-16-size); font-weight:var(--type-button-1-bold-weight); }
    .projected-inspector { margin-block-start:var(--space-12); padding-block-start:var(--space-12); border-block-start:1px solid var(--color-border); }
    .projected-inspector dl { display:grid; grid-template-columns:minmax(70px,.7fr) minmax(0,1.3fr); gap:var(--space-4) var(--space-8);
      margin:0; font-size:var(--type-body-16-size); }
    .projected-inspector dt { color:var(--color-secondary-text); }
    .projected-inspector dd { margin:0; overflow-wrap:anywhere; }
    /* B10 — the property inspector keeps every control still: the disclosure button sits under the fixed
       heading, and every word the inspector writes lives in this one box of fixed height, which scrolls on
       its own. Opening, growing, shrinking or an arriving answer changes only what is inside it. */
    /* The disclosure button is the box's first child and stays at its top (sticky): the box always holds a control,
       so it is never a keyboard stop of its own on any font (the c360 Linux lesson), and the button never moves. */
    .projected-inspector .inspector-more { display:block; inline-size:100%; margin:0; text-align:start; position:sticky;
      inset-block-start:0; z-index:1; background:var(--color-surface-primary); }
    .projected-inspector .inspector-more:hover:not(:disabled) { background:var(--color-surface-primary); }
    /* overflow-anchor:none — nothing inside the box is ever the page's scroll anchor, so replacing what the box
       shows (a facet opened, a Save answer) can never scroll the page (the review's M1: 40-54 px). */
    .inspector-scroll { block-size:22rem; overflow-y:auto; overscroll-behavior:contain; overflow-anchor:none; margin-block-start:var(--space-8);
      padding-inline-end:var(--space-4); }
    .inspector-kind { margin:0; font-weight:var(--type-button-1-bold-weight); }
    /* A face swap may rewrap the explanatory sentence. Keep its full, scrollable text in a
       constant box so the rewrite field below it does not make the inspector scroll itself. */
    .inspector-what { margin:var(--space-4) 0 var(--space-8); font-size:var(--type-body-16-size);
      block-size:4lh; overflow-y:auto; }
    #inspector dd.inspector-does { block-size:4lh; overflow-y:auto; }
    /* The projection repeats authored words above the stable rewrite field. When an edit from
       Preview changes those words, their old variable-height dd grew by 72px and safeDraw kept
       the rewrite field still by moving #inspector-scroll 54→126. Keep the words available in
       their own scrollable block, with the outer scroll and every control in place. */
    #inspector dd[data-author-words] { overflow-wrap:anywhere; }
    /* c367 D6' (e): an answer keeps the words block the author last saw; longer words are clipped (never a scroller, never a Tab stop) until the next author act; the rewrite field below holds them whole. */
    #inspector dd[data-author-words][data-hold-block] { block-size:attr(data-hold-block px, auto); overflow:clip; }
    .projected-inspector dd.inspector-does, .projected-inspector dd.inspector-facet-row { grid-column:2; color:var(--color-secondary-text); }
    .inspector-edit label { display:grid; gap:var(--space-4); margin-block:var(--space-8) 0; color:var(--color-secondary-text);
      font-size:var(--type-body-16-size); font-weight:var(--type-button-1-bold-weight); }
    #inspector-edit-field { min-block-size:var(--space-48); }
    .inspector-kept { margin:var(--space-8) 0; color:var(--color-secondary-text); font-size:var(--type-body-16-size);
      block-size:3lh; overflow-y:auto; }
    .projected-inspector dl.inspector-more-list { margin-block-start:var(--space-8); }
    .projected-inspector [hidden] { display:none; }
    .inspector-notice { margin:0; font-size:var(--type-body-16-size); }
    .inspector-notice:empty { display:none; }
    .inspector-spacer { block-size:0; }
    /* c367: the "Based on" line keeps its line whether or not an element is chosen, so the title field below it
       never moves when an adopted document changes what is selected. */
    .inspector-based-on { margin:var(--space-8) 0 0; color:var(--color-secondary-text); font-size:var(--type-body-16-size); min-block-size:1lh; }
    /* c367: the selection chip never wraps the chosen element's title into a taller heading; a long title ends
       in an ellipsis inside the chip, and the inspector tabs below keep their place. */
    #selection-chip { display:block; max-width:50%; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; line-height:1.5; }
    .action-stack { display:grid; grid-template-columns:1fr 1fr; gap:var(--space-8); margin-block-start:var(--space-16); }
    .action-stack .primary, .action-stack .quiet { grid-column:1 / -1; }
    /* c367 r5: the purpose sentence keeps two lines from its first paint. Longer confirmation,
       refusal and version messages remain whole in the scrolling paragraph. This provisional
       size is judged against the complete bilingual census in the one final seat run. */
    [data-education-purpose-panel] { grid-column:1 / -1; }
    [data-education-purpose-status] { min-block-size:6lh; overflow-wrap:anywhere; }

    /* INSPECTOR PILL TABS (Flow 3): AI Assist / Health / Cultural / Fact. AI Assist wraps the
       existing author fields and the existing projected inspector — live. The other three are
       declared-deferred panels: no live handler, no data, a stated reason in both languages. */
    .inspector-tabs { display:flex; flex-wrap:wrap; gap:var(--space-4); margin-block-start:var(--space-12);
      padding:var(--space-4); border:1px solid var(--color-border); border-radius:var(--space-80); background:var(--color-page-background); }
    .inspector-tabs .tab { flex:0 0 auto; min-width:70px; border-color:transparent; border-radius:var(--space-80);
      background:transparent; padding:var(--space-8) var(--space-12); font-size:var(--type-body-16-size); }
    #tab-ai-assist { inline-size:240px; }
    #tab-rich-text { inline-size:136px; }
    #tab-health { inline-size:104px; }
    #tab-cultural { inline-size:112px; }
    #tab-fact { inline-size:96px; }
    .inspector-tabs .tab[aria-selected="true"] { background:var(--color-surface-primary); color:var(--color-state-pressed); border-color:var(--color-secondary-accent); }
    .inspector-panel-body { margin-block-start:var(--space-12); }
    /* c367: AI Assist is a box of one size, as Health is (c360 r4, below). The author fields carry whatever the
       adopted document carries — a third beat, a picture and its controls, a media sentence, the projected
       inspector's wrapped words, the treatments' status — and on main every one of those moved Create, Save
       and Reload by the difference when a Reload, a Restore or an Undo answered (measured 24 px at AR 1440 and
       AR 390). The box scrolls its content; the actions below it keep their place. */
    #ai-assist-panel:not([hidden]) { block-size:30rem; overflow-y:auto; overscroll-behavior:contain; overflow-anchor:none; }
    .pro-mode { margin-block-start:var(--space-16); padding-block-start:var(--space-12); border-block-start:1px solid var(--color-border); }
    .pro-mode h3 { color:var(--color-secondary-text); }

    .response-drawer { margin-block-start:var(--space-16); padding:var(--space-12) var(--space-16); color:var(--color-secondary-text); }
    .response-drawer summary { cursor:pointer; font-size:var(--type-body-16-size); font-weight:var(--type-button-1-bold-weight); }
    .response-drawer pre { max-height:180px; margin:var(--space-12) 0 0; overflow:auto; color:var(--color-primary-text);
      white-space:pre-wrap; font-family:var(--font-family-mono); font-size:var(--type-body-16-size); line-height:1.5; }

    .canvas-card [dir="auto"] { unicode-bidi:plaintext; }

    @media (max-width:1080px) {
      .studio-grid { grid-template-columns:minmax(200px, .7fr) minmax(0, 2fr); }
      .inspector-panel { grid-column:1 / -1; }
      .author-fields { grid-template-columns:repeat(3, minmax(0, 1fr)); }
      .action-stack { grid-template-columns:repeat(4, minmax(0, 1fr)); }
      .action-stack .primary, .action-stack .quiet { grid-column:auto; }
    }
    @media (max-width:760px) {
      .studio-shell { width:min(100% - var(--space-16), 700px); padding-block-start:var(--space-8); }
      .studio-header { align-items:flex-start; flex-direction:column; }
      .header-actions { width:100%; justify-content:flex-start; flex-wrap:wrap; }
      #header-mode-tabs { flex-wrap:wrap; }
      .email-field { flex:1 1 190px; }
      .email-field input { width:100%; }
      /* c367: on a phone the sign-in button takes a row of its own, so the row it sits on never depends on the
         width of the signed-in word or the saved chip beside it: those two share the row after it, whose height
         is the chip's whatever they say (neither wraps, above). An id selector, it outranks the ≤420 px
         `.header-actions .button` basis below at every width it shares, so that basis never lets the button share
         the chip's row. */
      #signin { flex:1 1 100%; }
      .status-strip { align-items:flex-start; flex-direction:column; gap:var(--space-4); }
      /* c365 r7/r8: where a restore sentence can wrap (760 px and below; at 1024 and 1440 each fits one line), the version
         panel's status keeps two lines, the longest of the loaded, restoring and restored sentences in Arabic and English,
         so confirming a restore never moves the list under the author. */
      #version-history-status { min-block-size:2lh; }
      /* B3 / contract A (c372 r7, on c365's one status box): an answer (a save, a reload) changes the saved/edited chip,
         and the chip keeps a line of its own, so it never pushes or resizes the controls beside it on a phone. The
         studio's sentence keeps no reserve of its own: it shares c365's one box, as tall as the sentence it shows (no
         blank band), and each ordinary answer, the save that lands while the author is still acting among them, is one
         line at 320 px in Arabic and English, so it moves nothing. */
      .header-actions #docState { flex:1 0 100%; }
      /* c367: on a phone the note under the canvas keeps two lines, so the preview's sentence replacing the design's
         never moves what sits below the canvas (the status strip reserves nothing more: c365's shared cell, above). */
      .canvas-note { min-block-size:2lh; }
      .studio-grid { grid-template-columns:minmax(0, 1fr); }
      .outline-panel { order:2; }
      .canvas-column { order:1; }
      .inspector-panel { order:3; grid-column:auto; }
      .viewport-frame { min-height:480px; }
      .canvas { block-size:442px; padding:var(--space-16) var(--space-12); }
      .empty-state { margin-block-start:calc(var(--space-80) + var(--space-32)); }
      .author-fields { grid-template-columns:minmax(0, 1fr); }
      .action-stack { grid-template-columns:1fr 1fr; }
      .action-stack .primary, .action-stack .quiet { grid-column:1 / -1; }
    }
    @media (max-width:420px) {
      .header-actions .button { flex:1 1 90px; }
      .canvas-toolbar { align-items:flex-start; flex-direction:column; }
      .mode-controls { width:100%; }
      .mode-controls button { flex:1; }
      .action-stack { grid-template-columns:minmax(0, 1fr); }
      .action-stack .primary, .action-stack .quiet { grid-column:auto; }
    }
    @media (prefers-reduced-motion: reduce) {
      *, *::before, *::after { scroll-behavior:auto !important; transition-duration:.001ms !important; animation-duration:.001ms !important; animation-iteration-count:1 !important; }
    }

    /* Transient panels share one containing block, taken out of the flow that holds
       the author's controls. A reveal paints inside this block. It does not move a
       control that is already on screen. */
    .studio-transient { position:fixed; z-index:4; inset-inline:0; inset-block-end:0;
      max-height:30vh; overflow:auto; pointer-events:none; }
    .studio-transient > .panel { pointer-events:auto; }
    /* The publish action comes first. Reserve the tray before the asynchronous
       facts arrive so its bottom anchor cannot move the action upward either.
       All facts remain in the scrollable flow, including technical details. */
    .studio-transient:has(#live-publish-panel:not([hidden])) {
      min-height:30vh; scrollbar-gutter:stable; }

    /* The reservation is laid out, not matched to devices. 20rem holds the whole
       published answer (the action, its sentence, the reader link and the lifetime
       sentence) at the narrowest reflow width, 320 CSS px; a wider tray wraps them
       into fewer lines. It never takes the top 12rem of the screen, where the
       header's Author / Design / Publish switch sits, so on a short screen (a phone
       held sideways) the tray is smaller and the answer scrolls inside it, the
       lifetime sentence included; never below the tray's usual 30vh. It is fixed
       before publication, so the action does not move when the answer appears.
       c367 r18 (measured on the Linux CI image): at 320px the lifetime sentence can
       take a fourth line (the answer needs 323.5px), so the tray keeps one line more
       than 20rem. L1 reserves another line before the answer for the reader link
       whose target is now 48px. Keep this room while the link is hidden; the CI
       box diagnostic checks that the live panel and preview title stay still. */
    .studio-transient:has(#live-publish-panel:not([hidden])) {
      min-height:max(30vh, min(calc(20rem + 2lh), 70vh, 100vh - 12rem));
      max-height:max(30vh, min(calc(20rem + 2lh), 70vh, 100vh - 12rem)); }

    .publish-preview-panel { margin-block:var(--space-16); padding:var(--space-24); }
    .publish-preview-panel[hidden] { display:none; }
    .publish-preview-panel h2 { font-size:var(--type-body-16-size); }
    .publish-preview-panel p { max-width:80ch; }
    .publish-preview-panel dt { font-weight:var(--type-button-1-bold-weight); }
    .publish-preview-panel dd { margin-inline:0; margin-block-end:var(--space-12); overflow-wrap:anywhere; }

/* Retained facts keep raw identities readable in either writing direction. */
#candidate-facts-content { overflow-wrap: anywhere; }
#candidate-facts-content dd { margin-inline-start: 0; }
#candidate-facts-content pre { white-space: pre-wrap; }
#candidate-facts-content section { border-block-start: 1px solid var(--color-border); padding-block: var(--space-8); }

/* H1 previews belong to the Story inspector and never apply styles to the Story. */
#h1-selection-panel { margin-block: var(--space-16); padding-block-start: var(--space-16); border-block-start: 1px solid var(--color-control-border); min-width: 0; }
/* c367: the treatments' status keeps three lines, so idle, loading, refused and retained — the read that answers
   after sign-in — never change the panel's height (measured on main: +24 px at 1440 and 390 for Create, Save,
   Reload and everything below when the refused sentence replaced the idle one). */
#h1-status { min-block-size: 3lh; }
#h1-provider-note { min-block-size: 2lh; }
#h1-selection-panel label { display: grid; gap: var(--space-4); margin-block: var(--space-8); }
#h1-selection-panel input, #h1-selection-panel textarea { width: 100%; min-width: 0; box-sizing: border-box; }
#h1-selection-panel p, #h1-selection-panel h4, #h1-selection-panel dd { overflow-wrap: anywhere; }
.h1-actions { display: flex; flex-wrap: wrap; gap: var(--space-8); }
#h1-import { inline-size:224px; }
#h1-generate { inline-size:200px; }
#h1-refresh { inline-size:208px; }
/* The localized purpose button is longer than a phone's content width in English. Give it
   a fixed available width; both faces wrap inside the same two-line room on phones. */
/* c367 r15 (C, measured): a button's line height is the face's own (normal), so its 2lh reserve followed the face
   (EN 64→58px). The reserve now counts the page's line. */
#education-purpose-control { inline-size:min(100%, 296px); line-height:1.5; min-block-size:calc(2lh + var(--space-16)); }
/* c367 r15 (C, measured): the confirm button's width followed its words' face (EN 180→179px, AR 128→131px). */
#education-purpose-confirm { inline-size:192px; }
#h1-cards { display: grid; gap: var(--space-12); }
.h1-treatment { padding: var(--space-12); border: 1px solid var(--color-control-border); border-radius: var(--space-8); background: var(--color-surface-primary); }
.h1-treatment h4 { margin-block: 0 var(--space-8); }
.h1-treatment dl { margin-block: var(--space-8); }
.h1-treatment dt { font-weight: var(--type-button-1-bold-weight); margin-block-start: var(--space-4); }
.h1-treatment dd { margin-inline-start: 0; font-size: var(--type-body-16-size); }
.h1-treatment button[aria-pressed="true"] { font-weight: var(--type-button-1-bold-weight); outline: 2px solid currentColor; outline-offset: 2px; }

.authoring-colour-unset { opacity: 0; }

    /* Publication status and reader access follow the stationary action. */
    #publish-details-text { white-space:pre-wrap; overflow-wrap:anywhere; font-size:var(--type-body-16-size); }
    #published-reader-link:not([hidden]) { display:block; min-width:var(--space-48); min-block-size:var(--space-48); margin-block-start:var(--space-16);
      color:var(--color-state-pressed); text-decoration:underline; overflow-wrap:anywhere; }

    .version-history-row { padding-block:var(--space-16); border-block-end:1px solid var(--color-control-border); }
    .version-history-row label { display:block; margin-block-start:var(--space-12); }
    .version-history-row button { margin-inline-end:var(--space-8); }
    .version-history-dialog { color:var(--color-primary-text); background:var(--color-surface-primary);
      border:1px solid var(--color-control-border); border-radius:var(--space-16);
      padding:var(--space-24); max-inline-size:min(80ch, 90vw); max-block-size:90vh; overflow:auto; }

    /* The history never pushes the Studio down by its whole length: the list scrolls inside a bounded panel, and
       each focused control is scrolled into view by the browser. c367: the bound is the list's one size, not
       its ceiling, so the row a Restore or an Undo adds never moves the Studio below the open panel. */
    #version-history-list { list-style:none; padding-inline-start:0; block-size:50vh; max-block-size:50vh; overflow-y:auto; overscroll-behavior:contain; overflow-anchor:none; }
    /* c365 r7: the status line above the list is never the page's scroll anchor. */
    #version-history-status { overflow-anchor:none; }
/* c360 r3: the check's answer (its status, its findings and the publishing diagnostic) arrives inside a box whose
   size never changes, so Save, Reload, Create and Collect below it never move when an answer arrives (THREAD-14
   scar 2). A long answer scrolls inside the box; the box keeps its size from the moment Health opens.
   c360 r4: the box takes no part in scroll anchoring, so an answer rewriting it never scrolls the page (at 1024 px the
   browser anchored inside it and scrolled the whole Studio). Health itself is a box of fixed size too, so no answer
   inside it (the check, the diagnostic, or the facts line that wraps in English) pushes the controls below it. */
#prepublish-check-panel { min-block-size: 24rem; overflow: visible; overflow-anchor: none; }
/* c360 r4c: the box is a scroller only while it holds a control (a finding's button, the diagnostic and its copy
   button), and then it keeps exactly its reserved size. With the check's words alone it is never a scroller: it
   reserves room for them and grows rather than overflow. A scroller with nothing focusable inside is made a keyboard
   stop of its own by the browser, and on Linux fonts the words alone overflowed the old 18rem box, which put an extra
   Tab stop between Health and Collect (PR 455, CI). 24rem holds those words at the default text size on every
   platform, so the box keeps one size in both states and nothing below it moves. */
#prepublish-check-panel:has(button:not([hidden]), pre:not([hidden])) { block-size: 24rem; overflow-y: auto; overscroll-behavior: contain; }
/* c360 r5: the status line keeps three lines, so "Checking…" and the longer answer that replaces it never move the
   finding controls below it (English wraps the answer onto a second line). */
#prepublish-check-status { min-block-size: 3lh; }
#publishing-diagnostic-text { white-space: pre-wrap; overflow-wrap: anywhere; }
#health-panel:not([hidden]) { block-size: 30rem; overflow-y: auto; overscroll-behavior: contain; overflow-anchor: none; }
/* c360 r4: while the publish panel is open it fills the tray reserved for it, so an answer growing inside it (a refusal,
   its finding controls, the reader link) never covers a control that was clickable before the answer. */
.studio-transient:has(#live-publish-panel:not([hidden])) { block-size: max(30vh, min(calc(20rem + 2lh), 70vh, 100vh - 12rem)); }
.studio-transient > #live-publish-panel:not([hidden]) { box-sizing: border-box; min-block-size: 100%; }
/* c360 r5: the facts line keeps two lines, so its English sentence wrapping never pushes Collect (r3 review M1). */
#candidate-facts-status { min-block-size: 2lh; }

    /* A quiet reading of the author's actual progress: read, never a tab stop. Nothing in it changes size when an
       answer arrives: every "— Done" is laid out from the start and only shown, the last step's label and its link
       share one cell, and the current step is marked by a border colour, never by a wider weight or a thicker line. */
    .first-story-steps { display: flex; flex-wrap: wrap; gap: var(--space-12); padding: var(--space-4) var(--space-16); margin-block: var(--space-16); list-style: none; border: 1px solid var(--color-border); border-radius: var(--space-8); }
    .first-story-steps li { padding: var(--space-4); border-block-end: 2px solid transparent; }
    .first-story-steps [aria-current="step"] { border-block-end-color: currentColor; }
    .first-story-steps [hidden] { display: inline; visibility: hidden; }
    /* Keep the final link's target reserved while it is invisible, so publication does not move the strip. */
    #first-story-open { display:inline-flex; align-items:center; min-width:var(--space-48); min-height:var(--space-48); }
    /* Reserve the 48px target in every state; its extra height comes from the strip's block padding. */
    .first-step-cell { display: inline-grid; min-block-size: var(--space-48); align-items: center; }
    .first-step-cell > * { grid-area: 1 / 1; }

    /* The first-story choices and the demo label fill a slot whose size is set from the start by an invisible copy
       of both, so the author's fields below never move when the sign-in answer offers them or Create takes them away. */
    .first-story-slot { display: grid; }
    .first-story-slot > * { grid-area: 1 / 1; min-width: 0; }
    .first-story-slot-sizer { visibility: hidden; pointer-events: none; }
    #first-story-choices, .first-story-sizer-row { display: flex; flex-wrap: wrap; gap: var(--space-8); }
    /* c367 r15 (C, measured on every face cell): a block whose LINE COUNT follows the face moved every control below it
       when the faces landed (AR: the two choices took two rows on the fallback and one on the real face, 104→48px; EN
       320: the demo label 48→24px; EN 390: the placement hint 72→48px; EN 320: the steps strip re-wrapped). Each such
       block now keeps one line count in both faces: the choices stack, the label keeps two lines, and at phone widths
       the hint keeps three lines and each first-story step its own row. Words, faces and sizes are unchanged. */
    #first-story-choices, .first-story-sizer-row { flex-direction: column; align-items: flex-start; }
    .first-story-sizer-label { min-block-size: 2lh; }
    @media (max-width:420px) {
      #placement-hint { min-block-size: 3lh; }
      .first-story-steps { flex-direction: column; }
    }
    /* … and two notes that wrapped to three lines on the fallback and two on the real face (72→48px: the H1 retention
       notice at EN 1440/390, the education purpose boundary at AR 1440) keep three lines at every width. */
    #h1-retention, #education-purpose-boundary { min-block-size: 3lh; }
    #first-story-choices[hidden] { display: none; }
    .first-story-sizer-row > span { box-sizing: border-box; min-height: var(--space-48); min-width: var(--space-48);
      padding: var(--space-8) var(--space-16); border: 1px solid transparent; font-size: var(--type-button-1-bold-size);
      font-weight: var(--type-button-1-bold-weight); line-height: normal; }

/* The reading inside the Studio (c379): the card's link colour, control minimum and focus ring read the five reading
   properties app/shared/reading-profile-drawing.mjs paints on the card — the author's profile, or the reader's own
   default — never a design token; the Studio's own link colour does not apply inside the card. Controls take the
   author's minimum in both axes. Words inside a sentence (links, note references, note words, back links) keep
   WCAG 2.5.8's inline exception; landing points (tabindex -1) are not targets. */
.preview-card a { color:var(--reading-link-color); }
/* c390 r19: a note reference carries no text of its own (its sentence reads whole); its number is drawn here, as in the reader. */
.preview-card [data-rich-text-note-ref]::after { content:"(" attr(data-rich-text-note-label) ")"; }
.preview-card :is(a[href], button, input, select, textarea, summary, [role="button"], [tabindex]:not([tabindex="-1"])):not([data-rich-text-content] *) {
  display:inline-flex; align-items:center;
  min-width:var(--reading-target-min); min-height:var(--reading-target-min); }
.preview-card :is(a[href], button, input, select, textarea, summary, [role="button"], [tabindex]):focus-visible {
  outline:var(--reading-focus-width) solid var(--reading-focus-color);
  outline-offset:var(--reading-focus-offset); }
/* c367 D6': the canvas box follows the author. Its measured height (px) arrives as data-fit-block on the author's own acts;
   'auto' (not a length) gives the box its content's height for the one measurement. */
.canvas[data-fit-block] { block-size:attr(data-fit-block px, auto); }
