/**
 * Release Manifest (DEV-2755) — Version Control tab.
 *
 * The visual direction is the approved prototype (`release-manifest-v11.html`):
 * a timeline sidebar, dense monospaced version data, dependency cards. The
 * prototype is a single dark GitHub-ish palette; this port expresses it through
 * the FT tokens in `styles.css`, so it follows the app's light AND dark themes.
 * No raw hex here except the solution pip, which is data carried in the
 * snapshot.
 *
 * Everything is namespaced `mf-` — the tab renders wholesale from strings and
 * must not inherit or leak the release/cleanup table styles.
 */

/* Announcement-only text: the copy confirmation is visual on the button and
   spoken here, so the tick is never the only signal. */
.mf-sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}

/* ── shell ─────────────────────────────────────────────────────────────── */
.mf-shell {
    display: grid;
    grid-template-columns: 244px minmax(0, 1fr);
    gap: 24px;
    align-items: start;
}

.mf-sidebar {
    position: sticky;
    top: 16px;
    max-height: calc(100vh - 140px);
    overflow-y: auto;
    background: var(--bg-secondary);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-lg);
    padding: 8px 0 12px;
}

/* A size container, so what is inside can lay itself out by the COLUMN's width
   rather than the window's. The two disagree at exactly one point that matters:
   at 900px the sidebar moves above and this column suddenly gains 268px, so a
   viewport query would collapse a layout precisely where it has just gained the
   room for it. Both consumers render into this element (`index.html` and the
   published shell), so the rules below always have their container. */
.mf-body { min-width: 0; container-type: inline-size; }

@media (max-width: 900px) {
    .mf-shell { grid-template-columns: minmax(0, 1fr); }
    .mf-sidebar { position: static; max-height: none; }
}

/* ── sidebar timeline ──────────────────────────────────────────────────── */
.mf-sol-group + .mf-sol-group { margin-top: 6px; }

.mf-sol-label {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 16px 6px;
    font-size: 0.8rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: var(--text-muted);
}

.mf-sol-pip { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }

.mf-tl-items { list-style: none; margin: 0; padding: 0 8px; }

.mf-tl-item {
    display: flex;
    align-items: flex-start;
    gap: 9px;
    width: 100%;
    min-height: 44px;
    padding: 8px;
    text-align: left;
    background: transparent;
    border: 1px solid transparent;
    border-radius: var(--radius);
    cursor: pointer;
    font-family: inherit;
    transition: background var(--transition), border-color var(--transition);
}

.mf-tl-item:hover { background: var(--surface-sunken); }
.mf-tl-item:focus-visible { outline: none; box-shadow: var(--focus-ring); }
/* 0.985, not 0.995: half a percent on a 228px row is under a pixel and a press
   state nobody can see is not a press state. */
.mf-tl-item:active { transform: scale(0.985); transition: transform var(--press); }

.mf-tl-item.is-active {
    background: var(--accent-tint);
    border-color: var(--accent-tint-strong);
}

.mf-tl-dot-col {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding-top: 5px;
    align-self: stretch;
    flex-shrink: 0;
}

.mf-tl-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.mf-tl-dot.is-sprint { background: var(--success); }
.mf-tl-dot.is-hotfix { background: var(--warning); }
/* Hollow, because a draft is not yet a statement about what shipped. */
.mf-tl-dot.is-draft { background: transparent; border: 1.5px dashed var(--text-muted); }
.mf-tl-line { width: 1px; flex: 1; min-height: 8px; margin-top: 3px; background: var(--border-color); }

.mf-tl-info { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.mf-tl-name { font-size: 0.85rem; font-weight: 500; color: var(--text-primary); line-height: 1.3; }
.mf-tl-item.is-active .mf-tl-name { color: var(--accent-text); }
.mf-tl-sol { font-size: 0.8rem; font-family: var(--font-mono); color: var(--text-muted); }

.mf-tl-badge {
    align-self: flex-start;
    margin-top: 2px;
    padding: 1px 6px;
    border-radius: 4px;
    font-family: var(--font-mono);
    font-size: 0.8rem;
    font-weight: 500;
}

.mf-tl-badge.is-sprint {
    background: hsl(var(--status-success-bg));
    color: hsl(var(--status-success-text));
}

.mf-tl-badge.is-hotfix {
    background: hsl(var(--status-warning-bg));
    color: hsl(var(--status-warning-text));
}

.mf-tl-badge.is-draft {
    background: var(--surface-sunken);
    border: 1px dashed var(--border-strong);
    color: var(--text-muted);
}

/* ── release header ────────────────────────────────────────────────────── */
.mf-rel-head { margin-bottom: 24px; }

.mf-rel-eyebrow {
    font-family: var(--font-mono);
    font-size: 0.8rem;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-muted);
    margin-bottom: 5px;
}

.mf-rel-title {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 12px;
    margin: 0 0 8px;
    font-size: 1.5rem;
    font-weight: 700;
    letter-spacing: -0.02em;
    color: var(--text-primary);
}

.mf-sol-badge {
    padding: 3px 10px;
    border-radius: 4px;
    font-family: var(--font-mono);
    font-size: 0.85rem;
    font-weight: 500;
    background: var(--accent-tint);
    color: var(--accent-text);
    border: 1px solid var(--accent-tint-strong);
}

.mf-rel-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 0; }

.mf-chip {
    padding: 3px 9px;
    border-radius: 4px;
    font-family: var(--font-mono);
    font-size: 0.8rem;
    background: var(--bg-secondary);
    border: 1px solid var(--border-color);
    color: var(--text-secondary);
}

.mf-chip.is-success {
    background: hsl(var(--status-success-bg));
    border-color: hsl(var(--status-success-border));
    color: hsl(var(--status-success-text));
}

.mf-chip.is-warning {
    background: hsl(var(--status-warning-bg));
    border-color: hsl(var(--status-warning-border));
    color: hsl(var(--status-warning-text));
}

.mf-chip.is-danger {
    background: hsl(var(--status-error-bg));
    border-color: hsl(var(--status-error-border));
    color: hsl(var(--status-error-text));
}

.mf-rel-notes {
    margin-top: 12px;
    padding: 12px 16px;
    background: var(--bg-secondary);
    border: 1px solid var(--border-color);
    border-radius: var(--radius);
    font-size: 0.95rem;
    line-height: 1.7;
    color: var(--text-secondary);
}

/* A security fix. Stated as a word, never as colour alone: it decides whether
   a customer upgrades tonight, and a reader who cannot see the colour would
   not be told. */
.mf-item-sec {
    display: inline-block;
    margin-top: 3px;
    padding: 1px 7px;
    border-radius: 4px;
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    background: hsl(var(--status-error-bg));
    color: hsl(var(--status-error-text));
}

.mf-item-row.is-security { border-left: 2px solid hsl(var(--status-error-text)); padding-left: 8px; }

.mf-item-secbox {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-top: 5px;
    font-size: 0.8rem;
    color: var(--text-muted);
    cursor: pointer;
}

/* The release-wide editor. Sits between the draft bar and the document, so it
   is framed as part of the correction rather than as part of the reading.

   Built as a SETTINGS LIST, not as stacked fieldsets. Before, each of the two
   statements carried an uppercase legend, a full-bleed hint and a control
   floating in a wide empty card — three treatments for two questions, and no
   proximity saying which sentence explained which field. Now one statement is
   one row: label and control on one line, the rule that governs it directly
   under, hairlines between the rows. The card's own header says what the whole
   form is for, so no row has to repeat it.

   The surface is the checklist entry's (card fill, hairline, large radius) —
   the two forms are read one after the other and a second surface treatment
   would read as a second screen. */
.mf-rel-edit {
    display: flex;
    flex-direction: column;
    padding: 18px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-lg);
    background: var(--bg-secondary);
}

.mf-rel-ehead { margin-bottom: 16px; }

/* Tightened tracking as the size grows, the way a heading wants it — body copy
   below stays at the default. */
.mf-rel-etitle {
    margin: 0;
    font-size: 1rem;
    font-weight: 600;
    letter-spacing: -0.01em;
    color: var(--text-primary);
}

.mf-rel-esub {
    margin: 4px 0 0;
    max-width: 62ch;
    font-size: 0.85rem;
    line-height: 1.6;
    color: var(--text-muted);
}

/* Inset rows on the card's own fill, one hairline between them. The gap is
   inside the rows, so the divider sits exactly halfway between two statements
   rather than being pushed against one of them. */
.mf-rel-rows {
    border-top: 1px solid var(--border-color);
}

.mf-rel-row {
    padding: 14px 0;
    border-bottom: 1px solid var(--border-color);
}

/* Label and control on one line: the answer is short, and a field on its own
   line under a label doubles the height of the row for nothing. It wraps at a
   narrow width rather than squeezing the field. */
.mf-rel-rowhead {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px 14px;
}

.mf-rel-rowlabel {
    font-size: 0.9rem;
    font-weight: 600;
    color: var(--text-primary);
}

/* The rule the control follows, kept to a readable measure and tied to the
   field by `aria-describedby` — not merely by sitting under it. */
.mf-rel-rowhint {
    margin: 6px 0 0;
    max-width: 62ch;
    font-size: 0.82rem;
    line-height: 1.6;
    color: var(--text-muted);
}

/* Sized to hold its own placeholder, and pushed to the end of the row so the
   two rows' controls line up on one right edge. */
.mf-rel-vinput {
    flex: 0 1 15rem;
    margin-left: auto;
    padding: 7px 10px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius);
    background: var(--bg-primary);
    color: var(--text-primary);
    font-family: var(--font-mono);
    font-size: 0.85rem;
}

/* The same focus treatment the rest of this file uses (`--focus-ring` plus the
   accent border), not a bare outline: two forms read one after the other must
   not answer the keyboard differently. */
.mf-rel-vinput:focus-visible,
.mf-rel-etext:focus-visible,
.mf-rel-ework:focus-visible {
    outline: none;
    border-color: var(--accent-text);
    box-shadow: var(--focus-ring);
}

/* How many issues the release records — the one fact the row's first line
   cannot show by itself. `None` is a word rather than a `0`, because a zero in
   a pill reads as a counter that failed to load. */
.mf-rel-count {
    margin-left: auto;
    padding: 2px 9px;
    border-radius: var(--radius-pill, 999px);
    background: var(--accent-tint);
    color: var(--accent-text);
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.01em;
}

.mf-rel-count.is-none {
    background: var(--surface-sunken);
    color: var(--text-muted);
    font-weight: 500;
}

.mf-rel-erows {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin: 12px 0 0;
    padding: 0;
    list-style: none;
}

/* No issues yet: the list holds nothing, so it takes no room and the empty
   state below stands in its place. */
.mf-rel-erows:empty { display: none; }

/* One issue is one card: the problem and its workaround are two sentences
   about the same thing, and side by side at equal weight they read as two
   unrelated fields. */
.mf-rel-erow {
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: start;
    gap: 10px;
    padding: 10px 12px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius);
    background: var(--bg-primary);
}

/* The position, so a reader of the rendered list can find the row that made a
   given bullet. Aligned to the first field's text, not to the card. */
.mf-rel-enum {
    padding-top: 7px;
    font-family: var(--font-mono);
    font-size: 0.78rem;
    color: var(--text-muted);
}

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

.mf-rel-etext,
.mf-rel-ework {
    width: 100%;
    padding: 7px 10px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius);
    background: var(--bg-secondary);
    color: var(--text-primary);
    font-size: 0.875rem;
}

/* The workaround is the optional, quieter half — said by weight, not by a
   second label nobody reads twice. */
.mf-rel-ework { font-size: 0.85rem; color: var(--text-secondary); }

.mf-rel-erm { width: 28px; height: 28px; }

/* Only a row the operator just added. 4px and 160ms: enough to say "this one
   is new, your cursor is in it", short enough that it is over before the first
   keystroke lands. */
@keyframes mf-rel-rowin {
    from { opacity: 0; transform: translateY(-4px); }
}

.mf-rel-erow.is-new { animation: mf-rel-rowin 160ms ease-out; }

/* Motion here is decoration on a form; reduced motion gets the row without
   the entrance rather than a gentler version of it. */
@media (prefers-reduced-motion: reduce) {
    .mf-rel-erow.is-new { animation: none; }
}

/* No issues yet is an ANSWER, and it is the answer the document will print, so
   the form states it rather than showing a gap between a hint and a button.
   Shown by the list being empty — no JavaScript keeps the two in step. */
.mf-rel-empty {
    display: none;
    margin: 12px 0 0;
    padding: 10px 12px;
    border-radius: var(--radius);
    background: var(--surface-sunken);
    font-size: 0.85rem;
    line-height: 1.6;
    /* Not the hint's muted grey: this is the section's current STATE, and two
       grey paragraphs in a row read as one paragraph nobody finishes. */
    color: var(--text-secondary);
}

.mf-rel-erows:empty + .mf-rel-empty { display: block; }

/* Adding an issue is a quiet, repeatable action inside the row, not a second
   primary button competing with Save. It presses on pointer-down, the way a
   physical control answers before it is released. */
.mf-rel-add {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    /* WCAG 2.2 target size (minimum): 24x24 CSS px, cleared with room. */
    min-height: 32px;
    margin-top: 10px;
    padding: 6px 12px 6px 9px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius);
    background: var(--bg-primary);
    color: var(--text-secondary);
    font-size: 0.85rem;
    font-weight: 500;
    cursor: pointer;
    transition: background var(--transition), border-color var(--transition),
                color var(--transition);
}

.mf-rel-add:hover {
    border-color: var(--border-strong);
    color: var(--text-primary);
    background: var(--surface-sunken);
}

.mf-rel-add:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.mf-rel-add:active { transform: scale(0.98); transition: transform var(--press); }

.mf-rel-addglyph {
    font-size: 1rem;
    line-height: 1;
    color: var(--accent-text);
}

/* Save is fenced off by the last row's hairline: the rule is what the button
   is answering for, and the status sits beside it rather than under the form
   where it would be read as a third statement. */
.mf-rel-efoot {
    display: flex;
    align-items: center;
    gap: 12px;
    padding-top: 14px;
}

.mf-rel-saved {
    margin-right: auto;
    font-size: 0.82rem;
    font-family: var(--font-mono);
    color: var(--text-muted);
}

@media (max-width: 720px) {
    /* The control drops under its label rather than squeezing to a sliver. */
    .mf-rel-vinput { flex-basis: 100%; margin-left: 0; }
    /* The remove button keeps the corner; the fields take the width. */
    .mf-rel-erow { grid-template-columns: auto 1fr auto; }
}


/* Known issues. Stated as prose, not as a table: each is one sentence about
   one thing, and the workaround belongs directly under the sentence it fixes. */
.mf-known {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin: 0;
    padding: 0;
    list-style: none;
}

/* Outlined in the warning hue the way `.mf-ck-item.is-breaking` is outlined in
   the danger one — a whole border, not a thick side tab. The heading above
   already says "Known Issues", so the colour is never the only signal. */
.mf-known-item {
    padding: 12px 16px;
    background: var(--bg-secondary);
    border: 1px solid hsl(var(--status-warning-border));
    border-radius: var(--radius);
}

.mf-known-text {
    display: block;
    font-size: 0.9rem;
    line-height: 1.6;
    color: var(--text-primary);
}

.mf-known-work {
    display: block;
    margin-top: 6px;
    font-size: 0.85rem;
    line-height: 1.6;
    color: var(--text-secondary);
}

/* When the document was issued. Provenance, not content: it sits quietly at
   the end of the header rather than competing with the release name. */
.mf-rel-issued {
    margin-top: 12px;
    font-size: 0.8rem;
    color: var(--text-muted);
}

.mf-gap-note {
    margin: 0 0 20px;
    padding: 9px 14px;
    border-radius: var(--radius);
    background: var(--surface-sunken);
    border: 1px solid var(--border-color);
    font-size: 0.85rem;
    color: var(--text-secondary);
}

/* ── sections ──────────────────────────────────────────────────────────── */
.mf-section { margin-bottom: 34px; }

/* `.card h2` / `.card h3` in styles.css are more specific than a bare class,
   and this tab lives inside a card — the section headings are eyebrows, not
   card titles, so they are qualified to win. */
.mf-body .mf-sec-title {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0 0 14px;
    padding-bottom: 8px;
    border-bottom: 1px solid var(--border-color);
    font-size: 0.8rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: var(--text-muted);
}

.mf-sub, .mf-count {
    font-family: var(--font-mono);
    font-size: 0.8rem;
    font-weight: 400;
    text-transform: none;
    letter-spacing: 0;
    color: var(--text-muted);
}

.mf-empty, .mf-items-empty, .mf-dep-empty {
    margin: 0;
    padding: 8px 0;
    font-size: 0.85rem;
    color: var(--text-muted);
}

.mf-state {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 10px;
    padding: 28px 0;
    font-size: 0.95rem;
    color: var(--text-secondary);
}

.mf-state p { margin: 0; }

/* ── release notes ─────────────────────────────────────────────────────── */
.mf-items-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
@media (max-width: 760px) { .mf-items-cols { grid-template-columns: 1fr; } }

.mf-items-col-title {
    display: flex;
    align-items: center;
    gap: 6px;
    margin: 0 0 8px;
    font-size: 0.8rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.07em;
}

.mf-items-col-title.is-task { color: var(--accent-text); }
/* `--danger` is a FILL colour: as 10px text on the dark canvas it measures
   3.05:1. The status-error TEXT token is the one chosen to be read. */
.mf-items-col-title.is-bug { color: hsl(var(--status-error-text)); }

/* A row is one to four lines tall — a title wraps, an editable row carries the
   security box — so the two columns are capped by HEIGHT, not by a row count:
   counting rows leaves one column ending far above the other, which is the
   thing that made the section unreadable. The 3px padding is for the focus
   ring on the Jira links, which `overflow` would otherwise clip. */
.mf-items-list {
    list-style: none;
    margin: 0;
    padding: 3px;
    display: flex;
    flex-direction: column;
    gap: 6px;
    max-height: 440px;
    overflow-y: auto;
    overscroll-behavior: contain;
}

/* The rows past `MF_ITEMS_VISIBLE` are in the document from the start; only
   their column's class decides whether they take part in the layout, so the
   disclosure never re-renders anything and never loses the reader's place. */
.mf-items-col:not(.is-expanded) .mf-item-row.is-extra { display: none; }

.mf-items-more {
    width: 100%;
    min-height: 32px;
    margin-top: 6px;
    padding: 6px 12px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius);
    background: transparent;
    color: var(--text-secondary);
    font-family: inherit;
    font-size: 0.85rem;
    font-weight: 500;
    cursor: pointer;
    transition: background var(--transition), border-color var(--transition),
                color var(--transition);
}

.mf-items-more:hover { background: var(--surface-sunken); border-color: var(--border-strong); color: var(--text-primary); }
.mf-items-more:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.mf-items-more:active { transform: scale(0.98); transition: transform var(--press); }

.mf-item-row {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    padding: 9px 12px;
    background: var(--bg-secondary);
    border: 1px solid var(--border-color);
    border-radius: var(--radius);
    transition: border-color var(--transition);
}

.mf-item-row:hover { border-color: var(--border-strong); }
.mf-item-row.is-bug { border-left: 2px solid var(--danger); }
.mf-item-row.is-task { border-left: 2px solid var(--accent-primary); }

.mf-item-jira {
    display: inline-flex;
    align-items: center;
    /* WCAG 2.2 target size (minimum): 24x24 CSS px. */
    min-height: 24px;
    flex-shrink: 0;
    padding: 2px 7px;
    border-radius: 4px;
    font-family: var(--font-mono);
    font-size: 0.8rem;
    font-weight: 500;
    white-space: nowrap;
    text-decoration: none;
    background: var(--surface-sunken);
    border: 1px solid var(--border-color);
    color: var(--text-secondary);
}

.mf-item-row.is-bug .mf-item-jira {
    background: hsl(var(--status-error-bg));
    border-color: hsl(var(--status-error-border));
    color: hsl(var(--status-error-text));
}

.mf-item-row.is-task .mf-item-jira {
    background: hsl(var(--status-info-bg));
    border-color: hsl(var(--status-info-border));
    color: hsl(var(--status-info-text));
}

a.mf-item-jira:hover { text-decoration: underline; }
a.mf-item-jira:focus-visible { outline: none; box-shadow: var(--focus-ring); }

.mf-item-body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.mf-item-title { font-size: 0.85rem; line-height: 1.45; color: var(--text-primary); }

.mf-item-customer {
    align-self: flex-start;
    font-family: var(--font-mono);
    font-size: 0.8rem;
    color: var(--text-muted);
}

/* ── affected customers ────────────────────────────────────────────────── */
/* `align-items: start` and not the grid default: a customer with one fix was
   stretched to the height of the one with nine, and the empty half of the card
   read as a section that had failed to load. */
/* Affected Customers: the names, and nothing else. The defects they are
   waiting for are stated once, in Release Notes above — a second copy here was
   the one that had to be kept in step with the first. Chips rather than a
   bulleted column: a release is read for "is my operator in this list", and
   twenty names wrap into a block the eye scans in one pass. */
.mf-cust-names {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.mf-cust-name {
    padding: 5px 12px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-pill, 999px);
    background: var(--bg-secondary);
    font-size: 0.85rem;
    font-weight: 500;
    color: var(--text-primary);
}


/* ── version table ─────────────────────────────────────────────────────── */
.mf-table-scroll { overflow-x: auto; }
.mf-vtable { width: 100%; border-collapse: collapse; }

.mf-vtable thead th {
    padding: 0 12px 8px;
    text-align: left;
    white-space: nowrap;
    border-bottom: 1px solid var(--border-color);
    font-size: 0.8rem;
    font-weight: 500;
    color: var(--text-muted);
}

.mf-vtable thead th:first-child { padding-left: 0; }

/* The table is full width, so a browser hands the slack to whichever column is
   widest — Docker Pull took 422px for 250px of tag and parked Changelog alone
   at the far edge. The slack belongs to the LAST column instead: the six values
   then read as one block, and the empty space is where a reader's eye already
   stops. The first column is floored so a component name never wraps to three
   lines in the process. */
.mf-vtable th:first-child, .mf-vtable td:first-child { min-width: 12rem; }
.mf-vtable th:last-child, .mf-vtable td:last-child { width: 100%; }
.mf-vtable tbody tr { border-bottom: 1px solid var(--border-color); transition: background var(--transition); }
.mf-vtable tbody tr:hover { background: var(--surface-sunken); }
.mf-vtable td { padding: 9px 12px; vertical-align: middle; }
.mf-vtable td:first-child { padding-left: 0; }

.mf-layer-row th {
    padding: 16px 0 5px;
    text-align: left;
    border-bottom: 1px solid var(--border-color);
    font-size: 0.8rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: var(--text-muted);
}

.mf-layer-row:hover { background: transparent; }

.mf-comp-name { display: block; font-size: 0.95rem; font-weight: 500; color: var(--text-primary); }
.mf-comp-sub { display: block; margin-top: 1px; font-family: var(--font-mono); font-size: 0.8rem; color: var(--text-muted); }
.mf-ver-str { font-family: var(--font-mono); font-size: 0.85rem; color: var(--text-primary); }
.mf-bld-str { font-family: var(--font-mono); font-size: 0.8rem; color: var(--text-secondary); }
.mf-pull-none { font-family: var(--font-mono); font-size: 0.8rem; color: var(--text-muted); }

/* Where a version came from, when that is not this release (B2, B3). Quiet:
   it qualifies the number beside it rather than competing with it. */
.mf-ver-src {
    display: inline-block;
    margin-left: 6px;
    padding: 1px 5px;
    border-radius: 4px;
    font-size: 0.72rem;
    letter-spacing: 0.02em;
    background: var(--bg-secondary);
    color: var(--text-muted);
}

.mf-ver-src.is-manual { background: var(--accent-tint); color: var(--accent-text); }

/* The editor's version cell. The field is sized to a version, not to the
   column: a full-width input in a table reads as the cell itself. */
.mf-ver-edit { display: flex; align-items: center; gap: 6px; }

.mf-ver-input {
    width: 7.5rem;
    padding: 4px 8px;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--bg-primary);
    color: var(--text-primary);
    font-family: var(--font-mono);
    font-size: 0.85rem;
}

.mf-ver-input:focus-visible {
    outline: 2px solid var(--accent-text);
    outline-offset: 1px;
    border-color: var(--accent-text);
}

/* Status: colour AND word together — never colour alone. */
.mf-sbadge {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 3px 8px;
    border-radius: 4px;
    font-family: var(--font-mono);
    font-size: 0.8rem;
    font-weight: 500;
    white-space: nowrap;
    border: 1px solid transparent;
}

.mf-sbadge::before { content: ''; width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; background: currentColor; }

.mf-s-updated {
    background: hsl(var(--status-success-bg));
    border-color: hsl(var(--status-success-border));
    color: hsl(var(--status-success-text));
}

.mf-s-new {
    background: hsl(var(--status-info-bg));
    border-color: hsl(var(--status-info-border));
    color: hsl(var(--status-info-text));
}

.mf-s-hotfix {
    background: hsl(var(--status-warning-bg));
    border-color: hsl(var(--status-warning-border));
    color: hsl(var(--status-warning-text));
}

.mf-s-stable {
    background: var(--surface-sunken);
    border-color: var(--border-color);
    color: var(--text-muted);
}

.mf-pull-cmd {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    max-width: 340px;
    min-height: 24px;
    padding: 4px 8px;
    border-radius: 4px;
    background: var(--surface-sunken);
    border: 1px solid var(--border-color);
    color: var(--text-secondary);
    font-family: var(--font-mono);
    font-size: 0.8rem;
    cursor: pointer;
    white-space: nowrap;
    transition: border-color var(--transition), color var(--transition), background var(--transition);
}

.mf-pull-cmd:hover { border-color: var(--accent-primary); color: var(--text-primary); }
.mf-pull-cmd:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.mf-pull-cmd:active { transform: scale(0.97); transition: transform var(--press); }

.mf-pull-tag { overflow: hidden; text-overflow: ellipsis; }
.mf-copy-icon { flex-shrink: 0; opacity: 0.55; transition: opacity var(--transition); }
.mf-pull-cmd:hover .mf-copy-icon { opacity: 1; }

/* Copy confirmation: the tick is paired with the aria-live message, so the
   colour change is never the only signal. */
.mf-pull-cmd.is-copied {
    background: hsl(var(--status-success-bg));
    border-color: hsl(var(--status-success-border));
    color: hsl(var(--status-success-text));
}

.mf-pull-cmd.is-copied .mf-pull-tag::after { content: ' ✓ copied'; }

.mf-cl-link { font-size: 0.8rem; color: var(--accent-text); }
.mf-cl-link:focus-visible { outline: none; box-shadow: var(--focus-ring); border-radius: 4px; }

/* What a repo-owned release produced (DEV-3006): one line under its version
   row, read as part of that row — hence no rule between them and no top
   padding. Links in the accent text, images as the table's own copy control,
   and a state word beside anything the recorder could not confirm. */
.mf-vtable tbody tr:has(+ .mf-vrow-artifacts) { border-bottom: 0; }
.mf-vrow-artifacts td { padding-top: 0; }
.mf-art-label {
    display: inline-block;
    margin-right: 8px;
    font-size: 0.72rem;
    letter-spacing: 0.02em;
    color: var(--text-muted);
}
.mf-art-list {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px 12px;
    margin: 0;
    padding: 0;
    list-style: none;
}
.mf-art { display: inline-flex; align-items: center; gap: 6px; font-size: 0.8rem; }
.mf-art-link { color: var(--accent-text); overflow-wrap: anywhere; }
.mf-art-link:focus-visible { outline: none; box-shadow: var(--focus-ring); border-radius: 4px; }
.mf-art-name { color: var(--text-secondary); }
.mf-art-state { font-size: 0.72rem; color: var(--text-muted); }
.mf-art-state.is-missing { color: hsl(var(--status-warning-text)); }

/* ── compatibility ─────────────────────────────────────────────────────── */
.mf-acs-pivot {
    margin-bottom: 18px;
    padding: 14px 18px;
    background: var(--accent-tint);
    border: 1px solid var(--accent-tint-strong);
    border-radius: var(--radius-lg);
}

.mf-acs-pivot-title { margin: 0 0 10px; font-size: 0.85rem; font-weight: 600; color: var(--accent-text); }

.mf-compat-pills { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 6px; }

.mf-compat-pill {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 4px 10px;
    border-radius: 4px;
    background: var(--bg-secondary);
    border: 1px solid var(--border-color);
    font-size: 0.8rem;
}

.mf-pill-name { font-weight: 500; color: var(--text-primary); }
.mf-pill-ver { font-family: var(--font-mono); font-size: 0.8rem; color: var(--text-muted); }

/* 360px, not 300: below that the two inner columns are ~140px each, which is
   narrower than "FT Device Network 1.0.3" and made every second card ellipsise
   a component name. Same `align-items: start` reason as the customer grid — a
   card with one dependency was stretched to the height of one with three. */
.mf-dep-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
    align-items: start;
    gap: 10px;
}

.mf-dep-card {
    background: var(--bg-secondary);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-lg);
    overflow: hidden;
}

.mf-dep-card.is-core { border-color: var(--accent-tint-strong); }

.mf-dep-card-head {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 14px;
    background: var(--surface-sunken);
    border-bottom: 1px solid var(--border-color);
}

.mf-dep-card-name { flex: 1; margin: 0; font-size: 0.85rem; font-weight: 600; color: var(--text-primary); }
.mf-dep-card-ver { font-family: var(--font-mono); font-size: 0.8rem; color: var(--text-secondary); }

.mf-core-tag {
    padding: 1px 6px;
    border-radius: 4px;
    font-family: var(--font-mono);
    font-size: 0.8rem;
    background: var(--accent-tint);
    border: 1px solid var(--accent-tint-strong);
    color: var(--accent-text);
}

/* R9. On a hot-fix the grid holds only the patched components, so the card
   carries the mark that says why it is the only one here. */
.mf-dep-card.is-patched { border-color: hsl(var(--status-warning-border)); }

.mf-patched-tag {
    padding: 1px 6px;
    border-radius: 4px;
    font-family: var(--font-mono);
    font-size: 0.8rem;
    background: hsl(var(--status-warning-bg));
    border: 1px solid hsl(var(--status-warning-border));
    color: hsl(var(--status-warning-text));
}

.mf-dep-cols { display: grid; grid-template-columns: 1fr 1fr; }

/* A grid item's `min-width` is `auto`, so each column grew to its widest row
   and the pair overflowed the card — which `overflow: hidden` then trimmed off
   the RIGHT, printing `Provision Portal 4.0.` for version 4.0.0. In a document
   whose whole subject is which version shipped, that is the worst possible
   thing to lose. `min-width: 0` puts the shortening back on the name, where the
   `title` attribute still spells it out. */
.mf-dep-col { min-width: 0; }
.mf-dep-col:first-child { border-right: 1px solid var(--border-color); }

.mf-dep-col-title {
    margin: 0;
    padding: 6px 10px;
    border-bottom: 1px solid var(--border-color);
    font-size: 0.8rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-muted);
}

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

.mf-dep-row {
    display: flex;
    align-items: center;
    gap: 7px;
    padding: 6px 10px;
    border-bottom: 1px solid var(--border-color);
    font-size: 0.8rem;
}

.mf-dep-row:last-child { border-bottom: none; }
.mf-dep-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; color: var(--text-primary); }
.mf-dep-ver { flex-shrink: 0; font-family: var(--font-mono); font-size: 0.8rem; color: var(--text-muted); }
/* No version recorded. Dimmer than a version so the eye does not read the dash
   as one, and never hidden: the dependency is the fact this row states. */
.mf-dep-ver.is-unversioned { opacity: 0.55; }

/* An edge that is not a plain requirement says so in WORDS. Colour alone would
   carry the whole meaning, which fails the reader who cannot see it and the one
   printing the page — and these two rows change what a customer installs. */
.mf-dep-alt {
    flex-shrink: 0;
    font-size: 0.75rem;
    font-style: italic;
    color: var(--text-muted);
}

.mf-dep-kind {
    flex-shrink: 0;
    font-size: 0.75rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-muted);
    border: 1px solid var(--border-color);
    border-radius: 4px;
    padding: 0 5px;
}

/* The name recedes with it: an optional component is not what the reader of
   this card is being told to deploy. */
.mf-dep-row.is-optional .mf-dep-name { color: var(--text-muted); font-weight: 400; }
.mf-dep-empty { padding: 10px; text-align: center; }

/* The dot repeats the badge status; the version beside it carries the meaning
   in text, so this stays decorative. */
.mf-dep-dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; background: var(--text-muted); }
.mf-dep-dot.mf-s-updated { background: var(--success); }
.mf-dep-dot.mf-s-new { background: var(--accent-primary); }
.mf-dep-dot.mf-s-hotfix { background: var(--warning); }
.mf-dep-dot.mf-s-stable { background: var(--text-muted); }

/* ── deployment checklist (R10, R11) ───────────────────────────────────── */
.mf-checklist { display: flex; flex-direction: column; gap: 8px; }

.mf-ck-item {
    background: var(--bg-secondary);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-lg);
    overflow: hidden;
}

/* A migration is the one thing on this page that must not be discovered late,
   so the entry is outlined in the danger hue AND says so in the banner — the
   border is never the only signal. */
.mf-ck-item.is-breaking { border-color: var(--danger); }

/* "New version, nothing to do at deployment" is an answer, so the row stays —
   just quieter than the ones that ask for work. */
.mf-ck-item.is-quiet { opacity: 0.7; }

.mf-ck-title { margin: 0; font-size: inherit; font-weight: inherit; }

.mf-ck-head {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    min-height: 44px;
    padding: 10px 16px;
    text-align: left;
    background: transparent;
    border: 0;
    color: inherit;
    font-family: inherit;
    cursor: pointer;
    transition: background var(--transition);
}

.mf-ck-head.is-static { cursor: default; }
button.mf-ck-head:hover { background: var(--surface-sunken); }
button.mf-ck-head:focus-visible { outline: none; box-shadow: var(--focus-ring); }
/* A row this wide answers a press with colour, not with size: scaling a
   1200px bar by the 0.97 a button uses looks like the layout slipped, and the
   0.997 that was here instead moved it by less than two pixels — an animation
   that pretends to exist. The tint is the same one a selected control takes. */
button.mf-ck-head:active { background: var(--accent-tint); transition: background var(--press); }

.mf-ck-name { flex: 1; min-width: 0; font-size: 0.95rem; font-weight: 500; color: var(--text-primary); }
.mf-ck-ver { flex-shrink: 0; font-family: var(--font-mono); font-size: 0.8rem; color: var(--text-secondary); white-space: nowrap; }

/* The build names itself. Two dotted numbers with nothing between them are a
   guess — and the head row, unlike the version table, has no column headings
   to lean on. The word is Sans among Mono on purpose: it is a label, not a
   value to compare character by character (DESIGN.md, the Machine-Text Rule). */
.mf-ck-build {
    margin-left: 8px;
    font-family: var(--font-sans);
    font-size: 0.8rem;
    color: var(--text-muted);
}
.mf-ck-tags { display: flex; flex-wrap: wrap; gap: 5px; }

/* Below the phone breakpoint the head row runs out of room: the component name
   is the only flexible child, so it shrank under its own text and the version
   beside it printed straight through the wrap. The row WRAPS there instead —
   name on its own line, then version, tags and provenance under it. Scoped to
   the narrow width on purpose: this head is shared by the release page, the
   editor and the Upgrade Path, and the desktop layout of all three is right.
   `overflow-wrap` is the second half of it: a component name with no spaces
   would otherwise overflow a 390px line whatever the flex rules say. */
@media (max-width: 640px) {
    .mf-ck-head { flex-wrap: wrap; row-gap: 4px; }
    .mf-ck-name { flex: 1 1 100%; overflow-wrap: anywhere; }
}

.mf-ck-tag {
    padding: 2px 6px;
    border-radius: 4px;
    font-family: var(--font-mono);
    font-size: 0.8rem;
    font-weight: 500;
    background: var(--surface-sunken);
    color: var(--text-secondary);
}

.mf-ck-tag.is-env { background: hsl(var(--status-info-bg)); color: hsl(var(--status-info-text)); }
.mf-ck-tag.is-compose { background: var(--accent-tint); color: var(--accent-text); }
.mf-ck-tag.is-newfile { background: hsl(var(--status-success-bg)); color: hsl(var(--status-success-text)); }
.mf-ck-tag.is-dbmigr { background: hsl(var(--status-error-bg)); color: hsl(var(--status-error-text)); }
.mf-ck-tag.is-none { background: var(--surface-sunken); color: var(--text-muted); }

/* A human's override of the generated text, marked where it is read. */
.mf-ck-origin {
    margin-left: 6px;
    padding: 1px 6px;
    border-radius: 4px;
    font-family: var(--font-mono);
    font-size: 0.8rem;
    background: var(--accent-tint);
    color: var(--accent-text);
}

.mf-ck-chevron { display: flex; flex-shrink: 0; color: var(--text-muted); transition: transform var(--transition); }
.mf-ck-head[aria-expanded="true"] .mf-ck-chevron { transform: rotate(90deg); }

.mf-ck-body { border-top: 1px solid var(--border-color); }
.mf-ck-body[hidden] { display: none; }
.mf-ck-body:not([hidden]) { animation: mf-ck-open 0.16s var(--ease-out); }

@keyframes mf-ck-open {
    from { opacity: 0; transform: translateY(-4px); }
    to   { opacity: 1; transform: none; }
}

.mf-ck-warn {
    display: flex;
    align-items: flex-start;
    gap: 9px;
    margin: 14px 16px 0;
    padding: 10px 14px;
    border-radius: var(--radius);
    background: hsl(var(--status-error-bg));
    border: 1px solid hsl(var(--status-error-border));
    font-size: 0.85rem;
    line-height: 1.6;
    color: hsl(var(--status-error-text));
}

.mf-ck-warn svg { flex-shrink: 0; margin-top: 2px; }

.mf-ck-summary { padding: 14px 16px; }

.mf-ck-sub {
    margin: 0 0 10px;
    font-size: 0.8rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-muted);
}

.mf-ck-lines { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 7px; }
.mf-ck-line { display: flex; align-items: flex-start; gap: 9px; font-size: 0.85rem; }

/* The glyph repeats what the sentence beside it already says. */
.mf-ck-icon { display: flex; flex-shrink: 0; margin-top: 2px; color: var(--text-muted); }
.mf-ck-text { flex: 1; min-width: 0; line-height: 1.55; color: var(--text-secondary); }

/* A value the operator pastes into a terminal, inside a sentence they only
   read. 4px radius, per the Radius-Steps-Down Rule: this is an inline chip in
   dense text, not a control. */
.mf-ck-code {
    padding: 1px 5px;
    border-radius: 4px;
    background: var(--surface-sunken);
    border: 1px solid var(--border-color);
    font-family: var(--font-mono);
    font-size: 0.8rem;
    color: var(--text-primary);
    word-break: break-all;
}

/* Inline in a sentence, but still a target: WCAG 2.2 asks for 24x24 CSS px. */
.mf-ck-flink {
    display: inline-flex;
    align-items: center;
    min-height: 24px;
    font-family: var(--font-mono);
    font-size: 0.8rem;
    color: var(--accent-text);
    white-space: nowrap;
}
a.mf-ck-flink:hover { text-decoration: underline; }
a.mf-ck-flink:focus-visible { outline: none; box-shadow: var(--focus-ring); border-radius: 4px; }

/* ── diff viewer ───────────────────────────────────────────────────────── */
.mf-ck-diffs { padding: 12px 16px 16px; border-top: 1px solid var(--border-color); }
.mf-ck-tabs { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 8px; }

.mf-ck-tab {
    min-height: 24px;
    padding: 3px 10px;
    border-radius: var(--radius);
    border: 1px solid var(--border-color);
    background: transparent;
    color: var(--text-secondary);
    font-family: var(--font-mono);
    font-size: 0.8rem;
    cursor: pointer;
    transition: background var(--transition), border-color var(--transition), color var(--transition);
}

.mf-ck-tab:hover { color: var(--text-primary); border-color: var(--border-strong); }
.mf-ck-tab:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.mf-ck-tab:active { transform: scale(0.97); transition: transform var(--press); }

.mf-ck-tab.is-active {
    background: var(--accent-tint);
    border-color: var(--accent-tint-strong);
    color: var(--accent-text);
}

.mf-ck-diff { border: 1px solid var(--border-color); border-radius: var(--radius); overflow: hidden; }
.mf-ck-diff[hidden] { display: none; }

.mf-ck-diff-bar {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0;
    padding: 7px 12px;
    background: var(--surface-sunken);
    border-bottom: 1px solid var(--border-color);
    font-family: var(--font-mono);
    font-size: 0.8rem;
    color: var(--text-secondary);
}

.mf-ck-fname { font-weight: 500; color: var(--text-primary); }

.mf-ck-dtable { width: 100%; border-collapse: collapse; font-family: var(--font-mono); font-size: 0.85rem; }
.mf-ck-dtable td { padding: 2px 0; vertical-align: top; }

/* No line-number columns: the diff compares parsed guide structures, not file
   text, so there is no position to print and a number here would be fiction. */
.mf-d-sign { width: 22px; min-width: 22px; text-align: center; font-weight: 700; user-select: none; }
.mf-d-code { padding: 0 12px 0 4px; white-space: pre-wrap; word-break: break-all; }

tr.mf-d-add { background: hsl(var(--status-success-bg)); }
tr.mf-d-add .mf-d-sign, tr.mf-d-add .mf-d-code { color: hsl(var(--status-success-text)); }
tr.mf-d-rem { background: hsl(var(--status-error-bg)); }
tr.mf-d-rem .mf-d-sign, tr.mf-d-rem .mf-d-code { color: hsl(var(--status-error-text)); }
tr.mf-d-ctx .mf-d-code { color: var(--text-secondary); }

/* ── checklist editor (D7) ─────────────────────────────────────────────── */
/* Denser than the app's `.form-group` scale on purpose: these controls sit
   inside a checklist entry and have to read as part of it. */

.mf-draft-bar {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    margin-bottom: 18px;
    padding: 12px 16px;
    border-radius: var(--radius-lg);
    background: hsl(var(--status-warning-bg));
    border: 1px solid hsl(var(--status-warning-border));
}

.mf-draft-text {
    flex: 1;
    min-width: 260px;
    margin: 0;
    font-size: 0.85rem;
    line-height: 1.6;
    color: hsl(var(--status-warning-text));
}

/* The catalogue-drift note, directly under the draft bar. Deliberately NOT a
   second warning slab: two filled warning boxes stacked read as one alarm and
   the eye stops separating them. This one is a plain surface with a warning
   rule down its edge — present, subordinate to the bar above it, and it is a
   note rather than a gate: nothing about publishing is wrong because a project
   record is behind. */
.mf-drift {
    margin: -6px 0 18px;
    padding: 12px 16px;
    border: 1px solid var(--border);
    border-left: 3px solid hsl(var(--status-warning-border));
    border-radius: var(--radius-lg);
    background: var(--surface-sunken);
}

.mf-drift-text {
    margin: 0;
    font-size: 0.85rem;
    line-height: 1.6;
    color: var(--text-secondary);
}

.mf-drift-text strong { color: var(--text-primary); }

.mf-drift-list {
    list-style: none;
    margin: 8px 0 0;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    gap: 6px 10px;
    font-size: 0.85rem;
    color: var(--text-primary);
}

.mf-drift-list li {
    padding: 3px 9px;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--bg-secondary);
}

.mf-ck-edit { padding: 14px 16px 0; --mf-ed-control: 32px; }

/* ONE height for every control in this form.
   The row held four: a 41px select, a 29px field, a 28px remove button and 26px
   chips. The 41 is the interesting one — `ui-select` (DESIGN.md) swaps the
   native control for a trigger styled against the app's `.form-group` scale
   (`10px 14px` / 0.95rem), so the enhancement lands a full-size control in a
   form that is deliberately denser than that scale. Same shape as the width
   gotcha this file already records: what has to be corrected is the TRIGGER,
   not only the element it replaced — and both are covered so the row holds its
   line with or without the enhancement.
   32px is on the 4px base, comfortably past the 24px target minimum, and still
   tighter than the app's 41. */
.mf-ck-edit .mf-ck-eicon,
.mf-ck-edit .mf-ck-etext,
.mf-ck-edit .ui-select-trigger,
.mf-ck-edit .mf-ck-erm,
.mf-ck-edit .mf-ck-tagbox,
.mf-ck-edit .btn-small {
    height: var(--mf-ed-control);
}

/* Vertical padding is what the height now decides; flex centring places the
   label. Left at `10px` the trigger's own text would outgrow the box. */
.mf-ck-edit .ui-select-trigger {
    padding-block: 0;
    font-size: 0.85rem;
}

.mf-ck-edit .mf-ck-erm { width: var(--mf-ed-control); }

.mf-ck-editnote {
    margin: 0 0 14px;
    padding: 9px 12px;
    border-radius: var(--radius);
    background: var(--accent-tint);
    border: 1px solid var(--accent-tint-strong);
    font-size: 0.85rem;
    line-height: 1.6;
    color: var(--accent-text);
}

.mf-ck-check {
    display: flex;
    align-items: flex-start;
    gap: 9px;
    /* WCAG 2.2 target size (minimum): 24x24 CSS px. */
    min-height: 24px;
    margin-bottom: 16px;
    font-size: 0.85rem;
    line-height: 1.6;
    color: var(--text-primary);
    cursor: pointer;
}

.mf-ck-check input { width: 16px; height: 16px; margin-top: 3px; flex-shrink: 0; accent-color: var(--danger); }

/* "Nothing to do at deployment" states an absence of work, not a hazard — it
   takes the ordinary accent rather than the breaking checkbox's red. */
.mf-ck-check input[name="nothing"] { accent-color: var(--accent-text); }

.mf-ck-fieldset { margin: 0 0 16px; padding: 0; border: none; }
.mf-ck-fieldset .mf-ck-sub { padding: 0; }

.mf-ck-hint {
    margin: 0 0 10px;
    font-size: 0.85rem;
    line-height: 1.6;
    color: var(--text-muted);
}

.mf-ck-erows { list-style: none; margin: 0 0 8px; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.mf-ck-erow { display: flex; align-items: center; gap: 6px; }

.mf-ck-eicon,
.mf-ck-etext {
    padding: 6px 9px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius);
    background: var(--bg-primary);
    color: var(--text-primary);
    font-size: 0.85rem;
    font-family: inherit;
    transition: border-color var(--transition), box-shadow var(--transition);
}

.mf-ck-eicon { flex-shrink: 0; cursor: pointer; }

/* Fixed width, not `auto`. A select sizes itself to its selected option, so a
   row switched from "Note" to "Variable" grew and pushed its own text field
   right — three rows of one list ended up with three different left edges.
   The width holds the longest label ("Variable") plus the chevron.
   It is set on the WRAPPER, not on the <select>: `ui-select` (DESIGN.md)
   replaces the native control with a styled trigger and leaves the real select
   absolutely positioned at `width:100%` behind it, so a width on the element
   itself measures nothing. Both are covered so the row is a column with or
   without the enhancement. */
.mf-ck-erow .ui-select,
.mf-ck-erow > .mf-ck-eicon { width: 8.5rem; flex-shrink: 0; }
.mf-ck-etext { flex: 1; min-width: 0; }

.mf-ck-eicon:focus-visible,
.mf-ck-etext:focus-visible,
.mf-ck-erm:focus-visible,
.mf-ck-check input:focus-visible,
.mf-ck-tagbox input:focus-visible {
    outline: none;
    border-color: var(--accent-text);
    box-shadow: var(--focus-ring);
}

.mf-ck-erm {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* Square, on the form's one control height (above). Clears the WCAG 2.2
       target-size minimum of 24x24 CSS px with room to spare. */
    flex-shrink: 0;
    border: 1px solid var(--border-color);
    border-radius: var(--radius);
    background: var(--bg-primary);
    color: var(--text-muted);
    cursor: pointer;
    transition: color var(--transition), border-color var(--transition);
}

.mf-ck-erm:hover { color: hsl(var(--status-error-text)); border-color: hsl(var(--status-error-border)); }

.mf-ck-tagboxes { display: flex; flex-wrap: wrap; gap: 6px; }

.mf-ck-tagbox {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    /* Height comes from the form's one control height (above); this clears the
       WCAG 2.2 target-size minimum of 24x24 CSS px. */
    padding: 2px 9px 2px 7px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius);
    background: var(--bg-primary);
    font-family: var(--font-mono);
    font-size: 0.8rem;
    color: var(--text-secondary);
    cursor: pointer;
    transition: border-color var(--transition);
}

.mf-ck-tagbox:hover { border-color: var(--border-strong); }

/* While "Nothing to do at deployment" is on, the file kinds are not a question
   that has an answer — they are muted and refuse the pointer rather than
   accepting a tick the switch would overrule. */
.mf-ck-tagbox.is-disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

.mf-ck-tagbox.is-disabled:hover { border-color: var(--border-color); }
.mf-ck-tagbox.is-disabled input { cursor: not-allowed; }
.mf-ck-tagbox input { width: 14px; height: 14px; accent-color: var(--accent-text); }
.mf-ck-tagbox:has(input:checked) { border-color: var(--accent-tint-strong); background: var(--accent-tint); color: var(--accent-text); }

.mf-ck-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding-bottom: 14px; }
.mf-ck-saved { font-size: 0.8rem; font-family: var(--font-mono); color: var(--text-muted); }

@media (prefers-reduced-motion: reduce) {
    .mf-ck-body:not([hidden]) { animation: none; }
    .mf-ck-chevron { transition: none; }
    .mf-ck-eicon, .mf-ck-etext, .mf-ck-erm, .mf-ck-tagbox { transition: none; }
}

/* ── mode switch + Upgrade Path (R12) ──────────────────────────────────── */
/* The sidebar's own sub-navigation: the same tint-plus-accent-text active
   treatment the app's tabs use, at the sidebar's scale. */
.mf-modes {
    display: flex;
    gap: 4px;
    padding: 8px 10px 10px;
    margin-bottom: 4px;
    border-bottom: 1px solid var(--border-color);
}

/* In Upgrade Path the release list is removed, so the tabs are all the sidebar
   holds — and the rule that separates them from the list was left underlining
   nothing, above 16px of empty box that read as a panel which had failed to
   render. */
.mf-modes:last-child { margin-bottom: 0; padding-bottom: 8px; border-bottom: none; }

.mf-mode-tab {
    flex: 1;
    min-height: 34px;
    padding: 7px 8px;
    border: 1px solid transparent;
    border-radius: var(--radius);
    background: transparent;
    color: var(--text-secondary);
    font-family: inherit;
    font-size: 0.85rem;
    font-weight: 500;
    cursor: pointer;
    transition: background var(--transition), border-color var(--transition),
                color var(--transition);
}

/* The published artifact draws the same two tabs as NAVIGATION — its views are
   two documents on a static host, not two states of one screen. A button
   centres its own label; an anchor and a span have to be told. */
a.mf-mode-tab, span.mf-mode-tab {
    display: flex;
    align-items: center;
    justify-content: center;
    text-decoration: none;
}

.mf-mode-tab:hover { background: var(--surface-sunken); color: var(--text-primary); }
.mf-mode-tab:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.mf-mode-tab:active { transform: scale(0.985); transition: transform var(--press); }

.mf-mode-tab.is-active {
    background: var(--accent-tint);
    border-color: var(--accent-tint-strong);
    color: var(--accent-text);
    font-weight: 600;
}

/* The ANSWER is as wide as the column, exactly like a release document: the two
   modes render the same sections with the same components, and capping this one
   at 780px made the identical checklist narrower here than there — with 400px of
   empty page beside it, which reads as a layout that failed rather than as a
   measure someone chose.
   What stays narrow is what is prose or a control, not data. */
.mf-up-intro {
    margin: 0 0 18px;
    max-width: 62ch;
    font-size: 0.95rem;
    line-height: 1.6;
    color: var(--text-secondary);
}

/* ONE line: label, control, label, control, button — the pair is a single
   sentence ("from X to Y"), and stacked in two rows on a wide page it left a
   short strip with air on both sides of it. A sunken strip rather than a
   bordered card: the panel is already inside one, and a card in a card drew a
   second boundary around nothing.
   It spans the column, edge to edge with the sections below it. A capped strip
   stopping short of them read as a cut-off panel rather than as a chosen
   measure — the same complaint the 780px cap on the answer earned. */
.mf-up-form {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto minmax(0, 1fr) auto;
    gap: 10px 12px;
    align-items: center;
    padding: 14px 18px;
    background: var(--surface-sunken);
    border-radius: var(--radius-lg);
}

.mf-up-form > label {
    font-family: var(--font-mono);
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-muted);
}

/* `ui-select` wraps the native control in `.ui-select` and keeps it in place,
   so the grid child is one or the other depending on whether the enhancement
   has run — both have to fill the column. */
.mf-up-form > select,
.mf-up-form > .ui-select { min-width: 0; width: 100%; }

/* Last on the line, and SECONDARY: the answer recomputes on `change`, so an
   accent slab here claimed to be the point of a screen whose point is the
   answer below it. It stays as the form's submit and the keyboard entry
   point — quiet rather than gone. */
.mf-up-form > .btn { white-space: nowrap; }

/* The published artifact loads `ui-select.js` too, so this is the state before
   the enhancement runs and the state of a reader with no JavaScript — not a
   second design. It has to look like the widget that replaces it. */
.mf-up-form select {
    min-height: 42px;
    padding: 10px 38px 10px 14px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius);
    background-color: var(--bg-primary);
    color: var(--text-primary);
    font-family: inherit;
    font-size: 0.95rem;
    appearance: none;
    -webkit-appearance: none;
    cursor: pointer;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%235a616b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 12px center;
    background-size: 16px;
    transition: border-color var(--transition), box-shadow var(--transition);
}

.mf-up-form select:hover { border-color: var(--border-strong); }

.mf-up-form select:focus-visible {
    outline: none;
    border-color: var(--accent-text);
    box-shadow: var(--focus-ring);
}

[data-theme="dark"] .mf-up-form select {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23aab0b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
}

/* The artifact follows the reader's system preference instead of the app's
   toggle, so the dark chevron has to be selected the same way the tokens are. */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .mf-up-form select {
        background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23aab0b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
    }
}

/* Two rows when one line no longer fits the column: the same From/To reading
   downward, labels against their controls, the button closing the strip on the
   controls' right edge. */
@container (max-width: 900px) {
    .mf-up-form {
        grid-template-columns: auto minmax(0, 1fr);
        gap: 10px 14px;
        padding: 16px 18px;
    }
    .mf-up-form > label { justify-self: end; }
    .mf-up-form > .btn { grid-column: 2; justify-self: end; margin-top: 4px; }
}

/* A phone: a label above its control, and a button worth hitting with a thumb.
   `justify-self: end` on a label would push it against a control it no longer
   shares a line with. */
@container (max-width: 520px) {
    .mf-up-form { grid-template-columns: minmax(0, 1fr); gap: 6px; }
    .mf-up-form > label { justify-self: start; margin-top: 8px; }
    .mf-up-form > .btn { grid-column: 1; justify-self: stretch; margin-top: 12px; }
}

.mf-up-out:empty { display: none; }

/* The answer is a new region, so it is separated from the controls by a rule
   rather than by another box. It re-renders on every change; the entrance is
   what says the number above just changed. */
.mf-up-result {
    margin-top: 24px;
    padding-top: 22px;
    border-top: 1px solid var(--border-color);
    animation: mf-up-in 200ms var(--ease-out) both;
}

@keyframes mf-up-in {
    from { opacity: 0; transform: translateY(4px); }
    to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
    .mf-up-result { animation: none; }
}

.mf-up-head { margin-bottom: 22px; }

/* The heading IS the answer — "2 components require action" — so it takes the
   Title step, the same rank `.mf-rel-title` holds in the other mode. It used to
   read "Upgrade checklist", naming the tool the reader had just used. */
.mf-up-title {
    margin: 0 0 8px;
    font-size: 1.5rem;
    font-weight: 600;
    letter-spacing: -0.01em;
    color: var(--text-primary);
}

/* One line where there were three rows of chips in three treatments. */
.mf-up-meta {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 8px;
    margin: 0;
    font-size: 0.9rem;
    color: var(--text-secondary);
}

.mf-up-pair { font-family: var(--font-mono); color: var(--text-primary); }

.mf-up-sep { color: var(--text-muted); }

/* Stated in words, and the word is what carries it — the colour only confirms.
   Zero is stated too: "no breaking changes" is the sentence support repeats to
   a customer, and silence would leave them wondering whether it was checked. */
.mf-up-breaking { font-weight: 600; color: hsl(var(--status-warning-text)); }
.mf-up-breaking.is-none { font-weight: 400; color: var(--text-muted); }

.mf-up-steps {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    margin: 12px 0 0;
}

.mf-up-step {
    padding: 3px 9px;
    border-radius: 4px;
    background: var(--accent-tint);
    border: 1px solid var(--accent-tint-strong);
    font-family: var(--font-mono);
    font-size: 0.8rem;
    color: var(--accent-text);
}

/* A hot-fix reads differently from a sprint here — the path is the one place
   both stand in one row, and "which of these was an emergency" is the reader's
   next question. Border and label carry it, never colour alone. */
.mf-up-step.is-hotfix {
    background: hsl(var(--status-warning-bg));
    border-color: hsl(var(--status-warning-border));
    color: hsl(var(--status-warning-text));
}

.mf-up-arrow { color: var(--text-muted); }

.mf-up-notice {
    margin: 16px 0 0;
    padding: 12px 14px;
    background: var(--surface-sunken);
    border: 1px solid var(--border-color);
    border-radius: var(--radius);
    font-size: 0.95rem;
    color: var(--text-secondary);
}

/* One release's items inside the path's Release Notes. The name is a rule
   across the width rather than another card: these are sub-parts of ONE
   section, and boxing each of them would say they are separate sections. */
.mf-up-rel + .mf-up-rel { margin-top: 26px; }

.mf-up-rel-head {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 12px;
}

.mf-up-rel-name {
    font-family: var(--font-mono);
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--text-primary);
}

/* Same distinction the walked-release chips make, carried by a word rather
   than by colour alone — the date beside it is what a hot-fix's name never
   says. */
.mf-up-rel-name.is-hotfix::after {
    content: " hot-fix";
    font-weight: 400;
    color: hsl(var(--status-warning-text));
}

.mf-up-rel-date {
    font-family: var(--font-mono);
    font-size: 0.8rem;
    color: var(--text-muted);
}

.mf-up-rel-rule {
    flex: 1;
    height: 1px;
    background: var(--border-color);
}

/* Which release still asks for a step — only rendered in the combined
   checklist, where entries from several releases stand side by side. */
.mf-ck-from {
    font-family: var(--font-mono);
    font-size: 0.8rem;
    color: var(--text-muted);
    white-space: nowrap;
}

@media (prefers-reduced-motion: reduce) {
    .mf-mode-tab { transition: none; }
}

/* Taking a hand-added item back off. The checklist editor's remove button
   (`.mf-ck-erm`) with a size of its own: that form sets one on its own control
   height, and a Release Notes row is not on that scale. `auto` margin pushes
   it to the end of the row so every row's button sits in one column, however
   long the summary beside it runs. */
.mf-item-rm {
    /* WCAG 2.2 target size (minimum): 24x24 CSS px. */
    width: 24px;
    height: 24px;
    margin-left: auto;
}

/* Adding one. Laid out as label, field, button on one line with the hint
   under the whole row, so the guidance is read once rather than per control;
   it wraps to its own lines when the column is too narrow to hold them. */
.mf-items-add {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    margin-top: 18px;
    padding-top: 16px;
    border-top: 1px solid var(--border-color);
}

.mf-items-add-label { font-size: 0.85rem; font-weight: 600; color: var(--text-secondary); }

.mf-items-add-input {
    width: 100%;
    max-width: 10rem;
    padding: 7px 10px;
    font-family: var(--font-mono);
    font-size: 0.85rem;
    border: 1px solid var(--border-color);
    border-radius: var(--radius);
    background: var(--bg-primary);
    color: var(--text-primary);
}

.mf-items-add-input:focus-visible {
    outline: none;
    border-color: var(--accent-text);
    box-shadow: var(--focus-ring);
}

.mf-items-add-status { font-size: 0.8rem; color: var(--text-muted); }

/* The hint owns the next line whatever fits above it. */
.mf-items-add .mf-ck-hint { flex-basis: 100%; margin: 0; max-width: 62ch; }

/* The patch, editable. Same panel as the reader's table, with the row turned
   into three controls: what kind of line it is, the line itself, and a way to
   take it out. Mono at the reader's size, so a corrected patch is read at the
   same rhythm as a generated one. */
.mf-ck-diff.is-editable { padding-bottom: 12px; }

.mf-ck-drows {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin: 0;
    padding: 10px 12px;
    list-style: none;
}

.mf-ck-drow {
    display: grid;
    grid-template-columns: 8.5rem 1fr auto;
    gap: 8px;
    align-items: center;
}

.mf-ck-dsign,
.mf-ck-dtext {
    height: 32px;
    padding: 0 10px;
    border: 1px solid var(--border-color);
    border-radius: var(--radius);
    background: var(--bg-primary);
    color: var(--text-primary);
    font-family: var(--font-mono);
    font-size: 0.85rem;
}

/* The sign is the row's meaning; the colour is the same one the reader's
   table uses, so a `+` row looks like an added line while it is being typed. */
.mf-ck-dsign { cursor: pointer; }

.mf-ck-dsign:focus-visible,
.mf-ck-dtext:focus-visible {
    outline: none;
    border-color: var(--accent-text);
    box-shadow: var(--focus-ring);
}

.mf-ck-drm { width: 32px; height: 32px; }

.mf-ck-diff.is-editable [data-mf-diff-add] { margin-left: 12px; }

@media (max-width: 720px) {
    /* The sign keeps its own line above the text rather than squeezing both. */
    .mf-ck-drow { grid-template-columns: 1fr auto; }
    .mf-ck-dsign { grid-column: 1 / -1; }
}

/* A hot-fix lists the rest of the platform for context: the stack this build
   runs on, not what it shipped. Quietened rather than hidden — hidden, the
   reader has to go and find another document to answer "and what is everything
   else at?"; at full weight, every row reads as something to deploy. */
.mf-layer-row.is-context th { color: var(--text-muted); font-weight: 500; }

.mf-vrow-context .mf-comp-name,
.mf-vrow-context .mf-ver-str,
.mf-vrow-context .mf-bld-str { color: var(--text-secondary); font-weight: 400; }

.mf-vrow-context .mf-pull-cmd { opacity: 0.72; }
.mf-vrow-context .mf-pull-cmd:hover,
.mf-vrow-context .mf-pull-cmd:focus-visible { opacity: 1; }

/* The publication e-mail's state, above a published document. Not a warning:
   nothing is wrong while it waits — the portal is simply still building. */
.mf-announce-note {
    margin: 0 0 18px;
    padding: 10px 14px;
    border-radius: var(--radius);
    border: 1px solid var(--border-color);
    background: var(--surface-sunken);
    font-size: 0.85rem;
    line-height: 1.6;
    color: var(--text-secondary);
}

.mf-announce-note.is-failed {
    background: var(--warning-bg);
    border-color: hsl(var(--status-warning-border));
    color: var(--text-primary);
}
