S1 has two halves and they are easy to conflate.
One is that validation was a native dialog: "Please complete all required
fields: Project Name, Number, Client, Division, and Site Location." names five
fields at once, highlights none of them, and scrolls nowhere. The other is that
validateStep guarded steps 1, 5 and 6 while the MARKUP marks required fields on
1, 3, 5, 6 and 7 - so two steps' asterisks meant nothing at all, which is worse
than no asterisk.
Both fixed, and the second is the one worth measuring: the probe reads the
required-field list out of work-package-suite.html rather than out of STEP_GATES,
because a probe that read the table would agree with whatever the table says and
prove nothing. Both notations count - an asterisk in a <label>, and the red span
beside step 3's role titles.
Now: an error per FIELD, rendered at it, associated by aria-describedby, marked
aria-invalid, announced through role="alert", and the first one focused and
scrolled into view. The error boxes are BUILT from the gate table rather than
written into the markup twelve times - adding a required field is one row, and
its error element, its association and its announcement all follow. A
markup-side error box somebody forgets to add is an error nobody ever sees.
An error clears as you type rather than on the next submit. An error still
showing over a field you have just corrected teaches people to ignore errors.
And nothing paints a step you have not tried to leave: the rail asks
stepGateMet(), which reads the same fields and marks none of them.
The thirteen dialogs
Every one is now the thing it should have been - an error at the field it is
about, or an announcement in a live region with the role T4.5 established:
errors interrupt, confirmations do not.
A dialog is not merely ugly. It blocks the page, cannot be placed or styled, a
screen reader can present it only as a modal interruption, and it is one OK
button whatever it says - so "sample data loaded" and "you cannot do that"
arrived identically.
Two deserve naming. The empty-comment alert became an inline error on the
feedback textarea. And showAnalytics() was a confirm() carrying the entire
usage summary as its body - a wall of text in a dialog whose only dismissal
was also the download button. The summary is the useful part, so it is shown,
with the download offered as an action beside it. That function has no caller
in the wizard's markup (the "Usage data" button is the creator's, calling the
creator's own showAnalytics), and it was converted rather than deleted:
deleting a feature is not what this task was asked to do, and its dialog
counted toward the number this task has to drive to zero.
html/work-package-suite.html #wp-toast, an error box on the textarea
html/work-package-suite-app.js STEP_GATES widened; per-field messages;
ensureErrorBoxes; wizardToast; 13 removals
html/work-package-suite-styles.css .wp-toast
tests/validation_check.py new - 81 checks
tests/stepper_check.py its "the alert T5.8 still owns" check now
asserts the opposite, by name
Done when
[x] every step with required fields validates them - 5 steps, from the markup
[x] each error renders at its field and is associated via aria-describedby
[x] submitting an invalid step focuses AND scrolls to the first error
(scroll checked by bounding box, not by trusting scrollIntoView)
[x] errors announce to screen readers
[x] the wizard's native dialog count is 0
The count, recorded both ways because BL-017 says the metric counts prose
work-package-suite-app.js 0 raw, 0 with comments stripped
app-wide 64 raw, 64 stripped, against wave 0's 79
wp-creation-app.js 43 (wave 7), users.js 10 and
admin.js 6 and index.html 5 (wave 9)
The wizard contributes none of what is left, which the probe asserts rather
than leaving to the total.
Verified one at a time
validation_check 81/81 new
stepper_check 71/71
sections_check 88/88
browser_check 71/71
a11y 22/22
url_state 23/23
autosave 34/34
locations 58/58
aggregates 16/16
pipeline 43/43
launcher 58/58
f_items F1-F5 FIXED, F6 REPRODUCES (T7.2)
No colour literal added. The toast says "error" by a red rule AND by staying
until dismissed where a confirmation times out - two channels, not one (C1).
Question for the PR, per CLAUDE.md: step 3's two role TITLES are validated
because the markup marks them required, but the two role NAME pickers beside
them are not marked and so are not gated. A sign-off role with nobody in it is
arguably the more useful thing to catch. The markup is what was built to; if the
intent was the names, that is two rows in STEP_GATES.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1016 lines
33 KiB
CSS
1016 lines
33 KiB
CSS
/* Names only. Every value comes from theme-light.css, which this page loads
|
|
first — this sheet declares nothing of its own (T3.2 / S5 / C3). The names
|
|
stay because work-package-suite-app.js reads ten of them from JavaScript,
|
|
where a rename fails silently. See docs/reference/tokens.md section 9. */
|
|
:root {
|
|
--primary: var(--cds-interactive-01);
|
|
--primary-light: var(--cds-highlight);
|
|
--success: var(--cds-support-success);
|
|
--warning: var(--wp-status-warning-text);
|
|
--warning-bg: var(--wp-status-warning-bg);
|
|
--danger: var(--cds-support-error);
|
|
--text: var(--cds-text-primary);
|
|
--text-light: var(--cds-text-secondary);
|
|
--text-dim: var(--cds-text-helper); /* S11: was --cds-ui-04 (#8d8d8d, 3.32:1) */
|
|
--border: var(--cds-border-subtle);
|
|
--border-strong: var(--cds-border-strong);
|
|
--bg: var(--cds-background);
|
|
--bg-card: var(--cds-layer);
|
|
--appbar: var(--wp-appbar-bg);
|
|
--shadow: var(--wp-shadow-none);
|
|
/* Neutral, where the creator's --shadow-lg is cool-tinted. Same geometry in
|
|
both — 0 4px 16px — so the two can be merged later without moving anything;
|
|
merging is still a rendered change, so not here. tokens.md section 8-F. */
|
|
--shadow-lg: var(--wp-shadow-lg-neutral);
|
|
}
|
|
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
body {
|
|
font-family: var(--wp-font-sans);
|
|
color: var(--text);
|
|
background: var(--bg);
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.app-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-height: 100vh;
|
|
}
|
|
|
|
/* HEADER — dark UI Shell bar
|
|
Four groups share this bar, not two: .header-left and .header-right are in the
|
|
markup, and wp-chrome.js injects the project switcher and search between them
|
|
while auth-guard.js appends the user menu after. At 1024px that is more than
|
|
fits on one 48px line, and a fixed height meant the overflow had nowhere to go
|
|
but on top of its neighbours. min-height + wrap lets the bar grow instead.
|
|
One row still measures exactly 48px, so nothing moves at desk width. */
|
|
.header {
|
|
background: var(--appbar);
|
|
color: var(--wp-appbar-fg);
|
|
padding: 0 16px;
|
|
min-height: 48px;
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
row-gap: 6px;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
/* Sizes to its content and never narrower than the logo.
|
|
This was `flex: 1; min-width: 0`, which put it in a dead tie with the chrome
|
|
wp-chrome.js injects as its sibling (`flex: 1 1 auto; min-width: 0`): both
|
|
claimed the same run of the bar, both were allowed to shrink to nothing, and
|
|
the chrome's wider content won every time. .header-left computed to 0 while
|
|
its `flex-shrink: 0` logo kept its 106px and overflowed — so the project
|
|
switcher rendered straight across the logo (F3).
|
|
Now the chrome is the only one that grows, and `min-width: auto` restores the
|
|
content-based floor, which is the logo plus the gap: the inner title block
|
|
keeps its own `min-width: 0`, so the project name still gives way first, by
|
|
the ellipsis .header-subtitle already carries. Truncation policy itself is
|
|
B2's, not this task's. */
|
|
.header-left {
|
|
flex: 0 1 auto;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 14px;
|
|
min-width: auto;
|
|
}
|
|
|
|
/* Prime logo (white-background wordmark) sits in a white chip on the dark bar */
|
|
.logo {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: var(--cds-ui-01);
|
|
padding: 4px 8px;
|
|
border-radius: 4px;
|
|
text-decoration: none;
|
|
transition: opacity 0.2s;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.logo:hover { opacity: 0.92; }
|
|
.logo img { height: 24px; width: auto; display: block; }
|
|
|
|
.logo-icon {
|
|
width: 36px;
|
|
height: 36px;
|
|
background: var(--primary-light);
|
|
border-radius: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 18px;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.header-title {
|
|
font-size: 15px;
|
|
font-weight: 600;
|
|
margin-bottom: 0;
|
|
color: var(--wp-appbar-fg);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.header-subtitle {
|
|
font-size: 12px;
|
|
color: var(--wp-appbar-fg-dim);
|
|
min-height: 16px;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
/* Holds its natural size. Left to the default `flex-shrink: 1` it was squeezed
|
|
narrower than its buttons, so "Load Sample" ran underneath "Feedback". The
|
|
chrome between the two groups is the only flexible one, which is what gives
|
|
the three a defined order of giving way: right keeps its buttons, left keeps
|
|
its logo, the middle absorbs the difference. */
|
|
.header-right {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
flex: 0 0 auto;
|
|
}
|
|
|
|
.header-button {
|
|
padding: 7px 14px;
|
|
background: transparent;
|
|
color: var(--wp-appbar-fg);
|
|
border: 1px solid var(--wp-appbar-border);
|
|
border-radius: 0;
|
|
cursor: pointer;
|
|
font-size: 14px;
|
|
font-weight: 400;
|
|
transition: background 0.15s, border-color 0.15s;
|
|
}
|
|
|
|
.header-button:hover {
|
|
background: var(--wp-appbar-hover);
|
|
border-color: var(--wp-appbar-border);
|
|
}
|
|
|
|
/* .step-counter — the "1 / 10" pill — is gone with A4/S9. The step rail carries
|
|
position, progress and reachability; a badge in the app bar carried one third
|
|
of that and was the only thing on the page that did. */
|
|
|
|
/* MAIN NAVIGATION — underline tabs */
|
|
.main-nav {
|
|
display: flex;
|
|
gap: 0;
|
|
padding: 0 16px;
|
|
background: var(--bg-card);
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
|
|
.nav-tab {
|
|
padding: 13px 18px;
|
|
background: none;
|
|
border: none;
|
|
border-bottom: 3px solid transparent;
|
|
border-radius: 0;
|
|
cursor: pointer;
|
|
font-size: 15px;
|
|
font-weight: 400;
|
|
color: var(--text-light);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
transition: background 0.15s, color 0.15s;
|
|
}
|
|
|
|
.nav-tab:hover {
|
|
background: var(--bg);
|
|
color: var(--text);
|
|
}
|
|
|
|
.nav-tab.active {
|
|
background: none;
|
|
color: var(--text);
|
|
border-bottom-color: var(--primary);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.tab-icon { font-size: 16px; }
|
|
|
|
/* CONTENT AREA
|
|
The SOP wizard reads better with a bound on line length, but 1000px on a 1920
|
|
screen wasted half the display — and it also squeezed the embedded Work Package
|
|
Creator (an iframe living in here) into a ~930px column with its own scrollbar
|
|
inside the page's. Wider cap for the wizard; the embedded tools go full-bleed
|
|
(see .content-area.embed-full below). */
|
|
.content-area {
|
|
flex: 1;
|
|
padding: 2rem;
|
|
max-width: 1700px;
|
|
margin: 0 auto;
|
|
width: 100%;
|
|
}
|
|
|
|
/* Work Package Creation / Dashboard: the iframe fills the window below the app
|
|
chrome and owns the only scrollbar, so the creator's sticky save bar and
|
|
navigator drawer position against a real viewport instead of scrolling away. */
|
|
.content-area.embed-full {
|
|
/* `flex: none` matters: .content-area is a column flex item with `flex: 1`, whose
|
|
flex-basis:0% overrides `height` and leaves the used height INDEFINITE — so a
|
|
child's `height:100%` resolves to auto and the iframe collapses to its 150px
|
|
default. Opting out of flex sizing makes the height definite. */
|
|
flex: none;
|
|
max-width: none;
|
|
padding: 0;
|
|
height: calc(100vh - var(--wp-chrome-h, 96px));
|
|
overflow: hidden;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
.content-area.embed-full > .tool.active {
|
|
flex: 1 1 auto;
|
|
min-height: 0; /* let it shrink instead of overflowing the shell */
|
|
height: 100%;
|
|
}
|
|
#wp-frame {
|
|
width: 100%;
|
|
border: 0;
|
|
min-height: calc(100vh - 200px);
|
|
}
|
|
#wp-frame.fill {
|
|
display: block;
|
|
height: 100%;
|
|
min-height: 0;
|
|
}
|
|
/* No page scrollbar while a full-bleed tool is open — the iframe scrolls. */
|
|
body.embed-full { overflow: hidden; }
|
|
|
|
.tool {
|
|
display: none;
|
|
}
|
|
|
|
.tool.active {
|
|
display: block;
|
|
}
|
|
|
|
/* ══ STEP RAIL — vertical stepper (A4 / S9 / C1) ═══════════════════════════════
|
|
Replaces `.step-nav > .steps-container > .step-item`: ten <div onclick> chips
|
|
in a horizontal scroller. They were unreachable by keyboard, said nothing
|
|
about progress, and the "1 / 10" pill in the app bar was doing the one job
|
|
they should have been doing.
|
|
|
|
The rail is a column beside the form rather than a strip above it, so all ten
|
|
steps and their states are visible while you work in one. Below 900px there is
|
|
no room for a second column, so it collapses — see the breakpoint at the foot
|
|
of this block.
|
|
|
|
Spacing comes from --wp-s* (the suite's only real spacing scale) rather than
|
|
fresh literals; type does not, because there is no type token to consume yet.
|
|
That is BL-010 and it is unchanged in kind by this task. */
|
|
#tool-sop.active {
|
|
display: grid;
|
|
grid-template-columns: 15rem minmax(0, 1fr);
|
|
gap: var(--wp-s5);
|
|
align-items: start;
|
|
}
|
|
|
|
.step-rail {
|
|
grid-column: 1;
|
|
grid-row: 1 / span 2; /* beside the form AND its navigation bar */
|
|
/* Step 5 is roughly three screens tall. A rail that scrolls away is a rail you
|
|
have to hunt for, which is how the app ended up with a counter in the bar. */
|
|
position: sticky;
|
|
top: var(--wp-s4);
|
|
background: var(--bg-card);
|
|
border: 1px solid var(--border);
|
|
border-radius: 0;
|
|
padding: var(--wp-s3);
|
|
}
|
|
|
|
.step-rail-list {
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.step-rail-item { margin: 0; }
|
|
|
|
/* Every step is a real button, in the tab order, activated by Enter and Space
|
|
for free. Arrow keys, Home and End are added in work-package-suite-app.js. */
|
|
.step-btn {
|
|
width: 100%;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--wp-s3);
|
|
padding: var(--wp-s2);
|
|
background: none;
|
|
border: none;
|
|
border-left: 3px solid transparent;
|
|
border-radius: 0;
|
|
text-align: left;
|
|
font: inherit;
|
|
color: var(--text-light);
|
|
cursor: pointer;
|
|
transition: background 0.15s, color 0.15s;
|
|
}
|
|
|
|
.step-btn:hover { background: var(--bg); color: var(--text); }
|
|
|
|
.step-btn-marker {
|
|
flex: none;
|
|
width: var(--wp-ctl-sm);
|
|
height: var(--wp-ctl-sm);
|
|
border: 1px solid var(--border-strong);
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
line-height: 1;
|
|
}
|
|
|
|
.step-btn-body { min-width: 0; display: flex; flex-direction: column; }
|
|
.step-btn-label { font-size: 13px; font-weight: 500; }
|
|
|
|
/* The state in words. C1: completed / current / unavailable must be tellable
|
|
apart without colour, so each carries a label here AND a marker shape — filled
|
|
disc, filled disc with a tick, dashed outline — not a hue alone. */
|
|
.step-btn-state { font-size: 11px; color: var(--text-light); }
|
|
.step-btn-state:empty { display: none; }
|
|
|
|
.step-btn.is-current {
|
|
background: var(--primary-light);
|
|
border-left-color: var(--primary);
|
|
color: var(--text);
|
|
font-weight: 600;
|
|
}
|
|
.step-btn.is-current .step-btn-marker {
|
|
background: var(--primary);
|
|
border-color: var(--primary);
|
|
color: var(--cds-text-on-color);
|
|
}
|
|
|
|
/* Green because this is a status, not an action — see tokens.md section 12.
|
|
Green never fills a button in this suite; it fills the badge on one. */
|
|
.step-btn.is-complete .step-btn-marker {
|
|
background: var(--success);
|
|
border-color: var(--success);
|
|
color: var(--cds-text-on-color);
|
|
}
|
|
|
|
/* Still focusable and still in the tab order: a control removed from the tab
|
|
order is a control a keyboard user cannot even discover, and "you cannot go
|
|
there yet, here is why" is worth reaching. aria-disabled, not disabled. */
|
|
.step-btn.is-locked { cursor: not-allowed; }
|
|
.step-btn.is-locked .step-btn-marker { border-style: dashed; }
|
|
|
|
.step-rail-msg {
|
|
margin: var(--wp-s2) 0 0;
|
|
font-size: 12px;
|
|
color: var(--danger);
|
|
}
|
|
.step-rail-msg:empty { display: none; }
|
|
|
|
/* Wide widths: the list IS the control, so the disclosure has nothing to do. */
|
|
.step-rail-toggle { display: none; }
|
|
|
|
/* STEP CONTENT */
|
|
.step-content {
|
|
grid-column: 2;
|
|
grid-row: 1;
|
|
background: var(--bg-card);
|
|
padding: 2rem;
|
|
border: 1px solid var(--border);
|
|
border-radius: 0;
|
|
}
|
|
|
|
.step { display: none; }
|
|
|
|
.step h2 {
|
|
font-size: 22px;
|
|
font-weight: 400;
|
|
letter-spacing: -0.01em;
|
|
margin-bottom: 0.75rem;
|
|
color: var(--text);
|
|
}
|
|
|
|
.notice {
|
|
font-size: 13px;
|
|
color: var(--text-light);
|
|
background: var(--primary-light);
|
|
padding: 0.75rem 1rem;
|
|
border-radius: 0;
|
|
margin-bottom: 1.5rem;
|
|
border-left: 4px solid var(--primary);
|
|
}
|
|
|
|
/* FIELDS
|
|
The wizard's fields were one per row, which looked right in a 1000px column but
|
|
stretches a text input across the screen now that the content area is wide. Flow
|
|
them into as many ~340px columns as fit; `.col1` still forces a single column for
|
|
the fields that genuinely want the width (long text, textareas). */
|
|
.field-grid {
|
|
display: grid;
|
|
gap: 1.5rem;
|
|
grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
|
|
}
|
|
|
|
.field-grid.col1 { grid-template-columns: 1fr; }
|
|
|
|
.field {
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.field label {
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
margin-bottom: 0.5rem;
|
|
color: var(--text);
|
|
}
|
|
|
|
/* An editable field is white on --border-strong, which is what an editable field
|
|
is on every other page in the suite. It used to be filled with var(--bg) — this
|
|
sheet's PAGE BACKGROUND, #f4f4f4 — so an empty required field was indistinguishable
|
|
from a locked one and people did not type in them (F5/A3).
|
|
|
|
The cause was the wizard declaring its own token set and therefore never seeing
|
|
--cds-field, which theme-light.css had been supplying to this page all along.
|
|
T1.5 reached past the local tokens with a fallback; T3.2 removed the local tokens
|
|
entirely, so the fallback is now dead code and the canonical token is simply
|
|
consumed. No field-specific colour is declared on this page. */
|
|
.field input,
|
|
.field select,
|
|
.field textarea {
|
|
padding: 0.75rem;
|
|
border: 1px solid var(--border-strong);
|
|
border-radius: 0;
|
|
font-size: 14px;
|
|
font-family: inherit;
|
|
color: var(--text);
|
|
background: var(--cds-field);
|
|
transition: border-color 0.2s;
|
|
}
|
|
|
|
/* Editable fields being white means genuinely locked ones have to say so, or the
|
|
signal inverts rather than being fixed. --cds-field-02 is the theme's own
|
|
secondary field surface and matches .locked-field in the creator.
|
|
The border is deliberately the same on both states: identical --border-strong is
|
|
what makes a field read as a field, and the fill is what carries locked versus
|
|
editable. Verified as #ffffff vs #f4f4f4 against an identical border. */
|
|
.field input:disabled,
|
|
.field select:disabled,
|
|
.field textarea:disabled,
|
|
.field input[readonly],
|
|
.field textarea[readonly] {
|
|
background: var(--cds-field-02);
|
|
color: var(--text-light);
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
/* One focus ring for every control on this page (BL-002).
|
|
All three focus rules here removed the outline. Two replaced it with a 3px
|
|
var(--primary-light) glow — #edf5ff against a #ffffff field, which is a 1.05:1
|
|
edge: a faint halo on the card, invisible on the field itself. The third,
|
|
.seq-step input.seq-label, removed the outline with no replacement at all.
|
|
CLAUDE.md allows outline:none only with a replacement of at least equal
|
|
visibility, so two were marginal and one was a straight violation.
|
|
|
|
This is Carbon's ring and the one console.css:69 and wp-chrome.css:206 already
|
|
draw — 2px of --cds-focus inset over the control's own edge — so it is the app's
|
|
existing idiom rather than a fourth. border-color stays as a second cue and as
|
|
the fallback if outline is ever suppressed. */
|
|
.field input:focus,
|
|
.field select:focus,
|
|
.field textarea:focus,
|
|
.user-pick:focus,
|
|
.seq-step input.seq-label:focus {
|
|
outline: 2px solid var(--cds-focus);
|
|
outline-offset: -2px;
|
|
border-color: var(--primary);
|
|
}
|
|
|
|
/* Helper text under a field, by either of the two names the markup uses: <small>
|
|
inside a .field, and .field-hint anywhere. They were two rules with byte-identical
|
|
declarations — the duplication that produced the bug this block used to carry a
|
|
comment about, in miniature. One rule now, so they cannot drift apart again.
|
|
|
|
The colour is deliberately left as --text-dim. #8d8d8d on white measures 3.3:1,
|
|
under the 4.5:1 floor, and console.css:103 already rejected it for exactly this
|
|
job — but propagating that fix is T4.6, which re-measures rather than inheriting
|
|
either published figure. Changing it here would be a rendered change in a wave
|
|
that must produce none, and would take T4.6's decision away from it. */
|
|
.field small,
|
|
.field-hint {
|
|
font-size: 12px;
|
|
color: var(--text-dim);
|
|
margin-top: 0.25rem;
|
|
}
|
|
.field-hint strong { color: var(--text-light); }
|
|
|
|
/* The sign-off name pickers sit outside .field, so they got no form styling at all.
|
|
They carried the F5/A3 defect too, and were missed because the review counted
|
|
inputs inside .field: --bg fill on a --border hairline is the grey-on-grey that
|
|
made empty required fields read as locked. These are enabled selects and now look
|
|
like every other enabled control in the suite — --cds-field on --border-strong.
|
|
Focus is the shared ring above. */
|
|
.user-pick {
|
|
padding: 0.75rem; border: 1px solid var(--border-strong); border-radius: 0;
|
|
font-size: 14px; font-family: inherit; color: var(--text); background: var(--cds-field);
|
|
}
|
|
|
|
/* ROLES */
|
|
.required-roles {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.role-required {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 1rem;
|
|
background: var(--bg);
|
|
border-radius: 0;
|
|
border: 1px solid var(--border);
|
|
}
|
|
|
|
.role-checkbox {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.role-checkbox input[type="checkbox"] {
|
|
width: 18px;
|
|
height: 18px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.role-checkbox label {
|
|
margin: 0;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* WP TYPES */
|
|
#wp-types-table { overflow-x: auto; }
|
|
|
|
.wp-type-row {
|
|
display: grid;
|
|
grid-template-columns: 1.1fr 80px 1.3fr 1.6fr 1.2fr;
|
|
gap: 0.85rem;
|
|
align-items: center;
|
|
padding: 0.75rem 1rem;
|
|
background: var(--bg);
|
|
border-radius: 0;
|
|
margin-bottom: 0.5rem;
|
|
border: 1px solid var(--border);
|
|
}
|
|
|
|
.wp-types-header {
|
|
display: grid;
|
|
grid-template-columns: 1.1fr 80px 1.3fr 1.6fr 1.2fr;
|
|
gap: 0.85rem;
|
|
padding: 0.5rem 1rem;
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
letter-spacing: 0.04em;
|
|
text-transform: uppercase;
|
|
color: var(--text-light);
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
/* BUTTONS — roles are documented in docs/reference/tokens.md section 12 (A5) */
|
|
/* primary */
|
|
.add-btn {
|
|
padding: 0.7rem 1.25rem;
|
|
background: var(--wp-btn-primary-bg);
|
|
color: var(--wp-btn-primary-fg);
|
|
border: none;
|
|
border-radius: 0;
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: background 0.2s;
|
|
}
|
|
|
|
.add-btn:hover { background: var(--wp-btn-primary-hover); }
|
|
|
|
/* secondary */
|
|
.nav-btn {
|
|
padding: 0.7rem 1.4rem;
|
|
background: var(--wp-btn-secondary-bg);
|
|
border: 1px solid var(--wp-btn-secondary-border);
|
|
border-radius: 0;
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
color: var(--wp-btn-secondary-fg);
|
|
cursor: pointer;
|
|
transition: all 0.15s;
|
|
}
|
|
|
|
.nav-btn:hover { border-color: var(--wp-btn-secondary-hover-fg); color: var(--wp-btn-secondary-hover-fg); background: var(--bg); }
|
|
|
|
/* A5: this was green — the loudest control in the suite, on a button whose job
|
|
is to save a form. The completion it announced is still announced, on the
|
|
launcher card's border and status line, which is where a status belongs. */
|
|
.nav-btn.primary {
|
|
background: var(--wp-btn-primary-bg);
|
|
color: var(--wp-btn-primary-fg);
|
|
border-color: var(--wp-btn-primary-bg);
|
|
}
|
|
|
|
.nav-btn.primary:hover { background: var(--wp-btn-primary-hover); border-color: var(--wp-btn-primary-hover); color: var(--wp-btn-primary-fg); }
|
|
|
|
.nav-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
|
|
/* ══ LOCATION LIST — CR-005 (step 11) ═════════════════════════════════════════
|
|
Building / floor / sector, configured per project. The code beside each name
|
|
is the thing CR-018 rolls cost up by, and it is shown rather than hidden so it
|
|
is obvious that renaming a value does not move it. */
|
|
.loc-actions { display: flex; gap: var(--wp-s2); flex-wrap: wrap; margin-top: var(--wp-s3); }
|
|
|
|
.loc-report { margin-top: var(--wp-s3); font-size: 13px; }
|
|
.loc-report:empty { display: none; }
|
|
.loc-report-line { margin: 0 0 var(--wp-s2); }
|
|
.loc-report-group { margin-top: var(--wp-s3); }
|
|
.loc-report-title { font-size: 12px; font-weight: 600; color: var(--text-light); }
|
|
.loc-report-list { margin: var(--wp-s1) 0 0; padding-left: var(--wp-s5); }
|
|
.loc-report-list li { margin-bottom: var(--wp-s1); color: var(--text-light); }
|
|
.loc-line { font-weight: 600; color: var(--text); }
|
|
/* A report that lost rows says so in its own right, not only by wording. */
|
|
.loc-report.is-problem { border-left: 3px solid var(--warning); padding-left: var(--wp-s3);
|
|
background: var(--warning-bg); padding-top: var(--wp-s2); padding-bottom: var(--wp-s2); }
|
|
|
|
#loc-list { display: flex; flex-direction: column; margin-top: var(--wp-s3); }
|
|
.loc-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--wp-s3);
|
|
padding: var(--wp-s2);
|
|
border-bottom: 1px solid var(--border);
|
|
flex-wrap: wrap;
|
|
}
|
|
/* Indentation is the hierarchy, and it is not the only cue — .loc-level names
|
|
the level in words beside it (C1: never colour or position alone). */
|
|
.loc-depth-1 { padding-left: var(--wp-s5); }
|
|
.loc-depth-2 { padding-left: var(--wp-s6); }
|
|
.loc-level {
|
|
flex: none; min-width: 68px;
|
|
font-size: 11px; font-weight: 600; letter-spacing: .04em;
|
|
color: var(--text-light);
|
|
}
|
|
.loc-name {
|
|
flex: 1 1 180px; min-width: 0;
|
|
padding: var(--wp-s1) var(--wp-s2);
|
|
border: 1px solid var(--border-strong);
|
|
border-radius: 0;
|
|
background: var(--cds-field);
|
|
color: var(--text);
|
|
font: inherit;
|
|
font-size: 13px;
|
|
}
|
|
.loc-code {
|
|
flex: none;
|
|
font-family: var(--wp-font-mono);
|
|
font-size: 12px;
|
|
color: var(--text-light);
|
|
}
|
|
.loc-toggle { display: flex; align-items: center; gap: var(--wp-s1); font-size: 12px; color: var(--text-light); }
|
|
/* A deactivated value is still listed — that is how it is brought back — but it
|
|
reads as out of use, by strikethrough as well as by the unticked box. */
|
|
.loc-row.is-off .loc-name { text-decoration: line-through; color: var(--text-light); }
|
|
.loc-row.is-off .loc-code { text-decoration: line-through; }
|
|
|
|
.loc-addrow {
|
|
display: flex; align-items: center; gap: var(--wp-s2); flex-wrap: wrap;
|
|
margin-top: var(--wp-s4);
|
|
}
|
|
.loc-addlabel { font-size: 12px; font-weight: 600; color: var(--text-light); }
|
|
.loc-addrow select, .loc-addrow input {
|
|
padding: var(--wp-s2);
|
|
border: 1px solid var(--border-strong);
|
|
border-radius: 0;
|
|
background: var(--cds-field);
|
|
color: var(--text);
|
|
font: inherit;
|
|
font-size: 13px;
|
|
}
|
|
.loc-addrow input { flex: 1 1 200px; min-width: 0; }
|
|
|
|
/* ══ SECTION TOGGLES — CR-006 (step 12) ═══════════════════════════════════════
|
|
Ten rows, each a real <label> wrapping a real checkbox, so the whole row is a
|
|
target and the browser gives the keyboard and screen-reader behaviour for
|
|
free. The state is said in words as well as by the tick (C1). */
|
|
#section-toggles { display: flex; flex-direction: column; margin-top: var(--wp-s3); }
|
|
|
|
.section-toggle {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: var(--wp-s3);
|
|
padding: var(--wp-s3);
|
|
border: 1px solid var(--border);
|
|
border-left: 3px solid var(--success);
|
|
background: var(--bg-card);
|
|
margin-bottom: -1px; /* collapse the hairline between rows */
|
|
cursor: pointer;
|
|
}
|
|
.section-toggle:hover { background: var(--bg); }
|
|
.section-toggle input { flex: none; width: 18px; height: 18px; margin-top: 2px; }
|
|
.section-toggle-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: var(--wp-s1); }
|
|
.section-toggle-name { font-size: 14px; font-weight: 600; color: var(--text); }
|
|
.section-toggle-note { font-size: 12px; color: var(--text-light); }
|
|
.section-toggle-state {
|
|
flex: none;
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
color: var(--text-light);
|
|
white-space: nowrap;
|
|
}
|
|
/* Off is said three ways: the box is unticked, the row says "Not used", and the
|
|
rule down its left edge goes from green to grey. Colour is the one nobody has
|
|
to be able to see. */
|
|
.section-toggle.is-off { border-left-color: var(--border-strong); }
|
|
.section-toggle.is-off .section-toggle-name { color: var(--text-light); }
|
|
|
|
/* CR-002: a field inside a section, not an eleventh section. Indented and
|
|
smaller so the nesting is visible, and it inherits every other rule above so
|
|
the two cannot look like different kinds of control. */
|
|
.field-toggle {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: var(--wp-s3);
|
|
padding: var(--wp-s2) var(--wp-s3) var(--wp-s2) var(--wp-s6);
|
|
border: 1px solid var(--border);
|
|
border-left: 3px solid var(--success);
|
|
background: var(--bg);
|
|
margin-bottom: -1px;
|
|
cursor: pointer;
|
|
}
|
|
.field-toggle:hover { background: var(--cds-layer-hover); }
|
|
.field-toggle input { flex: none; width: 16px; height: 16px; margin-top: 2px; }
|
|
.field-toggle .section-toggle-name { font-size: 13px; }
|
|
.field-toggle.is-off { border-left-color: var(--border-strong); }
|
|
.field-toggle.is-off .section-toggle-name { color: var(--text-light); }
|
|
|
|
/* ══ ANNOUNCEMENTS — S1 / T5.8 ════════════════════════════════════════════════
|
|
Where the wizard's thirteen native dialogs went. Fixed to the bottom of the
|
|
viewport so it is visible from any scroll position on a step that is three
|
|
screens tall, and never over the step navigation, which is where somebody's
|
|
attention is when a validation message fires. */
|
|
.wp-toast {
|
|
position: fixed;
|
|
left: 50%;
|
|
bottom: var(--wp-s5);
|
|
transform: translateX(-50%);
|
|
z-index: 1400; /* over the drawer scrim, under a modal */
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: var(--wp-s3);
|
|
max-width: min(640px, calc(100vw - 2 * var(--wp-s4)));
|
|
padding: var(--wp-s3) var(--wp-s4);
|
|
background: var(--cds-ui-05);
|
|
color: var(--cds-text-on-color);
|
|
box-shadow: var(--wp-shadow-toast);
|
|
font-size: 13px;
|
|
line-height: 1.5;
|
|
}
|
|
.wp-toast[hidden] { display: none; }
|
|
.wp-toast-text { flex: 1 1 auto; min-width: 0; }
|
|
/* An error looks different, not just louder: a red rule down its edge, and it
|
|
stays until dismissed where a confirmation times out (C1 — never colour
|
|
alone, and the persistence is the second channel). */
|
|
.wp-toast.is-alert { border-left: 4px solid var(--danger); }
|
|
.wp-toast-action,
|
|
.wp-toast-close {
|
|
flex: none;
|
|
background: none;
|
|
border: 1px solid var(--wp-appbar-border);
|
|
border-radius: 0;
|
|
color: var(--cds-text-on-color);
|
|
font: inherit;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
padding: var(--wp-s1) var(--wp-s2);
|
|
cursor: pointer;
|
|
}
|
|
.wp-toast-close { border-color: transparent; padding: var(--wp-s1); }
|
|
.wp-toast-action:hover,
|
|
.wp-toast-close:hover { background: var(--wp-appbar-hover); }
|
|
|
|
/* .field-error is declared once, in theme-light.css — the launcher's create form
|
|
and T5.8's step validation use the same component. */
|
|
|
|
/* NAVIGATION */
|
|
.step-navigation {
|
|
grid-column: 2;
|
|
grid-row: 2;
|
|
display: flex;
|
|
gap: 1rem;
|
|
justify-content: space-between;
|
|
padding: 1.5rem;
|
|
background: var(--bg-card);
|
|
border-radius: 0;
|
|
box-shadow: var(--shadow);
|
|
}
|
|
|
|
/* COMMENTS */
|
|
.comments-section {
|
|
margin-top: 2rem;
|
|
padding-top: 1.5rem;
|
|
border-top: 2px solid var(--border);
|
|
}
|
|
|
|
.comments-toggle {
|
|
padding: 0.5rem 1rem;
|
|
background: var(--primary-light);
|
|
color: var(--primary);
|
|
border: 1px solid var(--primary);
|
|
border-radius: 0;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.comments-toggle:hover { background: var(--primary); color: var(--cds-text-on-color); }
|
|
|
|
/* CONSTRUCTION SEQUENCE (drag & drop) */
|
|
#sequence-list { display: flex; flex-direction: column; gap: 8px; }
|
|
.seq-step {
|
|
display: flex; align-items: center; gap: 12px; padding: 11px 14px;
|
|
background: var(--bg-card); border: 1px solid var(--border); border-radius: 0;
|
|
box-shadow: var(--shadow); transition: border-color .12s, box-shadow .12s, opacity .12s;
|
|
}
|
|
.seq-step:hover { border-color: var(--primary); }
|
|
.seq-step.dragging { opacity: .35; border-style: dashed; }
|
|
.seq-step.drag-over { border-color: var(--primary); box-shadow: 0 0 0 3px var(--primary-light); }
|
|
.seq-handle { cursor: grab; color: var(--text-dim); font-size: 16px; line-height: 1; user-select: none; flex-shrink: 0; }
|
|
.seq-handle:active { cursor: grabbing; }
|
|
.seq-num {
|
|
width: 24px; height: 24px; flex-shrink: 0; border-radius: 50%; background: var(--primary-light); color: var(--primary);
|
|
font-size: 11px; font-weight: 600; display: flex; align-items: center; justify-content: center;
|
|
}
|
|
.seq-step input.seq-label {
|
|
border: 1px solid transparent; background: transparent; font-size: 14px; padding: 5px 8px; color: var(--text); flex: 1; border-radius: 4px;
|
|
}
|
|
/* Fill only — the ring itself is the shared rule above, which this used to opt out
|
|
of entirely with a bare `outline: none`. */
|
|
.seq-step input.seq-label:focus { background: var(--bg); }
|
|
/* danger, filled: a 28px square is too small for an outline to read */
|
|
.seq-del {
|
|
background: var(--wp-btn-danger-fill-bg); color: var(--wp-btn-danger-fill-fg); border: none; border-radius: 4px;
|
|
width: 28px; height: 28px; cursor: pointer; font-weight: 600; flex-shrink: 0;
|
|
}
|
|
.seq-arrow { text-align: center; color: var(--text-dim); font-size: 13px; line-height: .4; margin: -2px 0; }
|
|
.seq-step.gate { border-color: var(--warning); background: var(--warning-bg); border-style: dashed; }
|
|
.seq-step.gate .seq-label { color: var(--warning); font-weight: 500; }
|
|
.seq-gate-badge {
|
|
flex-shrink: 0; padding: 3px 9px; border-radius: 20px; background: var(--warning); color: var(--cds-text-on-color);
|
|
font-size: 9px; font-weight: 600; letter-spacing: .08em; white-space: nowrap;
|
|
}
|
|
|
|
/* STEP COMMENTS DROPDOWN */
|
|
.comments-dropdown {
|
|
position: fixed;
|
|
top: 80px;
|
|
right: 2rem;
|
|
width: 360px;
|
|
max-width: calc(100vw - 2rem);
|
|
background: var(--bg-card);
|
|
border: 1px solid var(--border);
|
|
border-radius: 0;
|
|
box-shadow: var(--shadow-lg);
|
|
padding: 1.25rem;
|
|
z-index: 1200;
|
|
}
|
|
|
|
.comments-dropdown-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.comments-dropdown-close {
|
|
background: none;
|
|
border: none;
|
|
font-size: 16px;
|
|
cursor: pointer;
|
|
color: var(--text-light);
|
|
line-height: 1;
|
|
}
|
|
|
|
.comments-dropdown-close:hover { color: var(--text); }
|
|
|
|
/* MODAL */
|
|
.modal {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background: var(--wp-scrim-neutral);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
z-index: 1000;
|
|
}
|
|
|
|
.modal-content {
|
|
background: var(--bg-card);
|
|
border-radius: 0;
|
|
padding: 2rem;
|
|
max-width: 600px;
|
|
max-height: 80vh;
|
|
overflow-y: auto;
|
|
box-shadow: var(--shadow-lg);
|
|
}
|
|
|
|
.modal-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 1.5rem;
|
|
border-bottom: 1px solid var(--border);
|
|
padding-bottom: 1rem;
|
|
}
|
|
|
|
.modal-header h3 { font-size: 18px; margin: 0; }
|
|
|
|
.modal-close {
|
|
background: none;
|
|
border: none;
|
|
font-size: 20px;
|
|
cursor: pointer;
|
|
color: var(--text-dim);
|
|
}
|
|
|
|
.modal-close:hover { color: var(--text); }
|
|
|
|
/* UTILITY */
|
|
.sub-heading {
|
|
font-size: 14px;
|
|
font-weight: 700;
|
|
color: var(--text);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.05em;
|
|
}
|
|
|
|
/* RESPONSIVE */
|
|
|
|
/* Below 900px there is no room for a rail beside the form. Ten vertical rows
|
|
stacked above the fields is most of a 390px screen before you reach the first
|
|
input, so the rail becomes a disclosure: where you are, tap to see the rest.
|
|
It is still the same ten buttons — nothing is hidden from the keyboard that is
|
|
not also hidden from the mouse. */
|
|
@media (max-width: 899px) {
|
|
#tool-sop.active { grid-template-columns: minmax(0, 1fr); gap: var(--wp-s4); }
|
|
.step-rail,
|
|
.step-content,
|
|
.step-navigation { grid-column: 1; grid-row: auto; }
|
|
/* The disclosure button carries the box at this width; the rail itself is only
|
|
a wrapper, and two nested borders read as two controls. */
|
|
.step-rail { position: static; background: none; border: 0; padding: 0; }
|
|
.step-rail:not(.is-collapsed) .step-rail-list {
|
|
background: var(--bg-card);
|
|
border: 1px solid var(--border);
|
|
padding: var(--wp-s2);
|
|
}
|
|
|
|
.step-rail-toggle {
|
|
display: flex;
|
|
width: 100%;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: var(--wp-s2);
|
|
padding: var(--wp-s3);
|
|
background: var(--bg-card);
|
|
border: 1px solid var(--border-strong);
|
|
border-radius: 0;
|
|
font: inherit;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
color: var(--text);
|
|
cursor: pointer;
|
|
}
|
|
.step-rail-toggle-chev { transition: transform 0.15s; }
|
|
.step-rail.is-collapsed .step-rail-list { display: none; }
|
|
.step-rail:not(.is-collapsed) .step-rail-toggle { margin-bottom: var(--wp-s2); }
|
|
.step-rail:not(.is-collapsed) .step-rail-toggle-chev { transform: rotate(180deg); }
|
|
/* Gloved hands on a tablet: 44px minimum, which the 26px marker plus 8px of
|
|
padding does not reach on its own. */
|
|
.step-btn { min-height: 44px; }
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.header { height: auto; flex-direction: column; align-items: stretch; text-align: center; gap: 0.75rem; padding: 12px 16px; }
|
|
.main-nav { flex-wrap: wrap; }
|
|
.content-area { padding: 1rem; }
|
|
.step-content { padding: 1rem; }
|
|
.wp-type-row { grid-template-columns: 1fr; }
|
|
.wp-types-header { display: none; }
|
|
.step-navigation { flex-direction: column; }
|
|
}
|