/*
 * Relay components.
 * Depends on relay-tokens.css + relay-base.css. No colour literals except the
 * channel brand colours, which are data (they belong to LinkedIn, TikTok etc.)
 * and are set per-channel from the ChannelDefinition, not chosen here.
 */

/* =========================================================================
   Buttons
   One primary per view — the single cobalt action. Everything else is
   secondary, ghost, or (rarely) danger. Labels say what happens:
   "Generate outputs", "Schedule", "Regenerate" — never "Submit".
   ========================================================================= */

.btn {
    display: inline-flex;
    align-items: center;
    /* Centred on both axes, here, once. A button sized to its own content shows
       no difference — but the moment one is stretched (`flex: 1` in a context
       bar, `width: 100%` in a rail) the label went left, and a label that wraps
       at 390px put both its lines flush left in a tall box. Three call sites had
       already patched this by hand; the ones that had not are what a reader
       notices. `text-align` is the second half of it: with `justify-content`
       centring the text as one flex item, its *lines* are still laid out by
       `text-align`, which defaults to the start edge. */
    justify-content: center;
    text-align: center;
    gap: 0.4rem;
    padding: 7px 12px;
    border: 1px solid transparent;
    border-radius: var(--r-md);
    font-family: var(--font-ui);
    font-size: 12px;
    font-weight: 600;
    line-height: 1;
    text-decoration: none;
    cursor: pointer;
    transition: background .15s, border-color .15s, color .15s, transform .05s;
}

.btn:active {
    transform: translateY(1px);
}

.btn--primary {
    background: var(--signal);
    color: var(--pri-on);
}

.btn--primary:hover {
    background: var(--signal-strong);
    color: var(--pri-on);
}

/* Transparent, not white. No secondary button in any of the mockup frames
   declares a background: it is an outline on whatever it sits on, and a white box
   on --paper draws an edge the design doesn't have. The disabled state below is
   the one place a secondary gets a fill. */
.btn--secondary {
    background: none;
    border-color: var(--line-strong);
    color: var(--ink-2);
}

.btn--secondary:hover {
    border-color: var(--signal);
    color: var(--signal);
}

/* At row scale the bordered button is the row's own action, so it carries full
   ink. At page scale it sits beside a primary and steps back to --ink-2. */
.btn--sm.btn--secondary {
    color: var(--ink);
}

.btn--ghost {
    background: transparent;
    color: var(--ink-2);
}

.btn--ghost:hover {
    background: var(--surface-2);
    color: var(--ink);
}

.btn--danger {
    background: transparent;
    border-color: color-mix(in srgb, var(--critical) 45%, transparent);
    color: var(--critical);
}

.btn--danger:hover {
    background: var(--critical-tint);
    color: var(--critical);
}

/* The listing-row scale. Contextbar buttons are full size — the 46px bar was
   designed around a 31px button — and this is the one that sits in a table row. */
.btn--sm {
    padding: 6px 10px;
    border-radius: 7px;
    font-size: 11px;
}

/* A glyph leading a button's label. `.btn` already carries the gap, so this only
   has to size and quieten it: these are Unicode line art rather than an icon
   set, and at label weight they compete with the word they belong to. */
.btn__ic {
    font-size: 1.15em;
    line-height: 1;
    opacity: .7;
}

/* U+270E is the LOWER RIGHT PENCIL — its tip points down and to the right, the
   mirror of the way every edit control in the world draws one, which is exactly
   what it looks like: a pencil facing the wrong way. Flipped, it points
   down-left.

   The other candidates are worse. U+2710 points up-right, which is no more
   correct and reads as an arrow. U+270F is the right shape but has an emoji
   presentation, and a platform that ignores the U+FE0E we would have to send
   puts a yellow pencil in a monochrome toolbar. U+270E has no emoji form at all,
   so mirroring the safe glyph beats picking a risky one. */
.btn__ic--flip {
    display: inline-block;
    transform: scaleX(-1);
}

/* The page scale: an action inside the page body rather than in the chrome. The
   mockups run chrome buttons at 7px/12px and in-page ones a notch larger — "Save
   channel", "Save pillar", "Generate for 2 pieces" — and the difference is real:
   a button beside a heading needs more weight than one in a 46px bar. */
.btn--page {
    padding: 8px 13px;
}

/* The field scale: an action at the end of a row of inputs, which has to match
   their height — "Mark as posted" beside the link field. */
.btn--field {
    padding: 10px 13px;
    font-size: 12.5px;
}

/* Full ink at this size: the button closes a row of fields and reads as part of
   it, so --ink-2 is a shade too quiet there. */
.btn--field.btn--secondary {
    color: var(--ink);
}

/* The modal-footer scale. A shade larger than a page button because a dialog's
   footer is the only thing in it competing for the eye, and a shade smaller than
   the field scale because it isn't lining up with an input. */
.btn--modal {
    padding: 9px 13px;
    font-size: 12.5px;
}

/* Relay addition — the source spec had no disabled or busy state.
   Both keep the button's shape so the row doesn't reflow. */
.btn:disabled,
.btn[aria-disabled="true"] {
    background: var(--surface-2);
    border-color: var(--line);
    color: var(--ink-3);
    cursor: not-allowed;
    transform: none;
}

.btn:disabled:hover,
.btn[aria-disabled="true"]:hover {
    background: var(--surface-2);
    border-color: var(--line);
    color: var(--ink-3);
}

.btn--busy {
    position: relative;
    color: transparent !important;
    pointer-events: none;
}

.btn--busy::after {
    content: "";
    position: absolute;
    inset: 0;
    margin: auto;
    width: 13px;
    height: 13px;
    border: 2px solid currentcolor;
    border-top-color: transparent;
    border-radius: 50%;
    color: var(--ink-2);
    animation: relay-spin .8s linear infinite;
}

.btn--primary.btn--busy::after {
    color: var(--pri-on);
}

/* HTMX request state: swap the idle/busy label pair without extra JS. */
.hx-busy-label {
    display: none;
}

.htmx-request .hx-idle-label {
    display: none;
}

.htmx-request .hx-busy-label {
    display: inline;
}

/* =========================================================================
   Forms
   High-contrast label, quiet border, a cobalt focus ring reusing the action
   colour. Help text explains before the operator can go wrong; errors say
   what to fix.
   ========================================================================= */

.field {
    display: flex;
    flex-direction: column;
    gap: var(--s1);
    margin-bottom: var(--s4);
    max-width: 380px;
}

.field:last-child {
    margin-bottom: 0;
}

.field--wide {
    max-width: none;
}

.field__label {
    font-family: var(--font-ui);
    /* 12px throughout the mockups' forms, not the 11px of an eyebrow: a field
       label is read while typing, not scanned. */
    font-size: var(--t-meta);
    font-weight: 600;
    line-height: 1;
    color: var(--ink);
}

.field__label .req {
    color: var(--critical);
}

/* A mono aside on the label line, e.g. "comma-separated · case-insensitive". */
.field__hint {
    font-family: var(--font-data);
    font-size: 11px;
    font-weight: 400;
    line-height: 1;
    color: var(--ink-3);
}

.input,
.textarea,
.select {
    width: 100%;
    padding: 0.5rem 0.7rem;
    border: 1px solid var(--line-strong);
    border-radius: var(--r-md);
    background: var(--surface);
    color: var(--ink);
    font-family: var(--font-ui);
    font-size: 12.5px;
    line-height: 1.2;
    transition: border-color .15s, box-shadow .15s;
}

.input--mono,
.textarea--mono {
    font-family: var(--font-data);
}

.input:focus,
.textarea:focus,
.select:focus {
    outline: none;
    border-color: var(--signal);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--signal) 22%, transparent);
}

.input::placeholder,
.textarea::placeholder {
    color: var(--ink-3);
}

.textarea {
    min-height: 84px;
    resize: vertical;
}

/* An input carrying a value the operator has narrowed to — the mockups give
   active filters a cobalt border so you can see the query at a glance. */
.input--active,
.select--active {
    border-color: var(--signal);
    color: var(--signal);
    font-weight: 600;
}

/* The chevron is two CSS gradients — no SVG, no icon font, and it re-tints
   with --ink-3 in dark automatically. */
.select {
    appearance: none;
    padding-right: 2rem;
    background-image:
        linear-gradient(45deg, transparent 50%, var(--ink-3) 50%),
        linear-gradient(135deg, var(--ink-3) 50%, transparent 50%);
    background-position: calc(100% - 16px) 55%, calc(100% - 11px) 55%;
    background-size: 5px 5px, 5px 5px;
    background-repeat: no-repeat;
}

/* The pill scale, for a select used as a filter chip rather than a form field.
   Narrower and rounder: it sits in a toolbar beside a search box, not in a
   column of labelled inputs. */
.select--pill {
    width: auto;
    padding: 7px 26px 7px 12px;
    /* The lighter border: a chip sits on the page, not in a form. */
    border-color: var(--line);
    border-radius: var(--r-pill);
    font-size: 12px;
    font-weight: 500;
    line-height: 1;
    color: var(--ink-2);
}

.field__help {
    font-family: var(--font-ui);
    font-size: var(--t-help);
    line-height: 1.45;
    color: var(--ink-3);
}

/* Set solid, where the help text is a single clause sitting under a label in a
   row rather than a wrapping sentence — "Included in every pull". */
.field__help--tight {
    line-height: 1;
}

/* Relay addition — the source spec defined no error state. */
.field--error .input,
.field--error .textarea,
.field--error .select {
    border-color: var(--critical);
}

.field--error .input:focus,
.field--error .textarea:focus,
.field--error .select:focus {
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--critical) 22%, transparent);
}

.field__error {
    display: flex;
    align-items: flex-start;
    gap: var(--s1);
    font-size: 11.5px;
    font-weight: 600;
    color: var(--critical);
}

/* Checkbox / radio rows: the control sits beside its label, help text under. */
.check {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: var(--s2);
    align-items: start;
    padding: var(--s2) var(--s3);
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    background: var(--surface);
    cursor: pointer;
}

.check--on {
    border-color: var(--signal);
    background: var(--signal-tint);
}

.check input {
    margin: 0;
    accent-color: var(--signal);
    /* Sit the box on the label's first line rather than on the top of its line
       box. A checkbox is 13px against a 16.7–18.8px line, so start-aligned the
       two share a top edge and the box reads as riding high above its own text.

       Half that leading, as a nudge — not `align-self: center`, which centres on
       the whole grid row and so drops the box to the middle of a *wrapped*
       label: the Base Piece source pickers wrap to two lines at 390px, and the
       checkbox landed 9px below the line it belongs to. The input's own font
       metrics are no help here (the UA pins it to 16px whatever the label does),
       so this is measured: within 0.7px on every .check in the build. */
    align-self: start;
    margin-block-start: 2.5px;
}

.check__label {
    font-size: 12.5px;
    font-weight: 600;
    color: var(--ink);
}

.check__help {
    grid-column: 2;
    font-size: var(--t-help);
    line-height: 1.5;
    color: var(--ink-3);
}

/* A two-state switch, used for Active / Media required. */
.toggle-switch {
    display: inline-flex;
    align-items: center;
    gap: var(--s2);
    cursor: pointer;
}

/* The word beside a switch in a header row: quieter and a notch smaller than the
   same label inside a fieldset, where it heads its own explanation. */
.toggle-switch__label {
    font-family: var(--font-ui);
    font-size: 12px;
    font-weight: 600;
    line-height: 1;
    color: var(--ink-2);
}

.toggle-switch__track {
    position: relative;
    flex: none;
    width: 32px;
    height: 19px;
    border-radius: var(--r-pill);
    background: var(--line-strong);
    transition: background .15s;
}

.toggle-switch__track::after {
    content: "";
    position: absolute;
    top: 2px;
    left: 2px;
    width: 15px;
    height: 15px;
    border-radius: 50%;
    background: var(--surface);
    transition: transform .15s var(--ease);
}

.toggle-switch input {
    position: absolute;
    opacity: 0;
    pointer-events: none;
}

.toggle-switch input:checked + .toggle-switch__track {
    background: var(--signal);
}

.toggle-switch input:checked + .toggle-switch__track::after {
    transform: translateX(13px);
}

.toggle-switch input:focus-visible + .toggle-switch__track {
    outline: 2px solid var(--signal);
    outline-offset: 2px;
}

/* A token field — keyword chips plus a ghost "add" affordance. */
.tokens {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s2);
    min-height: 70px;
    padding: 11px;
    border: 1px solid var(--line-strong);
    border-radius: var(--r-md);
    background: var(--surface);
}

.token {
    display: inline-flex;
    align-items: center;
    gap: var(--s2);
    line-height: 1;
    padding: 6px 9px;
    border: 1px solid color-mix(in srgb, var(--signal) 30%, transparent);
    border-radius: 6px;
    background: var(--signal-tint);
    font-size: 12px;
    font-weight: 500;
    color: var(--ink);
}

/* A token that has never matched anything — dashed so it reads as a suggestion
   to drop, not an error. */
.token--idle {
    border-style: dashed;
    border-color: var(--marigold);
    background: var(--marigold-tint);
    color: var(--marigold);
}

.token--neutral {
    border-color: var(--line);
    background: var(--surface-2);
    color: var(--ink-2);
}

.token__count {
    font-family: var(--font-data);
    font-size: 10px;
    color: var(--ink-3);
}

.token--idle .token__count {
    color: var(--marigold);
}

.token__remove {
    border: 0;
    background: none;
    padding: 0;
    color: inherit;
    font-size: 11px;
    cursor: pointer;
    opacity: .7;
}

.token__remove:hover {
    opacity: 1;
}

.token--add {
    border-style: dashed;
    border-color: var(--line-strong);
    background: none;
    color: var(--ink-3);
    cursor: pointer;
}

/* The same chip as a typing surface — `7d`'s "Add a keyword…". Inherits the chip
   geometry; the width is just enough for its own placeholder. */
input.token--add {
    width: 12ch;
    font: inherit;
    color: var(--ink-2);
    cursor: text;
}

input.token--add:focus-visible {
    border-style: solid;
    border-color: var(--signal);
    outline: none;
}

/* `7e`'s expiry presets — the common answers as radios, the date input for the
   rest. */
.presetrow {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s2) var(--s4);
    padding: 2px 0 6px;
}

.presetrow__opt {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-family: var(--font-ui);
    font-size: 12px;
    color: var(--ink-2);
    cursor: pointer;
}

/* --- The pillar chip — `7b`'s select-as-badge on the Core Article editor.

   A native <select> wearing the read view's pill: dot, name, caret. The cobalt
   border and soft ring are the mockup's own resting state — this is the one
   required decision on the row, and the emphasis is the point. */
.pillarchip {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 0 11px;
    border: 1px solid var(--signal);
    border-radius: 8px;
    background: var(--surface);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--signal) 14%, transparent);
}

.pillarchip__dot {
    flex: none;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--signal);
}

/* The select disappears into the chip: the chip is the control's whole face. */
.pillarchip select {
    appearance: none;
    -webkit-appearance: none;
    padding: 8px 2px;
    border: 0;
    background: none;
    font: 600 12px/1 var(--font-ui);
    color: var(--ink);
    cursor: pointer;
}

.pillarchip select:focus-visible {
    outline: none;
}

.pillarchip:focus-within {
    border-color: var(--signal-strong);
}

.pillarchip__caret {
    font-size: 10px;
    color: var(--ink-3);
    pointer-events: none;
}

/* --- The slug under the title — `7b`'s quiet mono line, as a live field.

   No box: the mockup draws text, and the field wears exactly that. The dashed
   rule on hover/focus is what says "editable" instead of a border, and the "/"
   prefix stays outside the input so it can never be deleted into the value. */
.slugfield {
    display: inline-flex;
    align-items: baseline;
    font-family: var(--font-data);
    font-size: 11px;
    line-height: 1.4;
    color: var(--ink-3);
}

.slugfield__input {
    width: 40ch;
    min-width: 6ch;
    max-width: 100%;
    padding: 1px 0 2px;
    border: 0;
    border-bottom: 1px dashed transparent;
    background: none;
    font: inherit;
    color: inherit;
}

/* Grows with its content where the browser can (Chrome 123+); the 40ch width
   above is the fallback. */
@supports (field-sizing: content) {
    .slugfield__input {
        width: auto;
        field-sizing: content;
    }
}

.slugfield__input:hover {
    border-bottom-color: var(--line-strong);
}

.slugfield__input:focus-visible {
    outline: none;
    border-bottom-color: var(--signal);
    color: var(--ink-2);
}

/* --- `4d`'s picker anatomy: the chooser card beside a stack of floating aside
   cards, exactly as the mockup composes them — the aside is not a pane of the
   card. Group headers are full-width bands; rows share the card's 17px inset. */
.artpick {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(280px, 340px);
    gap: var(--s5);
    align-items: start;
    width: min(1120px, 96vw);
    max-height: calc(100vh - 2 * var(--s5));
}

.artpick__card {
    display: flex;
    flex-direction: column;
    min-width: 0;
    max-height: calc(100vh - 2 * var(--s5));
    background: var(--surface);
    border: 1px solid var(--line-strong);
    border-radius: 12px;
    box-shadow: var(--shadow-3);
    overflow: hidden;
}

.artpick__head {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding: 15px 17px;
    border-bottom: 1px solid var(--line);
}

.artpick__title {
    margin: 0;
    font: 600 15px/1.3 var(--font-display);
    letter-spacing: -.01em;
    color: var(--ink);
}

.artpick__sub {
    font: 400 11px/1.4 var(--font-data);
    color: var(--ink-3);
}

.artpick__filters {
    display: flex;
    flex-direction: column;
    gap: 9px;
    padding: 13px 17px;
    background: var(--surface-2);
    border-bottom: 1px solid var(--line);
}

.artpick__filterrow {
    display: flex;
    align-items: center;
    gap: 8px;
}

.artpick__filterrow .select {
    flex: 1;
    min-width: 0;
}

/* A statement, not a control: the tick is drawn filled and stays that way. */
.artpick__approved {
    display: flex;
    align-items: center;
    gap: 8px;
    font: 400 11.5px/1 var(--font-ui);
    color: var(--ink-2);
    white-space: nowrap;
}

.artpick__tick {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 15px;
    height: 15px;
    border: 1px solid var(--line-strong);
    border-radius: 4px;
    background: var(--signal);
    color: var(--pri-on);
    font-size: 10px;
}

.artpick__count {
    font: 400 10.5px/1.4 var(--font-data);
    color: var(--ink-3);
}

.artpick__list {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
}

.artpick__foot {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 14px 17px;
    background: var(--surface-2);
    border-top: 1px solid var(--line);
}

/* The pillar band: a full-width strip, not an indented heading. */
.artgroup {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 9px 17px;
    background: var(--surface-2);
    border-bottom: 1px solid var(--line);
}

.artgroup__dot {
    flex: none;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--signal);
}

.artgroup__name {
    font: 700 10px/1 var(--font-ui);
    letter-spacing: .09em;
    text-transform: uppercase;
    color: var(--ink-2);
}

.artgroup__meta {
    font: 400 10.5px/1 var(--font-data);
    color: var(--ink-3);
}

/* One article. The 20px first column keeps every title at the same left edge,
   which is what makes the groups read as one list. */
.artrow {
    display: grid;
    grid-template-columns: 20px minmax(0, 1fr) auto;
    gap: 11px;
    align-items: flex-start;
    padding: 13px 17px;
    border-bottom: 1px solid var(--line);
    cursor: pointer;
}

.artrow:has(> .selectbox:checked) {
    background: var(--signal-tint);
}

/* The box itself is `.selectbox`, shared with the pulse picker; only its place
   in this row belongs here. */
.artrow > .selectbox {
    margin-top: 2px;
}

/* A draft row keeps the column, so its title lines up with the rest — but there
   is nothing to tick. */
.artrow__ghost {
    width: 15px;
    height: 15px;
    margin-top: 2px;
    flex: none;
    border: 1px solid var(--line-strong);
    border-radius: 4px;
    background: var(--surface);
}

.artrow__body {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 3px;
}

.artrow__title {
    font: 600 13px/1.35 var(--font-ui);
    color: var(--ink);
}

.artrow__standfirst {
    font: 400 11.5px/1.45 var(--font-ui);
    color: var(--ink-2);
}

.artrow__meta {
    font: 400 10.5px/1.4 var(--font-data);
    color: var(--ink-3);
}

.artrow__side {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 6px;
    margin-top: 2px;
    white-space: nowrap;
    text-align: right;
}

.artrow__state {
    font: 600 10.5px/1 var(--font-ui);
    color: var(--signal);
    visibility: hidden;
}

.artrow:has(> .selectbox:checked) .artrow__state {
    visibility: visible;
}

.artrow__state--go {
    visibility: visible;
    text-decoration: none;
}

/* A draft: present so the list doesn't look wrong to whoever just wrote it,
   unselectable because a draft cannot be a source. */
.artrow--off {
    cursor: default;
    background: var(--surface-2);
}

.artrow--off .artrow__title {
    color: var(--ink-2);
}

/* The floating aside cards on the picker's right. */
.artpick__rail {
    display: flex;
    flex-direction: column;
    gap: 14px;
    max-height: calc(100vh - 2 * var(--s5));
    overflow-y: auto;
}

.asidecard {
    display: flex;
    flex-direction: column;
    gap: 9px;
    padding: 15px;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 12px;
    box-shadow: var(--shadow-1);
}

.asidecard--warn {
    border-color: var(--marigold);
}

.asidecard__h {
    font: 700 11px/1 var(--font-ui);
    letter-spacing: .1em;
    text-transform: uppercase;
    color: var(--ink-3);
}

.asidecard--warn .asidecard__h {
    color: var(--marigold);
}

.asidecard__p {
    margin: 0;
    font: 400 var(--t-help)/1.55 var(--font-ui);
    color: var(--ink-2);
}

.asidecard__foot {
    font: 400 11px/1.5 var(--font-ui);
    color: var(--ink-3);
    border-top: 1px solid var(--line);
    padding-top: 9px;
}

.asidecard--warn .asidecard__foot {
    border-top-color: color-mix(in srgb, var(--marigold) 25%, transparent);
}

/* One already-attached title, with its detach. */
.attrow {
    display: flex;
    align-items: center;
    gap: 8px;
    border-left: 3px solid var(--signal);
    padding: 5px 0 5px 9px;
    font: 400 12px/1.4 var(--font-ui);
    color: var(--ink-2);
}

.attrow__title {
    flex: 1;
    min-width: 0;
    overflow-wrap: break-word;
}

.attrow__x {
    flex: none;
    border: 0;
    background: none;
    padding: 0 2px;
    font: 600 11px/1 var(--font-ui);
    color: var(--ink-3);
    cursor: pointer;
}

.attrow__x:hover {
    color: var(--critical);
}

/* One breakpoint, the same 820px the rest of the UI uses. Between there and the
   full width the rail gives ground instead of a second break: the picker's own
   width is already `min(1120px, 96vw)`, so the split just gets tighter. */
@media (max-width: 820px) {
    .artpick {
        grid-template-columns: minmax(0, 1fr);
        overflow-y: auto;
    }

    .artpick__rail {
        max-height: none;
    }

    .artpick__card {
        max-height: 70vh;
    }
}

/* `7d`'s "1 · 2 · 3" — the order things happen in, as a list rather than prose. */
.steps {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin: 0;
    padding: 0 0 0 1.4em;
    font-family: var(--font-ui);
    font-size: 12px;
    line-height: 1.45;
    color: var(--ink-2);
}

.steps li::marker {
    font-family: var(--font-data);
    font-size: 11px;
    color: var(--signal);
}

/* =========================================================================
   Segmented control — Month|Week|List, Filename|Meaning, Images|Video|Audio
   ========================================================================= */

.seg {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    padding: 2px;
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    background: var(--surface-2);
}

.seg__item {
    padding: 5px 12px;
    border: 0;
    border-radius: 6px;
    background: none;
    font-family: var(--font-ui);
    font-size: 12px;
    font-weight: 600;
    line-height: 1;
    color: var(--ink-3);
    text-decoration: none;
    cursor: pointer;
}

.seg__item:hover {
    color: var(--ink);
}

.seg__item[aria-current="true"],
.seg__item--on {
    background: var(--surface);
    box-shadow: var(--shadow-1);
    color: var(--ink);
}

/* The compact variant, used where the segmented control is a filter over a
   listing rather than the listing's main control: it tints rather than lifts. */
.seg--sm .seg__item {
    padding: 5px 10px;
    font-size: 11.5px;
}

.seg--sm .seg__item[aria-current="true"],
.seg--sm .seg__item--on {
    background: var(--surface-2);
    box-shadow: none;
}

/* The meaning-search variant fills with cobalt rather than lifting. */
.seg--accent .seg__item[aria-current="true"],
.seg--accent .seg__item--on {
    background: var(--signal);
    box-shadow: none;
    color: var(--pri-on);
}

/* =========================================================================
   Status pills
   Lifecycle is draft → scheduled → posted. Each state carries a colour AND a
   shape cue (the dot), so it reads without relying on colour alone.
   `--live` is cobalt and means "in progress right now" — never a lifecycle
   state.
   ========================================================================= */

.pill {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 4px 9px;
    border: 1px solid transparent;
    border-radius: var(--r-pill);
    font-family: var(--font-ui);
    font-size: 11px;
    font-weight: 600;
    line-height: 1;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    white-space: nowrap;
}

.pill__glyph {
    flex: none;
    width: 7px;
    height: 7px;
    border-radius: 50%;
}

.pill--draft {
    border-color: var(--line);
    background: var(--surface-2);
    color: var(--ink-3);
}

/* "none yet" is a sentence fragment, not a lifecycle state — small caps and
   letter-spacing would make it shout something it doesn't mean. */
.pill--plain {
    letter-spacing: 0;
    text-transform: none;
}

.pill--draft .pill__glyph {
    background: var(--ink-3);
}

.pill--scheduled {
    border-color: color-mix(in srgb, var(--marigold) 30%, transparent);
    background: var(--marigold-tint);
    color: var(--marigold);
}

.pill--scheduled .pill__glyph {
    background: var(--marigold);
}

.pill--posted {
    border-color: color-mix(in srgb, var(--posted) 30%, transparent);
    background: var(--posted-tint);
    color: var(--posted);
}

.pill--posted .pill__glyph {
    background: var(--posted);
}

.pill--live {
    border-color: color-mix(in srgb, var(--signal) 30%, transparent);
    background: var(--signal-tint);
    color: var(--signal);
}

.pill--live .pill__glyph {
    background: var(--signal);
}

.pill--critical {
    border-color: color-mix(in srgb, var(--critical) 30%, transparent);
    background: var(--critical-tint);
    color: var(--critical);
}

.pill--critical .pill__glyph {
    background: var(--critical);
}

/* A small neutral tag — "5 outputs", "RICH TEXT", collection names. */
.chip-tag {
    display: inline-flex;
    align-items: center;
    gap: var(--s1);
    padding: 0.12rem 0.45rem;
    border: 1px solid var(--line);
    border-radius: var(--r-sm);
    background: var(--surface-2);
    font-family: var(--font-ui);
    font-size: 11px;
    font-weight: 600;
    line-height: 1;
    color: var(--ink-2);
}

/* A removable active-filter chip. */
.filter-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--s2);
    padding: 6px 10px;
    border: 1px solid color-mix(in srgb, var(--signal) 30%, transparent);
    border-radius: var(--r-pill);
    background: var(--signal-tint);
    font-size: 11.5px;
    font-weight: 500;
    line-height: 1;
    color: var(--signal);
    text-decoration: none;
}

/* =========================================================================
   Channel glyph — the one real icon in the system. Brand colour comes from
   the ChannelDefinition, so it is set inline per channel.
   ========================================================================= */

.chn {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    font-size: 12px;
    line-height: 1;
    color: var(--ink-2);
}

.chn__dot {
    display: inline-flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    border-radius: 4px;
    font-family: var(--font-data);
    font-size: 8px;
    font-weight: 700;
    line-height: 1;
    /* The ink comes with the background, inline, from `ChannelDefinition.ink`.
       Not fixed here: a tile whose colour is a *token* — Email's
       `var(--marigold)` — flips with the theme, and white ink on sand is
       unreadable. `--pri-on` is the fallback for anything that arrives without
       an ink of its own. */
    color: var(--pri-on);
}

/* --- Staged form sections — `7c`'s numbered steps.

   The channel form is the heaviest in the app; the numbers are what stop twelve
   fields reading as one wall. The rule down the left is the same recessed line the
   prose fields use, so a stage reads as a group rather than a box. */
.stage {
    display: grid;
    grid-template-columns: 24px minmax(0, 1fr);
    gap: 4px 11px;
    align-items: center;
}

.stage__n {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    background: var(--signal-tint);
    font-family: var(--font-data);
    font-size: 11px;
    font-weight: 700;
    line-height: 1;
    color: var(--signal);
}

.stage__h {
    font-family: var(--font-ui);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .1em;
    text-transform: uppercase;
    color: var(--ink-3);
}

.stage__body {
    grid-column: 2;
    padding: var(--s3) 0 var(--s2);
}

/* =========================================================================
   Panels and cards
   ========================================================================= */

.panel {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-1);
    overflow: hidden;
}

.panel__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--s3);
    padding: 13px 18px;
    border-bottom: 1px solid var(--line);
}

.panel__body {
    padding: var(--s4) 18px;
}

.panel__foot {
    padding: 14px 18px;
    border-top: 1px solid var(--line);
    background: var(--surface-2);
}

.cards {
    display: grid;
    gap: var(--s3);
    /* Four across on the 1440px canvas. `1h` draws three, but at that width the card
       came out 453 × 126 — a 3.6:1 bar rather than a card. Four tracks give ~337px,
       which with the taller body below lands nearer 2:1. `min()` keeps it from
       overflowing a narrow window, and auto-fill steps down to three, two and one on
       the way in.

       auto-fill rather than auto-fit: a pillar with one article plus its "new
       article" tile should leave the remaining tracks empty, not stretch two cards
       across the whole row — a consistent card size is what makes the groups
       scannable against each other. */
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 320px), 1fr));
}

.card {
    position: relative;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-1);
    overflow: hidden;
}

/* A card in a grid of them stands to a floor, so a short one and a long one read as
   the same kind of object. Without it a title that happens to fit on one line
   produced a card two-thirds the height of its neighbour, and the row looked ragged
   rather than deliberate. The footer is pushed to the bottom by `.card__body` below,
   so the extra height becomes breathing room and not a gap in the middle. */
.cards .card {
    display: flex;
    min-height: 168px;
}

/* The status stripe encodes state — information, not decoration. */
.card--striped {
    border-left: 3px solid var(--line-strong);
}

.card--draft {
    border-left-color: var(--ink-3);
}

.card--scheduled {
    border-left-color: var(--marigold);
}

.card--posted {
    border-left-color: var(--posted);
}

/* The fan-out hub: a Base Piece is the one card outlined in the action colour. */
.card--hub {
    border-color: var(--signal);
}

.card__body {
    padding: var(--s4);
}

/* Inside the grid the body fills its card and spaces its own rows, which is what
   turns a taller card into air around the content rather than a void under it. */
.cards .card__body {
    display: flex;
    flex: 1;
    flex-direction: column;
    padding: 17px 18px;
}

/* The footer sits on the floor of the card, wherever that ends up. */
.cards .card__body > .card__foot:last-child {
    margin-top: auto;
    padding-top: var(--s3);
}

.card__kicker {
    display: flex;
    align-items: center;
    gap: var(--s2);
    margin-bottom: var(--s2);
    font-family: var(--font-data);
    font-size: 10px;
    line-height: 1;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ink-3);
}

.card__dot {
    flex: none;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--signal);
}

.card__title {
    margin: 0 0 var(--s1);
    font-family: var(--font-ui);
    font-size: 13px;
    font-weight: 600;
    line-height: 1.35;
    color: var(--ink);
}

.card__excerpt {
    margin: 0 0 var(--s3);
    font-size: var(--t-help);
    line-height: 1.45;
    color: var(--ink-2);
}

.card__foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--s2);
}

.card__meta {
    font-family: var(--font-data);
    font-size: 10.5px;
    line-height: 1;
    font-variant-numeric: tabular-nums;
    color: var(--ink-3);
}

/* A stat tile: big mono number, label, sub-line. */
.stat {
    display: flex;
    flex-direction: column;
    gap: 3px;
    padding: 14px 16px;
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    background: var(--surface);
    box-shadow: var(--shadow-1);
    color: inherit;
    text-decoration: none;
}

a.stat:hover {
    border-color: var(--signal);
}

.stat__num {
    font-family: var(--font-data);
    font-size: var(--t-h1);
    font-weight: 600;
    line-height: 1;
    letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums;
    color: var(--ink);
}

.stat__label {
    font-size: 12px;
    font-weight: 600;
    line-height: 1.3;
    color: var(--ink-2);
}

.stat__sub {
    font-family: var(--font-data);
    font-size: 11px;
    line-height: 1.3;
    color: var(--ink-3);
}

/* =========================================================================
   Listing table
   ========================================================================= */

.table-wrap {
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    background: var(--surface);
    box-shadow: var(--shadow-1);
    /* Wide tables scroll inside their own box; the page never scrolls sideways. */
    overflow-x: auto;
}

table.listing {
    width: 100%;
    min-width: 520px;
    border-collapse: collapse;
    font-size: 11.5px;
}

table.listing thead th {
    /* 7px of horizontal padding per cell reproduces the mockup's 14px grid gap
       between columns, with 16px restored at the row's outer edges below. */
    padding: 10px 7px;
    border-bottom: 1px solid var(--line);
    background: var(--surface-2);
    font-family: var(--font-ui);
    font-size: 10px;
    font-weight: 700;
    line-height: 1;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    text-align: left;
    white-space: nowrap;
    color: var(--ink-3);
}

table.listing tbody td {
    line-height: 1;
    padding: 12px 7px;
    border-bottom: 1px solid var(--line);
    vertical-align: middle;
    color: var(--ink-2);
}

/* Restore the row's outer inset, which the per-cell 7px would otherwise halve. */
table.listing th:first-child,
table.listing td:first-child {
    padding-left: 16px;
}

table.listing th:last-child,
table.listing td:last-child {
    padding-right: 16px;
}

table.listing tbody tr:last-child td {
    border-bottom: 0;
}

table.listing tbody tr:hover {
    background: var(--surface-2);
}

table.listing .ttl {
    font-size: 13px;
    font-weight: 600;
    line-height: 1.35;
    color: var(--ink);
}

table.listing .sub {
    display: block;
    margin: 0;
    font-family: var(--font-data);
    font-size: 10.5px;
    font-weight: 400;
    line-height: 1.4;
    letter-spacing: 0;
    color: var(--ink-3);
}

td.actions {
    text-align: right;
    white-space: nowrap;
}

td.actions > * + * {
    margin-left: 6px;
}

/* Pagination */
.pagination {
    display: flex;
    align-items: center;
    gap: var(--s3);
    margin-top: var(--s3);
}

.pagination__count {
    font-family: var(--font-data);
    font-size: 11.5px;
    line-height: 1;
    color: var(--ink-3);
}

.pagination__pages {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-left: auto;
}

.pagination__page {
    min-width: 30px;
    padding: 7px 11px;
    line-height: 1;
    border: 1px solid var(--line-strong);
    border-radius: 7px;
    background: var(--surface);
    font-size: 11.5px;
    font-weight: 600;
    text-align: center;
    color: var(--ink);
    text-decoration: none;
}

.pagination__page[aria-current="page"] {
    border-color: transparent;
    background: var(--signal);
    color: var(--pri-on);
}

.pagination__page--off {
    border-color: var(--line);
    background: var(--surface-2);
    color: var(--ink-3);
    pointer-events: none;
}

/* =========================================================================
   Calendar
   ========================================================================= */

.cal {
    display: grid;
    /* A real week, not the design system's 4-column demo. */
    grid-template-columns: repeat(7, minmax(0, 1fr));
    gap: 6px;
}

.cal__weekdays {
    display: grid;
    grid-template-columns: repeat(7, minmax(0, 1fr));
    gap: 6px;
    margin-bottom: 6px;
}

.cal__weekday {
    font-family: var(--font-ui);
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--ink-3);
}

.cal__day {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-height: 104px;
    padding: var(--s2);
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    background: var(--surface);
}

/* Two different kinds of quiet, which `1b` is careful to distinguish and this
   used to collapse into one.

   A weekend is still a day you can schedule on, so it gets the recessed surface
   and keeps its date at full strength. A spillover day from the adjacent month is
   not part of this month at all, so its number goes faint as well — at that point
   it is a marker, not a label, and `--line-strong` against `--surface-2` is the
   mockup's own value. Applying that faintness to weekends made a quarter of every
   month unreadable. */
.cal__day--weekend,
.cal__day--muted {
    background: var(--surface-2);
}

.cal__day--muted .cal__date {
    color: var(--line-strong);
}

/* A day the cadence report says is short — dashed, so the gap reads as an
   invitation rather than an error. */
.cal__day--gap {
    border-style: dashed;
    border-color: var(--marigold);
}

/* Drag-and-drop scheduling target. */
.cal__day--dropping {
    border-color: var(--signal);
    background: var(--signal-tint);
}

.cal__date {
    display: flex;
    justify-content: space-between;
    font-family: var(--font-data);
    font-size: 11px;
    line-height: 1.3;
    font-variant-numeric: tabular-nums;
    color: var(--ink-3);
}

.cal__today {
    padding: 0 0.4rem;
    border-radius: var(--r-pill);
    background: var(--signal);
    font-weight: 700;
    color: var(--pri-on);
}

.cal-chip {
    display: block;
    width: 100%;
    padding: 2px 5px;
    border: 0;
    border-left: 3px solid var(--ink-3);
    border-radius: 3px;
    background: var(--surface-2);
    font-family: var(--font-ui);
    font-size: 10.5px;
    font-weight: 600;
    line-height: 1.35;
    text-align: left;
    color: var(--ink);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    /* Chips open the detail popover, so they are pointers, not inert text. */
    cursor: pointer;
}

.cal-chip:hover {
    border-color: var(--signal);
}

.cal-chip--draft {
    border-left-color: var(--ink-3);
}

.cal-chip--scheduled {
    border-left-color: var(--marigold);
}

.cal-chip--posted {
    border-left-color: var(--posted);
}

.cal-chip--gap {
    border: 1px dashed var(--marigold);
    background: none;
    color: var(--marigold);
    cursor: default;
}

.cal-chip--dragging {
    opacity: .4;
}

/* =========================================================================
   Cadence report — a small data viz. Filled green where the target is met,
   amber where there is a gap to fill, with the target as a hard tick.
   ========================================================================= */

.cadence {
    display: grid;
    gap: 9px;
}

.cadence__row {
    display: grid;
    /* Wider than the mockup's 64px: its channels were LinkedIn/Reels/TikTok,
       and the seeded names run to "Instagram Reels". These names are links that
       filter the table, so truncating them to eight characters would make the
       row unusable rather than merely tight. */
    grid-template-columns: 96px minmax(0, 1fr) 34px;
    align-items: center;
    gap: 9px;
}

.cadence__name {
    font-size: 12px;
    font-weight: 600;
    line-height: 1;
    /* Plain ink by default: in the rail these are labels, not actions. Only the
       list view's rows filter the table, and those opt into `--link`. */
    color: var(--ink);
    /* Real channel names ("Email Newsletter", "Instagram Reels") are longer than
       the design's samples and would wrap to two lines, breaking the row rhythm.
       Truncate instead; the full name is in the title attribute. */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.cadence__track {
    position: relative;
    display: block;
    height: 18px;
    border: 1px solid var(--line);
    border-radius: var(--r-sm);
    background: var(--surface-2);
    overflow: hidden;
}

.cadence__fill {
    position: absolute;
    inset: 0 auto 0 0;
}

.cadence__fill--ok {
    background: color-mix(in srgb, var(--posted) 40%, var(--surface));
}

.cadence__fill--gap {
    background: color-mix(in srgb, var(--marigold) 45%, var(--surface));
}

.cadence__target {
    position: absolute;
    top: 0;
    bottom: 0;
    width: 2px;
    background: var(--ink);
    opacity: .55;
}

.cadence__val {
    font-family: var(--font-data);
    font-size: 11px;
    line-height: 1;
    font-variant-numeric: tabular-nums;
    text-align: right;
    color: var(--ink-2);
}

.cadence__val--gap {
    color: var(--marigold);
}

/* Opt-in: see relay/partials/cadence.html `highlight_gaps`. */

/* =========================================================================
   Generation progress
   Never one opaque bar — the operator sees exactly which channel is being
   written, and which failed.
   ========================================================================= */

.gen {
    display: grid;
    gap: 6px;
}

/* No box per step: this list lives inside a card, and a border around each row
   would be a second frame inside the one already there. The state is carried by
   the icon and the right-hand word, which is where the eye goes anyway. */
.gen__step {
    display: grid;
    grid-template-columns: 18px minmax(0, 1fr) auto;
    align-items: center;
    gap: 9px;
    font-family: var(--font-ui);
    font-size: 11.5px;
    font-weight: 400;
    line-height: 1;
    color: var(--ink-2);
}

/* Queued: dimmed rather than hidden, so the length of the run is visible from
   the start and the last channel doesn't appear out of nowhere. */
.gen__step--wait {
    opacity: .6;
}

.gen__ic {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    font-family: var(--font-ui);
    font-size: 9px;
    font-weight: 700;
    line-height: 1;
}

.gen__ic--done {
    background: var(--posted-tint);
    color: var(--posted);
}

.gen__ic--run {
    border: 2px solid var(--signal);
    border-top-color: transparent;
    animation: relay-spin .8s linear infinite;
}

.gen__ic--wait {
    border: 2px dashed var(--line-strong);
}

/* Relay addition — the source spec had no failed step. */
.gen__ic--fail {
    background: var(--critical-tint);
    color: var(--critical);
}

.gen__step--fail .gen__label {
    color: var(--critical);
}

.gen__label {
    font-weight: 600;
    color: var(--ink);
}

.gen__state {
    font-family: var(--font-data);
    font-size: 10.5px;
    line-height: 1;
    color: var(--ink-3);
}

.gen__state--fail {
    color: var(--critical);
}

/* The one being written: cobalt, so the live row is findable in a list of five
   without reading any of them. */
.gen__step--run .gen__state {
    color: var(--signal);
}

/* A progress bar that can show a success/failure split in one track. */
.bar {
    display: flex;
    height: 5px;
    border-radius: 3px;
    background: var(--line);
    overflow: hidden;
}

.bar__ok {
    background: var(--posted);
}

/* Still running: cobalt, because nothing about it is an outcome yet. */
.bar__run {
    background: var(--signal);
}

.bar__fail {
    background: var(--critical);
}

/* =========================================================================
   Banners, empty states, blocked states
   ========================================================================= */

.banner {
    display: flex;
    align-items: flex-start;
    gap: var(--s3);
    padding: 12px 18px;
    border: 1px solid transparent;
    font-size: 12.5px;
    line-height: 1.5;
    color: var(--ink-2);
}

/* At page level (the Django-messages slot in _base.html) the banner sits flush
   against the screen's first panel; inline uses space themselves via .stack. */
.page > .banner {
    margin-bottom: var(--s4);
}

.banner__ic {
    display: inline-flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    font-size: 11px;
    font-weight: 700;
}

.banner--ok {
    border-color: color-mix(in srgb, var(--posted) 30%, transparent);
    background: var(--posted-tint);
}

.banner--ok .banner__ic {
    background: var(--posted);
    color: var(--posted-tint);
}

.banner--warn {
    border-color: color-mix(in srgb, var(--marigold) 30%, transparent);
    background: var(--marigold-tint);
}

.banner--warn .banner__ic {
    background: var(--marigold);
    color: var(--marigold-tint);
}

.banner--error {
    border-color: color-mix(in srgb, var(--critical) 30%, transparent);
    background: var(--critical-tint);
}

.banner--error .banner__ic {
    background: var(--critical);
    color: var(--critical-tint);
}

.banner--info {
    border-color: color-mix(in srgb, var(--signal) 30%, transparent);
    background: var(--signal-tint);
}

.banner--info .banner__ic {
    background: var(--signal);
    color: var(--pri-on);
}

.banner__close {
    border: 0;
    background: none;
    color: var(--ink-3);
    font-size: 14px;
    cursor: pointer;
}

/* Every empty state says what to do next, not just that it's empty. */
.empty {
    display: flex;
    flex-direction: column;
    gap: var(--s2);
    align-items: flex-start;
    min-height: 168px;
    padding: var(--s4);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    background: var(--surface);
}

/* The good empty — nothing waiting is a win, not a void. */
.empty--good {
    border-color: color-mix(in srgb, var(--posted) 30%, transparent);
    background: var(--posted-tint);
}

/* First run — there is nothing yet because nothing has been made yet. */
.empty--first {
    border-color: color-mix(in srgb, var(--signal) 30%, transparent);
    background: var(--signal-tint);
}

.empty__context {
    font-family: var(--font-data);
    font-size: 10px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-3);
}

.empty__title {
    margin: 0;
    font-size: 14px;
    font-weight: 700;
    line-height: 1.3;
    color: var(--ink);
}

.empty__body {
    margin: 0;
    font-size: var(--t-help);
    line-height: 1.5;
    color: var(--ink-2);
}

.empty__actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s2);
    margin-top: auto;
}

/* A blocked action — shown where the action would be, never as a dead end. */
.blocked {
    display: flex;
    flex-direction: column;
    gap: var(--s2);
    padding: var(--s3) var(--s4);
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    background: var(--surface-2);
}

.blocked--locked {
    border-color: color-mix(in srgb, var(--marigold) 40%, transparent);
    background: var(--marigold-tint);
}

.blocked__title {
    font-size: 13px;
    font-weight: 600;
    line-height: 1.3;
    color: var(--ink);
}

.blocked__body {
    margin: 0;
    font-size: 11.5px;
    line-height: 1.5;
    color: var(--ink-2);
}

/* A dashed drop zone for a required-but-missing medium. */
.dropzone {
    display: flex;
    align-items: center;
    gap: var(--s3);
    padding: var(--s3);
    border: 1px dashed var(--line-strong);
    border-radius: var(--r-md);
    background: var(--surface-2);
}

.dropzone--required {
    border-color: color-mix(in srgb, var(--critical) 45%, transparent);
    background: var(--critical-tint);
}

/* =========================================================================
   Popover, drawer, modal, toast
   ========================================================================= */

.popover {
    position: fixed;
    z-index: var(--z-popover);
    width: 268px;
    padding: var(--s3);
    border: 1px solid var(--line-strong);
    border-radius: 10px;
    background: var(--surface);
    box-shadow: var(--shadow-2);
}

.popover__head {
    display: flex;
    align-items: center;
    gap: var(--s2);
    margin-bottom: var(--s2);
}

.popover__title {
    margin: 0 0 var(--s3);
    font-size: 13px;
    font-weight: 600;
    line-height: 1.35;
    color: var(--ink);
}

/* Two columns, not one row. These were glyph-only squares — ◉ ✎ ⧉ ⟲ — with the
   name only in a `title`, which meant four identical-looking blank buttons until
   you hovered one. Labelled, they do not fit across 268px, and shrinking the
   popover's type to make them fit would be solving the wrong problem. Two
   columns is deterministic where `flex-wrap` would depend on how long the words
   are in whichever language is loaded. */
.popover__actions {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 6px;
}

/* The primary action gets its own row across both columns: it is the one that
   costs money, so it should not sit in a grid cell beside Copy. */
.popover__actions .popover__regen {
    grid-column: 1 / -1;
}

.popover__close {
    margin-left: auto;
    border: 0;
    background: none;
    color: var(--ink-3);
    font-size: 14px;
    line-height: 1;
    cursor: pointer;
}

/* The scrim starts below the top bar rather than over it: the drawer is modal
   over the content, but you should still be able to read where you are. Two
   heights, because not every screen has a context bar. */
.scrim {
    position: fixed;
    inset: 56px 0 0 0;
    z-index: var(--z-scrim);
    background: rgba(22, 24, 43, .28);
}

body:has(.contextbar) .scrim {
    top: 102px;
}

/* A modal is different: it owns the whole viewport, chrome included. */
.modal .scrim,
.scrim--full {
    inset: 0;
}

.drawer {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    z-index: var(--z-drawer);
    display: flex;
    flex-direction: column;
    width: min(456px, 100vw);
    border-left: 1px solid var(--line-strong);
    background: var(--surface);
    box-shadow: -12px 0 32px rgba(22, 24, 43, .16);
}

.drawer__head {
    display: flex;
    align-items: flex-start;
    gap: var(--s3);
    padding: var(--s4) 20px;
    border-bottom: 1px solid var(--line);
}

.drawer__title {
    margin: 0;
    font-family: var(--font-display);
    font-size: 15px;
    font-weight: 600;
    line-height: 1.3;
    letter-spacing: -0.01em;
    color: var(--ink);
}

.drawer__sub {
    font-family: var(--font-data);
    font-size: var(--t-meta);
    line-height: 1.4;
    color: var(--ink-3);
}

.drawer__body {
    flex: 1;
    overflow-y: auto;
    padding: var(--s4) 20px;
}

.drawer__foot {
    padding: 14px 20px;
    border-top: 1px solid var(--line);
    background: var(--surface-2);
    font-size: 11px;
    line-height: 1.5;
    color: var(--ink-2);
}

.modal {
    position: fixed;
    inset: 0;
    z-index: var(--z-modal);
    display: grid;
    place-items: center;
    padding: var(--s4);
    background: rgba(22, 24, 43, .28);
}

.modal__card {
    display: flex;
    flex-direction: column;
    width: min(520px, 100%);
    max-height: calc(100vh - 2 * var(--s4));
    border: 1px solid var(--line-strong);
    border-radius: var(--r-lg);
    background: var(--surface);
    box-shadow: var(--shadow-3);
    overflow: hidden;
}

.modal__card--wide {
    width: min(680px, 100%);
}

/* A modal whose job is now to report a refusal. The border carries it rather than
   a banner inside: the whole dialog is about the failure, and a banner would push
   the field that needs editing further down. */
.modal__card--fail {
    border-color: color-mix(in srgb, var(--critical) 40%, transparent);
}

/* The provider's own error type. Mono because it is a code you learn to recognise
   — `content_policy_violation` twice means reword, not retry. */
.failcode {
    font-family: var(--font-data);
    font-size: 12px;
    font-weight: 600;
    color: var(--critical);
}

.modal__head {
    display: flex;
    align-items: flex-start;
    gap: var(--s3);
    padding: 15px 17px;
    border-bottom: 1px solid var(--line);
}

.modal__title {
    margin: 0;
    font-family: var(--font-display);
    font-size: 15px;
    font-weight: 600;
    line-height: 1.3;
    letter-spacing: -0.01em;
    color: var(--ink);
}

/* The dismiss glyph. A bare 15px mark, not a button chip: every frame that has
   one draws it as a glyph in --ink-3, and a bordered 32px square in the corner of
   a dialog head reads as an action worth considering rather than a way out. The
   hit area is padded out to 28px so it stays tappable. */
.modal__x,
.drawer__x {
    flex: none;
    margin: -6px -6px 0 0;
    padding: 6px;
    border: 0;
    background: none;
    font-family: var(--font-ui);
    font-size: 15px;
    font-weight: 400;
    line-height: 1;
    color: var(--ink-3);
    cursor: pointer;
}

.modal__x:hover,
.drawer__x:hover {
    color: var(--ink);
}

.modal__sub {
    font-family: var(--font-data);
    font-size: var(--t-meta);
    color: var(--ink-3);
}

.modal__body {
    flex: 1;
    overflow-y: auto;
    padding: var(--s4) 17px;
}

.modal__foot {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 14px 17px;
    border-top: 1px solid var(--line);
}

/* Rendered in place rather than as an overlay — the states gallery. A modal's
   viewport constraints do not apply there, and the cards should line up. */
.modal__card--static {
    position: static;
    width: auto;
    max-height: none;
    height: 100%;
    box-shadow: var(--shadow-1);
}

.modal__card--static .modal__body {
    flex: 1;
    overflow: visible;
}

/* A destructive confirm always names the thing and the consequence. */
.modal__card--danger {
    border-color: var(--critical);
}

.modal__card--danger .modal__title {
    color: var(--critical);
}

.modal__card--warn {
    border-color: var(--marigold);
}

.toasts {
    position: fixed;
    right: var(--s4);
    bottom: var(--s4);
    z-index: var(--z-toast);
    display: flex;
    flex-direction: column;
    gap: var(--s2);
    width: min(380px, calc(100vw - 2 * var(--s4)));
}

.toast {
    display: flex;
    align-items: flex-start;
    gap: var(--s2);
    padding: var(--s3);
    border: 1px solid var(--line-strong);
    border-radius: var(--r-md);
    background: var(--surface);
    box-shadow: var(--shadow-2);
    font-size: 12.5px;
    color: var(--ink);
}

.toast__close {
    flex: none;
    margin: -2px -4px 0 0;
    padding: 2px 4px;
    border: 0;
    background: none;
    font-size: 12px;
    line-height: 1;
    color: var(--ink-3);
    cursor: pointer;
}

.toast__close:hover {
    color: var(--ink);
}

.toast--ok {
    border-color: color-mix(in srgb, var(--posted) 40%, transparent);
}

.toast--error {
    border-color: color-mix(in srgb, var(--critical) 40%, transparent);
}

.toast--warn {
    border-color: color-mix(in srgb, var(--marigold) 40%, transparent);
}

/* =========================================================================
   Diff segments — used by compare, approvals and the article river
   ========================================================================= */

.ins {
    background: var(--posted-tint);
    border-bottom: 1px solid var(--posted);
    padding: 1px 0;
}

.del {
    background: var(--critical-tint);
    color: var(--ink-2);
    text-decoration: line-through;
    text-decoration-color: color-mix(in srgb, var(--critical) 50%, transparent);
    padding: 1px 0;
}

.diff-block--ins {
    background: var(--posted-tint);
    border-left: 3px solid var(--posted);
    padding: 8px 0 8px 12px;
}

.diff-block--del {
    background: var(--critical-tint);
    border-left: 3px solid var(--critical);
    padding: 8px 0 8px 12px;
    color: var(--ink-2);
    text-decoration: line-through;
    text-decoration-color: color-mix(in srgb, var(--critical) 40%, transparent);
}

.diff-legend {
    display: flex;
    align-items: center;
    gap: var(--s4);
    font-family: var(--font-ui);
    font-size: 11px;
    line-height: 1;
    color: var(--ink-3);
}

.diff-legend__swatch {
    display: inline-block;
    width: 11px;
    height: 11px;
    border-radius: 2px;
    margin-right: 6px;
    vertical-align: -1px;
}

/* =========================================================================
   Timeline — the history drawer's revision list
   ========================================================================= */

.timeline__row {
    display: grid;
    grid-template-columns: 16px minmax(0, 1fr);
    gap: var(--s3);
}

.timeline__rail {
    display: flex;
    flex-direction: column;
    align-items: center;
}

.timeline__dot {
    flex: none;
    width: 9px;
    height: 9px;
    margin-top: 4px;
    border-radius: 50%;
    background: var(--line-strong);
}

.timeline__dot--current {
    width: 10px;
    height: 10px;
    background: var(--signal);
    box-shadow: 0 0 0 3px var(--signal-tint);
}

.timeline__line {
    width: 1px;
    flex: 1;
    background: var(--line);
}

.timeline__body {
    display: flex;
    flex-direction: column;
    gap: 7px;
    padding-bottom: 18px;
}

/* Keyword/field change chips inside a revision entry. */
.change {
    display: inline-flex;
    align-items: center;
    line-height: 1;
    padding: 4px 7px;
    border: 1px solid var(--line);
    border-radius: var(--r-sm);
    background: var(--surface-2);
    font-size: 11px;
    font-weight: 500;
    color: var(--ink-2);
}

.change--add {
    border-color: color-mix(in srgb, var(--posted) 30%, transparent);
    background: var(--posted-tint);
    color: var(--posted);
}

.change--remove {
    border-color: color-mix(in srgb, var(--critical) 30%, transparent);
    background: var(--critical-tint);
    color: var(--critical);
}

/* =========================================================================
   Thumbnail grid — media library and the image chooser
   ========================================================================= */

.thumbs {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
    gap: 11px;
}

.thumbs--compact {
    grid-template-columns: repeat(3, minmax(0, 1fr));
}

.thumb {
    display: block;
    border: 1px solid var(--line);
    border-radius: 9px;
    background: var(--surface);
    overflow: hidden;
    text-decoration: none;
    cursor: pointer;
    padding: 0;
}

.thumb--on {
    border: 2px solid var(--signal);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--signal) 15%, transparent);
}

/* An asset that isn't embedded yet won't show up in meaning search. */
.thumb--unindexed {
    border-color: var(--marigold);
}

.thumb__frame {
    position: relative;
    display: block;
    height: 82px;
    background: var(--surface-2);
}

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

.thumb__score {
    position: absolute;
    top: 5px;
    right: 5px;
    padding: 3px 4px;
    border-radius: 3px;
    background: rgba(0, 0, 0, .45);
    font-family: var(--font-data);
    font-size: 8.5px;
    font-weight: 600;
    line-height: 1;
    color: #fff;
}

/* The best match gets the action colour so the ranking is legible at a glance. */
.thumb__score--best {
    background: var(--signal);
    color: var(--pri-on);
}

.thumb__name {
    display: block;
    padding: 6px 7px;
    font-family: var(--font-data);
    font-size: 9.5px;
    line-height: 1.3;
    color: var(--ink-2);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* =========================================================================
   Chooser result rows — the pulse item picker
   ========================================================================= */

.choice {
    display: grid;
    grid-template-columns: 20px minmax(0, 1fr) auto;
    gap: 11px;
    align-items: flex-start;
    padding: 13px 17px;
    border: 0;
    border-bottom: 1px solid var(--line);
    background: none;
    text-align: left;
    width: 100%;
    cursor: pointer;
}

.choice--on {
    background: var(--signal-tint);
}

.choice__box {
    width: 16px;
    height: 16px;
    margin-top: 2px;
    border: 1px solid var(--line-strong);
    border-radius: 4px;
}

.choice--on .choice__box {
    display: flex;
    align-items: center;
    justify-content: center;
    border-color: transparent;
    background: var(--signal);
    color: var(--pri-on);
    font-size: 10px;
}

.choice__title {
    font-size: 13px;
    font-weight: 600;
    line-height: 1.35;
    color: var(--ink);
}

.choice__excerpt {
    font-size: 11.5px;
    line-height: 1.45;
    color: var(--ink-2);
}

.choice__meta {
    font-family: var(--font-data);
    font-size: 10.5px;
    line-height: 1;
    color: var(--ink-3);
}

/* The matched term inside a search result. */
mark {
    background: color-mix(in srgb, var(--signal) 22%, transparent);
    color: var(--ink);
    padding: 0 1px;
}

/* The list view's rows are denser than the attention rows, and the mockups draw
   their pills a half-point smaller to suit. */
.pill--sm {
    padding: 4px 8px;
    font-size: 10.5px;
}

/* Cadence rows that filter a table are actions, so they take the action colour. */
.cadence__name--link {
    color: var(--signal);
    text-decoration: underline;
    text-decoration-color: color-mix(in srgb, var(--signal) 40%, transparent);
}

/* =========================================================================
   Media browser — the meaning-search grid
   ========================================================================= */

/* The search field with its mode toggle inside it. One field, two ways of
   asking, so the choice sits where the question is typed. */
.msearch {
    display: flex;
    align-items: center;
    gap: 0;
    max-width: 560px;
    flex: 1 1 320px;
    background: var(--surface);
    border: 1px solid var(--line-strong);
    border-radius: 10px;
    overflow: hidden;
}

.msearch:focus-within {
    border-color: var(--signal);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--signal) 18%, transparent);
}

.msearch__input {
    flex: 1;
    min-width: 0;
    padding: 0 12px;
    border: 0;
    background: none;
    font-family: var(--font-ui);
    font-size: 14px;
    line-height: 1;
    color: var(--ink);
}

.msearch__input:focus {
    outline: none;
}

.msearch__input::placeholder {
    color: var(--ink-3);
}

.msearch__modes {
    display: flex;
    align-items: center;
    gap: 2px;
    flex: none;
    padding: 4px;
    margin-right: 4px;
    border-radius: var(--r-md);
    background: var(--surface-2);
}

.msearch__mode {
    padding: 5px 9px;
    border: 0;
    border-radius: 6px;
    background: none;
    font-family: var(--font-ui);
    font-size: 11px;
    font-weight: 600;
    line-height: 1;
    color: var(--ink-3);
    text-decoration: none;
    cursor: pointer;
    white-space: nowrap;
}

.msearch__mode:hover {
    color: var(--ink);
}

.msearch__mode[aria-current="true"] {
    background: var(--signal);
    color: var(--pri-on);
}

/* A mode that can't run says so instead of pretending. */
.msearch__mode[aria-disabled="true"] {
    color: var(--ink-3);
    opacity: .5;
    cursor: not-allowed;
}

.mediabar {
    display: flex;
    align-items: center;
    gap: var(--s3);
    flex-wrap: wrap;
}

/* --- The grid. Bigger cards than the chooser's: this is a browsing screen,
   and the picture is the content. */
.thumbs--media {
    grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
    gap: 12px;
}

.thumbs--media .thumb {
    border-radius: 10px;
    box-shadow: var(--shadow-1);
}

/* A clip's filename carries more information than an image's — the extension
   and the shape both matter — so it is set a notch larger. */
.thumbs--video .thumb__title {
    font-size: 12px;
}

.thumbs--media .thumb__frame {
    height: 118px;
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    padding: 7px;
}

/* The picture fills the frame behind the badges rather than sitting beside them
   as a flex item. */
.thumbs--media .thumb__frame img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.thumbs--media .thumb__frame > span {
    position: relative;
    z-index: 1;
}

.thumb__body {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 8px 9px;
}

.thumb__title {
    font-family: var(--font-ui);
    font-size: 11.5px;
    font-weight: 600;
    line-height: 1.3;
    color: var(--ink);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.thumb__meta {
    font-family: var(--font-data);
    font-size: 10px;
    line-height: 1.3;
    color: var(--ink-3);
}

/* Badges sit in the frame's flow rather than absolutely, so an AI mark on the
   left and a score on the right space themselves. */
.thumb__badge {
    padding: 3px 5px;
    border-radius: 4px;
    background: rgba(0, 0, 0, .45);
    font-family: var(--font-data);
    font-size: 9px;
    font-weight: 600;
    line-height: 1;
    color: #fff;
}

.thumb__badge--score {
    background: rgba(0, 0, 0, .4);
}

.thumb__badge--best {
    background: var(--signal);
    color: var(--pri-on);
}

.thumb__badge--right {
    margin-left: auto;
}

/* A video frame carries a play affordance and its shape/length. */
.thumb__play {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 20px;
    color: #fff;
    text-shadow: 0 1px 4px rgba(0, 0, 0, .5);
    pointer-events: none;
}

/* Nothing to show and nothing to rank: an unindexed clip states its own gap. */
.thumb__blank {
    display: flex;
    flex-direction: column;
    gap: 3px;
    align-items: center;
    justify-content: center;
    height: 100%;
    width: 100%;
    padding: 0 10px;
    text-align: center;
    font-family: var(--font-ui);
    font-size: 11px;
    font-weight: 600;
    line-height: 1.3;
    color: var(--marigold);
}

.thumb__blank small {
    font-weight: 400;
    font-size: 10px;
    color: var(--ink-3);
}

/* --- Audio: a list, because there is no frame to look at. */
.audiorow {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 60px 110px auto;
    gap: var(--s3);
    align-items: center;
    padding: 11px 17px;
    border-bottom: 1px solid var(--line);
}

.audiorow:last-child {
    border-bottom: 0;
}

.audiorow__name {
    display: flex;
    align-items: center;
    gap: 9px;
    min-width: 0;
    font-family: var(--font-ui);
    font-size: 12.5px;
    font-weight: 600;
    line-height: 1.3;
    color: var(--ink);
    text-decoration: none;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.audiorow__value {
    font-family: var(--font-data);
    font-variant-numeric: tabular-nums;
    font-size: 11.5px;
    line-height: 1;
    color: var(--ink-3);
    white-space: nowrap;
}

.audiorow__note {
    font-family: var(--font-ui);
    font-size: 11px;
    line-height: 1;
    color: var(--ink-3);
    white-space: nowrap;
}

.audiorow__glyph {
    flex: none;
    width: 22px;
    height: 22px;
    border-radius: 6px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--surface-2);
    border: 1px solid var(--line);
    font-size: 11px;
    color: var(--ink-2);
}

/* An unindexed card is a link plus its own fix. The action has to sit outside
   the anchor (a form inside a link is invalid), but it is styled to read as the
   card body's last line, which is where the mockup puts it. */
.thumb__action {
    display: block;
    padding: 0 9px 9px;
}

/* Wraps the pair so they still read as one card. */
.thumbcell {
    display: flex;
    flex-direction: column;
    border: 1px solid var(--line);
    border-radius: 10px;
    background: var(--surface);
    box-shadow: var(--shadow-1);
    overflow: hidden;
}

.thumbcell--unindexed {
    border-color: var(--marigold);
}

.thumbcell .thumb {
    border: 0;
    border-radius: 0;
    box-shadow: none;
}

/* A text action inside a listing row. The mockups draw these as links rather
   than buttons — a row full of bordered buttons competes with the content it is
   attached to. */
.linkbtn {
    border: 0;
    padding: 0;
    background: none;
    font-family: var(--font-ui);
    font-size: 11.5px;
    font-weight: 600;
    line-height: 1;
    color: var(--signal);
    text-decoration: none;
    cursor: pointer;
    white-space: nowrap;
}

.linkbtn:hover {
    color: var(--signal-strong);
}

.linkbtn--sm {
    font-size: 11px;
}

.linkbtn--quiet {
    color: var(--ink-3);
}

.linkbtn--quiet:hover {
    color: var(--ink);
}

.linkbtn--done {
    color: var(--posted);
    cursor: default;
}

/* The card-body scale: a line of meta that happens to be actionable. */
.linkbtn--xs {
    font-size: 10.5px;
    line-height: 1.3;
}

/* A value pill: a small bordered chip holding a measurement rather than a
   status. Sentence case, unlike `.pill`, because it holds words you read
   ("11 d left") rather than a label you recognise. */
.tag {
    display: inline-flex;
    align-items: center;
    width: fit-content;
    padding: 4px 8px;
    border: 1px solid var(--line);
    border-radius: var(--r-pill);
    background: var(--surface-2);
    font-family: var(--font-ui);
    font-size: 11px;
    font-weight: 600;
    line-height: 1;
    color: var(--ink-3);
    white-space: nowrap;
}

.tag--warn {
    border-color: color-mix(in srgb, var(--marigold) 30%, transparent);
    background: var(--marigold-tint);
    color: var(--marigold);
}

.tag--critical {
    border-color: color-mix(in srgb, var(--critical) 30%, transparent);
    background: var(--critical-tint);
    color: var(--critical);
}

/* =========================================================================
   Revision lists — the compare rail and the staleness rail
   ========================================================================= */

/* One line per revision. Three states: the current one, the one being compared
   against, and the rest. */
.revrow {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 7px 9px;
    border: 1px solid transparent;
    border-radius: 7px;
    font-family: var(--font-ui);
    font-size: 11.5px;
    font-weight: 400;
    line-height: 1;
    color: var(--ink-2);
    text-decoration: none;
}

.revrow:hover {
    background: var(--surface-2);
    color: var(--ink);
}

.revrow__v {
    font-weight: 600;
    white-space: nowrap;
}

.revrow__when {
    font-family: var(--font-data);
    font-size: 10.5px;
    color: var(--ink-3);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.revrow__flag {
    margin-left: auto;
    font-family: var(--font-data);
    font-size: 10px;
    font-weight: 600;
    color: var(--ink-3);
    white-space: nowrap;
}

.revrow--current {
    background: var(--signal-tint);
    color: var(--ink);
}

.revrow--current .revrow__flag {
    color: var(--signal);
}

.revrow--comparing {
    background: var(--surface-2);
    border-color: var(--line);
    color: var(--ink);
}

/* Which version each output was generated from. Amber where an output has
   fallen behind the piece; dashed where the channel has no output at all. */
.srow {
    display: grid;
    grid-template-columns: 20px minmax(0, 1fr) auto;
    gap: 9px;
    align-items: center;
    padding: 9px 10px;
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    background: var(--surface-2);
    text-decoration: none;
}

.srow__name {
    display: block;
    font-family: var(--font-ui);
    font-size: 12px;
    font-weight: 600;
    line-height: 1.3;
    color: var(--ink);
}

.srow__detail {
    display: block;
    font-family: var(--font-data);
    font-size: 10px;
    line-height: 1.3;
    color: var(--posted);
}

.srow__action {
    font-family: var(--font-ui);
    font-size: 10.5px;
    font-weight: 600;
    line-height: 1;
    color: var(--signal);
    white-space: nowrap;
}

.srow--stale {
    border-color: var(--marigold);
    background: var(--marigold-tint);
}

.srow--stale .srow__detail,
.srow--unknown .srow__detail {
    color: var(--marigold);
}

.srow--unknown {
    border-color: var(--line-strong);
}

/* Nothing generated yet: an outline, because there is nothing there. */
.srow--missing {
    border: 1px dashed var(--line-strong);
    background: none;
}

.srow--missing .srow__name {
    color: var(--ink-2);
}

.srow--missing .srow__detail {
    color: var(--ink-3);
}

/* The keyframe strip on a clip's detail screen: what the index actually holds. */
.kfstrip {
    display: flex;
    gap: 5px;
    overflow-x: auto;
    padding-bottom: 4px;
}

.kfstrip__cell {
    flex: none;
    width: 46px;
    height: 30px;
    border: 1px solid var(--line);
    border-radius: 4px;
    background: var(--surface-2);
    display: flex;
    align-items: flex-end;
    justify-content: center;
    font-family: var(--font-data);
    font-size: 8.5px;
    line-height: 1;
    padding-bottom: 3px;
    color: var(--ink-3);
}

.kfstrip__cell--match {
    border-color: var(--signal);
    background: var(--signal-tint);
    color: var(--signal);
}

/* A labelled key/value grid — the AI-data and File panels on a media detail.
   The key is set in the data face because it is a field name; the value in the UI
   face because it is a sentence. */
.facts {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: 6px 14px;
    font-family: var(--font-ui);
    font-size: 11.5px;
    font-weight: 400;
    line-height: 1.5;
    color: var(--ink-2);
}

.facts dt {
    font-family: var(--font-data);
    color: var(--ink-3);
    white-space: nowrap;
}

.facts dd {
    margin: 0;
    overflow-wrap: anywhere;
}

/* A value that is itself an identifier stays in the data face. */
.facts dd.is-id {
    font-family: var(--font-data);
    font-size: 11px;
}

.facts dd.is-empty {
    color: var(--ink-3);
}

/* =========================================================================
   Media detail — the AI-data panels and the asset header
   ========================================================================= */

/* The in-panel button scale: smaller than a page action, larger than a row
   action, because a rail card is neither. */
.btn--panel {
    padding: 7px 11px;
    border-radius: 7px;
    font-size: 11.5px;
}

/* Inside a tinted card a secondary button *does* get a fill — it is the only
   place in the mockups where one does, and it is what keeps the button legible
   against the tint. Full ink for the same reason. */
.btn--panel.btn--secondary {
    background: var(--surface);
    color: var(--ink);
}

/* A tinted card whose colour states what it is about: green for the search
   index, cobalt for the generation record. */
.aicard {
    display: flex;
    flex-direction: column;
    gap: 11px;
    padding: 15px;
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    background: var(--surface-2);
}

.aicard__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--s3);
}

.aicard__state {
    display: flex;
    align-items: center;
    gap: 5px;
    font-family: var(--font-ui);
    font-size: 11px;
    font-weight: 600;
    line-height: 1;
    white-space: nowrap;
}

.aicard__dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: currentColor;
}

.aicard__note {
    font-family: var(--font-ui);
    font-size: 11px;
    font-weight: 400;
    line-height: 1.5;
    color: var(--ink-2);
    border-top: 1px solid var(--line);
    padding-top: 10px;
}

.aicard--indexed {
    border-color: color-mix(in srgb, var(--posted) 30%, transparent);
    background: var(--posted-tint);
}

.aicard--indexed .panel-h,
.aicard--indexed .aicard__state {
    color: var(--posted);
}

.aicard--indexed .aicard__note {
    border-top-color: color-mix(in srgb, var(--posted) 25%, transparent);
}

/* Not indexed is the state that needs acting on, so it borrows the warning
   colour rather than sitting quietly in the neutral card. */
.aicard--unindexed {
    border-color: color-mix(in srgb, var(--marigold) 35%, transparent);
    background: var(--marigold-tint);
}

.aicard--unindexed .panel-h,
.aicard--unindexed .aicard__state {
    color: var(--marigold);
}

.aicard--unindexed .aicard__note {
    border-top-color: color-mix(in srgb, var(--marigold) 25%, transparent);
}

.aicard--generated {
    border-color: color-mix(in srgb, var(--signal) 35%, transparent);
    background: var(--signal-tint);
}

.aicard--generated .panel-h {
    color: var(--signal);
}

.aicard--generated .aicard__note {
    border-top-color: color-mix(in srgb, var(--signal) 25%, transparent);
}

/* The frozen prompt, shown as the record it is rather than as an editable box. */
.promptbox {
    margin: 0;
    padding: 10px 11px;
    border: 1px solid color-mix(in srgb, var(--signal) 25%, transparent);
    border-radius: var(--r-md);
    background: var(--surface);
    font-family: var(--font-data);
    font-size: 12px;
    font-weight: 400;
    line-height: 1.6;
    color: var(--ink);
    white-space: pre-wrap;
}

/* The asset itself, with its dimensions burnt into the corner — the numbers
   belong on the picture, not in a field beside it. */
.mediahero {
    position: relative;
    display: block;
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    overflow: hidden;
    background: var(--surface-2);
}

.mediahero img {
    display: block;
    width: 100%;
    height: auto;
}

.mediahero__spec {
    position: absolute;
    left: 10px;
    bottom: 10px;
    padding: 4px 7px;
    border-radius: var(--r-sm);
    background: rgba(0, 0, 0, .45);
    font-family: var(--font-data);
    font-size: 10.5px;
    font-weight: 400;
    line-height: 1;
    color: rgba(255, 255, 255, .5);
}

/* An audio asset has nothing to look at, so the hero states that instead. */
.mediahero--blank {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 9px;
    min-height: 140px;
    font-family: var(--font-ui);
    font-size: 12.5px;
    color: var(--ink-3);
}

/* Where this asset is already in use. The marigold rule marks a row whose
   output is scheduled: changing the file changes something already queued. */
.usedrow {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 6px 0 6px 10px;
    border-left: 2px solid var(--line-strong);
    font-family: var(--font-ui);
    font-size: 12px;
    line-height: 1.4;
    color: var(--ink-2);
    text-decoration: none;
}

.usedrow--imminent {
    border-left-color: var(--marigold);
}

.usedrow__when {
    margin-left: auto;
    font-family: var(--font-data);
    font-size: 10.5px;
    line-height: 1;
    color: var(--ink-3);
    white-space: nowrap;
}

.usedrow--imminent .usedrow__when {
    color: var(--marigold);
}

/* =========================================================================
   Confirms and blocked states
   ========================================================================= */

/* A confirm's title is set in the UI face, not the display face a drawer uses:
   it is a question being asked of you, not a heading over content. */
.confirm__title {
    margin: 0;
    font-family: var(--font-ui);
    font-size: 14.5px;
    font-weight: 600;
    line-height: 1.3;
    color: var(--ink);
}

.modal__card--danger .confirm__title {
    color: var(--critical);
}

.confirm__body {
    margin: 0;
    font-family: var(--font-ui);
    font-size: 12.5px;
    font-weight: 400;
    line-height: 1.55;
    color: var(--ink-2);
}

/* The number that changes the decision, called out inside the sentence rather
   than pulled into a separate line where it would read as a statistic. */
.confirm__body strong {
    font-weight: 600;
    color: var(--ink);
}

/* The safer thing to do instead. Offered rather than merely warned about: the
   whole point of naming the consequence is to give somebody a way out of it. */
.confirm__alt {
    padding: 10px 11px;
    border: 1px solid color-mix(in srgb, var(--marigold) 30%, transparent);
    border-radius: var(--r-md);
    background: var(--marigold-tint);
    font-family: var(--font-ui);
    font-size: 11.5px;
    font-weight: 400;
    line-height: 1.5;
    color: var(--ink-2);
}

.confirm__alt strong {
    font-weight: 600;
    color: var(--ink);
}

/* The destructive option is never the primary: it sits beside the safe default,
   outlined rather than filled. */
.btn--destructive {
    background: var(--surface);
    border-color: color-mix(in srgb, var(--critical) 40%, transparent);
    color: var(--critical);
}

.btn--destructive:hover {
    background: var(--critical-tint);
    color: var(--critical);
}

/* A blocked state, shown where the action would have been. Never a dead end:
   every one of these carries the way forward. */
.blockcard {
    display: flex;
    flex-direction: column;
    gap: var(--s2);
    min-height: 132px;
    padding: 16px;
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    background: var(--surface-2);
}

.blockcard--locked {
    border-color: var(--marigold);
    background: var(--marigold-tint);
}

.blockcard__title {
    display: flex;
    align-items: center;
    gap: var(--s2);
    font-family: var(--font-ui);
    font-size: 13px;
    font-weight: 600;
    line-height: 1.3;
    color: var(--ink);
}

.blockcard__body {
    font-family: var(--font-ui);
    font-size: 12px;
    font-weight: 400;
    line-height: 1.55;
    color: var(--ink-2);
}

/* Pinned to the bottom so a row of these lines its actions up regardless of how
   much explaining each one needed. */
.blockcard__actions {
    display: flex;
    gap: 7px;
    flex-wrap: wrap;
    margin-top: auto;
}


/* =========================================================================
   Rich text — a contenteditable bound to a hidden textarea
   ========================================================================= */

.richtext {
    display: flex;
    flex-direction: column;
    gap: var(--s2);
}

.richtext__bar {
    display: flex;
    align-items: center;
    gap: 2px;
    width: fit-content;
    padding: 3px;
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    background: var(--surface-2);
}

.richtext__btn {
    min-width: 26px;
    padding: 5px 8px;
    border: 0;
    border-radius: 5px;
    background: none;
    font-family: var(--font-ui);
    font-size: 12px;
    font-weight: 600;
    line-height: 1;
    color: var(--ink-2);
    cursor: pointer;
}

.richtext__btn:hover {
    background: var(--surface);
    color: var(--ink);
}

/* On — the caret is in bold, or in a heading, or in a link. Same treatment as the
   theme toggle, which is the same shape of control: a borderless button in a small
   bordered rail. Must come after :hover — equal specificity, and pressed has to win
   when you hover the button that is already on. */
.richtext__btn[aria-pressed="true"] {
    background: var(--signal-tint);
    color: var(--signal);
}

.richtext__btn[aria-pressed="true"]:hover {
    background: color-mix(in srgb, var(--signal) 18%, transparent);
}

/* ✦ Tighten. Cobalt because it acts rather than formats — the same rule that
   makes every other model-backed control in Relay the action colour — and named
   in words rather than given a glyph, because there is no icon for "rewrite this
   more tightly" that anyone would read correctly. */
.richtext__btn--ai {
    white-space: nowrap;
    color: var(--signal);
}

.richtext__btn--ai:hover {
    background: var(--signal-tint);
    color: var(--signal);
}

/* In flight. Quiet rather than hidden: the button is the only thing on screen
   that says a request is happening. */
.richtext__btn--ai:disabled {
    color: var(--ink-3);
    cursor: default;
}

.richtext__divider {
    width: 1px;
    height: 16px;
    margin: 0 3px;
    background: var(--line);
}

/* The hidden field is what actually posts. Kept in the DOM rather than synced
   through JS state so a submit that beats the sync still sends something. */
.richtext__source {
    display: none;
}

.richtext__surface {
    min-height: 220px;
    padding: 18px 20px;
    border: 1px solid var(--line-strong);
    border-radius: var(--r-md);
    background: var(--surface);
    font-family: var(--font-ui);
    font-size: 15.5px;
    line-height: 1.7;
    color: var(--ink);
}

.richtext__surface:focus {
    outline: none;
    border-color: var(--signal);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--signal) 22%, transparent);
}

/* The surface holds article prose, so it is styled as prose — what you see
   while writing is what the diff and the generator will read. */
.richtext__surface > * {
    margin: 0 0 var(--s4);
    max-width: 68ch;
}

.richtext__surface > :last-child {
    margin-bottom: 0;
}

.richtext__surface h2 {
    font-family: var(--font-display);
    font-size: 19px;
    font-weight: 600;
    line-height: 1.3;
    letter-spacing: -0.015em;
}

.richtext__surface blockquote {
    padding-left: 14px;
    border-left: 2px solid var(--line-strong);
    color: var(--ink-2);
}

/* The reading-scale surface: `.article__body p`'s type, so the body reads on this
   screen exactly as it will read to an approver. Borderless for the same reason
   the title is — at this size the box is louder than the prose. */
.richtext__surface--prose {
    /* Sized by its content, like the prose it replaces. A 220px floor is right for
       a field in a form and wrong here: it would leave a hole under a short
       article, and the reading view has no hole. `4em` keeps an empty one big
       enough to aim at. */
    min-height: 4em;
    padding: 0;
    border-color: transparent;
    background: none;
    font-size: 15.5px;
    line-height: 1.7;
}

.richtext__surface--prose > * {
    max-width: 70ch;
}

.richtext__surface--prose:hover {
    border-color: var(--line);
}

.richtext__surface--prose:focus {
    border-color: transparent;
    border-left-color: var(--signal);
    /* Indented on focus rather than boxed: the rule marks where you are writing
       without moving anything, because a border appearing would reflow the prose. */
    padding-left: 14px;
    margin-left: -15px;
    border-left-width: 1px;
    box-shadow: none;
}

.richtext__surface--sm {
    min-height: 96px;
    font-size: 14px;
}

/* The summary, at `.article__summary`'s scale and with its left rule — the rule is
   what the reading view uses to set the summary apart, so the field keeps it
   rather than swapping it for a box. */
.richtext__surface--lede {
    min-height: 3em;
    max-width: 64ch;
    padding: 0 0 0 14px;
    border: 0;
    border-left: 2px solid var(--line-strong);
    border-radius: 0;
    background: none;
    font-size: 16px;
    line-height: 1.6;
    color: var(--ink-2);
}

.richtext__surface--lede:focus {
    border-left-color: var(--signal);
    box-shadow: none;
}

/* --- Quiet mode: the field is the reading view until somebody writes in it.
   The toolbar keeps its space at rest so revealing it reflows nothing; only its
   opacity changes. A page that is both the article and its editor cannot open
   with two toolbars as the first thing on it. */
.richtext--quiet .richtext__bar {
    opacity: 0;
    transition: opacity .12s var(--ease);
}

.richtext--quiet:hover .richtext__bar,
.richtext--quiet:focus-within .richtext__bar {
    opacity: 1;
}

/* Help text likewise: it answers a question you only have while writing. */
.richtext--quiet .field__help {
    opacity: 0;
    transition: opacity .12s var(--ease);
}

.richtext--quiet:focus-within .field__help {
    opacity: 1;
}

/* Keyboard users never hover, and the :focus-within rule above already covers
   them: the bar is inside the field, so tabbing onto a toolbar button reveals the
   bar it is in. A rule on the button itself would do nothing — a child cannot be
   more opaque than its parent. */

.richtext__surface:empty::before {
    content: attr(data-placeholder);
    color: var(--ink-3);
}

/* =========================================================================
   Core Article — the approval bar, impact tiles and the river
   ========================================================================= */

/* A full-width bar under the chrome, not a banner in the page: the decision is
   about the whole screen, and it should not scroll away from the text it is
   about. */
.approvalbar {
    display: flex;
    align-items: center;
    gap: var(--s3);
    flex-wrap: wrap;
    padding: 11px 22px;
    border-bottom: 1px solid color-mix(in srgb, var(--marigold) 35%, transparent);
    background: var(--marigold-tint);
}

.approvalbar__label {
    font-family: var(--font-ui);
    font-size: 11px;
    font-weight: 700;
    line-height: 1;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--marigold);
    white-space: nowrap;
}

.approvalbar__text {
    font-family: var(--font-ui);
    font-size: 12.5px;
    font-weight: 400;
    line-height: 1.4;
    color: var(--ink-2);
}

/* Request changes borrows the bar's own colour rather than the neutral border,
   so the two actions read as a pair of answers to one question. */
.approvalbar .btn--secondary {
    border-color: color-mix(in srgb, var(--marigold) 50%, transparent);
}

/* An article already sent back sits in the same bar with a cooler tint: it is
   waiting on the writer now, not the approver. */
.approvalbar--returned {
    border-bottom-color: var(--line);
    background: var(--surface-2);
}

.approvalbar--returned .approvalbar__label {
    color: var(--ink-3);
}

/* --- Prose fields: a plain textarea wearing the reading view's type.
   No rich-text control on the Core Article — the only formatting prose needs is
   paragraphs, and a blank line in a textarea already means that. Each value below
   is copied from the element the field replaces, so the page does not change size
   or shape the moment somebody types in it. */

.prosefield__wrap {
    display: flex;
    flex-direction: column;
    gap: 6px;
    min-width: 0;
}

/* Matching the read view's spacing above the lede, so the gap under the title is
   the same whether you are writing it or reading it. */
.prosefield__wrap--lede {
    margin-top: 5px;
}

/* The field's name, inside its own help line. These fields deliberately have no
   label — on a saved article the text *is* the heading, the standfirst, the body
   — so the help text is the only thing that can say which field it belongs to,
   and it only appears once the caret is in one. Without the name it is a
   sentence of advice floating between two paragraphs. */
.field__name {
    font-weight: 650;
    color: var(--ink-2);
}

/* Help text holds back until you are writing, so the page opens as the article. */
.prosefield__wrap .field__help {
    opacity: 0;
    transition: opacity .12s var(--ease);
}

.prosefield__wrap:focus-within .field__help {
    opacity: 1;
}

.prosefield {
    display: block;
    width: 100%;
    max-width: var(--measure-body);
    /* Sized to its content by relay.js; the floor is what an empty one offers to
       be clicked on. */
    min-height: 4em;
    /* The same 2px + 14px inset the lede carries, so the two prose fields share one
       text edge. The rule is transparent at rest and coloured on focus, which means
       revealing it reflows nothing — the alternative, adding the padding only on
       focus, shunted the whole paragraph sideways as you clicked into it. */
    padding: 0 0 0 14px;
    border: 1px solid transparent;
    border-left: 2px solid transparent;
    background: none;
    font-family: var(--font-ui);
    font-size: 15.5px;
    font-weight: 400;
    line-height: 1.7;
    color: var(--ink);
    resize: none;
    overflow: hidden;
}

.prosefield::placeholder {
    color: var(--ink-3);
}

/* Hover shows the edge on the left rule rather than around the whole field, so the
   affordance appears exactly where the focus rule will. Declared before `:focus`
   deliberately — the two have equal specificity, so hovering the field you are
   already typing in would otherwise grey out the cobalt rule. */
.prosefield:hover {
    border-color: transparent;
    border-left-color: var(--line);
}

.prosefield:focus {
    outline: none;
    border-color: transparent;
    /* A rule where you are writing rather than a box around it. */
    border-left-color: var(--signal);
}

/* The summary keeps the left rule the reading view gives it, so the field and the
   paragraph it replaces are the same shape. */
.prosefield--lede {
    max-width: var(--measure-lede);
    /* One line, not three. relay.js grows the field to its content, but a floor
       taller than the content wins over the height it sets — so a one-sentence
       standfirst sat in a three-line box with two lines of nothing under it, and
       the rule down its left edge ran past the text to prove it. The body keeps
       a taller floor (`.prosefield`) because an empty body is a page to fill;
       an empty standfirst is a sentence, and its placeholder says so. */
    min-height: 1.6em;
    padding-left: 14px;
    border-left: 2px solid var(--line-strong);
    font-size: 16px;
    line-height: 1.6;
    color: var(--ink-2);
}

.prosefield--lede:hover {
    border-color: transparent;
    border-left-color: var(--line-strong);
}

.prosefield--lede:focus {
    border-left: 2px solid var(--signal);
    padding-left: 14px;
    margin-left: 0;
}

/* --- Article prose. The read view and the editing surface share these, so what
   you write is what the approver reads.

   That sharing is the point of the screen: there is no separate edit page, so a
   title being typed has to look exactly like the title being read. Everything
   below therefore comes in pairs — a display element and the field that replaces
   it — and the pair has to agree. */

/* The metadata caption above the heading. Bare, deliberately: a box around the
   pillar and the slug makes the metadata look more important than the article. */
.article__meta {
    display: flex;
    align-items: center;
    gap: var(--s2);
    flex-wrap: wrap;
    margin-bottom: 9px;
}

/* The same row while editing. The controls stay small and quiet, because they are
   still a caption — a full-width select here would read as a form. */
.article__metafield {
    display: flex;
    min-width: 0;
}

.article__metafield .select,
.article__metafield .input {
    width: auto;
    min-width: 150px;
    padding: 5px 9px;
    padding-right: 26px;
    border-radius: var(--r-sm);
    font-size: 11.5px;
    line-height: 1.2;
}

/* Wide enough for a real slug. A slug can be long, and clipping it in the one place
   it is editable is how a typo in the middle of one goes unnoticed — which is
   exactly what `width: 30ch` was doing: the box is sized border-box, so the 9px of
   padding either side ate three characters and `the-cost-of-unclear-ownership`
   arrived as `the-cost-of-unclear-ownersh`. The padding and borders are added on top
   here so the character count means characters, and 40 covers a slug derived from a
   full title rather than only a short one. */
.article__metafield .input {
    width: calc(40ch + 20px);
    max-width: 100%;
    padding-right: 9px;
    font-family: var(--font-data);
}

.article__title {
    margin: 0;
    max-width: var(--measure-title);
    font-family: var(--font-display);
    font-size: var(--t-display);
    font-weight: 600;
    line-height: 1.12;
    letter-spacing: -0.03em;
    text-wrap: balance;
    color: var(--ink);
}

.article__summary {
    /* The lede is a different job from the title — one names the article, the other
       is what the generator is told — so it gets air rather than sitting in the
       heading's block. */
    margin: 5px 0 0;
    max-width: var(--measure-lede);
    padding-left: 14px;
    border-left: 2px solid var(--line-strong);
    font-family: var(--font-ui);
    font-size: 16px;
    font-weight: 400;
    line-height: 1.6;
    color: var(--ink-2);
}

.article__body {
    display: flex;
    flex-direction: column;
    gap: var(--s4);
}

.article__body p {
    margin: 0;
    max-width: var(--measure-body);
    /* The same inset the editing field carries, so the paragraph an approver reads
       breaks in the same places as the one that was typed. The diff modifiers below
       replace this border with their own coloured one, which is why the inset is
       expressed the same way they express theirs. */
    padding-left: 14px;
    border-left: 2px solid transparent;
    font-family: var(--font-ui);
    font-size: 15.5px;
    font-weight: 400;
    line-height: 1.7;
    color: var(--ink);
}

/* The rule above is more specific than `.diff-block--*`, so inside the article body
   a diff block has to be given its own border back explicitly — without this it
   inherited the transparent one and the ins/del markers lost their colour, leaving
   only the background tint to carry the change.

   Width and padding stay the diff block's own 3px/12px, which lands its text within
   a pixel of an unchanged paragraph: the column edge still reads as straight, and
   the heavier rule is the point of the marker. */
.article__body p.diff-block--ins,
.article__body p.diff-block--del {
    padding-left: 12px;
    border-left-width: 3px;
}

.article__body p.diff-block--ins {
    border-left-color: var(--posted);
}

.article__body p.diff-block--del {
    border-left-color: var(--critical);
}

.article__body h2 {
    margin: 6px 0 0;
    font-family: var(--font-display);
    font-size: 19px;
    font-weight: 600;
    line-height: 1.3;
    letter-spacing: -0.015em;
    color: var(--ink);
}

/* An input that has to look like the heading it will become. */
.input--title {
    font-family: var(--font-display);
    font-size: var(--t-h1);
    font-weight: 600;
    line-height: 1.2;
    letter-spacing: -0.02em;
}

/* The title field, at the title's own scale. Every value here is `.article__title`
   — the two must not drift, or the heading changes size the moment you touch it.
   Borderless until focused: at the display size a box around the words is the
   loudest thing on the screen, and the field is obviously a field as soon as the
   caret is in it. */
.input--article {
    padding: 0;
    border-color: transparent;
    background: none;
    max-width: var(--measure-title);
    font-family: var(--font-display);
    font-size: var(--t-display);
    font-weight: 600;
    line-height: 1.12;
    letter-spacing: -0.03em;
    color: var(--ink);
}

/* A textarea, so the title wraps the way the heading does. It grows to its
   content in relay.js; the overflow guard is for the frame before that runs. */
textarea.input--article {
    max-width: var(--measure-title);
    /* Room for the descenders. The display serif's glyphs are taller than a 1.12
       line box, so the tail of a g sits below it — which an <h2> simply lets spill
       into the margin, and a textarea with `overflow:hidden` cuts off. Padding
       rather than a taller line, so the field still wraps and stacks exactly like
       the heading it stands in for. */
    padding-bottom: 5px;
    overflow: hidden;
    resize: none;
    text-wrap: balance;
}

.input--article:hover {
    /* A hairline on hover: enough to say "this is editable", not enough to box it. */
    border-bottom-color: var(--line-strong);
}

.input--article:focus {
    border-color: transparent;
    border-bottom-color: var(--signal);
    box-shadow: none;
}

/* --- Impact tiles. Three numbers, one of which changes the decision. */
.impact {
    display: flex;
    gap: var(--s2);
}

.impact__tile {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 9px 10px;
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    background: var(--surface-2);
}

.impact__n {
    font-family: var(--font-data);
    font-size: 19px;
    font-weight: 600;
    line-height: 1;
    color: var(--ink);
}

.impact__label {
    font-family: var(--font-ui);
    font-size: 10.5px;
    font-weight: 400;
    line-height: 1.3;
    color: var(--ink-3);
}

/* The one figure worth colouring: outputs already queued to go out. */
.impact__tile--live {
    border-color: color-mix(in srgb, var(--marigold) 30%, transparent);
    background: var(--marigold-tint);
}

.impact__tile--live .impact__n,
.impact__tile--live .impact__label {
    color: var(--marigold);
}

/* A base piece that draws on this article. The rule's colour says whether
   anything downstream of it is already committed. */
.impactrow {
    display: flex;
    align-items: center;
    gap: var(--s2);
    padding: 5px 0 5px 9px;
    border-left: 3px solid var(--line-strong);
    font-family: var(--font-ui);
    font-size: 12px;
    line-height: 1.4;
    color: var(--ink-2);
    text-decoration: none;
}

.impactrow--queued {
    border-left-color: var(--marigold);
}

.impactrow--posted {
    border-left-color: var(--posted);
}

.impactrow__count {
    margin-left: auto;
    font-family: var(--font-data);
    font-size: 10.5px;
    line-height: 1;
    color: var(--ink-3);
    white-space: nowrap;
}

/* --- The river: revisions with the approval decisions folded in. */
.river__row {
    display: grid;
    grid-template-columns: 16px 1fr;
    gap: 11px;
}

.river__rail {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
}

.river__dot {
    flex: none;
    width: 9px;
    height: 9px;
    margin-top: 4px;
    border-radius: 50%;
    background: var(--line-strong);
}

.river__dot--pending {
    background: var(--marigold);
}

.river__dot--approved {
    background: var(--posted);
}

.river__dot--rejected {
    background: var(--critical);
}

.river__line {
    width: 1px;
    flex: 1;
    background: var(--line);
}

.river__body {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding-bottom: 14px;
}

.river__label {
    font-family: var(--font-ui);
    font-size: 12.5px;
    font-weight: 600;
    line-height: 1.35;
    color: var(--ink);
}

.river__label--past {
    color: var(--ink-2);
}

.river__meta {
    font-family: var(--font-data);
    font-size: 11px;
    font-weight: 400;
    line-height: 1.45;
    color: var(--ink-3);
}

/* --- Comments. The reason a locked article is not a dead end. */
.note-row {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 11px 0;
    border-bottom: 1px solid var(--line);
}

.note-row:last-child {
    border-bottom: 0;
}

.note-row__head {
    display: flex;
    align-items: baseline;
    gap: var(--s2);
}

.note-row__who {
    font-family: var(--font-ui);
    font-size: 12px;
    font-weight: 600;
    line-height: 1;
    color: var(--ink);
}

.note-row__body {
    font-family: var(--font-ui);
    font-size: 12.5px;
    line-height: 1.55;
    color: var(--ink-2);
}

/* Resolved stays on the record — the discussion behind a change is worth more
   than a tidy list. */
.note-row--resolved .note-row__body,
.note-row--resolved .note-row__who {
    color: var(--ink-3);
}

/* =========================================================================
   Base Piece editor — three columns: sources, the idea, the expansion
   ========================================================================= */

/* Sources left, text centre, expansion right. Collapses to one column below the
   breakpoint by reordering rather than hiding: everything here is load-bearing. */
.split3 {
    display: grid;
    grid-template-columns: 268px minmax(0, 1fr) 356px;
    gap: var(--s5);
    align-items: start;
}

@media (max-width: 1180px) {
    .split3 {
        grid-template-columns: minmax(0, 1fr);
    }

    /* The text first on a narrow screen: it is what you came to write. */
    .split3__main {
        order: -1;
    }
}

.split3__rail {
    display: flex;
    flex-direction: column;
    gap: var(--s5);
    min-width: 0;
}

/* An attached source. Its own state is shown because that is what decides
   whether it can be a source at all. */
.srcrow {
    display: flex;
    flex-direction: column;
    gap: 5px;
    padding: 10px 11px;
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    text-decoration: none;
}

.srcrow__title {
    font-family: var(--font-ui);
    font-size: 12.5px;
    font-weight: 600;
    line-height: 1.35;
    color: var(--ink);
}

.srcrow__meta {
    display: flex;
    align-items: center;
    gap: 6px;
    font-family: var(--font-data);
    font-size: 10.5px;
    line-height: 1.4;
    color: var(--ink-3);
}

.srcrow__dot {
    flex: none;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--line-strong);
}

.srcrow__dot--approved {
    background: var(--posted);
}

.srcrow__dot--draft {
    background: var(--marigold);
}

/* A source that has aged out or was never approved: still listed, because
   removing it silently would be worse than showing why it is a problem. */
.srcrow--stale {
    border-color: color-mix(in srgb, var(--marigold) 40%, transparent);
    background: var(--marigold-tint);
}

/* A row that carries its own detach: the link takes the column layout the
   plain row had, and the ✕ sits beside it. */
.srcrow--held {
    flex-direction: row;
    align-items: flex-start;
    gap: 8px;
}

.srcrow__link {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 5px;
    text-decoration: none;
}

.srcrow__x {
    flex: none;
    border: 0;
    background: none;
    padding: 0 2px;
    font: 600 11px/1 var(--font-ui);
    color: var(--ink-3);
    cursor: pointer;
}

.srcrow__x:hover {
    color: var(--critical);
}

/* The attach target. Dashed because nothing is there yet — the same signal the
   drafts dock uses for an empty slot. */
.dashdrop {
    display: block;
    padding: 12px 11px;
    border: 1px dashed var(--line-strong);
    border-radius: var(--r-md);
    text-align: center;
    font-family: var(--font-ui);
    font-size: 11.5px;
    line-height: 1.4;
    color: var(--ink-3);
    text-decoration: none;
}

.dashdrop:hover {
    border-color: var(--signal);
    color: var(--signal);
}

/* --- The expansion rail: one row per active channel, gaps included. */
.exprow {
    display: grid;
    grid-template-columns: 20px minmax(0, 1fr) auto;
    align-items: center;
    gap: 11px;
    padding: 11px 12px;
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    background: var(--surface);
    text-decoration: none;
}

.exprow__ic {
    width: 18px;
    height: 18px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--surface-2);
    color: var(--ink-3);
    font-family: var(--font-ui);
    font-size: 10px;
    font-weight: 700;
    line-height: 1;
}

.exprow__name {
    display: block;
    font-family: var(--font-ui);
    font-size: 12.5px;
    font-weight: 600;
    line-height: 1.3;
    color: var(--ink);
}

.exprow__note {
    display: block;
    font-family: var(--font-data);
    font-size: 10.5px;
    line-height: 1.35;
    color: var(--ink-3);
}

.exprow--done .exprow__ic {
    background: var(--posted-tint);
    color: var(--posted);
}

/* Over the channel's limit, stale against the piece, or edited by hand: three
   different reasons to look, all of them amber rather than red — none is broken. */
.exprow--over,
.exprow--stale,
.exprow--edited {
    border-color: color-mix(in srgb, var(--marigold) 40%, transparent);
}

.exprow--over .exprow__ic,
.exprow--stale .exprow__ic,
.exprow--edited .exprow__ic {
    background: var(--marigold-tint);
    color: var(--marigold);
}

.exprow--over .exprow__note,
.exprow--stale .exprow__note,
.exprow--edited .exprow__note {
    color: var(--marigold);
}

/* Nothing generated: an outline, because there is nothing there. */
.exprow--missing {
    border-style: dashed;
    border-color: var(--line-strong);
    background: none;
}

.exprow--missing .exprow__name {
    color: var(--ink-2);
}

/* The editor's own footer: length, what the length means, and the save state. */
.editorfoot {
    display: flex;
    align-items: center;
    gap: var(--s3);
    flex-wrap: wrap;
    font-family: var(--font-data);
    font-size: 11px;
    line-height: 1;
    color: var(--ink-3);
}

.editorfoot__saved {
    margin-left: auto;
    color: var(--posted);
}

/* A stack of checkbox rows — a multi-select rendered as choices rather than a
   list box, so each option can carry its own state. */
.checks {
    display: grid;
    gap: 6px;
}

/* Unboxed variant: a single option inside a panel, where the box would be a
   second border around one already there. */
.check--plain {
    padding: 0;
    border: 0;
    background: none;
    font-family: var(--font-ui);
    font-size: 11.5px;
    font-weight: 400;
    line-height: 1.45;
    color: var(--ink-2);
}

/* =========================================================================
   Output editor — the channel switcher, the copy surface and the media slots
   ========================================================================= */

/* The switcher between one Base Piece's outputs. Brand tiles rather than text,
   because the channel's mark is already how the channel is named everywhere
   else in Relay — and five words would not fit here. */
.chnswitch {
    display: flex;
    align-items: center;
    gap: 2px;
    padding: 2px;
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    background: var(--surface-2);
}

.chnswitch__item {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 24px;
    border-radius: 6px;
    font-family: var(--font-data);
    font-size: 9px;
    font-weight: 700;
    line-height: 1;
    color: var(--ink-3);
    text-decoration: none;
}

.chnswitch__item:hover {
    background: var(--surface);
    color: var(--ink);
}

/* The one you are looking at wears its brand colour; the background is set
   inline from the ChannelDefinition, so only the text colour lives here. */
.chnswitch__item[aria-current="page"] {
    color: #fff;
}

/* A channel with nothing generated yet. Still shown — the gap is information —
   but it cannot be navigated to, so it must not look like it can. */
.chnswitch__item[aria-disabled="true"] {
    opacity: 0.4;
    cursor: default;
}

.chnswitch__item[aria-disabled="true"]:hover {
    background: none;
    color: var(--ink-3);
}

/* --- The header row above a copy surface: what it is, and how long it is. */
.fieldhead {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--s3);
}

.fieldhead__label {
    font-family: var(--font-ui);
    font-size: 12.5px;
    font-weight: 600;
    line-height: 1;
    color: var(--ink);
}

/* The clause after the em dash — "what you paste into LinkedIn". Part of the
   label, not help text underneath it, because it says what the field is for. */
.fieldhead__for {
    font-weight: 400;
    color: var(--ink-3);
}

.fieldhead__for--required {
    color: var(--critical);
}

.fieldhead__count {
    flex: none;
    font-family: var(--font-data);
    font-size: 11px;
    line-height: 1;
    color: var(--ink-3);
}

/* Within the channel's limit, and over it. Green here is not decoration: the
   length is the one thing about a caption a platform will actually enforce. */
/* A caption that says how a field behaves rather than how long it is — "filled
   in by hand" — runs a notch smaller: it is a footnote, not a measurement. */
.fieldhead__count--xs {
    font-size: 10.5px;
}

.fieldhead__count--ok {
    color: var(--posted);
}

.fieldhead__count--over {
    color: var(--critical);
}

/* --- The copy itself. One box, used both as a textarea for whoever may edit
   and as rendered paragraphs for whoever may not, so the text sits identically
   either way. */
.copybox {
    display: flex;
    flex-direction: column;
    gap: var(--s3);
    min-height: 210px;
    padding: 16px 18px;
    border: 1px solid var(--line-strong);
    border-radius: var(--r-ml);
    background: var(--surface);
}

.copybox p {
    margin: 0;
    font-family: var(--font-ui);
    font-size: 14.5px;
    font-weight: 400;
    line-height: 1.6;
    color: var(--ink);
}

textarea.copybox {
    display: block;
    width: 100%;
    font-family: var(--font-ui);
    font-size: 14.5px;
    font-weight: 400;
    line-height: 1.6;
    color: var(--ink);
    resize: vertical;
}

textarea.copybox:focus {
    outline: none;
    border-color: var(--signal);
    box-shadow: 0 0 0 3px var(--signal-tint);
}

/* A newsletter is read as one piece of prose, so its body gets the taller box
   and the looser leading the newsletter preview also uses. */
.copybox--long {
    min-height: 280px;
    padding: 17px 19px;
}

.copybox--long p,
textarea.copybox--long {
    line-height: 1.65;
}

/* A spoken script is shorter than a written caption by design. */
.copybox--script {
    min-height: 200px;
}

/* The settings scale: a channel's rules are prose, but they are a spec rather
   than copy, so they sit a notch below the caption they will be written against. */
.copybox--sm {
    min-height: 150px;
    padding: 13px 14px;
}

.copybox--sm p,
textarea.copybox--sm {
    font-size: 13.5px;
}

/* The subject line: one input at the weight the inbox will show it. */
.subjectline {
    display: block;
    width: 100%;
    padding: 11px 13px;
    border: 1px solid var(--line-strong);
    border-radius: var(--r-md);
    background: var(--surface);
    font-family: var(--font-ui);
    font-size: 15px;
    font-weight: 600;
    line-height: 1.3;
    color: var(--ink);
}

.subjectline:focus {
    outline: none;
    border-color: var(--signal);
    box-shadow: 0 0 0 3px var(--signal-tint);
}

/* The visual prompt: quieter than the copy, in the data face, because it is an
   instruction to a model rather than something anyone will read. */
.promptsurface {
    margin: 0;
    padding: 13px 15px;
    border: 1px solid var(--line);
    border-radius: var(--r-ml);
    background: var(--surface-2);
    font-family: var(--font-data);
    font-size: 13px;
    font-weight: 400;
    line-height: 1.6;
    color: var(--ink-2);
    white-space: pre-wrap;
}

textarea.promptsurface {
    display: block;
    width: 100%;
    min-height: 76px;
    resize: vertical;
}

textarea.promptsurface:focus {
    outline: none;
    border-color: var(--signal);
    box-shadow: 0 0 0 3px var(--signal-tint);
}

/* --- The two media slots, side by side. */
.slots {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 14px;
}

@media (max-width: 620px) {
    .slots {
        grid-template-columns: minmax(0, 1fr);
    }
}

.slot {
    display: flex;
    align-items: center;
    gap: 11px;
    min-height: 80px;
    padding: 11px;
    border: 1px solid var(--line);
    border-radius: var(--r-ml);
    background: var(--surface);
}

/* Nothing attached and nothing needed: an outline, because there is nothing
   there yet. */
.slot--empty {
    border-style: dashed;
    border-color: var(--line-strong);
}

/* Nothing attached and the channel cannot post without it. The one place in
   this screen that earns red: it is the reason the slot will not go out. */
.slot--required {
    border-style: dashed;
    border-color: var(--critical);
    background: var(--critical-tint);
}

.slot__thumb {
    display: flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: 74px;
    height: 56px;
    overflow: hidden;
    border-radius: 6px;
    /* The placeholder's own gradient: a stand-in for a picture, not a surface. */
    background: linear-gradient(135deg, var(--ink-2), var(--ink));
    font-family: var(--font-data);
    font-size: 9px;
    font-weight: 400;
    line-height: 1;
    color: color-mix(in srgb, #fff 50%, transparent);
}

.slot__thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* An empty thumb is a target, not a picture. */
.slot--empty .slot__thumb,
.slot--required .slot__thumb {
    border: 1px solid var(--line);
    background: var(--surface-2);
    font-family: var(--font-ui);
    font-size: 14px;
    color: var(--ink-3);
}

.slot--required .slot__thumb {
    border-color: color-mix(in srgb, var(--critical) 35%, transparent);
    background: var(--surface);
    font-size: 15px;
    color: var(--critical);
}

/* A vertical channel's slots are portrait: the thumb has to be the shape of
   the thing it stands for, or it tells you the wrong ratio. */
.slots--vertical .slot__thumb {
    width: 46px;
    height: 60px;
}

.slots--newsletter .slot__thumb {
    height: 50px;
}

.slot__body {
    display: flex;
    flex-direction: column;
    gap: 3px;
    min-width: 0;
}

.slot__name {
    font-family: var(--font-ui);
    font-size: 12px;
    font-weight: 600;
    line-height: 1.3;
    color: var(--ink);
    overflow-wrap: anywhere;
}

.slot--empty .slot__name {
    color: var(--ink-2);
}

.slot--required .slot__name {
    color: var(--critical);
}

.slot__meta {
    font-family: var(--font-data);
    font-size: 10px;
    line-height: 1.35;
    color: var(--ink-3);
}

/* Inside a required-and-missing slot the consequence is the meta line, so it
   gets full ink rather than the quiet grey. */
.slot--required .slot__meta {
    font-size: 10.5px;
    color: var(--ink-2);
}

.slot__actions {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
}

/* --- The rail's cards. Flatter than .panel: they sit on --surface-2 and a
   shadow would lift them off a surface that is already recessed. */
.railcard {
    display: flex;
    flex-direction: column;
    gap: 9px;
    padding: 13px 14px;
    border: 1px solid var(--line);
    border-radius: var(--r-ml);
    background: var(--surface);
}

/* One fact on its own line, weighted: the estimated duration of a script is
   the number that decides whether the script is the right length. */
.railcard--figure {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    padding: 12px 13px;
}

.railcard__name {
    font-family: var(--font-ui);
    font-size: 12px;
    font-weight: 600;
    line-height: 1;
    color: var(--ink);
}

.railcard__figure {
    font-family: var(--font-data);
    font-size: 15px;
    font-weight: 600;
    line-height: 1;
    color: var(--posted);
}

.railcard__figure--over {
    color: var(--critical);
}

/* The spec grid runs a notch tighter than a facts list: four rows read as one
   block rather than four. */
.facts--tight {
    row-gap: 5px;
}

.facts dd.is-required {
    color: var(--critical);
}

/* Inside a capability card the facts run a notch smaller: three rows of a
   dashboard, not a spec anybody reads through. */
.facts--card {
    font-size: 11px;
    row-gap: 4px;
}

/* --- Lineage: what this output came from, each with the state of its source. */
.lin {
    display: flex;
    align-items: center;
    gap: 7px;
    font-family: var(--font-ui);
    font-size: 11.5px;
    font-weight: 400;
    line-height: 1.4;
    color: var(--ink-2);
    text-decoration: none;
}

.lin:hover {
    color: var(--ink);
}

.lin__dot {
    flex: none;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--signal);
}

/* Amber where the source has moved on: a pillar item that has expired, or an
   article carrying a revision this output was not generated from. */
.lin__dot--pending {
    background: var(--marigold);
}

.lin__flag {
    font-family: var(--font-data);
    font-size: 10px;
    line-height: 1;
    color: var(--marigold);
}

.lin__foot {
    padding-top: var(--s2);
    border-top: 1px solid var(--line);
    font-family: var(--font-ui);
    font-size: 11px;
    line-height: 1.5;
    color: var(--ink-3);
}

/* --- The posted bar. The approval bar's shape in green: the output has gone
   out, and editing it now only corrects the record. */
.approvalbar--posted {
    padding: 10px 22px;
    border-bottom-color: color-mix(in srgb, var(--posted) 30%, transparent);
    background: var(--posted-tint);
}

.approvalbar--posted .approvalbar__label {
    color: var(--posted);
}

/* --- The live link, once it has been pasted in. */
.livelink {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    border: 1px solid var(--line-strong);
    border-radius: var(--r-md);
    background: var(--surface);
    font-family: var(--font-data);
    font-size: 13px;
    line-height: 1;
    color: var(--ink);
}

.livelink__url {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--ink);
    text-decoration: none;
}

/* Not yet pasted: a dashed field, the same signal an empty media slot uses. */
.livelink--empty {
    border-style: dashed;
    background: var(--surface-2);
    color: var(--ink-3);
}

/* =========================================================================
   Generate in bulk — the selectable list and the run cards
   ========================================================================= */

/* The bulk list is the listing table with a select column and a taller row: the
   rows carry two lines of text and a strip of channel tiles, so 12px crowds them. */
.listing--bulk tbody td {
    padding: 13px 7px;
}

table.listing--bulk .ttl {
    font-size: 13.5px;
}

/* A row action in a listing: smaller than a page button and transparent, so a
   column of them doesn't read as a column of white boxes. */
.btn--row {
    padding: 6px 10px;
    border-radius: 7px;
    font-size: 11.5px;
}

.btn--row.btn--secondary {
    color: var(--ink);
}

/* A selected row keeps its tint through hover — the selection is the state that
   matters, and losing it under the pointer makes counting selected rows hard. */
.listing tr[aria-selected="true"],
.listing tbody tr[aria-selected="true"]:hover {
    background: var(--signal-tint);
}

/* The row's checkbox. Native, restyled: a real checkbox gets keyboard support,
   the form serialisation and the label association for free. */
.selectbox {
    appearance: none;
    display: block;
    width: 15px;
    height: 15px;
    margin: 0;
    border: 1px solid var(--line-strong);
    border-radius: 4px;
    background: var(--surface);
    cursor: pointer;
}

.selectbox:checked {
    border-color: var(--signal);
    background: var(--signal);
}

/* The tick is drawn rather than typed, so it can't be missing from a font. */
.selectbox:checked::after {
    content: "";
    display: block;
    width: 4px;
    height: 8px;
    margin: 1px 0 0 4px;
    border: 2px solid var(--pri-on);
    border-top: 0;
    border-left: 0;
    transform: rotate(40deg);
}

/* --- Coverage: which channels this piece has reached, and which it hasn't.
   Per channel rather than "3/5", because which two are missing is the reason to
   be on this screen at all. */
.chncov {
    display: flex;
    align-items: center;
    gap: 4px;
}

.chncov__tile {
    display: flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    border-radius: 4px;
    font-family: var(--font-data);
    font-size: 8px;
    font-weight: 700;
    line-height: 1;
    /* Ink arrives inline with the background — see .chn__dot. */
    color: var(--pri-on);
}

/* A channel whose last generation failed. Distinct from a gap, and the
   distinction is the useful part: nobody has tried the empty one, and somebody
   has tried this one and it broke. */
.chncov__tile--fail {
    display: flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    border: 1px solid color-mix(in srgb, var(--critical) 40%, transparent);
    border-radius: 4px;
    background: var(--critical-tint);
    font-family: var(--font-data);
    font-size: 8px;
    font-weight: 700;
    line-height: 1;
    color: var(--critical);
}

/* A channel with nothing generated: the tile's outline, empty. */
.chncov__gap {
    flex: none;
    width: 16px;
    height: 16px;
    border: 1px dashed var(--line-strong);
    border-radius: 4px;
    background: var(--surface-2);
}

.chncov__n {
    margin-left: 3px;
    font-family: var(--font-data);
    font-size: 10.5px;
    line-height: 1;
    color: var(--ink-3);
}

/* The count carries the verdict: green when the piece reached every channel it
   was meant for, amber while it hasn't, red when something broke on the way. */
.chncov__n--ok {
    color: var(--posted);
}

.chncov__n--warn {
    color: var(--marigold);
}

.chncov__n--fail {
    color: var(--critical);
}

/* A listing row's second line, when what it carries is a warning rather than a
   measurement. Only the colour changes: the mockup keeps the mono face, and a
   flag that swapped typeface as well would make the row jump when it is raised. */
table.listing .sub--flag {
    color: var(--marigold);
}

/* --- The run cards in the rail. */

/* Lifted: the run in flight is the one thing on this screen that is changing,
   and it sits above the settings that describe what it is doing. */
.panel--lifted {
    box-shadow: var(--shadow-2);
}

.runrow {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--s3);
    font-family: var(--font-ui);
    font-size: 11.5px;
    font-weight: 400;
    line-height: 1.5;
    color: var(--ink-2);
    text-decoration: none;
}

.runrow:hover {
    color: var(--ink);
}

.runrow__name {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The tally, split rather than totalled: a run that half-failed is the one you
   want to see, and "3/5" says that where "3" doesn't. */
.runrow__tally {
    flex: none;
    font-family: var(--font-data);
    color: var(--posted);
}

.runrow__tally--fail {
    color: var(--critical);
}

/* The card's closing footnote — the model that ran, or the promise that the run
   survives leaving the page. */
.cardfoot {
    padding-top: 9px;
    border-top: 1px solid var(--line);
    font-family: var(--font-data);
    font-size: 10.5px;
    line-height: 1.45;
    color: var(--ink-3);
}

/* =========================================================================
   Settings — the master-detail pane, the prompt editor and the fact cards
   ========================================================================= */

/* The pane's own header: what you are editing, what it is made of, and the two
   actions that apply to it. */
.panehead {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 20px;
    flex-wrap: wrap;
}

.panehead__title {
    margin: 0;
    font-family: var(--font-display);
    font-size: 21px;
    font-weight: 600;
    line-height: 1.2;
    letter-spacing: -0.02em;
    color: var(--ink);
}

.panehead__sub {
    font-family: var(--font-data);
    font-size: var(--t-meta);
    line-height: 1;
    color: var(--ink-3);
}

.panehead__actions {
    display: flex;
    align-items: center;
    gap: 9px;
    flex-wrap: wrap;
}

/* The channel or pillar's mark in the master list. */
.listrow__mark {
    display: flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    border-radius: var(--r-sm);
    font-family: var(--font-data);
    font-size: 8px;
    font-weight: 700;
    line-height: 1;
    /* Ink arrives inline with the background — see .chn__dot. */
    color: var(--pri-on);
}

/* A pillar has no brand colour, so it gets a dot: filled when it is the one
   you're editing, hollow otherwise. */
.listrow__dot--on {
    background: var(--signal);
}

/* The row's right-hand figure — a cadence, or a count of what a pillar holds. */
.listrow__meta {
    margin-left: auto;
    font-family: var(--font-data);
    font-size: 10.5px;
    font-weight: 400;
    line-height: 1;
    color: var(--ink-3);
}

.listrow[aria-current="page"] .listrow__meta {
    color: var(--signal);
}

/* "＋ Add a channel" — an outline, because there is nothing there yet. */
.listrow--add {
    margin-top: var(--s2);
    border: 1px dashed var(--line-strong);
    color: var(--ink-3);
}

/* The add row while its blank form is open, so the rail still says where you are.
   Same treatment a selected listrow gets, minus the dot it has no object for. */
.listrow--add-on {
    background: var(--signal-tint);
    color: var(--signal);
}

/* The list's closing caveat: what deactivating or deleting actually does. Pinned
   to the bottom, because it answers a question asked at the moment of acting. */
.listrow__foot {
    margin-top: auto;
    padding: var(--s3) 10px 0;
    border-top: 1px solid var(--line);
    font-family: var(--font-ui);
    font-size: 11px;
    line-height: 1.5;
    color: var(--ink-3);
}

/* A bordered group inside the pane — the Media block, whose fields only mean
   something together. Flatter than .panel: it is a fieldset, not a card. */
.subpanel {
    display: flex;
    flex-direction: column;
    gap: 13px;
    padding: 15px 16px;
    border: 1px solid var(--line);
    border-radius: var(--r-ml);
    background: var(--surface);
}

/* Fields that only apply when the switch above them is on. Dimmed rather than
   hidden: what they would say is part of understanding the switch. */
.subpanel__dependent {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--s3);
    padding-top: 13px;
    border-top: 1px solid var(--line);
}

.subpanel__dependent[aria-disabled="true"] {
    opacity: .55;
}

@media (max-width: 720px) {
    .subpanel__dependent {
        grid-template-columns: minmax(0, 1fr);
    }
}

/* A row of a toggle and the sentence explaining what it decides. */
.switchrow {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--s3);
}

.switchrow__label {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.switchrow__name {
    font-family: var(--font-ui);
    font-size: 12.5px;
    font-weight: 600;
    line-height: 1;
    color: var(--ink);
}

/* --- The prompt template. Code, so it gets the code surface: dark in both
   themes, because an editor's syntax theme does not flip with the chrome. */
.promptcode {
    display: block;
    width: 100%;
    min-height: 150px;
    padding: 13px 14px;
    border: 1px solid var(--line-strong);
    border-radius: var(--r-ml);
    background: var(--code-surface);
    font-family: var(--font-data);
    font-size: 12px;
    font-weight: 400;
    line-height: 1.65;
    color: var(--code-ink);
    white-space: pre-wrap;
    resize: vertical;
    /* The built-in default is long. Capped and scrollable so reading it doesn't
       push the fields below it off the screen. */
    max-height: 340px;
    overflow: auto;
}

.promptcode:focus {
    outline: none;
    border-color: var(--signal);
    box-shadow: 0 0 0 3px var(--signal-tint);
}

/* A placeholder inside the rendered template. `mark` because it *is* a mark on
   the text, but without the UA highlight — the colour is the highlight here. */
.promptcode mark {
    padding: 0;
    background: none;
    color: var(--code-token);
}

/* The placeholders this channel's template may use. Clickable, so the name never
   has to be typed from memory — a mistyped one silently renders as itself. */
.phchip {
    padding: 4px 6px;
    border: 1px solid var(--line);
    border-radius: 4px;
    background: var(--surface-2);
    font-family: var(--font-data);
    font-size: 10px;
    font-weight: 400;
    line-height: 1;
    color: var(--ink-2);
    cursor: pointer;
}

.phchip:hover {
    border-color: var(--signal);
    color: var(--signal);
}

.phchips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s1);
}

/* --- Fact cards: a label on the left, its figure on the right. */
.statrow {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--s3);
    font-family: var(--font-ui);
    font-size: 12.5px;
    font-weight: 400;
    line-height: 1.6;
    color: var(--ink-2);
    text-decoration: none;
}

.statrow__val {
    flex: none;
    font-family: var(--font-data);
    color: var(--ink);
}

/* The tally scale: a column of counts inside a rail card, where 12.5px reads as a
   list of facts rather than a figure each. */
.statrow--sm {
    font-size: 12px;
    line-height: 1.3;
}

/* A qualifier on the figure — "· 1 in review". Amber because it is the part that
   means somebody has to do something. */
.statrow__note {
    font-size: 11px;
    color: var(--marigold);
}

/* A row whose state is a dot: a feed's health, a source's approval. */
.dotrow {
    display: flex;
    align-items: center;
    gap: var(--s2);
    font-family: var(--font-ui);
    font-size: 12.5px;
    font-weight: 400;
    line-height: 1.5;
    color: var(--ink-2);
    text-decoration: none;
}

.dotrow:hover {
    color: var(--ink);
}

/* Inside a listing cell the dot row is one line among several, so it takes the
   table's scale rather than the panel's. */
table.listing .dotrow {
    font-size: 11.5px;
    line-height: 1.3;
}

/* `display:block` because this dot is also used on its own in a table cell,
   where nothing blockifies it and an inline span ignores its width. */
.dotrow__dot {
    display: block;
    flex: none;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--posted);
}

.dotrow__dot--fail {
    background: var(--critical);
}

.dotrow__dot--idle {
    background: var(--line-strong);
}

/* Pulling fine but filed nowhere: its items only land if a keyword matches. */
.dotrow__dot--warn {
    background: var(--marigold);
}

.dotrow__flag {
    font-family: var(--font-data);
    font-size: 10.5px;
    color: var(--critical);
}

/* --- The dry run: test the prompt against a real piece before saving. */
.dryrun {
    display: flex;
    align-items: flex-start;
    gap: 18px;
    flex-wrap: wrap;
    padding: 15px 16px;
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    background: var(--surface);
    box-shadow: var(--shadow-1);
}

.dryrun__form {
    display: flex;
    flex: 1 1 280px;
    flex-direction: column;
    gap: 7px;
    min-width: 0;
}

.dryrun__result {
    flex: 1.2 1 300px;
    min-width: 0;
    padding: 12px 13px;
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    background: var(--surface-2);
}

.dryrun__head {
    display: block;
    margin-bottom: 7px;
    font-family: var(--font-data);
    font-size: 10px;
    line-height: 1;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-3);
}

.dryrun__body {
    margin: 0;
    font-family: var(--font-ui);
    font-size: 12.5px;
    font-weight: 400;
    line-height: 1.55;
    color: var(--ink-2);
}

/* The dry run refused, or came back over the limit. Both are the same kind of
   answer — the template needs work — so they wear the same colour. */
.dryrun__head--fail {
    color: var(--critical);
}

/* A second label inside the result, for the visual prompt. Quieter than the head:
   the caption is what was asked for, and the prompt is what came with it. */
.dryrun__sub {
    display: block;
    margin: 9px 0 3px;
    font-family: var(--font-data);
    font-size: 9.5px;
    line-height: 1;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-3);
}

/* A channel that requires media and got no prompt back. Amber rather than red:
   the run worked, the template is the thing that is wrong. */
.dryrun__sub--warn {
    text-transform: none;
    letter-spacing: 0;
    font-size: 10.5px;
    color: var(--marigold);
}

.dryrun__body--sm {
    font-size: 11.5px;
    color: var(--ink-3);
}

/* =========================================================================
   Settings — feeds, roles and the generation dashboard
   ========================================================================= */

/* The capability matrix. A table, because it is one: seven capabilities against
   three roles, and the answer to "can a scheduler approve" is a cell. */
table.listing--matrix {
    min-width: 0;
}

table.listing--matrix thead th + th,
table.listing--matrix tbody td + td {
    width: 42px;
    text-align: center;
}

table.listing--matrix thead th {
    padding: 10px 8px;
    font-size: 9.5px;
    line-height: 1.3;
    letter-spacing: 0.06em;
    text-align: center;
}

table.listing--matrix thead th:first-child {
    text-align: left;
}

table.listing--matrix tbody td {
    padding: 9px 8px;
    font-size: 11.5px;
    line-height: 1.4;
}

/* Granted, and not granted. The dash is deliberately faint: an absent capability
   is the normal case, not a warning. */
.matrix-yes {
    font-weight: 700;
    color: var(--posted);
}

.matrix-no {
    color: var(--line-strong);
}

/* --- A capability's card on the generation screen: which model, whether it is
   switched on, and what it has done. */
.onoff {
    display: flex;
    align-items: center;
    gap: 5px;
    font-family: var(--font-ui);
    font-size: 11px;
    font-weight: 600;
    line-height: 1;
    color: var(--posted);
}

.onoff__dot {
    flex: none;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--posted);
}

/* Off is grey rather than red: a capability nobody switched on is a choice, and
   the card says what that choice costs in its own footnote. */
.onoff--off {
    color: var(--ink-3);
}

.onoff--off .onoff__dot {
    background: var(--line-strong);
}

.modelname {
    font-family: var(--font-data);
    font-size: 13px;
    font-weight: 600;
    line-height: 1.3;
    color: var(--ink);
    overflow-wrap: anywhere;
}

/* A long provider-qualified identifier gets a notch less room. */
.modelname--long {
    font-size: 12px;
}

/* --- One recorded failure: what broke, in the provider's words, and whether
   anybody dealt with it. */
.failrow {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 12px 15px;
    border-bottom: 1px solid var(--line);
}

.failrow:last-child {
    border-bottom: 0;
}

.failrow__what {
    display: flex;
    align-items: center;
    gap: var(--s2);
    font-family: var(--font-ui);
    font-size: 12.5px;
    font-weight: 600;
    line-height: 1.3;
    color: var(--ink);
    text-decoration: none;
}

.failrow__code {
    font-family: var(--font-data);
    font-size: 11px;
    line-height: 1.5;
    color: var(--critical);
    overflow-wrap: anywhere;
}

.failrow__when {
    font-family: var(--font-data);
    font-size: 10.5px;
    line-height: 1;
    color: var(--ink-3);
}

/* Resolved: the failure happened, somebody retried, it worked. Kept in the list
   because the rate is the useful number, not the backlog. */
.failrow--resolved .failrow__code {
    color: var(--ink-3);
}

/* --- A checkbox whose label is a claim plus its consequence. Stacked, because
   the consequence is the part that decides the answer. */
.check--stacked {
    display: flex;
    align-items: flex-start;
    gap: 9px;
    padding: 0;
    border: 0;
    background: none;
    font-family: var(--font-ui);
    font-size: 12.5px;
    font-weight: 400;
    line-height: 1.5;
    color: var(--ink-2);
}

.check--stacked input {
    flex: none;
    margin-top: 1px;
}

.check--stacked strong {
    font-weight: 600;
    color: var(--ink);
}

/* --- Feed health as a row in the listing. */
.feedurl {
    display: block;
    /* Capped, because a table cell sizes to its content: without a ceiling a long
       reason line (a provider's full error text) widens the column and pushes every
       column after it off the screen. */
    max-width: 46ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-family: var(--font-data);
    font-size: 10.5px;
    font-weight: 400;
    line-height: 1.4;
    color: var(--ink-3);
}

/* A feed with no pillar leans entirely on keyword matching, and a feed that has
   been 404ing for a week looks identical to a healthy one unless it says so. */
.feedurl--warn {
    color: var(--marigold);
}

.feedurl--fail {
    color: var(--critical);
}

.listing tr[aria-selected="true"].row--fail,
.listing tbody tr.row--fail {
    background: var(--critical-tint);
}

.listing tbody tr.row--invited {
    opacity: .6;
}

/* Something upstream moved. Amber rather than red: the row is still correct, it
   is just no longer current — and the action is "review", not "fix". */
.listing tbody tr.row--flag {
    background: var(--marigold-tint);
}

/* =========================================================================
   The choosers — `4b`
   -------------------------------------------------------------------------
   Two pickers, one vocabulary. Both are a modal with a filter band under the
   head, a scrolling body of candidates, and a footer that names the selection
   before acting on it. The shared parts live on `.modal__*`; what differs is
   only how a candidate is drawn — a row for a pulse item, a tile for a picture.
   ========================================================================= */

/* The filter band. Tinted, so it reads as controls rather than as the first
   result — a search field the same colour as the list below it looks like a row. */
.modal__filters {
    display: flex;
    flex-direction: column;
    gap: 9px;
    padding: 13px 17px;
    border-bottom: 1px solid var(--line);
    background: var(--surface-2);
}

/* A search field inside a modal takes the width it is given: the modal is already
   a fixed measure, and 340px inside it leaves a gap that reads as a mistake. */
.modal__filters .searchbox {
    width: 100%;
    background: var(--surface);
    border-color: var(--line-strong);
    font-size: 12.5px;
    color: var(--ink);
}

/* "3 of 18 live items · expired items are hidden" — the count and the rule that
   produced it, together, because a count you can't reproduce is a count you have
   to trust. */
.modal__count {
    font-family: var(--font-data);
    font-size: 10.5px;
    line-height: 1;
    color: var(--ink-3);
}

.modal__foot .modal__count {
    font-size: 11.5px;
}

/* A select that is currently narrowing something. The mockup gives the pre-set
   pillar filter a cobalt border, which is the difference between "this is the
   default" and "this is filtering your results". */
.select--on {
    border-color: var(--signal);
    font-weight: 600;
    color: var(--signal);
}

/* Rows and tiles carry their own padding, so the body must not add any. */
.modal__body--flush {
    padding: 0;
}

/* --- A pulse item as a candidate. */
.pickrow {
    display: grid;
    grid-template-columns: 20px minmax(0, 1fr) auto;
    align-items: flex-start;
    gap: 11px;
    padding: 13px 17px;
    border-bottom: 1px solid var(--line);
    cursor: pointer;
}

.pickrow[aria-selected="true"] {
    background: var(--signal-tint);
}

.pickrow .selectbox {
    margin-top: 2px;
}

.pickrow__body {
    display: flex;
    flex-direction: column;
    gap: 3px;
    min-width: 0;
}

.pickrow__title {
    font-family: var(--font-ui);
    font-size: 13px;
    font-weight: 600;
    line-height: 1.35;
    color: var(--ink);
}

/* Why this row matched. Tinted rather than bolded: the word is already in a bold
   title, so weight has nothing left to say. */
.pickrow__title mark {
    padding: 0 1px;
    background: color-mix(in srgb, var(--signal) 22%, transparent);
    color: var(--ink);
}

/* What the generator will actually be told — which is why it is here at all, and
   why it is a sentence rather than a filename. */
.pickrow__excerpt {
    font-family: var(--font-ui);
    font-size: 11.5px;
    line-height: 1.45;
    color: var(--ink-2);
}

.pickrow__meta {
    font-family: var(--font-data);
    font-size: 10.5px;
    line-height: 1;
    color: var(--ink-3);
}

/* Shown only once the row is ticked: an unselected row saying "Selected" in grey
   is the kind of label people misread under time pressure. */
.pickrow__state {
    margin-top: 2px;
    font-family: var(--font-ui);
    font-size: 10.5px;
    font-weight: 600;
    line-height: 1;
    color: var(--signal);
    white-space: nowrap;
    visibility: hidden;
}

.pickrow[aria-selected="true"] .pickrow__state {
    visibility: visible;
}

/* The note under the last row, naming the filter that is narrowing the list. Not
   an empty state — there are results; there are just fewer than there could be. */
.picktail {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 15px 17px;
    border-bottom: 1px solid var(--line);
    background: var(--surface-2);
    font-family: var(--font-ui);
    font-size: 11.5px;
    line-height: 1.5;
    color: var(--ink-3);
}

/* Genuinely nothing to choose from. Distinct from the tail: no filter will fix
   this one, so it offers the action that will. */
.pickempty {
    display: flex;
    flex-direction: column;
    gap: 7px;
    padding: 28px 17px;
    text-align: center;
    align-items: center;
}

.pickempty__title {
    font-family: var(--font-display);
    font-size: 14px;
    font-weight: 600;
    line-height: 1.3;
    color: var(--ink);
}

.pickempty__body {
    max-width: var(--measure-lede);
    font-family: var(--font-ui);
    font-size: 12px;
    line-height: 1.55;
    color: var(--ink-2);
}

/* --- An image as a candidate. */

/* The search field carries the meaning/filename toggle inside it, because the
   toggle changes what the same words mean rather than what is being searched. */
.pickfind {
    display: flex;
    align-items: center;
    overflow: hidden;
    border: 1px solid var(--signal);
    border-radius: var(--r-md);
    background: var(--surface);
}

.pickfind__input {
    flex: 1;
    min-width: 0;
    height: 32px;
    padding: 0 11px;
    border: 0;
    background: none;
    font-family: var(--font-ui);
    font-size: 12.5px;
    color: var(--ink);
}

.pickfind__input:focus {
    outline: none;
}

.pickfind__modes {
    display: flex;
    align-items: center;
    gap: 2px;
    margin-right: 3px;
    padding: 3px;
    border-radius: 7px;
    background: var(--surface-2);
}

.pickfind__mode {
    padding: 5px 8px;
    border: 0;
    border-radius: 5px;
    background: none;
    font-family: var(--font-ui);
    font-size: 10.5px;
    font-weight: 600;
    line-height: 1;
    color: var(--ink-3);
    cursor: pointer;
}

.pickfind__mode[aria-pressed="true"] {
    background: var(--signal);
    color: var(--pri-on);
}

.pickfind__mode:disabled {
    cursor: not-allowed;
    opacity: .5;
}

.picktiles {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 11px;
}

/* A tile is a label around a radio: one image is attached, so the control that
   models it is a radio, and the whole tile is its hit area. */
.picktile {
    display: block;
    overflow: hidden;
    border: 1px solid var(--line);
    border-radius: 9px;
    cursor: pointer;
}

.picktile input {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
}

/* Selected: a heavier border plus a halo, because at this size a border-colour
   change alone is easy to miss across a grid. */
.picktile--on {
    border: 2px solid var(--signal);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--signal) 15%, transparent);
}

.picktile__thumb {
    position: relative;
    display: block;
    height: 82px;
    background: var(--surface-2);
}

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

/* The empty slot's ＋, which is now the control it always looked like. Styled
   from `.slot__thumb` so it is the same box; only the pointer and the hover
   say it does something. */
.slot__thumb--add {
    text-decoration: none;
    cursor: pointer;
}

/* Only ever rendered in the empty and required states, which is where the
   thumb has a light background and a real border to move. The ink is left
   alone: in the required state it is `--critical`, and hovering a blocked slot
   should not be what stops it looking blocked. */
.slot--empty .slot__thumb--add:hover {
    border-color: var(--signal);
    color: var(--signal);
}

.slot--required .slot__thumb--add:hover {
    border-color: var(--critical);
    background: var(--critical-tint);
}

/* A clip's tile: taller than a still's, because a 16:9 frame at 82px is a
   letterbox with nothing in it, and the play mark and the duration both have to
   sit on top of the picture. */
.picktile__thumb--video {
    height: 104px;
}

.picktile__play {
    position: absolute;
    top: 50%;
    left: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: rgba(0, 0, 0, .45);
    font-size: 11px;
    line-height: 1;
    color: #fff;
    transform: translate(-50%, -50%);
}

/* Ratio and duration — the two things that decide whether a clip can be used at
   all, which a thumbnail cannot show. */
.picktile__len {
    position: absolute;
    right: 5px;
    bottom: 5px;
    padding: 3px 5px;
    border-radius: 3px;
    background: rgba(0, 0, 0, .55);
    font-family: var(--font-data);
    font-size: 8.5px;
    font-weight: 600;
    line-height: 1;
    color: #fff;
}

/* The similarity score. The best match wears the accent and the rest a neutral
   scrim, so the ranking is visible without reading four decimals. */
.picktile__score {
    position: absolute;
    top: 5px;
    right: 5px;
    padding: 3px 4px;
    border-radius: 3px;
    background: rgba(0, 0, 0, .45);
    font-family: var(--font-data);
    font-size: 8.5px;
    font-weight: 600;
    line-height: 1;
    color: #fff;
}

.picktile__score--top {
    background: var(--signal);
}

.picktile__name {
    display: block;
    padding: 6px 7px;
    overflow: hidden;
    font-family: var(--font-data);
    font-size: 9.5px;
    font-weight: 400;
    line-height: 1.3;
    color: var(--ink-2);
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The way out of the chooser: the output already has a prompt written for it, so
   "nothing here is right" has an answer that isn't "give up". */
.pickgen {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-top: 13px;
    padding-top: 13px;
    border-top: 1px solid var(--line);
}

.pickgen__note {
    flex: 1;
    font-family: var(--font-ui);
    font-size: 11.5px;
    line-height: 1.5;
    color: var(--ink-3);
}

/* =========================================================================
   Global search — the ⌘K panel
   -------------------------------------------------------------------------
   The one part of Relay with no mockup frame behind it: every frame draws the
   topbar field closed. So it is assembled from the vocabulary the rest of the
   app already uses — the panel surface, the `panel-h` group label, the listing
   row's title/sub pairing — rather than inventing a palette look.
   ========================================================================= */

.srch {
    position: absolute;
    top: calc(100% + 6px);
    left: 50%;
    z-index: var(--z-popover);
    width: min(560px, calc(100vw - 2 * var(--s5)));
    max-height: min(64vh, 620px);
    overflow-y: auto;
    transform: translateX(-50%);
    border: 1px solid var(--line-strong);
    border-radius: var(--r-lg);
    background: var(--surface);
    box-shadow: var(--shadow-2);
}

.srch__group + .srch__group {
    border-top: 1px solid var(--line);
}

.srch__head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--s3);
    padding: 10px 14px 6px;
}

/* Three columns: what it is, where it sits, what state it is in. The state is
   right-aligned so a column of them can be read down without reading the titles. */
.srch__row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: baseline;
    gap: 4px 10px;
    padding: 8px 14px;
    font-family: var(--font-ui);
    text-decoration: none;
}

.srch__row:hover,
.srch__row:focus-visible,
.srch__row[aria-selected="true"] {
    background: var(--signal-tint);
    outline: none;
}

.srch__title {
    font-size: 13px;
    font-weight: 600;
    line-height: 1.35;
    color: var(--ink);
}

.srch__title mark {
    padding: 0 1px;
    background: color-mix(in srgb, var(--signal) 22%, transparent);
    color: var(--ink);
}

.srch__ctx {
    grid-column: 1;
    font-family: var(--font-data);
    font-size: 10.5px;
    line-height: 1;
    color: var(--ink-3);
}

/* A snippet of the thing's own text, rather than a label. Set in the UI face and
   given room to breathe: it is a sentence being quoted, not a slug. */
.srch__ctx--quote {
    font-family: var(--font-ui);
    font-size: 11.5px;
    line-height: 1.4;
    color: var(--ink-2);
}

.srch__ctx--quote mark {
    padding: 0 1px;
    background: color-mix(in srgb, var(--signal) 22%, transparent);
    color: var(--ink);
}

.srch__note {
    grid-row: 1;
    grid-column: 2;
    font-family: var(--font-data);
    font-size: 10.5px;
    line-height: 1;
    color: var(--ink-3);
    white-space: nowrap;
}

.srch__note--ok {
    color: var(--posted);
}

.srch__note--warn {
    color: var(--marigold);
}

.srch__note--fail {
    color: var(--critical);
}

.srch__foot {
    padding: 9px 14px;
    border-top: 1px solid var(--line);
    background: var(--surface-2);
    font-family: var(--font-data);
    font-size: 10px;
    line-height: 1;
    color: var(--ink-3);
    text-align: center;
}

.srch__empty {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 20px 18px;
}

.srch__emptytitle {
    font-family: var(--font-display);
    font-size: 14px;
    font-weight: 600;
    line-height: 1.3;
    color: var(--ink);
}

.srch__emptybody {
    font-family: var(--font-ui);
    font-size: 12px;
    line-height: 1.55;
    color: var(--ink-2);
}

/* =========================================================================
   Sign in and access — `6a`, `6b`, `6c`, `6e`
   -------------------------------------------------------------------------
   A split screen: a pinned dark panel saying what the tool is, and the form on
   --paper beside it. The panel's palette is theme-invariant and lives in
   relay-tokens.css as `--signin-*` — see the note there.

   These are the five screens Django's own auth views render, so the classes are
   shared across all of them: sign in, sign in rejected, ask for a reset, choose a
   new password, and the states in `6e` where the answer is no.
   ========================================================================= */

.signin {
    display: flex;
    min-height: 100vh;
    background: var(--surface);
}

/* --- The panel. */
.signin__panel {
    display: flex;
    flex: none;
    flex-direction: column;
    gap: 22px;
    width: 472px;
    padding: 38px 40px;
    background: var(--signin-panel);
    color: var(--signin-panel-ink);
}

.signin__brand {
    display: flex;
    align-items: center;
    gap: 10px;
    font-family: var(--font-display);
    font-size: 19px;
    font-weight: 600;
    line-height: 1;
    letter-spacing: -0.02em;
    color: var(--signin-panel-title);
}

/* Pushed to the bottom of the panel, so the mark sits alone at the top and the
   pitch reads as the last thing before the form. */
.signin__pitch {
    display: flex;
    flex-direction: column;
    gap: 14px;
    margin-top: auto;
}

.signin__claim {
    margin: 0;
    max-width: 26ch;
    font-family: var(--font-display);
    font-size: 21px;
    font-weight: 400;
    line-height: 1.4;
    letter-spacing: -0.015em;
    color: var(--signin-panel-title);
    text-wrap: pretty;
}

.signin__points {
    display: flex;
    flex-direction: column;
    gap: 7px;
    font-family: var(--font-ui);
    font-size: 12.5px;
    line-height: 1.5;
    color: var(--signin-panel-quiet);
}

.signin__point {
    display: flex;
    align-items: center;
    gap: 9px;
}

/* The three dots are the lifecycle colours the mark uses, in the same order — the
   panel is the legend for the logo above it. */
.signin__dot {
    flex: none;
    width: 6px;
    height: 6px;
    border-radius: 50%;
}

.signin__dot--a { background: var(--signin-dot-a); }
.signin__dot--b { background: var(--signin-dot-b); }
.signin__dot--c { background: var(--signin-dot-c); }

.signin__panelfoot {
    padding-top: 16px;
    border-top: 1px solid var(--signin-panel-line);
    font-family: var(--font-data);
    font-size: 11px;
    line-height: 1.5;
    color: var(--signin-panel-meta);
}

/* --- The form side. */
.signin__side {
    display: flex;
    flex: 1;
    align-items: center;
    justify-content: center;
    min-width: 0;
    padding: 38px 44px;
    background: var(--paper);
}

/* `6c` puts the second step beside the first, on the quieter surface. */
.signin__side--second {
    background: var(--surface-2);
    border-left: 1px solid var(--line);
}

.signin__form {
    display: flex;
    flex-direction: column;
    gap: 20px;
    width: 100%;
    max-width: 352px;
}

.signin__head {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.signin__step {
    font-family: var(--font-data);
    font-size: 11px;
    line-height: 1;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-3);
}

.signin__title {
    margin: 0;
    font-family: var(--font-display);
    font-size: 25px;
    font-weight: 600;
    line-height: 1.2;
    letter-spacing: -0.025em;
    color: var(--ink);
}

/* A step in a two-step flow is a smaller heading than the front door. */
.signin__title--step {
    font-size: 23px;
}

.signin__sub {
    margin: 0;
    font-family: var(--font-ui);
    font-size: var(--t-help);
    line-height: 1.5;
    color: var(--ink-2);
}

.signin__fields {
    display: flex;
    flex-direction: column;
    gap: 14px;
}

.signin__field {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

/* Label on the left, the escape hatch on the right — "Forgot?" belongs to the
   password, so it sits on the password's own line rather than under the form. */
.signin__labelrow {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--s3);
}

.signin__label {
    font-family: var(--font-ui);
    font-size: 12.5px;
    font-weight: 600;
    line-height: 1;
    color: var(--ink);
}

.signin__forgot {
    font-family: var(--font-ui);
    font-size: 11.5px;
    font-weight: 600;
    line-height: 1;
    color: var(--signal);
    text-decoration: none;
}

/* A taller, rounder input than the app's: this screen has three controls on it and
   nothing to line up with, so it can afford the room. */
.signin__input {
    width: 100%;
    padding: 12px 13px;
    border: 1px solid var(--line-strong);
    border-radius: 9px;
    background: var(--surface);
    font-family: var(--font-ui);
    font-size: 14px;
    line-height: 1;
    color: var(--ink);
}

/* The email is an identifier, so it is set in the data face — the same reason a
   slug is. */
.signin__input--id {
    font-family: var(--font-data);
}

.signin__input:focus {
    outline: none;
    border-color: var(--signal);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--signal) 22%, transparent);
}

/* The field that was rejected. On `6b` it is the password, because that is the one
   worth re-typing — the email is usually right. */
.signin__input--bad {
    border-color: var(--critical);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--critical) 12%, transparent);
}

/* The reveal sits inside the field, which is why the field is a wrapper. */
.signin__reveal {
    position: relative;
    display: flex;
    align-items: center;
}

.signin__reveal .signin__input {
    padding-right: 62px;
}

.signin__show {
    position: absolute;
    right: 11px;
    padding: 4px 2px;
    border: 0;
    background: none;
    font-family: var(--font-ui);
    font-size: 11px;
    font-weight: 600;
    line-height: 1;
    color: var(--ink-3);
    cursor: pointer;
}

.signin__show:hover {
    color: var(--ink);
}

.signin__keep {
    display: flex;
    align-items: center;
    gap: 9px;
    font-family: var(--font-ui);
    font-size: 12.5px;
    line-height: 1;
    color: var(--ink-2);
    cursor: pointer;
}

.signin__submit {
    display: block;
    width: 100%;
    padding: 13px;
    border: 0;
    border-radius: 9px;
    background: var(--signal);
    font-family: var(--font-ui);
    font-size: 13.5px;
    font-weight: 600;
    line-height: 1;
    color: var(--pri-on);
    text-align: center;
    cursor: pointer;
}

.signin__submit:hover {
    background: var(--signal-strong);
}

/* Who to ask. The panel's own line says access is by invitation; this says which
   person to go to, which is the useful half in a team of five. */
.signin__foot {
    padding-top: 16px;
    border-top: 1px solid var(--line);
    font-family: var(--font-ui);
    font-size: 11.5px;
    line-height: 1.5;
    color: var(--ink-3);
    text-align: center;
}

/* Without the rule above it, for the reset flow where it is a link rather than a
   note. */
.signin__back {
    font-family: var(--font-ui);
    font-size: 12.5px;
    font-weight: 600;
    line-height: 1;
    color: var(--signal);
    text-align: center;
    text-decoration: none;
}

/* --- `6b`'s rejection, and `6c`'s expiry note. */
.signin__alert {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 12px 13px;
    border: 1px solid color-mix(in srgb, var(--critical) 35%, transparent);
    border-radius: 9px;
    background: var(--critical-tint);
    font-family: var(--font-ui);
    font-size: 12.5px;
    line-height: 1.5;
    color: var(--ink);
}

.signin__alert__ic {
    display: flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: 19px;
    height: 19px;
    margin-top: 1px;
    border-radius: 50%;
    background: var(--critical);
    font-family: var(--font-ui);
    font-size: 11px;
    font-weight: 700;
    line-height: 1;
    color: var(--pri-on);
}

.signin__note {
    padding: 11px 12px;
    border: 1px solid color-mix(in srgb, var(--marigold) 30%, transparent);
    border-radius: 9px;
    background: var(--marigold-tint);
    font-family: var(--font-ui);
    font-size: var(--t-help);
    line-height: 1.5;
    color: var(--ink-2);
}

/* --- `6c`'s password strength. Four segments and the rules underneath, so "not
   strong enough" is never the whole message. */
.pwmeter {
    display: flex;
    flex-direction: column;
    gap: 7px;
}

.pwmeter__bars {
    display: flex;
    gap: 4px;
}

.pwmeter__bar {
    flex: 1;
    height: 4px;
    border-radius: var(--r-pill);
    background: var(--line);
}

.pwmeter__bar--on {
    background: var(--posted);
}

.pwmeter__rules {
    display: flex;
    flex-direction: column;
    gap: 4px;
    font-family: var(--font-ui);
    font-size: 11.5px;
    line-height: 1.5;
}

.pwmeter__rule {
    display: flex;
    align-items: center;
    gap: 7px;
    color: var(--ink-3);
}

.pwmeter__rule--met {
    color: var(--posted);
}

.pwmeter__mark {
    font-weight: 700;
}

/* --- `6e`. The six ways this tool says no, as cards.
   Bordered in the colour of the answer: amber for "not you", red for "not at all",
   green for "that worked", grey for "nothing to do here". */
.gate {
    display: grid;
    place-items: center;
    min-height: 100vh;
    padding: var(--s5);
    background: var(--paper);
}

.gatecard {
    display: flex;
    flex-direction: column;
    overflow: hidden;
    width: min(420px, 100%);
    border: 1px solid var(--line-strong);
    border-radius: var(--r-lg);
    background: var(--surface);
    box-shadow: var(--shadow-1);
}

.gatecard--warn { border-color: var(--marigold); }
.gatecard--fail { border-color: var(--critical); }
.gatecard--ok { border-color: var(--posted); }

.gatecard__body {
    display: flex;
    flex: 1;
    flex-direction: column;
    gap: 9px;
    padding: 16px 17px;
}

.gatecard__ic {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    border-radius: var(--r-md);
    background: var(--surface-2);
    font-family: var(--font-ui);
    font-size: 13px;
    font-weight: 700;
    line-height: 1;
    color: var(--ink-3);
}

.gatecard--warn .gatecard__ic {
    background: var(--marigold-tint);
    color: var(--marigold);
}

.gatecard--fail .gatecard__ic {
    background: var(--critical-tint);
    color: var(--critical);
}

.gatecard--ok .gatecard__ic {
    background: var(--posted-tint);
    color: var(--posted);
}

.gatecard__title {
    font-family: var(--font-display);
    font-size: 15px;
    font-weight: 600;
    line-height: 1.3;
    letter-spacing: -0.01em;
    color: var(--ink);
}

.gatecard__body p {
    margin: 0;
    font-family: var(--font-ui);
    font-size: 12.5px;
    line-height: 1.55;
    color: var(--ink-2);
}

/* The fact under the message: what the rule actually is, so the answer is
   checkable rather than just asserted. Pushed to the floor of the card. */
.gatecard__fact {
    margin-top: auto;
    font-family: var(--font-data);
    font-size: 11px;
    line-height: 1.5;
    color: var(--ink-3);
}

.gatecard__foot {
    display: flex;
    gap: var(--s2);
    padding: 12px 17px;
    border-top: 1px solid var(--line);
    background: var(--surface-2);
}

/* The card's own actions: the way onward fills the row, the way back doesn't. */
.gatecard__go {
    flex: 1;
    padding: 9px;
    border: 1px solid transparent;
    border-radius: var(--r-md);
    background: var(--signal);
    font-family: var(--font-ui);
    font-size: 12px;
    font-weight: 600;
    line-height: 1;
    color: var(--pri-on);
    text-align: center;
    text-decoration: none;
    cursor: pointer;
}

.gatecard__go:hover {
    background: var(--signal-strong);
    color: var(--pri-on);
}

.gatecard__alt {
    padding: 9px 12px;
    border: 1px solid var(--line-strong);
    border-radius: var(--r-md);
    background: var(--surface);
    font-family: var(--font-ui);
    font-size: 12px;
    font-weight: 600;
    line-height: 1;
    color: var(--ink-2);
    text-align: center;
    text-decoration: none;
    cursor: pointer;
}

.gatecard__alt:hover {
    border-color: var(--signal);
    color: var(--signal);
}

/* Narrow: the panel is the first thing to go. It is the pitch, not the tool, and
   at 820px the form needs the whole width. */
@media (max-width: 820px) {
    .signin {
        flex-direction: column;
    }

    .signin__panel {
        width: 100%;
        min-height: 0;
        padding: 24px 22px;
    }

    .signin__pitch {
        margin-top: 0;
    }

    .signin__side--second {
        border-left: 0;
        border-top: 1px solid var(--line);
    }
}

/* =========================================================================
   The narrow shell — components that only exist below the breakpoint
   -------------------------------------------------------------------------
   Defined here, with the rest of the components, but shipped switched off:
   each one's `display` is turned on by relay-mobile.css. That split is what
   lets relay-mobile.css contain nothing but `@media (max-width: 820px)`, and
   therefore lets one test prove the wide layout is untouched — see
   MobileShellIsolationTests. Nothing below is reachable on a wide screen:
   `display: none` takes these out of the tab order too, so the tab bar's
   controls cannot be focused into.
   ========================================================================= */

/* --- Bottom tab bar (mockup 8a) -----------------------------------------
   Five targets: the four desktop sections plus More, which carries Settings,
   Approvals and the approval count. Icons are inline SVG — the one place
   Relay uses a glyph set, because five tab affordances have no honest Unicode
   equivalent and a text-only tab bar is not the same control. */
.tabbar {
    display: none;
    position: fixed;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: var(--z-sticky);
    grid-template-columns: repeat(5, 1fr);
    align-items: end;
    padding: var(--s2) var(--s1) calc(12px + env(safe-area-inset-bottom));
    border-top: 1px solid var(--line);
    background: var(--surface);
}

.tabbar__tab {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--s1);
    min-height: 44px;
    padding: var(--s1) 0;
    border: 0;
    background: none;
    font-family: var(--font-ui);
    font-size: 10px;
    font-weight: 600;
    line-height: 1;
    color: var(--ink-3);
    text-decoration: none;
    cursor: pointer;
}

.tabbar__tab[aria-current="page"] {
    color: var(--signal);
}

.tabbar__tab svg {
    display: block;
}

/* The centre target. Outlined by default and filled only on Today — which is
   what every frame in the mockup draws, and the rule that falls out of it:
   Today is where you arrive with nothing started, so there creating is the
   next move; everywhere else you are already inside a piece of work. */
.tabbar__fab {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 46px;
    height: 36px;
    border: 1px solid var(--line);
    border-radius: 11px;
    background: var(--surface-2);
    color: var(--ink-3);
}

.tabbar__tab--go .tabbar__fab {
    border-color: transparent;
    background: var(--signal);
    color: var(--pri-on);
    box-shadow: var(--shadow-1);
}

.tabbar__tab--go .tabbar__label {
    color: var(--signal);
}

/* Sits on the More tab, so a queue building up is visible from any screen —
   the job the topbar's `.badge-count` does on a wide one. */
.tabbar__badge {
    position: absolute;
    top: -2px;
    right: 22px;
    min-width: 16px;
    height: 16px;
    padding: 0 var(--s1);
    border-radius: var(--r-pill);
    background: var(--marigold);
    font-family: var(--font-ui);
    font-size: 9.5px;
    font-weight: 700;
    line-height: 16px;
    text-align: center;
    color: var(--pri-on);
}

/* --- The narrow topbar's three extra slots ------------------------------ */

/* Replaces the logomark on a screen that is somewhere you drilled into. */
.topbar__back {
    display: none;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    margin-left: -10px;
    color: var(--ink);
}

.topbar__screen {
    display: none;
    min-width: 0;
    font-family: var(--font-display);
    font-size: 17px;
    font-weight: 600;
    line-height: 1;
    letter-spacing: -0.02em;
    color: var(--ink);
}

/* The one action a narrow topbar carries — History, on both editors. */
.topbar__action {
    display: none;
    flex: none;
    align-items: center;
    min-height: 38px;
    padding: 0 13px;
    border: 1px solid var(--line-strong);
    border-radius: var(--r-md);
    font-family: var(--font-ui);
    font-size: 12.5px;
    font-weight: 600;
    line-height: 1;
    color: var(--ink-2);
    text-decoration: none;
    cursor: pointer;
}

/* The search field collapses to this; pressing it focuses the real field,
   which is the same thing ⌘K already does. */
.topbar__searchbtn {
    display: none;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    background: var(--surface-2);
    color: var(--ink-2);
    cursor: pointer;
}

/* --- Bottom sheet -------------------------------------------------------
   The narrow form of a drawer, a modal and a side rail alike (mockups 8e, 9d,
   9e, 9f, 10g, 10i). Used directly by the More menu and the drafts dock;
   `.modal__card` and `.drawer` are restyled into this shape by
   relay-mobile.css rather than duplicating the markup. */
.sheet {
    position: fixed;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: var(--z-drawer);
    display: flex;
    flex-direction: column;
    max-height: 92vh;
    padding-bottom: env(safe-area-inset-bottom);
    border-radius: var(--r-lg) var(--r-lg) 0 0;
    background: var(--surface);
    box-shadow: 0 -10px 30px rgba(22, 24, 43, .18);
}

/* The grab handle. Drawn rather than marked up: it says "this pulls" and has
   no other meaning, so it is not something a screen reader should meet. */
.sheet::before {
    content: "";
    flex: none;
    width: 34px;
    height: 4px;
    margin: 10px auto var(--s1);
    border-radius: var(--r-pill);
    background: var(--line-strong);
}

.sheet__head {
    display: flex;
    align-items: baseline;
    gap: 9px;
    padding: var(--s3) var(--s4);
    border-bottom: 1px solid var(--line);
}

.sheet__title {
    margin: 0;
    font-family: var(--font-display);
    font-size: 18px;
    font-weight: 600;
    line-height: 1;
    letter-spacing: -0.02em;
    color: var(--ink);
}

.sheet__body {
    flex: 1;
    overflow-y: auto;
    overscroll-behavior: contain;
}

.sheet__foot {
    display: flex;
    flex: none;
    gap: 9px;
    padding: var(--s3) var(--s4) 18px;
    border-top: 1px solid var(--line);
}

.sheet__group {
    padding: var(--s3) var(--s4) 6px;
    font-family: var(--font-ui);
    font-size: 10.5px;
    font-weight: 700;
    line-height: 1;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--ink-3);
}

/* One row of a sheet's list. A button and a link have to look identical here,
   so the button's own borders and background are cleared rather than the two
   being styled apart. */
.sheet__row {
    display: flex;
    align-items: center;
    gap: var(--s3);
    width: 100%;
    min-height: 52px;
    padding: var(--s3) var(--s4);
    border: 0;
    border-bottom: 1px solid var(--line);
    background: none;
    font-family: var(--font-ui);
    font-size: 14px;
    font-weight: 600;
    line-height: 1.2;
    text-align: left;
    color: var(--ink);
    text-decoration: none;
    cursor: pointer;
}

.sheet__row:last-child {
    border-bottom: 0;
}

.sheet__row[aria-current="page"] {
    color: var(--signal);
}

.sheet__rowtext {
    min-width: 0;
    flex: 1;
}

.sheet__rowsub {
    display: block;
    margin-top: 3px;
    font-family: var(--font-data);
    font-size: 11px;
    font-weight: 400;
    line-height: 1.3;
    color: var(--ink-3);
}

.sheet__chev {
    flex: none;
    color: var(--ink-3);
}

/* --- Content graph as a lineage column (mockup 11d) ---------------------
   Pan-and-zoom does not work one-handed, so the canvas becomes a column: the
   focused node's ancestry above it, its dependants below, every row a link
   that re-focuses. Server-rendered from the same graph payload the SVG uses —
   see content/graph.py's `lineage()`. */
.lineage {
    display: none;
    flex-direction: column;
}

.lineage__node {
    display: flex;
    align-items: center;
    gap: 11px;
    min-height: 44px;
    padding: var(--s3) 13px;
    border: 1px solid var(--line);
    border-radius: var(--r-ml);
    background: var(--surface);
    text-decoration: none;
}

.lineage__node--focus {
    padding: 14px;
    border: 1.5px solid var(--signal);
    border-radius: 11px;
    background: var(--signal-tint);
    box-shadow: var(--shadow-1);
}

/* Downstream of something that hasn't been approved yet — the same amber the
   SVG's `.gph__node--rippled` carries, for the same reason. */
.lineage__node--rippled {
    border-color: var(--marigold);
    background: var(--marigold-tint);
}

.lineage__node--blocked {
    border-color: color-mix(in srgb, var(--critical) 30%, transparent);
    background: var(--critical-tint);
}

.lineage__node--idle {
    opacity: .65;
}

.lineage__dot {
    flex: none;
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--line-strong);
}

.lineage__dot--pillar,
.lineage__dot--scheduled {
    background: var(--signal);
}

.lineage__dot--rippled {
    background: var(--marigold);
}

.lineage__dot--posted {
    background: var(--posted);
}

.lineage__dot--blocked {
    background: var(--critical);
}

.lineage__text {
    min-width: 0;
    flex: 1;
}

.lineage__label {
    display: block;
    overflow: hidden;
    font-family: var(--font-ui);
    font-size: 12.5px;
    font-weight: 600;
    line-height: 1.3;
    white-space: nowrap;
    text-overflow: ellipsis;
    color: var(--ink);
}

.lineage__meta {
    display: block;
    margin-top: 2px;
    font-family: var(--font-data);
    font-size: 10.5px;
    line-height: 1.3;
    color: var(--ink-3);
}

.lineage__meta--rippled {
    color: var(--marigold);
}

.lineage__meta--posted {
    color: var(--posted);
}

.lineage__meta--blocked {
    color: var(--critical);
}

/* The thread between two nodes. A drawn line rather than a border so it can
   sit in the gap between two rounded boxes. */
.lineage__thread {
    width: 1px;
    height: 14px;
    margin-left: 22px;
    background: var(--line-strong);
}

.lineage__kicker {
    padding: 14px 0 10px 31px;
    font-family: var(--font-ui);
    font-size: 10.5px;
    font-weight: 700;
    line-height: 1;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--ink-3);
}

.lineage__kids {
    display: flex;
    flex-direction: column;
    gap: 7px;
    padding-left: 22px;
    border-left: 1px solid var(--line-strong);
}

/* --- The standalone preview's stage (mockup 10n) ------------------------
   The page you open in a new tab to answer one question: what will this look
   like where it is going. 10n stands the platform card on a dark ground so
   nothing of Relay's competes with it, and adds the checks that decide whether
   the copy fits — the ones a preview cannot show by looking.

   Not narrow-only: the question is the same on a laptop, and the frame is the
   answer's setting rather than a layout. */
.stage {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--s4);
    min-height: 100%;
    padding: 22px var(--s4) var(--s6);
    background: var(--stage);
}

.stage__card {
    width: 100%;
    max-width: 452px;
}

.stage__checks {
    display: flex;
    flex-direction: column;
    gap: var(--s2);
    width: 100%;
    max-width: 452px;
    padding: 13px 14px;
    border: 1px solid color-mix(in srgb, var(--stage-ink) 12%, transparent);
    border-radius: var(--r-lg);
    background: color-mix(in srgb, var(--stage-ink) 7%, transparent);
}

.stage__label {
    font-family: var(--font-ui);
    font-size: 10.5px;
    font-weight: 700;
    line-height: 1;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: color-mix(in srgb, var(--stage-ink) 55%, transparent);
}

.stage__check {
    display: flex;
    gap: 9px;
    font-family: var(--font-data);
    font-size: 12px;
    line-height: 1.4;
    color: color-mix(in srgb, var(--stage-ink) 85%, transparent);
}

/* A check that fails is the whole reason to have read the panel. */
.stage__check--bad {
    color: var(--critical);
}

.stage__note {
    max-width: var(--measure-lede);
    font-family: var(--font-data);
    font-size: 11px;
    line-height: 1.5;
    text-align: center;
    color: color-mix(in srgb, var(--stage-ink) 45%, transparent);
}

/* The bar over the stage, in the stage's own colours rather than the app's. */
.stage__bar {
    display: flex;
    align-items: center;
    gap: var(--s3);
    padding: 0 var(--s4);
    height: 54px;
    border-bottom: 1px solid color-mix(in srgb, var(--stage-ink) 10%, transparent);
    /* Mixed into the stage, not into transparency: the bar sits above `.stage`
       rather than inside it, so a translucent white had the page's own light
       ground behind it and came out pale with white text on top. */
    background: color-mix(in srgb, var(--stage-ink) 6%, var(--stage));
}

.stage__title {
    display: block;
    font-family: var(--font-ui);
    font-size: 13px;
    font-weight: 600;
    line-height: 1.2;
    color: var(--stage-ink);
}

.stage__sub {
    display: block;
    margin-top: 2px;
    font-family: var(--font-data);
    font-size: 10.5px;
    line-height: 1.3;
    color: color-mix(in srgb, var(--stage-ink) 55%, transparent);
}

.stage__copy {
    flex: none;
    min-height: 36px;
    padding: 0 13px;
    border: 0;
    border-radius: 9px;
    background: color-mix(in srgb, var(--stage-ink) 12%, transparent);
    font-family: var(--font-ui);
    font-size: 12.5px;
    font-weight: 600;
    line-height: 1;
    color: var(--stage-ink);
    cursor: pointer;
}

.stage__copy:hover {
    background: color-mix(in srgb, var(--stage-ink) 20%, transparent);
}

/* --- The brief a rewrite was working to (mockup 10m) --------------------
   Shown with the result because it is what explains a rewrite you disagree
   with — and it is configuration, so the card says where to change it. */
.briefcard {
    display: flex;
    flex-direction: column;
    gap: 7px;
    padding: 13px 14px;
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    background: var(--surface-2);
}

.briefcard__label {
    font-family: var(--font-ui);
    font-size: 10.5px;
    font-weight: 700;
    line-height: 1;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--ink-3);
}

.briefcard__text {
    font-family: var(--font-data);
    font-size: 12px;
    line-height: 1.5;
    color: var(--ink-2);
}

.briefcard__edit {
    font-family: var(--font-ui);
    font-size: 12px;
    font-weight: 600;
    line-height: 1;
    color: var(--signal);
    text-decoration: none;
}

/* --- Base Pieces rows (mockup 10p) --------------------------------------
   The narrow rendering of the Base Pieces listing. Off above the breakpoint,
   where the table says the same thing in columns — see
   relay/partials/piece_rows.html for why this is markup and not a restyle. */
.prows {
    display: none;
    flex-direction: column;
    gap: var(--s2);
}

.prow {
    display: flex;
    align-items: center;
    gap: var(--s3);
    padding: var(--s3) 14px;
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    background: var(--surface);
    box-shadow: var(--shadow-1);
}

/* Nothing generated from it yet. Not a status to read past — it is the work
   that has not started, which is why 10p tints the whole row. */
.prow--idle {
    border-color: color-mix(in srgb, var(--marigold) 30%, transparent);
    background: var(--marigold-tint);
}

.prow--flag {
    border-left: 3px solid var(--marigold);
}

.prow__body {
    display: flex;
    flex: 1;
    flex-direction: column;
    gap: 5px;
    min-width: 0;
    text-decoration: none;
}

.prow__title {
    font-family: var(--font-ui);
    font-size: 14px;
    font-weight: 600;
    line-height: 1.3;
    color: var(--ink);
}

.prow__meta {
    font-family: var(--font-data);
    font-size: 10.5px;
    line-height: 1.4;
    color: var(--ink-3);
}

.prow__meta--idle {
    color: var(--marigold);
}

.prow__meta--flag {
    color: var(--marigold);
}

.prow__cov {
    display: flex;
    align-items: center;
    gap: 4px;
    margin-top: 2px;
}

/* One tile per channel: filled where an output exists, dashed where it does
   not. Which two are missing is the thing worth seeing. */
.prow__tile {
    display: flex;
    align-items: center;
    justify-content: center;
    min-width: 22px;
    height: 22px;
    padding: 0 5px;
    border-radius: var(--r-sm);
    font-family: var(--font-data);
    font-size: 9.5px;
    font-weight: 700;
    line-height: 1;
}

.prow__tile--done {
    background: var(--posted-tint);
    color: var(--posted);
}

.prow__tile--fail {
    background: var(--critical-tint);
    color: var(--critical);
}

.prow__tile--gap {
    border: 1px dashed var(--line-strong);
    background: var(--surface);
    color: var(--ink-3);
}

.prow__count {
    margin-left: var(--s1);
    font-family: var(--font-data);
    font-size: 10px;
    color: var(--ink-3);
}

.prow__count--done {
    color: var(--posted);
}

.prow__count--warn {
    color: var(--marigold);
}

.prow__chev {
    flex: none;
    font-size: 17px;
    line-height: 1;
    color: var(--ink-3);
}

/* The action on an unexpanded row. Marigold rather than cobalt: it belongs to
   the tinted row it sits in, and expanding is the thing that row is asking
   for — not the screen's primary action. */
.prow__go {
    flex: none;
    min-height: 36px;
    padding: 0 13px;
    border: 0;
    border-radius: 9px;
    background: var(--marigold);
    font-family: var(--font-ui);
    font-size: 12px;
    font-weight: 600;
    line-height: 1;
    color: var(--pri-on);
    cursor: pointer;
}

/* --- Channel spec chips (mockup 11f) ------------------------------------
   What a channel *is*, on its row: shape, limit, media rule. Off above the
   breakpoint, where the detail pane sits beside the list and already says all
   of it — there the row is a nav item, not a summary. */
.listrow__specs {
    display: none;
    flex-basis: 100%;
    flex-wrap: wrap;
    gap: 5px;
    margin-top: 6px;
}

.spec {
    padding: 4px 8px;
    border: 1px solid var(--line);
    border-radius: var(--r-pill);
    background: var(--surface-2);
    font-family: var(--font-ui);
    font-size: 10.5px;
    font-weight: 600;
    line-height: 1;
    color: var(--ink-2);
}

/* The media rule is the one that stops a post going out, so it reads as the
   constraint it is rather than as another fact. */
.spec--critical {
    border-color: color-mix(in srgb, var(--critical) 30%, transparent);
    background: var(--critical-tint);
    color: var(--critical);
}

.spec--muted {
    border-style: dashed;
    background: none;
    color: var(--ink-3);
}

/* --- Pinned actions (mockups 10a, 10d, 11i) -----------------------------
   "Primary action pinned to a sticky footer, above the tab bar, never in a
   scroll" — one of the mobile brief's four touch rules. Off above the
   breakpoint, where the same controls live in the context bar and are already
   in view; relay-mobile.css makes it sticky. */
.actionbar {
    display: none;
}

/* --- Pane switcher (mockups 10a–10c, 10d) -------------------------------
   "Three-pane editors become one pane plus a segmented switcher." Off above
   the breakpoint, where all the panes are on screen together and switching
   would only hide two thirds of the editor. */
.paneswitch {
    display: none;
    gap: 4px;
    padding: 9px var(--s3);
    border-bottom: 1px solid var(--line);
    background: var(--surface);
}

.paneswitch__tab {
    display: flex;
    flex: 1;
    align-items: center;
    justify-content: center;
    gap: 6px;
    min-height: 38px;
    padding: 0 var(--s2);
    border: 0;
    border-radius: var(--r-md);
    background: none;
    font-family: var(--font-ui);
    font-size: 13px;
    font-weight: 600;
    line-height: 1;
    color: var(--ink-3);
    cursor: pointer;
}

.paneswitch__tab[aria-selected="true"] {
    background: var(--signal-tint);
    color: var(--signal);
}

.paneswitch__count {
    padding: 1px 6px;
    border: 1px solid var(--line);
    border-radius: var(--r-pill);
    background: var(--surface-2);
    font-family: var(--font-data);
    font-size: 10px;
    font-weight: 600;
    line-height: 1.5;
}

.paneswitch__tab[aria-selected="true"] .paneswitch__count {
    border-color: transparent;
    background: var(--surface);
    color: var(--signal);
}

/* --- Swipe to approve (mockups 8d, 8e) ----------------------------------
   The card slides to uncover these; relay-mobile.css positions them behind it.
   Off by default like everything else here — and this one has to be, because
   an unstyled `.swipe__action` is not invisible, it is the words "Approve" and
   "Send back" printed above the card. */
.swipe__action {
    display: none;
}

/* --- Agenda grouping (mockup 9a) ----------------------------------------
   The calendar's list view is a table on a wide screen and a day-grouped
   agenda on a narrow one. These header rows carry the day; they are in the
   markup at every width and revealed only below the breakpoint, so the wide
   table stays exactly the table it was. */
.agenda__day,
.agenda__gap {
    display: none;
}

/* --- The getting-started tutorial ---------------------------------------
   Four pieces, one job each: the chip that reaches the hub, the one-line strip
   that says what is next, the callout that points at a control, and the hub page
   itself.

   An earlier build put all of it in a bar pinned above the top bar. That bar
   displaced the whole application to say one sentence, needed its height
   measured in JavaScript so the page could reserve room, collided with the tab
   bar, and had to carry a welcome paragraph it had no room for. None of that
   exists here: nothing is fixed, and nothing needs measuring. */

/* The chip. Deliberately quiet — an outlined control the size of the theme
   toggle's neighbours, not a fifth navpill. Help is not a section of the app,
   and the nav has to keep reading as four. */
.coachbtn {
    display: inline-flex;
    flex: none;
    align-items: center;
    gap: 6px;
    padding: 6px 11px;
    border: 1px solid var(--line-strong);
    border-radius: var(--r-sm);
    background: var(--surface);
    font-size: var(--t-meta);
    font-weight: 600;
    color: var(--ink);
}

.coachbtn:hover,
.coachbtn[aria-current="page"] {
    border-color: var(--signal);
    color: var(--signal);
}

/* The one spot of marigold in the top bar, and the reason the control reads as
   an offer rather than a link. */
.coachbtn__dot {
    width: 6px;
    height: 6px;
    flex: none;
    border-radius: 50%;
    background: var(--marigold);
}

/* Present only once there is progress, which is what turns the control from an
   offer into a place. */
.coachbtn__count {
    font-family: var(--font-data);
    font-size: var(--t-eyebrow);
    color: var(--ink-3);
}

/* --- The strip ----------------------------------------------------------
   One line under the context bar, in the normal flow. A band rather than a
   card, so it reads as part of the chrome the context bar belongs to rather
   than as the page's first piece of content. */
.tutstrip {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s2) var(--s4);
    padding: 10px clamp(var(--s4), 3vw, var(--s6));
    border-bottom: 1px solid var(--line);
    background: var(--signal-tint);
}

.tutstrip__step {
    flex: none;
    font-family: var(--font-data);
    font-size: var(--t-eyebrow);
    letter-spacing: .06em;
    text-transform: uppercase;
    white-space: nowrap;
    color: var(--signal);
}

.tutstrip__text {
    display: flex;
    flex: 1 1 20rem;
    min-width: 0;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 2px var(--s2);
}

.tutstrip__title {
    font-size: var(--t-help);
    font-weight: 650;
    color: var(--ink);
}

.tutstrip__hint {
    font-size: var(--t-help);
    color: var(--ink-2);
}

.tutstrip__actions {
    display: flex;
    flex: none;
    align-items: center;
    gap: var(--s3);
}

.tutstrip__hub {
    font-size: var(--t-help);
    font-weight: 600;
    color: var(--signal);
}

/* --- The callout --------------------------------------------------------
   One bubble, beside the one control the current step is about. In flow, not
   overlaid: it is a sibling of the control, so the page makes room for it and
   nothing has to measure anything. That is also why there is no JS here — the
   whole mechanism is a template tag and this block. */
.cue {
    display: inline-flex;
    position: relative;
    max-width: 30rem;
    flex-direction: column;
    gap: 3px;
    margin: var(--s2) 0;
    padding: 9px 12px;
    border: 1px solid var(--signal);
    border-radius: var(--r-md);
    background: var(--signal-tint);
    animation: cueIn .18s var(--ease) both;
}

/* A small square rotated into a diamond, pointing at whatever comes next in
   the flow. Borders on two sides only, so the bubble's own edge continues
   through it. */
.cue__beak {
    position: absolute;
    bottom: -5px;
    left: 18px;
    width: 8px;
    height: 8px;
    border-right: 1px solid var(--signal);
    border-bottom: 1px solid var(--signal);
    background: var(--signal-tint);
    transform: rotate(45deg);
}

/* A callout paired with the control it points at.

   Two placements, and the rule between them is where the control sits:

   - Above a *field*, in the normal flow (the default). Fields stack down a page
     with room above them, so the bubble takes its own space and the page makes
     room — no measuring, nothing overlapping.
   - Below a *control in a row*, floated (`.cued`). Rows do not have that room.
     The top bar is a fixed 56px pinned to `top: 0`, so an in-flow bubble there
     ran off the top of the window and had its outline cut; a listing's cell is
     the width of its column, so one in there came out a word per line. Floating
     takes it out of the flow entirely: the bar stays 56px and the row stays a
     row.

   Still not an overlay engine. The bubble is a child of the wrapper that holds
   its control, so it is positioned by `position: absolute` against that one
   element — nothing is measured, no scroll or resize is listened to, and it
   cannot drift from the thing it points at because it is inside it. */
.cued {
    position: relative;
    display: inline-flex;
    align-items: center;
}

.cued .cue {
    position: absolute;
    top: calc(100% + 9px);
    left: 0;
    z-index: var(--z-popover);
    width: max-content;
    /* Against the viewport minus both margins, so the widest a bubble can be
       still leaves the clamp somewhere to put it. `vw` counts the scrollbar, so
       this is deliberately short of 100. */
    max-width: min(24rem, calc(92vw - 40px));
    margin: 0;
    background: var(--surface);
    box-shadow: var(--shadow-2);
}

/* Pointing up at the control above it, and opaque like the bubble, because a
   floated one sits over the page rather than beside it. */
.cued .cue__beak {
    top: -5px;
    right: auto;
    bottom: auto;
    left: 18px;
    border: 0;
    border-top: 1px solid var(--signal);
    border-left: 1px solid var(--signal);
    background: var(--surface);
}

.cue__kicker {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: var(--t-help);
    font-weight: 700;
    color: var(--signal);
}

.cue__step {
    font-family: var(--font-data);
    font-size: var(--t-eyebrow);
    font-weight: 600;
    letter-spacing: .04em;
    color: var(--ink-3);
}

.cue__body {
    font-size: var(--t-meta);
    line-height: 1.45;
    color: var(--ink-2);
}

/* The running count against the target. Marigold while short and green once
   there is enough, so it reads as a thing filling up rather than a warning. */
.cue__count {
    margin-top: 2px;
    font-family: var(--font-data);
    font-size: var(--t-eyebrow);
    color: var(--marigold);
}

.cue__count[data-cue-enough] {
    color: var(--posted);
}

@keyframes cueIn {
    from {
        opacity: 0;
        transform: translateY(-4px);
    }

    to {
        opacity: 1;
        transform: none;
    }
}

/* --- The hub page -------------------------------------------------------- */
.gs {
    display: flex;
    max-width: 68rem;
    flex-direction: column;
    gap: var(--s5);
}

.gs__loop {
    display: flex;
    flex-direction: column;
    gap: var(--s3);
    padding: var(--s4);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    background: var(--surface);
}

/* The content model, as four stages. Wraps to a column below 720px, where the
   arrows rotate rather than being swapped for different glyphs. */
.loop {
    display: flex;
    flex-wrap: wrap;
    align-items: stretch;
    gap: var(--s2);
    margin: 0;
    padding: 0;
    list-style: none;
}

.loop__stage {
    display: flex;
    flex: 1 1 12rem;
    flex-direction: column;
    gap: 3px;
    padding: 11px 13px;
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    background: var(--surface-2);
}

/* Filled in when the step that produces it is done, which is what lets the
   diagram double as progress rather than sit beside it as decoration. */
.loop__stage--done {
    border-color: color-mix(in srgb, var(--posted) 45%, transparent);
    background: var(--posted-tint);
}

.loop__eyebrow {
    font-family: var(--font-data);
    font-size: var(--t-eyebrow);
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--ink-3);
}

.loop__name {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: var(--t-help);
    font-weight: 650;
    color: var(--ink);
}

.loop__tick {
    display: inline-flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: 15px;
    height: 15px;
    border-radius: 50%;
    background: var(--posted);
    font-size: 9px;
    font-weight: 700;
    color: var(--paper);
}

.loop__line {
    font-size: var(--t-meta);
    line-height: 1.45;
    color: var(--ink-2);
}

.loop__count {
    margin-top: 2px;
    font-family: var(--font-data);
    font-size: var(--t-eyebrow);
    color: var(--posted);
}

.loop__arrow {
    display: flex;
    flex: none;
    align-items: center;
    font-size: 15px;
    color: var(--ink-3);
}

/* Narrow-only, and `display: none` here is what makes that true — the same
   shape every other narrow-only component in this file follows. */
.gs__note--narrow {
    display: none;
}

.gs__note {
    margin: 0;
    max-width: var(--measure-body);
    padding: 10px 12px;
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    background: var(--surface-2);
    font-size: var(--t-meta);
    line-height: 1.45;
    color: var(--ink-2);
}

.gs__progress {
    display: flex;
    align-items: center;
    gap: var(--s3);
}

.gs__count {
    flex: none;
    font-family: var(--font-data);
    font-size: var(--t-meta);
    white-space: nowrap;
    color: var(--ink-2);
}

.gs__bar {
    flex: 1;
    height: 6px;
    border-radius: var(--r-pill);
    background: var(--surface-2);
    overflow: hidden;
}

.gs__run {
    display: block;
    height: 6px;
    border-radius: var(--r-pill);
    background: var(--signal);
    transition: width .25s var(--ease);
}

.gs__run--done {
    background: var(--posted);
}

.gs__done {
    display: flex;
    flex-direction: column;
    gap: 7px;
    padding: var(--s4);
    border: 1px solid color-mix(in srgb, var(--posted) 45%, transparent);
    border-radius: var(--r-lg);
    background: var(--posted-tint);
}

.gs__donetitle {
    font-size: var(--t-lead);
    font-weight: 650;
    color: var(--ink);
}

.gs__doneline {
    margin: 0;
    max-width: var(--measure-body);
    font-size: var(--t-help);
    line-height: 1.5;
    color: var(--ink-2);
}

.gs__made {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 3px var(--s2);
    font-size: var(--t-meta);
    color: var(--ink-2);
}

.gs__madelabel {
    font-family: var(--font-data);
    font-size: var(--t-eyebrow);
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--ink-3);
}

/* The fine print under the finished panel's buttons: what closing does to the
   work, which is nothing. Worth saying out loud — "close" and "start over" both
   sound like they might throw something away. */
.gs__note {
    margin: 6px 0 0;
    max-width: var(--measure-body);
    font-size: var(--t-meta);
    line-height: 1.45;
    color: var(--ink-3);
}

.gs__doneactions {
    display: flex;
    align-items: center;
    gap: var(--s2);
    margin-top: 2px;
}

/* --- The eight rows ----------------------------------------------------- */
.steps {
    display: flex;
    flex-direction: column;
    margin: 0;
    padding: 0;
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    background: var(--surface);
    list-style: none;
    overflow: hidden;
}

.steprow {
    display: flex;
    align-items: flex-start;
    gap: var(--s3);
    padding: 14px var(--s4);
    border-top: 1px solid var(--line);
}

.steprow:first-child {
    border-top: 0;
}

/* The step you are on is the only one with weight, so the page answers "what
   now" before it answers "what is the list". */
.steprow--now {
    background: var(--signal-tint);
}

.steprow__mark {
    display: inline-flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    margin-top: 1px;
    border: 1px solid var(--line-strong);
    border-radius: 50%;
    font-family: var(--font-data);
    font-size: 11px;
    font-weight: 700;
    color: var(--ink-3);
}

.steprow--done .steprow__mark {
    border-color: transparent;
    background: var(--posted-tint);
    color: var(--posted);
}

.steprow--now .steprow__mark {
    border-color: transparent;
    background: var(--signal);
    color: var(--pri-on);
}

.steprow__text {
    display: flex;
    flex: 1;
    min-width: 0;
    flex-direction: column;
    gap: 2px;
}

.steprow__title {
    font-size: var(--t-help);
    font-weight: 650;
    color: var(--ink);
}

.steprow--done .steprow__title {
    color: var(--ink-2);
}

.steprow__line {
    max-width: var(--measure-body);
    font-size: var(--t-meta);
    line-height: 1.5;
    color: var(--ink-2);
}

.steprow--done .steprow__line,
.steprow--skipped .steprow__line {
    color: var(--ink-3);
}

.steprow__parts {
    display: flex;
    flex-wrap: wrap;
    gap: 4px var(--s3);
    margin-top: 4px;
}

.steprow__end {
    display: flex;
    flex: none;
    align-items: center;
    gap: var(--s2);
}

.steprow__note {
    font-family: var(--font-data);
    font-size: var(--t-eyebrow);
    color: var(--ink-3);
}

.steprow__view {
    font-size: var(--t-meta);
    font-weight: 600;
    color: var(--ink-3);
}

.steprow__view:hover {
    color: var(--signal);
}

.steprow__skip {
    padding: 0 var(--s1);
    border: 0;
    background: none;
    font-family: inherit;
    font-size: var(--t-meta);
    font-weight: 600;
    color: var(--ink-3);
    cursor: pointer;
}

.steprow__skip:hover {
    color: var(--ink);
}

/* The parts of a multi-part step, on the hub's current row. Shown together
   rather than one at a time, so the step reads as partly done instead of
   stalled — which is the same job the decimal in the strip's counter does. */
.coachpart {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: var(--t-meta);
    font-weight: 600;
    color: var(--ink-3);
}

.coachpart--ok {
    color: var(--posted);
}

.coachpart__mark {
    display: inline-flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: 14px;
    height: 14px;
    border: 1px solid var(--line-strong);
    border-radius: 50%;
    font-size: 9px;
    font-weight: 700;
}

.coachpart--ok .coachpart__mark {
    border-color: transparent;
    background: var(--posted-tint);
    color: var(--posted);
}

/* --- The resume card ----------------------------------------------------
   On Today, for someone who left the tutorial part-way. */
.resume__body {
    display: flex;
    flex-direction: column;
    gap: 7px;
    padding: var(--s4);
}

.resume__title {
    font-size: var(--t-lead);
    font-weight: 650;
    color: var(--ink);
}

.resume__line {
    margin: 0;
    max-width: var(--measure-body);
    font-size: var(--t-help);
    line-height: 1.5;
    color: var(--ink-2);
}

/* Pips rather than a bar: eight is few enough to count, and someone who can
   count them knows how much is left without reading a percentage. */
.resume__pips {
    display: flex;
    gap: 4px;
    margin: 2px 0 var(--s2);
}

.resume__pip {
    flex: 1;
    height: 6px;
    border-radius: var(--r-pill);
    background: var(--line);
}

.resume__pip--done {
    background: var(--posted);
}

.resume__pip--skipped {
    background: var(--line-strong);
}

.resume__actions {
    display: flex;
    align-items: center;
    gap: var(--s2);
}

/* A dialog is open, so every callout stands down.

   The scrim has just made the control the bubble points at unclickable, and a
   pointer at something you cannot press is worse than no pointer: the modal is
   the thing to deal with first. Safe to apply to all of them, because no cue
   anchor lives inside a dialog — the schedule sheet's cue is on the Place…
   button that opens it, and the choosers' are on the ＋ Attach buttons. Same
   selector the script uses to decide a dialog is open (relay.js), so the two
   cannot disagree about what "open" means. */
body:has([data-dialog-transient]:not(:empty)) .cue,
body:has([data-dialog]:not([hidden])) .cue {
    display: none;
}


/* =========================================================================
   Platform previews
   -------------------------------------------------------------------------
   The one place in Relay that hard-codes colour, and deliberately so: these
   are pictures OF LinkedIn, of a Reels player, of an inbox. They must not flip
   with our theme, because the platform they imitate will not flip with it
   either — a dark LinkedIn preview would be a preview of something that isn't
   going to happen. Every literal below is a platform's own value; nothing
   inside this block is a Relay surface. The frame *around* each preview is
   ours, so that keeps using tokens.
   ========================================================================= */

.prev__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--s3);
}

/* --- Feed (LinkedIn, Facebook) --------------------------------------- */

.prev-feed {
    display: flex;
    justify-content: center;
    padding: 16px;
    border: 1px solid var(--line);
    border-radius: var(--r-ml);
    background: #F3F2EF;
}

.prev-feed__card {
    width: 100%;
    max-width: 372px;
    overflow: hidden;
    border: 1px solid #E0DFDC;
    border-radius: var(--r-md);
    background: #fff;
    font-family: var(--font-ui);
}

.prev-feed__head {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 12px 13px 8px;
}

.prev-feed__avatar {
    display: flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    border-radius: 50%;
    background: #16182B;
    font-size: 12px;
    font-weight: 700;
    line-height: 1;
    color: #fff;
}

.prev-feed__who {
    display: flex;
    flex-direction: column;
    gap: 1px;
    min-width: 0;
}

.prev-feed__name {
    font-size: 13px;
    font-weight: 600;
    line-height: 1.3;
    color: #000;
}

.prev-feed__sub {
    font-size: 11px;
    font-weight: 400;
    line-height: 1.3;
    color: #666;
}

.prev-feed__body {
    padding: 2px 13px 10px;
    font-size: 13.5px;
    font-weight: 400;
    line-height: 1.5;
    color: #000;
    overflow-wrap: anywhere;
}

.prev-feed__more {
    color: #0A66C2;
}

.prev-feed__media {
    display: flex;
    align-items: flex-end;
    height: 196px;
    padding: 12px;
    overflow: hidden;
    background: linear-gradient(135deg, #43485E, #16182B);
}

/* An actual picture fills the band; the padding exists only to sit the
   ratio caption inside an empty one. */
.prev-feed__media--filled {
    padding: 0;
}

.prev-feed__media img,
.prev-feed__media video {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.prev-feed__caption {
    font-family: var(--font-data);
    font-size: 10px;
    line-height: 1;
    color: rgba(255, 255, 255, .45);
}

.prev-feed__foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 9px 13px;
    border-top: 1px solid #E9E8E5;
    font-size: 12px;
    font-weight: 600;
    line-height: 1;
    color: #666;
}

/* --- Vertical video (Reels, TikTok) ---------------------------------- */

.prev-vv {
    display: flex;
    justify-content: center;
    padding: 6px 0;
}

.prev-vv__phone {
    position: relative;
    width: 232px;
    height: 412px;
    overflow: hidden;
    border: 6px solid #1a1a1a;
    border-radius: 26px;
    background: #000;
    box-shadow: var(--shadow-2);
}

.prev-vv__fill {
    position: absolute;
    inset: 0;
    background: linear-gradient(165deg, #4a4f6b, #0d0f1c);
}

.prev-vv__fill img,
.prev-vv__fill video {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.prev-vv__status {
    position: absolute;
    top: 0;
    right: 0;
    left: 0;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 9px 11px;
    font-size: 10px;
    font-weight: 600;
    line-height: 1;
    color: #fff;
}

.prev-vv__status span:last-child {
    font-family: var(--font-data);
}

.prev-vv__title {
    position: absolute;
    top: 34px;
    right: 0;
    left: 0;
    text-align: center;
    font-size: 12px;
    font-weight: 700;
    line-height: 1;
    color: #fff;
}

.prev-vv__overlay {
    position: absolute;
    right: 52px;
    bottom: 14px;
    left: 11px;
    display: flex;
    flex-direction: column;
    gap: 7px;
}

.prev-vv__who {
    display: flex;
    align-items: center;
    gap: 7px;
}

.prev-vv__avatar {
    display: flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    background: #fff;
    font-size: 9px;
    font-weight: 700;
    line-height: 1;
    color: #111;
}

.prev-vv__handle {
    font-size: 11px;
    font-weight: 600;
    line-height: 1;
    color: #fff;
}

.prev-vv__follow {
    padding: 3px 5px;
    border: 1px solid rgba(255, 255, 255, .6);
    border-radius: 4px;
    font-size: 9px;
    font-weight: 500;
    line-height: 1;
    color: #fff;
}

.prev-vv__caption {
    margin: 0;
    font-size: 10.5px;
    font-weight: 400;
    line-height: 1.45;
    color: #fff;
}

/* Clamped, because the overlay grows upward from the bottom of the phone and an
   unclamped first paragraph would climb over the whole frame — which is also
   what the platform does to it. */
.prev-vv__caption {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
    overflow: hidden;
}

.prev-vv__caption span {
    opacity: .65;
}

.prev-vv__audio {
    font-family: var(--font-data);
    font-size: 9.5px;
    line-height: 1;
    color: rgba(255, 255, 255, .75);
}

.prev-vv__actions {
    position: absolute;
    right: 9px;
    bottom: 22px;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 13px;
    font-family: var(--font-data);
    font-size: 9px;
    line-height: 1;
    color: #fff;
}

.prev-vv__actions > span {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 3px;
}

.prev-vv__bar {
    position: absolute;
    right: 0;
    bottom: 0;
    left: 0;
    height: 2px;
    background: rgba(255, 255, 255, .25);
}

.prev-vv__bar span {
    display: block;
    width: 38%;
    height: 100%;
    background: #fff;
}

/* --- Newsletter (email) ---------------------------------------------- */

.prev-mail {
    display: flex;
    justify-content: center;
    padding: 14px;
    border: 1px solid var(--line);
    border-radius: var(--r-ml);
    background: #E8E8EA;
}

.prev-mail__card {
    width: 100%;
    overflow: hidden;
    border: 1px solid #DADADD;
    border-radius: 6px;
    background: #fff;
    font-family: var(--font-ui);
}

.prev-mail__envelope {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 11px 14px;
    border-bottom: 1px solid #EDEDEF;
}

.prev-mail__from {
    font-family: var(--font-data);
    font-size: 10px;
    line-height: 1;
    color: #888;
}

.prev-mail__from strong {
    font-weight: 600;
    color: #111;
}

.prev-mail__subject {
    font-size: 14.5px;
    font-weight: 600;
    line-height: 1.35;
    color: #000;
}

/* No subject line written yet. The inbox would show the first body words, so
   saying so is more use than an empty row. */
.prev-mail__subject--missing {
    font-weight: 400;
    color: #888;
}

.prev-mail__banner {
    height: 96px;
    overflow: hidden;
    background: linear-gradient(135deg, #C77E17, #8a5510);
}

.prev-mail__banner img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.prev-mail__body {
    display: flex;
    flex-direction: column;
    gap: 9px;
    padding: 14px;
}

.prev-mail__body p {
    margin: 0;
    font-size: 12px;
    font-weight: 400;
    line-height: 1.6;
    color: #1a1a1a;
    overflow-wrap: anywhere;
}

.prev-mail__body p.prev-mail__sig {
    color: #666;
}

.prev-mail__cta {
    padding-top: 11px;
    border-top: 1px solid #EDEDEF;
    font-size: 11.5px;
    font-weight: 600;
    line-height: 1;
    color: #C77E17;
}

.prev-mail__foot {
    font-family: var(--font-data);
    font-size: 9.5px;
    line-height: 1.4;
    color: #999;
}
