theme-light.css is now the only file in html/ that contains a colour literal. The five page stylesheets and all four inline <style> blocks declare names and nothing else. theme-light.css 191 declarations, 175 with a literal value console.css 28 declarations, 0 work-package-suite-styles.css 16 declarations, 0 wp-chrome.css 14 declarations, 0 wp-creation-styles.css 24 declarations, 0 wp-sidenav.css 0 declarations, 0 #0f62fe is declared in one sheet, down from five. The eleven occurrences left inside theme-light.css are Carbon's own v10-to-v11 alias layer, which the inventory records as deliberate and not the S5 defect. Names were kept, because 111 var() references live in .js files across 23 token names and a rename there fails silently - no build error, no console warning, just an unstyled element. The rule the refactor was built on: consolidation is not unification. Where two sheets declared the same value, they collapse. Where they declared DIFFERENT values for one role - the two shadows, the eight status borders doing four jobs, the three mono stacks - each value got its own canonical name and the pair is recorded for T3.5. Picking a winner between two near-identical greys is a rendered change, which this task forbids. The console's zebra stripe is the one that would have bitten: #fafafa is six points from #f4f4f4, and merging them erases the striping on the nine-column user table. Collecting the one-offs in one place made two things countable that were not before: twelve distinct shadows, and a ninth amber (#8a6d00 on the field view, four points from #8e6a00 and doing the same job - BL-009). VERIFICATION - the screenshot done-when could not do the job, so it was replaced. Captured against wave 2, 11 of 14 shots were pixel-identical and 3 were not. Capturing wave 2 against ITSELF produced the same 3 differences at the same bounding box, so those shots cannot distinguish a regression from the clock. Trap 2 in the brief is half wrong: users.html is stable at both widths; the unstable third is the creator at 1440px, and admin's captured page height varies by ~600px between runs (BL-012). So tests/token_check.py was added. It checks what wave 3 actually claims: that every custom property resolves to the same literal, and every element computes the same colours, shadows and type. That is stronger than a screenshot - it covers the hover, focus and disabled rules a screenshot never exercises, and it is deterministic. wave 2 vs T3.2, all 7 pages: 178/178 wave-2 token names resolve identically, +213 new 3,500 elements compute identically, zero added, zero removed 16 tokens differ in notation only (#fff -> #ffffff), which is the duplicate class this task existed to collapse Two detours worth not repeating: the element walk was first keyed by sibling index and reported 55 phantom differences on the SOP page, where three JS-injected overlays append in whichever order their async work finishes (BL-011); and the comparator now normalises notation before reporting, because otherwise it fails on its own success. f_items 5 FIXED / F6 REPRODUCES as expected. browser_check 71/71. ONE DONE-WHEN NOT MET, recorded rather than skipped: "no page stylesheet declares a raw color, spacing or type value". The colour half is met in full. 483 raw spacing values, 281 font-sizes and 65 radii remain inside rules, 492 of them in the creator. That is arithmetic, not effort: the creator's spacing is every integer from 1px to 14px, so no token exists that padding:9px 11px maps to without changing one of the numbers - and this task forbids changing a rendered value. The two requirements are mutually exclusive. Logged as BL-010 for T5.x and T7.1, where those pages are re-laid-out and the values get chosen again. New backlog: BL-009 (ninth amber), BL-010 (raw spacing/type in rules), BL-011 (overlay append race), BL-012 (unstable screenshot targets). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
210 lines
16 KiB
CSS
210 lines
16 KiB
CSS
/* Shared styling for the suite's dense admin pages — the Admin Console and the
|
|
User Directory. Both are mostly tables and toolbars, which is a different job
|
|
from the wizard pages, so they carry this sheet instead of theme-light.css's
|
|
form-heavy one. The palette, the square corners and the type are still Carbon's,
|
|
so the pages read as one product with the rest of the suite.
|
|
|
|
Two scales do all the spacing and all the control sizing; nothing that uses this
|
|
sheet should invent its own. Page-specific rules (per-ID scroll boxes, column
|
|
exceptions) stay in the page that owns them.
|
|
|
|
══ TOKENS ══════════════════════════════════════════════════════════════════
|
|
Names only. Every value comes from theme-light.css, which every page loads
|
|
first — this sheet declares nothing of its own (T3.2 / S5 / C3). The names
|
|
stay because admin.js and users.js read 21 of them from JavaScript, where a
|
|
rename fails silently: no build error, no console warning, just an unstyled
|
|
element. See docs/reference/tokens.md section 9. */
|
|
:root{ --bg:var(--cds-background); --surface:var(--cds-layer); --border:var(--cds-border-subtle);
|
|
--border-strong:var(--cds-border-strong); --text:var(--cds-text-primary);
|
|
--muted:var(--cds-text-secondary); --dim:var(--cds-ui-04); --accent:var(--cds-interactive-01);
|
|
--accent-hover:var(--cds-hover-primary); --accent-soft:var(--cds-highlight);
|
|
--green:var(--cds-support-success); --green-bg:var(--wp-status-success-bg);
|
|
--red:var(--cds-support-error); --red-bg:var(--wp-status-error-bg);
|
|
--amber:var(--wp-status-warning-text); --amber-bg:var(--wp-status-warning-bg);
|
|
--head-bg:var(--cds-layer-accent);
|
|
/* --zebra is six points lighter than --head-bg, not equal to it: collapsing
|
|
the two erases the striping on a nine-column table, which is the one place
|
|
striping is load-bearing. --row-hover is deliberately NOT --accent-soft;
|
|
see the note above table.grid tbody tr:hover. Both are one-offs and both
|
|
are named in docs/reference/tokens.md section 8-A/8-B. */
|
|
--zebra:var(--wp-table-zebra); --row-hover:var(--wp-table-row-hover);
|
|
/* The narrow mono stack, kept as-is: widening it changes the rendered face on
|
|
any machine without IBM Plex Mono installed, and this sheet's two pages are
|
|
the ones a font change would show up on. T3.5 picks one stack (8-H). */
|
|
--mono:var(--wp-font-mono-2);
|
|
--s1:var(--wp-s1); --s2:var(--wp-s2); --s3:var(--wp-s3);
|
|
--s4:var(--wp-s4); --s5:var(--wp-s5); --s6:var(--wp-s6);
|
|
--ctl:var(--wp-ctl); /* every button / input / select that sits in a form row */
|
|
--ctl-sm:var(--wp-ctl-sm); } /* every control that sits inside a table cell */
|
|
*{ box-sizing:border-box; }
|
|
body{ margin:0; font-family:var(--wp-font-sans-2); background:var(--bg); color:var(--text); }
|
|
|
|
/* ══ PAGE ══════════════════════════════════════════════════════════════════════
|
|
1240px, not 860: the user table is nine columns wide and at 860 it spilled
|
|
straight out of its own white card. Wide enough for that table, still a
|
|
readable measure for the prose, which is capped separately. */
|
|
.wrap{ max-width:1240px; margin:0 auto; padding:var(--s6) var(--s5) 80px; }
|
|
h1{ font-size:20px; line-height:1.2; margin:0 0 2px; }
|
|
.sub{ color:var(--muted); font-size:13px; line-height:1.5; margin:0 0 var(--s3); max-width:96ch; }
|
|
a.home{ color:var(--accent); font-size:13px; text-decoration:none; white-space:nowrap; }
|
|
a.home:hover{ text-decoration:underline; }
|
|
|
|
/* ══ CARDS ═════════════════════════════════════════════════════════════════════ */
|
|
.card{ background:var(--surface); border:1px solid var(--border); border-radius:0;
|
|
padding:var(--s4) var(--s5) var(--s5); margin-bottom:var(--s4); }
|
|
/* One card header everywhere: small uppercase accent label on a hairline. The
|
|
scripts also emit h2 for sub-sections inside a card with an inline margin-top —
|
|
the same treatment reads correctly as a divider there, so both get it. */
|
|
.card h2{ font-size:12px; font-weight:600; letter-spacing:.08em; text-transform:uppercase;
|
|
color:var(--accent); margin:0 0 var(--s3); padding-bottom:var(--s2); border-bottom:1px solid var(--border); }
|
|
.wrap code{ font-family:var(--mono); font-size:.92em; background:var(--bg); padding:1px 4px; }
|
|
|
|
/* ══ CONTROLS ══════════════════════════════════════════════════════════════════
|
|
Every button, input and select in a form row is exactly --ctl tall, so a
|
|
toolbar is one clean band instead of a ragged one. */
|
|
button{ font:inherit; font-size:13px; font-weight:600; line-height:1; white-space:nowrap;
|
|
height:var(--ctl); padding:0 var(--s3); border-radius:0; cursor:pointer;
|
|
border:1px solid var(--border-strong); background:var(--surface); color:var(--text); }
|
|
button:hover{ border-color:var(--accent); color:var(--accent); }
|
|
button:focus-visible{ outline:2px solid var(--accent); outline-offset:-3px; }
|
|
button:disabled, button:disabled:hover{ color:var(--dim); border-color:var(--border); background:var(--surface); cursor:default; }
|
|
button.primary{ background:var(--accent); border-color:var(--accent); color:var(--cds-text-on-color); }
|
|
button.primary:hover{ background:var(--accent-hover); border-color:var(--accent-hover); color:var(--cds-text-on-color); }
|
|
button.danger{ border-color:var(--red); color:var(--red); }
|
|
button.danger:hover{ background:var(--red-bg); border-color:var(--red); color:var(--red); }
|
|
.row{ display:flex; gap:var(--s2); flex-wrap:wrap; align-items:center; }
|
|
/* The filter / search / button strip at the top of a card. */
|
|
.toolbar{ display:flex; gap:var(--s2); flex-wrap:wrap; align-items:center; margin:0 0 var(--s3); }
|
|
.toolbar + .banner{ margin-top:0; }
|
|
.urow{ display:flex; gap:var(--s2); flex-wrap:wrap; align-items:center; }
|
|
/* Checkboxes are excluded: they are drawn by the platform and want none of a
|
|
text field's height, padding or border. */
|
|
.toolbar input:not([type=checkbox]), .toolbar select,
|
|
.urow input:not([type=checkbox]), .urow select{
|
|
height:var(--ctl); padding:0 var(--s2); font:inherit; font-size:13px; line-height:normal;
|
|
border:1px solid var(--border-strong); border-radius:0; background:var(--surface); color:var(--text); }
|
|
.toolbar select, .urow select{ cursor:pointer; padding-right:var(--s1); }
|
|
.toolbar input:focus-visible, .toolbar select:focus-visible,
|
|
.urow input:focus-visible, .urow select:focus-visible{ outline:2px solid var(--accent); outline-offset:-2px; }
|
|
.toolbar > input{ flex:1 1 240px; min-width:150px; }
|
|
.urow input:not([type=checkbox]){ flex:1 1 140px; min-width:0; }
|
|
/* Inline checkbox + label, sized to sit on the same line as the buttons. */
|
|
.chk{ display:inline-flex; align-items:center; gap:var(--s2); height:var(--ctl); padding:0 var(--s1);
|
|
font-size:13px; color:var(--muted); white-space:nowrap; cursor:pointer; }
|
|
.chk input{ width:16px; height:16px; margin:0; accent-color:var(--accent); cursor:pointer; }
|
|
|
|
/* ══ FEEDBACK: banners, notes, console output, key/value ════════════════════════ */
|
|
.banner{ margin:var(--s3) 0 0; padding:9px var(--s3); border-radius:0; font-size:13px; font-weight:600;
|
|
line-height:1.4; border:1px solid var(--border); border-left:3px solid var(--border-strong);
|
|
background:var(--surface); color:var(--text); }
|
|
.banner.ok{ background:var(--green-bg); color:var(--green); border-color:var(--wp-status-success-border-a); border-left-color:var(--green); }
|
|
.banner.bad{ background:var(--red-bg); color:var(--red); border-color:var(--wp-status-error-border-a); border-left-color:var(--red); }
|
|
.banner.warn{ background:var(--amber-bg); color:var(--amber); border-color:var(--wp-status-warning-border-a); border-left-color:var(--amber); }
|
|
/* --muted, not --dim: #8d8d8d on white is 3.3:1, under the 4.5:1 floor at 12px,
|
|
and the boxes the scripts fill are themselves .note — their primary toggle
|
|
labels inherit this colour. */
|
|
.note{ font-size:12px; line-height:1.55; color:var(--muted); margin-top:var(--s2); }
|
|
.note strong, .note em{ color:var(--text); }
|
|
pre.out{ background:var(--wp-term-bg); color:var(--wp-term-fg); border-radius:0; padding:var(--s3) var(--s4); font-family:var(--mono);
|
|
font-size:12px; line-height:1.55; white-space:pre-wrap; max-height:340px; overflow:auto; margin:var(--s3) 0 0; }
|
|
pre.out .p{ color:var(--wp-term-pass); font-weight:700; } pre.out .f{ color:var(--wp-term-fail); font-weight:700; }
|
|
table.kv{ border-collapse:collapse; font-size:13px; margin-top:var(--s2); }
|
|
table.kv th{ text-align:left; padding:var(--s1) var(--s5) var(--s1) 0; color:var(--muted); font-weight:600; white-space:nowrap; }
|
|
table.kv td{ padding:var(--s1) 0; font-variant-numeric:tabular-nums; font-weight:700; color:var(--text); }
|
|
|
|
/* ══ DATA TABLES ═══════════════════════════════════════════════════════════════
|
|
table.users is the name admin.js already emits; table.grid is the same object
|
|
under the shared name. One rule set serves both, so existing markup picks up the
|
|
dense styling without being rewritten. border-collapse is separate rather than
|
|
collapse because a collapsed border does not travel with a sticky header. */
|
|
table.grid, table.users{ width:100%; border-collapse:separate; border-spacing:0;
|
|
font-size:13px; color:var(--text); background:var(--surface); }
|
|
table.grid th, table.users th{ position:sticky; top:0; z-index:2; background:var(--head-bg);
|
|
text-align:left; padding:var(--s2) var(--s3); white-space:nowrap;
|
|
font-size:11px; font-weight:600; letter-spacing:.04em; text-transform:uppercase; color:var(--muted);
|
|
box-shadow:inset 0 -1px 0 var(--border); }
|
|
/* Cells never wrap: a wrapped cell turns one user into a 100px tall band and the
|
|
table stops reading as rows. Anything genuinely long truncates (.ell) or is
|
|
exempted by name in the page that owns the table. */
|
|
table.grid td, table.users td{ padding:var(--s1) var(--s3); border-bottom:1px solid var(--border);
|
|
vertical-align:middle; white-space:nowrap; }
|
|
table.grid tbody tr:last-child td, table.users tbody tr:last-child td{ border-bottom:none; }
|
|
table.grid tbody tr:nth-child(even) td, table.users tbody tr:nth-child(even) td{ background:var(--zebra); }
|
|
/* A neutral hover, not --accent-soft: that is .tag.admin's fill, and an "all
|
|
projects" pill sitting on its own colour disappears the moment you hover it. */
|
|
table.grid tbody tr:hover td, table.users tbody tr:hover td{ background:var(--row-hover); }
|
|
/* A row for an account this caller may see but not change. Dimmed as a whole so
|
|
the disabled controls aren't the only clue. */
|
|
table.grid tbody tr.is-locked td, table.users tbody tr.is-locked td{ color:var(--muted); }
|
|
/* Truncation has to hang off a block INSIDE the cell. max-width on a <td> is
|
|
advisory under table-layout:auto — the cell just grows to fit and the ellipsis
|
|
never appears, which is the usual reason this trick looks like it works in the
|
|
stylesheet and doesn't on the page. The scripts emit <td class="ell"><span>. */
|
|
.ell{ max-width:240px; }
|
|
.ell > span{ display:block; max-width:240px; overflow:hidden; text-overflow:ellipsis;
|
|
white-space:nowrap; }
|
|
/* Every action cell the scripts render is a .cellactions, and it must not wrap:
|
|
unwrapped, the three buttons stack and the row grows fourfold. */
|
|
.cellactions{ display:flex; flex-wrap:nowrap; align-items:center; gap:var(--s1); white-space:nowrap; }
|
|
/* Controls that live in a cell are one step smaller, which is what keeps a row at
|
|
~34px instead of ~100px. .chk is form-row sized by default, so it needs saying
|
|
again here or checkbox rows stand 6px taller than the rest. */
|
|
button.mini{ height:var(--ctl-sm); padding:0 var(--s2); font-size:12px; }
|
|
table.grid td .chk, table.users td .chk{ height:var(--ctl-sm); }
|
|
select.role-select{ height:var(--ctl-sm); max-width:170px; padding:0 var(--s1) 0 var(--s2);
|
|
font:inherit; font-size:12px; border:1px solid var(--border-strong); border-radius:0;
|
|
background:var(--surface); color:var(--text); cursor:pointer; }
|
|
select.role-select:hover{ border-color:var(--accent); }
|
|
select.role-select.is-admin{ color:var(--accent); border-color:var(--accent); font-weight:600; }
|
|
select.role-select:disabled{ color:var(--dim); border-color:var(--border); background:var(--bg); cursor:default; }
|
|
.tag{ display:inline-block; padding:1px 8px; border-radius:11px; font-size:11px; font-weight:600;
|
|
line-height:1.55; white-space:nowrap; vertical-align:middle; }
|
|
.tag.admin{ background:var(--accent-soft); color:var(--accent); }
|
|
.tag.super{ background:var(--wp-tag-super-bg); color:var(--wp-tag-super-text); }
|
|
.tag.user{ background:var(--cds-layer-hover); color:var(--muted); }
|
|
.tag.on{ background:var(--green-bg); color:var(--green); }
|
|
.tag.off{ background:var(--red-bg); color:var(--red); }
|
|
.tag.archived{ background:var(--amber-bg); color:var(--amber); }
|
|
.me-tag{ font-size:11px; color:var(--dim); margin-left:6px; white-space:nowrap; }
|
|
|
|
/* A wide table scrolls inside its own box so the page never scrolls sideways, and
|
|
the capped height is what gives the sticky header something to do. */
|
|
.tscroll{ overflow:auto; max-height:min(70vh,640px); overscroll-behavior:contain; }
|
|
|
|
/* ══ MODALS ════════════════════════════════════════════════════════════════════
|
|
The project-access dialog, shared by both pages. */
|
|
.modal-ov{ position:fixed; inset:0; background:var(--wp-scrim-cool-modal); display:flex; align-items:center;
|
|
justify-content:center; z-index:10002; padding:var(--s5); }
|
|
.modal-box{ background:var(--surface); border-radius:0; max-width:660px; width:100%; max-height:82vh;
|
|
display:flex; flex-direction:column; overflow:hidden; box-shadow:var(--wp-shadow-modal); }
|
|
.modal-head{ padding:var(--s3) var(--s4); border-bottom:1px solid var(--border); font-weight:700; }
|
|
.modal-body{ padding:var(--s3) var(--s4); overflow:auto; }
|
|
.modal-foot{ padding:var(--s3) var(--s4); border-top:1px solid var(--border);
|
|
display:flex; gap:var(--s2); justify-content:flex-end; }
|
|
.pickrow{ display:flex; align-items:center; gap:var(--s3); padding:var(--s2) var(--s1);
|
|
border-bottom:1px solid var(--border); font-size:13px; }
|
|
.pickrow:last-child{ border-bottom:none; }
|
|
.pickrow > label{ display:flex; align-items:center; gap:var(--s2); flex:1; min-width:0; cursor:pointer; }
|
|
.pickrow > label > span{ overflow:hidden; text-overflow:ellipsis; }
|
|
|
|
/* ══ GATES & WARNINGS ══════════════════════════════════════════════════════════ */
|
|
.gate-overlay{ position:fixed; inset:0; background:var(--bg); display:flex; align-items:center; justify-content:center; padding:var(--s5); z-index:9999; }
|
|
.gate-box{ background:var(--surface); border:1px solid var(--border); border-radius:0; padding:var(--s6); max-width:380px; width:100%; box-shadow:var(--wp-shadow-gate); }
|
|
.gate-box h2{ margin:0 0 var(--s1); padding:0; border:0; font-size:17px; text-transform:none; letter-spacing:0; color:var(--text); }
|
|
.gate-box p{ color:var(--muted); font-size:13px; margin:0 0 var(--s4); }
|
|
.gate-msg{ color:var(--red); font-size:12px; min-height:16px; margin-bottom:var(--s2); }
|
|
.secwarn{ background:var(--amber-bg); color:var(--amber); border:1px solid var(--amber); border-radius:0; padding:9px 13px; font-size:12px; margin-bottom:var(--s4); }
|
|
|
|
/* ══ NARROW SCREENS ════════════════════════════════════════════════════════════
|
|
The page itself must never scroll sideways; the wide tables scroll inside their
|
|
own box instead, and there they get the full page height to do it. */
|
|
@media (max-width:900px){
|
|
.wrap{ padding:var(--s4) var(--s3) 60px; }
|
|
.card{ padding:var(--s3) var(--s4) var(--s4); }
|
|
.toolbar > input{ flex:1 1 100%; }
|
|
.tscroll{ max-height:none; }
|
|
}
|
|
@media (max-width:620px){
|
|
.urow input, .urow select, .urow button{ flex:1 1 100%; }
|
|
}
|