CLAUDE.md lists CR-005 among the change requests that get "silently half-built if
you treat them as frontend-only". This is the server half and the wizard half
together: a new table, four routes, an Alembic revision, and step 11.
CODES, NOT DISPLAY STRINGS, because CR-018 rolls cost up by these values and a
rollup keyed on a label breaks the day somebody fixes a typo in it. Two columns
carry that: `code` is a node's own slug, derived once at import and never
recomputed; `path` is the full slug path, unique per project, and is what a work
package will store. Renaming a value changes `name` alone - the probe renames a
floor and demands its path comes back byte-identical, with its children's paths
intact.
DEACTIVATE, NEVER DELETE. There is no DELETE route, and the probe checks for its
absence (405) rather than trusting that nobody added one. Deactivating hides a
value from new work packages and cascades DOWN, because a floor nobody can pick
must not keep offering its sectors. Reactivating walks UP only - a sector may
have been switched off for its own reasons, and silently resurrecting it would
undo a decision nobody made twice. That asymmetry is deliberate and is pinned by
a named check so it does not get "fixed" into a surprise.
Import reports rather than merges. Rejected rows come back with the SOURCE line
number and a reason; duplicates are listed as duplicates, separated into "already
in this project" and "already on line N of this import". Reusing a parent is not
a duplicate - B1/L2/1P and B1/L2/2P share a building and a floor by design, and
only the full path repeating counts. Re-importing a deactivated value brings the
same row back rather than creating a second one; the probe checks the id.
One parser, on the server. A CSV is read in the browser and posted as text
exactly as a paste is, so "what does a blank column mean" has one answer.
Comma, semicolon and tab all work - a paste out of a spreadsheet is tab
separated and a saved CSV is not, and which one somebody has is a question the
machine can answer.
No guessed floor names. IMPLEMENTATION.md section 8 says the B100 list has not
been supplied. The seeded sample has "Sample" inside every string, and the probe
greps html/ and server/ for a location-shaped assignment containing any of the
review's real names.
server/models.py LocationNode
server/alembic/versions/e2a4c7d91b30_location_taxonomy.py
server/app.py GET/POST/PATCH + import, parser, slug
html/work-package-suite.html step 11, an 11th rail button
html/work-package-suite-app.js the step's logic; LAST_STEP replaces 10
html/work-package-suite-styles.css the list, the report
html/theme-light.css .field-error, now declared once
tests/locations_check.py new - 58 checks
tests/stepper_check.py STEP_COUNT 10 -> 11
Done when
[x] CSV upload and paste both work and report rejected rows with reasons
[x] duplicates are detected and reported rather than silently merged
[x] values are editable after import - rename, add, deactivate
[x] deactivating hides it from new work packages; an existing package
referencing it still resolves, because the row is retained
[x] values are stored as codes suitable for grouping
[x] no guessed real-world floor names exist anywhere in the code
Two decisions worth disagreeing with
Step 11, appended, not step 2, inserted. Locations belong beside Project by
subject. Renumbering 2-10 would touch every sop-step-N id, every
collectStepData case, every gate key and the analytics history - a large
silent-mismatch surface for an ordering change. The count now lives in one
place (LAST_STEP), so reordering later is cheap.
Any project member may edit the list, not only a Project Admin. It matches how
the SOP baseline itself is authored: the Project Admin gate is on CHANGING a
completed SOP, not on writing one. If the location list should be tighter than
the SOP it belongs to, that is a product call.
Verified one at a time
locations_check 58/58 new
stepper_check 70/70 (11 steps)
browser_check 71/71
a11y 22/22 sop now rings 38 focusable elements
url_state 23/23
autosave 34/34
aggregates 16/16
pipeline 43/43
launcher 58/58
f_items F1-F5 FIXED, F6 REPRODUCES (T7.2)
alembic upgrade / downgrade / upgrade all clean on a throwaway SQLite
file, and the migrated schema matches Base.metadata.create_all
column for column - dev auto-creates and production migrates,
so a divergence between the two is invisible until it ships
.field-error was declared in two page sheets by the end of T5.2 and would have
been three by T5.8, so it moved to theme-light.css. No colour literal added
anywhere: still 0 across all page sheets and inline blocks.
Question for the PR, per CLAUDE.md: the levels are fixed at building / floor /
sector. Micron's floors behave like buildings, which this handles by letting a
project use whichever levels it needs - but a job that wants a fourth level, or
different names for the three, cannot say so. Whether that is worth a
per-project level vocabulary is a product question; the schema would take it
without a migration.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
583 lines
25 KiB
CSS
583 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;
|
|
/* Four points from --wp-status-warning-text and doing the same job, on the
|
|
field view's warn pill. Almost certainly a typo rather than a decision, but
|
|
merging it moves a rendered colour, so T3.2 names it and T3.5 merges it.
|
|
BL-009 / docs/reference/tokens.md section 8-K. */
|
|
--wp-status-warning-text-alt: #8a6d00;
|
|
/* 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);
|
|
|
|
/* -- the second blue -------------------------------------------------------
|
|
#2563d6, not #0f62fe. Fills .sop-inherited — every field a work package
|
|
inherited from its SOP — at 7% alpha, which is why nobody has noticed a
|
|
second brand blue. Named here so it is visible; swapped at T3.5 (BL-008). */
|
|
--wp-sop-inherited-bg: rgba(37, 99, 214, .07);
|
|
}
|
|
|
|
/* 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;
|
|
}
|