Files
Project-SDE-WP-Suite/html/theme-light.css
n.siegfried 23ee0b052f T9.9 - C4 + the backlog sweep: nine entries closed, each re-measured first
The colour half (C4, approved Aug 18 - "change them"):
- BL-004: the help centre's own 52-colour palette collapsed onto theme tokens
- BL-005: the JS-built dialogs (auth-guard, wp-format) and project-data's
  badges read tokens; the creator's categorical badge palette moved to
  theme-light as --wp-chart-1..10, read by computed style at boot; the print
  popup - a document with no stylesheet - inlines live token VALUES
- BL-008: the second brand blue (#2563d6) is deleted; .sop-inherited tints
  with THE blue at the same 7% alpha
- BL-009: the ninth amber (--wp-status-warning-text-alt) is deleted
- theme-light gained the two missing feedback tokens the consoles carried as
  literals (--wp-status-success-text / -error-text)
- NEW tests/color_check.py 4/4: zero hex literals outside theme-light.css,
  comments stripped (the BL-017 lesson), with the exceptions named in full
  (meta theme-color cannot resolve a var; rgba alphas are opacity recipes)

The correctness half, each re-measured before touching, as the task ordered:
- BL-011 STILL REPRODUCED: the sync badge mounted on the first async sync
  event; its holder now mounts at DOMContentLoaded, so the three overlays land
  in script order deterministically
- BL-012 fixed and MEASURED: baseline_shots freezes Date and Math.random per
  document; two consecutive admin captures came back byte-identical
- BL-016 fixed: a step-less wizard URL is step 1; stepper_check's deliberately
  wrong pin flipped with the fix, exactly as the entry planned
- BL-018 fixed both halves: the false-complete write now requires the
  {sop,state} production shape, and browser_check.seed writes that shape -
  which un-detoured four probes' creators from the SOP gate. stepper_check
  re-pointed at projB (no SOP) because its premise is a wizard someone is
  STARTING, and projA now legitimately restores a finished one.
- BL-019 fixed: a stored cost code that left COST_CODES is kept as an option
  (the gov_wosize pattern), so opening a package no longer blanks its record
- hold_check's AST sweep refined in passing detection: it flagged T8.3's
  notification-row .status as a release transition; it now reads wp.status only

Every wave-9-pointing backlog entry is closed with its measurement recorded.

Verification (each probe run alone): color_check 4/4, stepper_check 71/71,
validation_check 77/77, url_state_check 23/23, autosave_check 34/34,
a11y_check 22/22, launcher_check 58/58, aggregates_check 16/16,
kitting_check 26/26, hold_check 50/50, mobile_check 24/24, frame_check 38/38,
sections_check 95/95, form_structure_check 50/51 (BL-022's question).

Items: C4, BL-004, BL-005, BL-008, BL-009, BL-011, BL-012, BL-016, BL-018, BL-019

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 14:27:26 -07:00

591 lines
25 KiB
CSS

/* IBM Carbon Design System - Light Theme (g10) */
:root {
--cds-interactive-01: #0f62fe;
--cds-interactive-02: #393939;
--cds-interactive-03: #0f62fe;
--cds-interactive-04: #0f62fe;
--cds-ui-background: #f4f4f4;
--cds-ui-01: #ffffff;
--cds-ui-02: #f4f4f4;
--cds-ui-03: #e0e0e0;
--cds-ui-04: #8d8d8d;
--cds-ui-05: #161616;
--cds-text-01: #161616;
--cds-text-02: #525252;
--cds-text-03: #a8a8a8;
--cds-text-04: #ffffff;
--cds-text-05: #6f6f6f;
--cds-text-error: #da1e28;
--cds-icon-01: #161616;
--cds-icon-02: #525252;
--cds-icon-03: #ffffff;
--cds-link-01: #0f62fe;
--cds-link-02: #0043ce;
--cds-inverse-link: #78a9ff;
--cds-field-01: #ffffff;
--cds-field-02: #f4f4f4;
--cds-inverse-01: #ffffff;
--cds-inverse-02: #393939;
--cds-support-01: #da1e28;
--cds-support-02: #198038;
--cds-support-03: #f1c21b;
--cds-support-04: #0043ce;
--cds-inverse-support-01: #fa4d56;
--cds-inverse-support-02: #42be65;
--cds-inverse-support-03: #f1c21b;
--cds-inverse-support-04: #4589ff;
--cds-overlay: rgba(22, 22, 22, .5);
--cds-danger: #da1e28;
--cds-focus: #0f62fe;
--cds-hover-primary: #0353e9;
--cds-active-primary: #002d9c;
--cds-hover-primary-text: #0043ce;
--cds-hover-secondary: #4c4c4c;
--cds-active-secondary: #6f6f6f;
--cds-hover-tertiary: #0353e9;
--cds-active-tertiary: #002d9c;
--cds-hover-ui: #e5e5e5;
--cds-hover-light-ui: #e5e5e5;
--cds-hover-selected-ui: #cacaca;
--cds-active-ui: #c6c6c6;
--cds-active-light-ui: #cacaca;
--cds-selected-ui: #e5e5e5;
--cds-selected-light-ui: #e5e5e5;
--cds-inverse-hover-ui: #353535;
--cds-hover-danger: #ba121b;
--cds-active-danger: #750e13;
--cds-hover-row: #f4f4f4;
--cds-visited-link: #0043ce;
--cds-disabled-01: #f4f4f4;
--cds-disabled-02: #bdbdbd;
--cds-disabled-03: #8d8d8d;
--cds-button-primary: #0f62fe;
--cds-button-secondary: #393939;
--cds-button-tertiary: #0f62fe;
--cds-button-danger-primary: #da1e28;
--cds-button-danger-secondary: #da1e28;
--cds-background: #f4f4f4;
--cds-background-inverse: #161616;
--cds-background-brand: #0f62fe;
--cds-background-active: #e5e5e5;
--cds-background-hover: #e8e8e8;
--cds-background-selected: #e5e5e5;
--cds-background-selected-hover: #cacaca;
--cds-layer: #ffffff;
--cds-layer-accent: #f4f4f4;
--cds-layer-accent-hover: #e8e8e8;
--cds-layer-accent-active: #e0e0e0;
--cds-layer-hover: #e8e8e8;
--cds-layer-active: #e5e5e5;
--cds-layer-selected: #e5e5e5;
--cds-layer-selected-hover: #cacaca;
--cds-layer-selected-inverse: #393939;
--cds-field: #ffffff;
--cds-field-hover: #e8e8e8;
--cds-border-subtle: #e0e0e0;
--cds-border-strong: #8d8d8d;
--cds-border-inverse: #393939;
--cds-border-interactive: #0f62fe;
--cds-border-subtle-selected: #c6c6c6;
--cds-text-primary: #161616;
--cds-text-secondary: #525252;
--cds-text-placeholder: #a8a8a8;
--cds-text-helper: #6f6f6f;
--cds-text-on-color: #ffffff;
--cds-text-inverse: #f4f4f4;
--cds-text-disabled: #bdbdbd;
--cds-link-primary: #0f62fe;
--cds-link-secondary: #0043ce;
--cds-link-visited: #0043ce;
--cds-link-inverse: #78a9ff;
--cds-link-primary-hover: #0353e9;
--cds-icon-primary: #161616;
--cds-icon-secondary: #525252;
--cds-icon-on-color: #ffffff;
--cds-icon-inverse: #f4f4f4;
--cds-icon-disabled: #bdbdbd;
--cds-support-error: #da1e28;
--cds-support-success: #198038;
--cds-support-warning: #f1c21b;
--cds-support-info: #0043ce;
--cds-support-error-inverse: #fa4d56;
--cds-support-success-inverse: #42be65;
--cds-support-warning-inverse: #f1c21b;
--cds-support-info-inverse: #4589ff;
--cds-interactive: #0f62fe;
--cds-shadow: rgba(0, 0, 0, .16);
/* Carbon's own $highlight, which the page sheets each declared for themselves
as --accent-soft / --primary-light / --accent-dim. Real Carbon name and
real Carbon value; this sheet simply never carried it. (T3.2 / S5) */
--cds-highlight: #edf5ff; /* blue-10 */
}
/* ============================================================================
SUITE TOKENS (--wp-*) — roles IBM Carbon does not define
----------------------------------------------------------------------------
Everything above is Carbon g10 and should stay that way. Everything here is a
value the suite invented because Carbon has no slot for it. Keeping the two
namespaces apart is what stops the next person "correcting" #8e6a00 into a
Carbon yellow and breaking the warning banners.
Together with the --cds-* block above, this is the one place a colour,
spacing or type value is defined (CLAUDE.md, "The token rule"). Page
stylesheets alias these; they declare nothing of their own.
Full mapping, and the provenance of every value: docs/reference/tokens.md
============================================================================ */
:root {
/* -- status fills and their text ------------------------------------------
The greens and reds are Carbon 10-step values; the amber pair is not
Carbon at all (Carbon yellow-10 is #fcf4d6). --wp-status-warning-text is
the suite's own accessible amber for text sitting on --wp-status-warning-bg. */
--wp-status-success-bg: #defbe6;
--wp-status-error-bg: #fff1f1;
--wp-status-warning-bg: #fdf6dd;
--wp-status-warning-text: #8e6a00;
/* BL-009, CLOSED at T9.9 (C4): the ninth amber (--wp-status-warning-text-alt,
#8a6d00, four points from this one) is deleted; its consumers use this. */
/* Carbon green-70. The value is Carbon, the role is not — Carbon has no
"hover for a green fill", because green is not one of its action colours.
Declared in no sheet today; written raw in five places. */
--wp-hover-success: #0e6027;
/* -- status borders -------------------------------------------------------
Eight values doing four jobs, because two sheets each picked their own.
Every one of them renders somewhere today, so T3.2 names all eight rather
than choosing four winners — choosing is a visual change. T3.5 decides.
See docs/reference/tokens.md section 8-D. */
--wp-status-success-border-a: #a7f0ba; /* green-20, Carbon — console */
--wp-status-success-border-b: #b6e3c6; /* not Carbon — creator */
--wp-status-error-border-a: #ffd7d9; /* red-20, Carbon — console */
--wp-status-error-border-b: #f3c4c4; /* not Carbon — creator, release banner */
--wp-status-error-border-c: #ffc4c4; /* not Carbon — creator, .crit-tag */
--wp-status-warning-border-a: #fddc69; /* yellow-30, Carbon — console */
--wp-status-warning-border-b: #f0d9ad; /* not Carbon — creator */
--wp-accent-border-a: #b9d2fb; /* neither is Carbon blue-20 (#d0e2ff) */
--wp-accent-border-b: #cdd9f2;
/* -- type ------------------------------------------------------------------
Three mono stacks and two sans stacks exist because five sheets each wrote
their own. They are NOT interchangeable: on a machine without IBM Plex Mono
installed — which is most of the target environment — each falls through to
a different face. Unifying them changes what admin and users render, so
T3.2 names all five and T3.5 picks. See docs/reference/tokens.md 6d, 8-H. */
--wp-font-sans: 'IBM Plex Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
--wp-font-sans-2: 'IBM Plex Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
--wp-font-mono: 'IBM Plex Mono', ui-monospace, 'Cascadia Mono', 'Segoe UI Mono', Consolas, monospace;
--wp-font-mono-2: 'IBM Plex Mono', 'Cascadia Mono', Consolas, monospace;
--wp-font-mono-3: 'IBM Plex Mono', ui-monospace, Consolas, monospace;
/* -- spacing ---------------------------------------------------------------
Promoted from console.css, which is the only sheet in the repo whose
spacing is a scale rather than a histogram. The wizard's rem ladder and the
creator's raw px are inventoried in docs/reference/tokens.md 6b and convert
as those pages are rebuilt (T5.x, T7.x) — not here, where any change to a
padding is a visual change. */
--wp-s1: 4px;
--wp-s2: 8px;
--wp-s3: 12px;
--wp-s4: 16px;
--wp-s5: 20px;
--wp-s6: 28px;
/* Control heights: --wp-ctl for anything in a form row, --wp-ctl-sm for
anything inside a table cell. This is what keeps a toolbar one clean band
and a table row ~34px instead of ~100px. */
--wp-ctl: 32px;
--wp-ctl-sm: 26px;
/* -- shape and elevation ---------------------------------------------------
Square corners are the Carbon idiom and the intent everywhere except the
creator, which drifted to 12 different radii (BL-007, owned by T7.1).
The two large shadows share their geometry exactly — 0 4px 16px — and
differ only in tint, so they can be unified later with no layout
consequence. Until then, both are named. */
--wp-radius-0: 0;
--wp-shadow-none: none;
--wp-shadow-lg-neutral: 0 4px 16px rgba(0, 0, 0, .16);
--wp-shadow-lg-cool: 0 4px 16px rgba(20, 30, 50, .12);
/* -- elevation -------------------------------------------------------------
Twelve distinct shadows, which is eleven more than a flat Carbon UI wants.
They are listed rather than merged for the same reason as the borders: each
one renders today and merging is a visual change. That the list is this long
is itself the finding — a later pass can collapse it to two or three now
that they are all visible in one place. */
--wp-shadow-pop: 0 8px 28px rgba(20, 30, 50, .22);
--wp-shadow-menu: 0 8px 24px rgba(20, 30, 50, .18);
--wp-shadow-menu-lg: 0 10px 26px rgba(20, 30, 50, .18);
--wp-shadow-modal: 0 12px 40px rgba(20, 30, 50, .3);
--wp-shadow-modal-lg: 0 20px 60px rgba(0, 0, 0, .3);
--wp-shadow-gate: 0 8px 30px rgba(20, 30, 50, .12);
--wp-shadow-rail: 6px 0 22px rgba(20, 30, 50, .16);
--wp-shadow-drawer: 2px 0 16px rgba(0, 0, 0, .4);
--wp-shadow-sticky: 0 -2px 10px rgba(20, 30, 50, .08);
--wp-shadow-navbar: 0 1px 4px rgba(20, 30, 50, .06);
--wp-shadow-tooltip: 0 4px 14px rgba(20, 30, 50, .22);
--wp-shadow-toast: 0 6px 24px rgba(0, 0, 0, .25);
/* -- scrims ----------------------------------------------------------------
Five overlay fills in three tints. --cds-overlay (rgba(22,22,22,.5)) is the
Carbon one; the drawer's is the same colour 5% darker for no recorded
reason, which makes it the cheapest merge in the set — but still a merge,
so still not here. See docs/reference/tokens.md section 8-F. */
--wp-scrim-drawer: rgba(22, 22, 22, .55);
--wp-scrim-cool: rgba(20, 30, 50, .28);
--wp-scrim-cool-modal: rgba(20, 30, 50, .5);
--wp-scrim-cool-strong: rgba(20, 28, 40, .55);
--wp-scrim-neutral: rgba(0, 0, 0, .5);
--wp-scrim-loading: rgba(244, 245, 247, .82);
--wp-scrim-frosted: rgba(255, 255, 255, .94); /* sticky section-nav backdrop */
--wp-on-accent-divider: rgba(255, 255, 255, .28);
/* -- surfaces that are not Carbon greys ------------------------------------
Four neutrals the suite invented, all within a few points of a Carbon step
and none of them equal to one. --wp-table-zebra is the dangerous one: it is
six points from --cds-layer-accent, and collapsing them erases the striping
on the nine-column user table. Section 8-A. */
--wp-table-zebra: #fafafa;
--wp-table-row-hover: #eef0f2;
--wp-nav-bg: #fbfbfc;
--wp-nav-hover: #eef0f3;
--wp-nav-active: #e8eaed;
--wp-pop-divider: #f0f0f0; /* popover hairlines in the shared chrome */
--wp-accent-soft-hover: #e2ecfc; /* hover on an --cds-highlight fill */
/* -- the Project Super User tag --------------------------------------------
Carbon purple-10 / purple-60. Real Carbon values with no --cds-* slot in
the subset this sheet carries. One consumer; promote into --cds-* if a
second ever appears. */
--wp-tag-super-bg: #e8daff;
--wp-tag-super-text: #6929c4;
/* -- console output --------------------------------------------------------
pre.out is a terminal emulator, not product surface. Green-on-dark pass and
red-on-dark fail is the convention being quoted; it should not track the
palette and must not be "corrected" to Carbon's support colours. */
--wp-term-bg: #0f1525;
--wp-term-fg: #d7e0f5;
--wp-term-pass: #56d364;
--wp-term-fail: #ff7b72;
/* -- dev mode --------------------------------------------------------------
Deliberately outside the palette. The banner is MEANT to look wrong; giving
it the product's amber would make it look intentional, which defeats it. */
--wp-dev-bg: #3a2a00;
--wp-dev-fg: #ffd479;
--wp-dev-rule: #ffb000;
/* -- button roles (A5) -----------------------------------------------------
Four roles and no fifth. Every button in the app is one of them, and the
class-to-role mapping is in docs/reference/tokens.md section 12.
primary the one action this screen exists for. Filled accent. At most one
per view.
secondary every other real action. White fill, --border-strong hairline,
accent on hover. This is the app's workhorse.
tertiary navigational or undoing. No fill, no border, accent text.
danger destructive. Outlined red by default; filled red only where the
control is small enough that an outline would not read (the ✕ on
a sequence row).
GREEN IS A STATUS COLOUR AND NEVER FILLS A BUTTON. It says cleared, ready,
complete — on a dot, a pill, a banner, a badge. Two buttons broke that rule
and are what A5 recorded: "SOP Complete" and "Save & View" shouted louder
than every other action in the suite while doing nothing more dangerous than
saving. They are primary now. The green did not go anywhere: the launcher
card still turns green when its SOP completes, on the border and the status
line, which is where a status belongs.
No value here is new — these are the fills the sheets already rendered,
given one definition so that "primary" means one thing. */
--wp-btn-primary-bg: var(--cds-interactive-01);
--wp-btn-primary-fg: var(--cds-text-on-color);
--wp-btn-primary-hover: var(--cds-hover-primary);
--wp-btn-primary-active: var(--cds-active-primary);
--wp-btn-secondary-bg: var(--cds-field);
--wp-btn-secondary-fg: var(--cds-text-primary);
--wp-btn-secondary-border: var(--cds-border-strong);
--wp-btn-secondary-hover-fg: var(--cds-interactive-01);
--wp-btn-tertiary-fg: var(--cds-interactive-01);
--wp-btn-tertiary-hover-fg: var(--cds-hover-primary-text);
--wp-btn-danger-fg: var(--cds-support-error);
--wp-btn-danger-border: var(--cds-support-error);
--wp-btn-danger-soft-bg: var(--wp-status-error-bg);
--wp-btn-danger-fill-bg: var(--cds-support-error);
--wp-btn-danger-fill-fg: var(--cds-text-on-color);
/* BL-008, CLOSED at T9.9 (C4, approved Aug 18): the second brand blue is
gone. .sop-inherited now tints with THE blue at the same 7% alpha. */
--wp-sop-inherited-bg: rgba(15, 98, 254, .07);
/* The console feedback trio's success text (auth-guard / project-data /
wp-format carried it as a literal until C4). */
--wp-status-success-text: #0e6027;
--wp-status-error-text: #a2191f;
/* The categorical badge palette (the creator's navigator). Data-vis colours,
not UI states - named here because here is the only place a colour value
may exist (C4); the app reads them by computed style at boot. */
--wp-chart-1: #0f62fe; --wp-chart-2: #8a3ffc; --wp-chart-3: #007d79;
--wp-chart-4: #d02670; --wp-chart-5: #ba4e00; --wp-chart-6: #1192e8;
--wp-chart-7: #198038; --wp-chart-8: #a56eff; --wp-chart-9: #9f1853;
--wp-chart-10: #005d5d;
}
/* Typography */
body {
font-family: var(--wp-font-sans);
color: var(--cds-text-primary);
background: var(--cds-background);
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
}
h1, h2, h3, h4, h5, h6 {
font-weight: 600;
line-height: 1.28572;
}
h1 { font-size: 2rem; }
h2 { font-size: 1.75rem; }
h3 { font-size: 1.25rem; }
h4 { font-size: 1rem; }
h5 { font-size: .875rem; }
h6 { font-size: .75rem; }
a {
color: var(--cds-link-primary);
text-decoration: none;
}
a:hover {
color: var(--cds-link-primary-hover);
text-decoration: underline;
}
button {
font-family: inherit;
cursor: pointer;
}
input, textarea, select {
font-family: inherit;
color: var(--cds-text-primary);
}
/* ============================================================================
App shell — shared "UI Shell" chrome (Prime Controls, IBM Carbon styling)
----------------------------------------------------------------------------
One dark top bar across every page so the suite reads as a single product.
The Prime Controls logo is a white-background wordmark, so it sits inside a
white "chip" on the near-black bar (reads as intentional, not a stray box).
Flip --wp-appbar-bg to a light value if a light header is ever preferred.
============================================================================ */
:root {
--wp-appbar-bg: #161616; /* near-black UI Shell bar */
--wp-appbar-fg: #ffffff;
--wp-appbar-fg-dim: #c6c6c6;
--wp-appbar-border: #6f6f6f; /* outline for ghost buttons on the bar */
--wp-appbar-hover: #353535;
--wp-appbar-height: 48px;
/* Carbon gray-90 — one step up from the bar itself. The raised surfaces that
sit ON the dark bar or the drawer: the chrome's search field, the drawer's
current row. Its two consumers both hardcoded it before T3.2. */
--wp-appbar-layer: #262626;
}
.wp-appbar {
background: var(--wp-appbar-bg);
color: var(--wp-appbar-fg);
display: flex;
align-items: center;
gap: 16px;
height: var(--wp-appbar-height);
padding: 0 16px;
position: sticky;
top: 0;
z-index: 100;
}
.wp-appbar-brand {
display: flex;
align-items: center;
gap: 12px;
height: 100%;
text-decoration: none;
color: var(--wp-appbar-fg);
}
.wp-appbar-brand:hover { text-decoration: none; opacity: .92; }
.wp-logo-chip {
display: inline-flex;
align-items: center;
justify-content: center;
background: var(--cds-ui-01);
border-radius: 4px;
padding: 4px 8px;
}
.wp-logo-chip img { height: 24px; width: auto; display: block; }
.wp-appbar-title {
font-size: 15px;
font-weight: 600;
color: var(--wp-appbar-fg);
white-space: nowrap;
letter-spacing: .01em;
}
.wp-appbar-title .wp-appbar-sub { font-weight: 400; color: var(--wp-appbar-fg-dim); }
.wp-appbar-spacer { flex: 1 1 auto; }
.wp-appbar-meta { font-size: 13px; color: var(--wp-appbar-fg-dim); white-space: nowrap; }
.wp-appbar-actions { display: flex; align-items: center; gap: 8px; }
/* Buttons and links that live on the dark bar */
.wp-appbar-btn {
background: transparent;
color: var(--wp-appbar-fg);
border: 1px solid var(--wp-appbar-border);
border-radius: 0;
padding: 7px 14px;
font-size: 14px;
font-family: inherit;
line-height: 1.2;
cursor: pointer;
text-decoration: none;
white-space: nowrap;
transition: background .15s, border-color .15s;
}
.wp-appbar-btn:hover { background: var(--wp-appbar-hover); color: var(--wp-appbar-fg); text-decoration: none; }
.wp-appbar-btn.primary { background: var(--cds-interactive-01); border-color: var(--cds-interactive-01); }
.wp-appbar-btn.primary:hover { background: var(--cds-hover-primary); border-color: var(--cds-hover-primary); }
.wp-appbar-btn:focus-visible { outline: 2px solid var(--wp-appbar-fg); outline-offset: 1px; }
.wp-appbar-count { font-size: 13px; color: var(--wp-appbar-fg-dim); padding: 0 2px; white-space: nowrap; }
/* Plain text links on the dark bar (Overview / Feedback / Help, Admin, etc.) */
.wp-appbar-link { color: var(--wp-appbar-fg-dim); text-decoration: none; font-size: 14px; white-space: nowrap; }
.wp-appbar-link:hover { color: var(--wp-appbar-fg); text-decoration: none; }
@media (max-width: 720px) {
.wp-appbar { height: auto; flex-wrap: wrap; gap: 8px; padding: 8px 12px; }
.wp-appbar-actions { flex-wrap: wrap; }
.wp-appbar-meta { width: 100%; order: 5; }
}
/* ============================================================================
DRAFT STATE INDICATOR (B5 / T4.4)
----------------------------------------------------------------------------
Reports the DRAFT — what wp-autosave.js is holding for you on this device.
The sync badge in project-data.js reports the OUTBOX, which is a different
question ("has the saved record reached the project"), and its wording now
says so. Two indicators, two sentences, neither readable as the other.
Lives in theme-light.css because both form pages mount the same component,
and a second copy in a page sheet is what wave 3 spent itself removing.
============================================================================ */
.wp-draft-status {
display: inline-flex;
align-items: center;
gap: 8px;
font-size: 12px;
font-weight: 400;
line-height: 1.3;
color: var(--cds-text-secondary);
white-space: nowrap;
}
.wp-draft-status.is-saved { color: var(--cds-support-success); font-weight: 600; }
.wp-draft-status.is-saving { color: var(--cds-text-secondary); }
/* A failed autosave is not a quieter version of a successful one — it means the
safety net is not there. Red, bold, and it keeps its retry. */
.wp-draft-status.is-failed {
color: var(--cds-support-error);
font-weight: 600;
white-space: normal;
}
.wp-draft-retry {
font: inherit;
font-size: 11px;
font-weight: 600;
padding: 2px 9px;
cursor: pointer;
background: var(--wp-btn-secondary-bg);
color: var(--wp-btn-danger-fg);
border: 1px solid var(--wp-btn-danger-border);
border-radius: var(--wp-radius-0);
}
.wp-draft-retry:hover { background: var(--wp-btn-danger-soft-bg); }
.wp-draft-retry:focus-visible { outline: 2px solid var(--cds-focus); outline-offset: -2px; }
/* An inline validation error, at the field it belongs to (C1).
----------------------------------------------------------------------------
Three surfaces grew one of these within wave 5 — the launcher's create-project
form (T5.2), the SOP wizard's location list (T5.4), and T5.8's step validation
— so it lives here rather than as three page-sheet rules that would drift.
The markup pairs it with aria-describedby and role="alert" on the element; the
:empty rule is what lets it sit in the page permanently and announce on change
rather than being created at the moment somebody needs to hear it. */
.field-error {
color: var(--cds-text-error);
font-size: 12px;
font-weight: 600;
margin-top: 4px;
}
.field-error:empty { display: none; }
/* ============================================================================
FOCUS (S12 / T4.7)
----------------------------------------------------------------------------
A keyboard user has to be able to see where they are. The suite's own sheets
removed the outline in six places and replaced it, at best, with a 3px
#edf5ff glow — a 1.05:1 edge against a white field, which is not a focus
indicator so much as a rumour of one. T3.4 fixed the wizard's three; this is
the app-wide floor underneath all of them.
:focus-visible, not :focus, so a mouse click does not leave a ring behind —
which is the reason people reach for `outline: none` in the first place.
2px of --cds-focus (#0f62fe). Against every background the app actually uses
it clears 3:1 comfortably: 8.6:1 on white, 7.8:1 on #f4f4f4, 4.9:1 on the
Carbon highlight blue. Drawn OUTSIDE the element by default (positive offset)
so it is not swallowed by a control's own border; components that need it
inset say so themselves.
This is a floor, not an override: it is a single-class-free selector list, so
any component rule with a class beats it and can draw its own ring.
============================================================================ */
:where(a, button, input, select, textarea, summary, [tabindex]:not([tabindex="-1"])):focus-visible {
outline: 2px solid var(--cds-focus);
outline-offset: 1px;
}
/* On the dark app bar and the drawer, blue-on-near-black is 2.4:1 and fails.
White is 15.9:1 against #161616 and is what wp-sidenav.css already used. */
:where(.wp-appbar, .wp-sidenav, .wp-navscrim)
:where(a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])):focus-visible {
outline-color: var(--wp-appbar-fg);
}
/* A FILLED control cannot take an inset ring: a blue ring inside a blue button
measures 1.00:1, which is not a subtle problem — it is no indicator at all.
These are every filled button in the suite (see docs/reference/tokens.md §12),
and their ring is pushed clear of the fill so it lands on the page behind them.
Class specificity, so it beats both the :where() floor above and the browser's
own default ring, which is what was winning on the creator's primary buttons. */
.btn-primary:focus-visible,
.btn-generate:focus-visible,
.use-btn:focus-visible,
.wp-nav-cta:focus-visible,
.wp-nav-cta-more:focus-visible,
.mode-btn.active:focus-visible,
.seq-del:focus-visible,
button.primary:focus-visible,
.card-button:focus-visible,
.submit-btn:focus-visible,
.comments-toggle:focus-visible,
.add-btn:focus-visible,
.ui-help-fab:focus-visible,
.wp-appbar-btn.primary:focus-visible {
outline: 2px solid var(--cds-focus);
outline-offset: 2px;
}