@felloinnovations/brand-kit 0.5.0 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +29 -9
- package/CHANGELOG.md +110 -0
- package/README.md +14 -6
- package/design-system/01-how-claude-decides.md +5 -3
- package/design-system/03-colour.md +22 -3
- package/design-system/05-space-and-layout.md +2 -1
- package/design-system/08-choosing-components.md +20 -4
- package/design-system/09-charts.md +7 -4
- package/design-system/10-patterns.md +1 -1
- package/design-system/11-page-recipes.md +40 -1
- package/design-system/13-motion.md +4 -4
- package/design-system/15-ai-experiences.md +11 -0
- package/design-system/17-decisions.md +51 -0
- package/design-system/components/AppShell/README.md +89 -21
- package/design-system/components/AppShell/preview.html +148 -3
- package/design-system/components/AppShell/style.css +47 -0
- package/design-system/components/Avatar/README.md +6 -3
- package/design-system/components/Avatar/preview.html +2 -1
- package/design-system/components/Avatar/style.css +3 -0
- package/design-system/components/Banner/README.md +9 -5
- package/design-system/components/Banner/preview.html +9 -0
- package/design-system/components/Banner/style.css +3 -0
- package/design-system/components/Button/README.md +29 -2
- package/design-system/components/Button/preview.html +33 -1
- package/design-system/components/Button/style.css +21 -0
- package/design-system/components/Calendar/README.md +148 -0
- package/design-system/components/Calendar/preview.html +449 -0
- package/design-system/components/Calendar/style.css +61 -0
- package/design-system/components/Card/README.md +13 -0
- package/design-system/components/Card/preview.html +23 -0
- package/design-system/components/Card/style.css +19 -0
- package/design-system/components/ChatCard/README.md +74 -3
- package/design-system/components/ChatCard/preview.html +126 -25
- package/design-system/components/ChatCard/style.css +6 -0
- package/design-system/components/CheckRow/README.md +1 -1
- package/design-system/components/CheckRow/preview.html +1 -1
- package/design-system/components/CheckRow/style.css +1 -1
- package/design-system/components/Chip/README.md +16 -7
- package/design-system/components/Chip/preview.html +25 -1
- package/design-system/components/Chip/style.css +5 -0
- package/design-system/components/Combobox/README.md +10 -4
- package/design-system/components/Combobox/preview.html +19 -4
- package/design-system/components/Combobox/style.css +7 -1
- package/design-system/components/CommentThread/README.md +138 -0
- package/design-system/components/CommentThread/preview.html +134 -0
- package/design-system/components/CommentThread/style.css +41 -0
- package/design-system/components/DateInput/README.md +38 -4
- package/design-system/components/DateInput/preview.html +50 -2
- package/design-system/components/DateInput/style.css +6 -0
- package/design-system/components/Dialog/README.md +26 -3
- package/design-system/components/Dialog/preview.html +80 -0
- package/design-system/components/Dialog/style.css +28 -3
- package/design-system/components/EmptyState/README.md +1 -1
- package/design-system/components/Fold/README.md +21 -0
- package/design-system/components/Fold/preview.html +44 -1
- package/design-system/components/Fold/style.css +30 -0
- package/design-system/components/InlineEdit/README.md +116 -0
- package/design-system/components/InlineEdit/preview.html +135 -0
- package/design-system/components/InlineEdit/style.css +34 -0
- package/design-system/components/Input/README.md +20 -1
- package/design-system/components/Input/preview.html +24 -0
- package/design-system/components/Input/style.css +13 -1
- package/design-system/components/KanbanBoard/README.md +69 -5
- package/design-system/components/KanbanBoard/preview.html +129 -0
- package/design-system/components/KanbanBoard/style.css +32 -1
- package/design-system/components/ListDetail/preview.html +1 -1
- package/design-system/components/Matrix/README.md +1 -1
- package/design-system/components/Matrix/preview.html +1 -1
- package/design-system/components/Motion/README.md +2 -2
- package/design-system/components/Motion/preview.html +8 -8
- package/design-system/components/PlanTimeline/README.md +144 -0
- package/design-system/components/PlanTimeline/preview.html +109 -0
- package/design-system/components/PlanTimeline/style.css +64 -0
- package/design-system/components/README.md +12 -7
- package/design-system/components/ScoreHero/README.md +2 -2
- package/design-system/components/ScoreHero/style.css +1 -1
- package/design-system/components/ShareBar/README.md +1 -1
- package/design-system/components/ShareBar/style.css +1 -1
- package/design-system/components/Skeleton/README.md +82 -17
- package/design-system/components/Skeleton/preview.html +84 -2
- package/design-system/components/Skeleton/style.css +9 -14
- package/design-system/components/Starter/README.md +1 -1
- package/design-system/components/Starter/preview.html +1 -1
- package/design-system/components/Stepper/README.md +81 -5
- package/design-system/components/Stepper/preview.html +64 -1
- package/design-system/components/Stepper/style.css +29 -0
- package/design-system/components/StreakCalendar/README.md +1 -0
- package/design-system/components/Table/README.md +8 -4
- package/design-system/components/Table/preview.html +16 -1
- package/design-system/components/Table/style.css +3 -1
- package/design-system/components/TextDiff/README.md +96 -0
- package/design-system/components/TextDiff/preview.html +42 -0
- package/design-system/components/TextDiff/style.css +21 -0
- package/design-system/components/TickStrip/README.md +1 -1
- package/design-system/components/TickStrip/preview.html +1 -1
- package/design-system/components/TickStrip/style.css +1 -1
- package/design-system/components/Timeline/README.md +4 -2
- package/design-system/components/TrendLine/README.md +38 -7
- package/design-system/components/TrendLine/preview.html +140 -3
- package/design-system/components/TrendLine/style.css +12 -2
- package/design-system/components/Well/README.md +1 -1
- package/design-system/components/Well/style.css +2 -2
- package/design-system/components/_house.css +1 -1
- package/design-system/components/bundle.css +501 -30
- package/design-system/tokens.json +17 -7
- package/dist/fello-core.css +2 -2
- package/dist/fello.css +2 -2
- package/dist/fello.scoped.css +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/scoped.js +2 -2
- package/dist/tokens.d.ts +16 -8
- package/dist/tokens.js +16 -8
- package/package.json +7 -2
- package/scripts/check-drift.mjs +325 -0
- package/scripts/token-model.mjs +2 -1
- package/tokens/_dark.css +3 -2
- package/tokens/_product.css +13 -11
- package/tokens/palettes.ts +1 -1
- package/tokens/shadcn-compat.css +1 -1
- package/tokens/shadcn-compat.hsl.css +1 -1
- package/tokens/tokens.css +23 -15
- package/tokens/tokens.md +56 -50
- package/tokens/tokens.root.css +23 -15
- package/tokens/tokens.ts +17 -9
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/* ---- InlineEdit: words that become a field in place. The rest state is a button holding the words, the editing state
|
|
2
|
+
an input or a textarea; both are __value, so they share one box (4 / 8 inside, pulled back 8 at the sides so the words
|
|
3
|
+
stay on the content edge) and the words' own type (the wrapper wears ws-section, ws-title or ws-body), and nothing jumps when
|
|
4
|
+
one turns into the other. At rest: no ground, a navy wash under the pointer, a pencil mark that shows on hover and
|
|
5
|
+
focus (always on touch, where there is no hover). Editing: the field's white ground, its fine edge and the focus
|
|
6
|
+
ring, the words keeping their own colour and weight. ---- */
|
|
7
|
+
.ws-inline-edit { display: grid; gap: var(--space-3xs); min-width: 0; }
|
|
8
|
+
.ws-inline-edit__value { display: block; box-sizing: border-box; width: calc(100% + var(--space-xs) * 2); min-width: 0; margin: 0 calc(var(--space-xs) * -1); padding: var(--space-xxs) var(--space-xs); border: 0; border-radius: var(--ws-radius-control); background: transparent; color: inherit; font: inherit; letter-spacing: inherit; text-align: start; text-wrap: inherit; overflow-wrap: anywhere; }
|
|
9
|
+
button.ws-inline-edit__value { cursor: text; transition: background-color var(--ws-duration-hover) var(--ws-ease-soft); }
|
|
10
|
+
@media (hover: hover) { button.ws-inline-edit__value:hover { background: var(--ws-hover); } }
|
|
11
|
+
button.ws-inline-edit__value:active { background: var(--ws-hover-strong); }
|
|
12
|
+
/* No value yet: the placeholder's words, still the target. */
|
|
13
|
+
.ws-inline-edit__value[data-empty] { color: var(--color-text-placeholder); }
|
|
14
|
+
/* The pencil after the words (a ws-icon at the words' size, 16 at most, so it sits on their line): a hint, never the only
|
|
15
|
+
target (the whole box is). */
|
|
16
|
+
.ws-inline-edit__mark { width: min(1em, var(--ws-icon)); height: min(1em, var(--ws-icon)); margin-inline-start: var(--space-xxs); color: var(--color-text-muted); opacity: 0; transition: opacity var(--ws-duration-hover) var(--ws-ease-soft); }
|
|
17
|
+
@media (hover: hover) { button.ws-inline-edit__value:hover .ws-inline-edit__mark { opacity: 1; } }
|
|
18
|
+
button.ws-inline-edit__value:focus-visible .ws-inline-edit__mark { opacity: 1; }
|
|
19
|
+
@media (pointer: coarse) { .ws-inline-edit__mark { opacity: 1; } }
|
|
20
|
+
/* Editing: the same box as a field. A textarea grows with its words where the browser can (field-sizing). */
|
|
21
|
+
.ws-inline-edit__value:is(input, textarea) { background: var(--ws-field); box-shadow: var(--ws-shadow-hairline-in); }
|
|
22
|
+
/* The ring is drawn inside the box, so it never crosses a label or a line 4 above or below. */
|
|
23
|
+
.ws-inline-edit__value:focus-visible, .ws-inline-edit__value--typing { outline-offset: calc(var(--ws-focus-width) * -1); }
|
|
24
|
+
textarea.ws-inline-edit__value { resize: none; field-sizing: content; }
|
|
25
|
+
.ws-inline-edit__value:is(input, textarea)::placeholder { color: var(--color-text-placeholder); }
|
|
26
|
+
.ws-inline-edit__value:is(input, textarea)[aria-invalid="true"] { box-shadow: inset 0 0 0 var(--ws-line) var(--ws-word-needs); }
|
|
27
|
+
/* Touch: the box is 40 tall at least, at rest and editing alike (so it still does not jump), and the editing words are
|
|
28
|
+
16 at least: a field under 16 px makes iOS zoom the page. */
|
|
29
|
+
@media (pointer: coarse) {
|
|
30
|
+
.ws-inline-edit__value { min-height: var(--ws-touch); }
|
|
31
|
+
.ws-inline-edit__value:is(input, textarea) { font-size: max(1em, var(--ws-text-value)); }
|
|
32
|
+
}
|
|
33
|
+
/* The editing state drawn still, for a preview or a mockup: the focus ring a real focus draws. */
|
|
34
|
+
.ws-inline-edit__value--typing { outline: var(--ws-focus-width) solid var(--ws-focus-color); }
|
|
@@ -6,6 +6,8 @@ edge while typing.
|
|
|
6
6
|
- One line of text, a number, an email or a phone number needs typing: a lead's email, a listing's address.
|
|
7
7
|
- A page or panel needs a search box (the kit has no dedicated search field: `Input` with `type="search"` and its icon).
|
|
8
8
|
- A form field needs a label above it and, below it, a hint or an error.
|
|
9
|
+
- Someone sets a colour (an organisation's brand colours, a palette): the colour field, a swatch that opens the
|
|
10
|
+
browser's picker beside its hex.
|
|
9
11
|
|
|
10
12
|
## Not for
|
|
11
13
|
|
|
@@ -21,6 +23,9 @@ edge while typing.
|
|
|
21
23
|
- Label: 12px medium, `--color-text-body`, above the field.
|
|
22
24
|
- Words typed: 14 / 20, `--ws-field-ink`; placeholder `--color-text-placeholder` (#6E6E6E).
|
|
23
25
|
- Search (`ws-search`): 36 tall (40 on touch), the 16 px glass 8 px in at the left, the words 32 in.
|
|
26
|
+
- Colour field (`ws-colour-field`): a swatch (`__swatch`, a native `<input type="color">`) square at the field's height
|
|
27
|
+
(32, 40 on touch), 4 px corners, the 0.5 px card edge drawn on the swatch so a white or cream colour still shows its
|
|
28
|
+
edge; 8 apart (`--space-xs`), the hex in a plain `ws-input` beside it, tabular figures.
|
|
24
29
|
- Message below: 12px, `--color-text-muted` for a hint, `--ws-word-needs` for an error, with a warning icon before it.
|
|
25
30
|
|
|
26
31
|
## Variants and sizes
|
|
@@ -29,6 +34,7 @@ edge while typing.
|
|
|
29
34
|
| --- | --- | --- |
|
|
30
35
|
| Default | 32px, as above | every field on a card or a form |
|
|
31
36
|
| Search | 36px (`--ws-control-h-md`), 4px corners, a hairline, a search icon inside at the left | a page's or a panel's search box (the system's rule) |
|
|
37
|
+
| Colour | a swatch square beside the hex field | a colour someone sets (brand colours, a palette) |
|
|
32
38
|
|
|
33
39
|
## States
|
|
34
40
|
|
|
@@ -53,6 +59,12 @@ edge while typing.
|
|
|
53
59
|
|
|
54
60
|
- Keyboard: a native `<input>`; Tab moves to and from it like any field; typing and browser autofill work unmodified.
|
|
55
61
|
- Pointer / touch: clicking or tapping anywhere in the box focuses it; the touch floor is 40px.
|
|
62
|
+
- Colour field: the swatch opens the browser's own picker (keyboard and screen readers come with it); picking writes
|
|
63
|
+
the hex into the field, and typing a valid hex (`#` and six characters, any case) moves the swatch. An invalid hex
|
|
64
|
+
marks the field (`aria-invalid`, the error message under the pair, "Needs six characters after the #, like
|
|
65
|
+
#FEFBF9") and leaves the swatch on the last good colour. The swatch takes an `aria-label` naming the colour
|
|
66
|
+
("Primary colour, pick from the palette"); the visible label belongs to the hex field. The swatch's fill is the
|
|
67
|
+
chosen colour itself, the one colour on a kit screen no token sets.
|
|
56
68
|
- Motion: the edge over the hover beat (200 ms) on `--ws-ease-soft`; an error message rises in (`ws-arrive`); still
|
|
57
69
|
under reduced motion.
|
|
58
70
|
|
|
@@ -103,9 +115,16 @@ edge while typing.
|
|
|
103
115
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="7" cy="7" r="4.5"/><path d="M13 13l-2.8-2.8"/></svg>
|
|
104
116
|
<input class="ws-input ws-search" type="search" placeholder="Search" aria-label="Search leads">
|
|
105
117
|
</span>
|
|
118
|
+
<div class="ws-field">
|
|
119
|
+
<label class="ws-label" for="primary">Primary colour</label>
|
|
120
|
+
<span class="ws-colour-field">
|
|
121
|
+
<input class="ws-colour-field__swatch" type="color" value="#093555" aria-label="Primary colour, pick from the palette">
|
|
122
|
+
<input class="ws-input" id="primary" value="#093555" spellcheck="false" autocomplete="off" maxlength="7">
|
|
123
|
+
</span>
|
|
124
|
+
</div>
|
|
106
125
|
```
|
|
107
126
|
|
|
108
127
|
## Styles
|
|
109
128
|
|
|
110
129
|
`design-system/components/Input/style.css`: `ws-input` (and `--typing`, the focus drawn still), `ws-search-wrap`,
|
|
111
|
-
`ws-search`. `ws-field`, `ws-label` and `ws-field-msg` are the house's (`_house.css`, Fields).
|
|
130
|
+
`ws-search`, `ws-colour-field` and its `__swatch`. `ws-field`, `ws-label` and `ws-field-msg` are the house's (`_house.css`, Fields).
|
|
@@ -47,6 +47,30 @@
|
|
|
47
47
|
<input class="ws-input" id="i-dis" value="FEL-00412" disabled>
|
|
48
48
|
</div>
|
|
49
49
|
|
|
50
|
+
<div class="ws-field">
|
|
51
|
+
<span class="ws-muted">Colour field: the swatch opens the picker, the hex beside it</span>
|
|
52
|
+
<label class="ws-label" for="i-colour">Primary colour</label>
|
|
53
|
+
<span class="ws-colour-field">
|
|
54
|
+
<input class="ws-colour-field__swatch" type="color" value="#093555" aria-label="Primary colour, pick from the palette">
|
|
55
|
+
<input class="ws-input" id="i-colour" value="#093555" spellcheck="false" autocomplete="off" inputmode="text" maxlength="7">
|
|
56
|
+
</span>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<div class="ws-field">
|
|
60
|
+
<span class="ws-muted">Colour field, a light colour (the swatch keeps its edge) and an invalid hex</span>
|
|
61
|
+
<label class="ws-label" for="i-colour-bad">Page colour</label>
|
|
62
|
+
<span class="ws-colour-field">
|
|
63
|
+
<input class="ws-colour-field__swatch" type="color" value="#fefbf9" aria-label="Page colour, pick from the palette">
|
|
64
|
+
<input class="ws-input" id="i-colour-bad" value="#FEFBF" spellcheck="false" autocomplete="off" maxlength="7" aria-invalid="true" aria-describedby="i-colour-bad-msg">
|
|
65
|
+
</span>
|
|
66
|
+
<p class="ws-field-msg ws-field-msg--error" id="i-colour-bad-msg">
|
|
67
|
+
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
68
|
+
<path d="M8 2.3 14.6 13.5H1.4L8 2.3Z"/><path d="M8 6.4v3"/><path d="M8 11.5v.05"/>
|
|
69
|
+
</svg>
|
|
70
|
+
Needs six characters after the #, like #FEFBF9
|
|
71
|
+
</p>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
50
74
|
<div class="ws-field">
|
|
51
75
|
<span class="ws-muted">Search box (36px, 4px corners)</span>
|
|
52
76
|
<label class="ws-label" for="i-search">Search leads</label>
|
|
@@ -18,6 +18,18 @@
|
|
|
18
18
|
.ws-search-wrap { position: relative; display: flex; min-width: 0; }
|
|
19
19
|
.ws-search-wrap svg { position: absolute; left: var(--space-xs); top: 50%; width: var(--ws-icon); height: var(--ws-icon); transform: translateY(-50%); color: var(--color-text-muted); pointer-events: none; }
|
|
20
20
|
.ws-search { height: var(--ws-control-h-md); padding: 0 var(--space-sm) 0 var(--space-xl); }
|
|
21
|
+
/* The colour field (#29): a swatch that opens the browser's colour picker (a native <input type="color">) and the hex
|
|
22
|
+
field beside it, 8 apart. The swatch is a square the field's height, its 4 px corners and 0.5 px edge drawn inside, so
|
|
23
|
+
a white or cream colour still shows its edge; its fill is the input's own value, the one colour the kit paints that
|
|
24
|
+
no token sets. An invalid hex marks the field and leaves the swatch on the last good colour. */
|
|
25
|
+
.ws-colour-field { display: flex; align-items: center; gap: var(--space-xs); min-width: 0; }
|
|
26
|
+
.ws-colour-field__swatch { flex-shrink: 0; width: var(--ws-control-h); height: var(--ws-control-h); padding: 0; border: 0; border-radius: var(--ws-radius-control); background: none; cursor: pointer; }
|
|
27
|
+
.ws-colour-field__swatch::-webkit-color-swatch-wrapper { padding: 0; }
|
|
28
|
+
/* The edge sits on the swatch itself: the browser paints the colour over anything drawn on the input's own box. */
|
|
29
|
+
.ws-colour-field__swatch::-webkit-color-swatch { border: var(--ws-hairline) solid var(--ws-edge-card); border-radius: inherit; }
|
|
30
|
+
.ws-colour-field__swatch::-moz-color-swatch { border: var(--ws-hairline) solid var(--ws-edge-card); border-radius: inherit; }
|
|
31
|
+
.ws-colour-field__swatch:disabled { cursor: not-allowed; opacity: 0.5; }
|
|
32
|
+
.ws-colour-field .ws-input { font-variant-numeric: tabular-nums; }
|
|
21
33
|
/* Touch: 40 tall (the house's touch height, restated after the field's own so it still wins), the words 16 so a phone
|
|
22
34
|
does not zoom the page into the field when it takes focus (iOS zooms any field under 16 px). */
|
|
23
|
-
@media (pointer: coarse) { .ws-input, .ws-search { height: var(--ws-touch); font-size: var(--ws-text-value); } }
|
|
35
|
+
@media (pointer: coarse) { .ws-input, .ws-search { height: var(--ws-touch); font-size: var(--ws-text-value); } .ws-colour-field__swatch { width: var(--ws-touch); height: var(--ws-touch); } }
|
|
@@ -24,7 +24,29 @@ Columns of cards for a pipeline: a stage per column, a card per thing moving thr
|
|
|
24
24
|
- A column head: at least 32 tall, the stage in 13 / 18 medium navy (`__title`), its count beside it in muted figures
|
|
25
25
|
(`__count`), sentence case, flush with the cards' edge.
|
|
26
26
|
- A card: `ws-card` (a link or a button when it opens the thing), 12 inside, a 28 px `Avatar` and the name (14 / 20
|
|
27
|
-
medium navy) 8 apart, a 13 / 18 muted meta line 4 under.
|
|
27
|
+
medium navy) 8 apart, a 13 / 18 muted meta line 4 under. That is the person card: a lead, an applicant.
|
|
28
|
+
- The standard card (a ticket, an onboarding, a deal: anything with a kind, a figure, an owner and acts): one anatomy
|
|
29
|
+
for every board, its slots in this order, 4 apart (8 before the chips and the foot), each left out when it has nothing
|
|
30
|
+
to say, never shown blank and never reordered:
|
|
31
|
+
1. The head (`__card-head`, 18 tall): the kind and its figure on the left (`__kind`, 13 / 18 medium muted words, the
|
|
32
|
+
figure in a `strong`, semibold navy tabular: "Expansion **$28K**"), the time in stage on the right (`__age`, 13 / 18
|
|
33
|
+
muted tabular figures: "4d", "6h").
|
|
34
|
+
2. The title (`a.__open`, 14 / 20 medium navy, two lines at most, whole in its `title`): the card's link. Its cover
|
|
35
|
+
(`::after`) spans the card, so the whole card opens the thing and the focus ring is drawn round the card, 2 px out.
|
|
36
|
+
Never underlined, at rest, under the pointer or anywhere else.
|
|
37
|
+
3. The meta (`__meta`): whose it is and the last thing that happened, one short line ("Harbor Lane Realty, last
|
|
38
|
+
activity Nov 2").
|
|
39
|
+
4. The chips (`__chips`, `Chip`s 4 apart, three at most): a status ("Deferred", sunk), a milestone ("TTV1 15d", live),
|
|
40
|
+
a blocker that needs the person ("No meeting booked", needs).
|
|
41
|
+
5. The foot (`__card-foot`, 28 tall): the people on the left, a 28 px `Avatar` and the owner's name (`__name`, 13 / 18
|
|
42
|
+
body) or an `AvatarStack` for a team; on the right (`__card-end`), a tally (`__tally`: a 12 px icon and muted
|
|
43
|
+
tabular figures, "1/6", its meaning in words for a screen reader) and one `IconButton` `small`, the card's act
|
|
44
|
+
("Move to Implementing").
|
|
45
|
+
6. An act under it (`__card-act`): one `Button` `well` `small`, full width, only when the stage waits on the person
|
|
46
|
+
for an act the board cannot show by moving the card ("Hand over", "Approve"). Never navy: a board holds many
|
|
47
|
+
cards, and a page has one navy button.
|
|
48
|
+
The card is an `article` (it holds acts, so it cannot be a link): the acts sit above the title's cover, so a press on
|
|
49
|
+
one is the act, not the card.
|
|
28
50
|
- A held card (`__card--dragging`, with `aria-grabbed="true"`; the app sets both while a card is held): North's
|
|
29
51
|
floating shadow (`--ws-shadow-pop`, with the dark rim) and a 1 degree tilt; the only thing on the board that floats.
|
|
30
52
|
- The column under a held card (`__column--over`, set by the app while the card is over it): one step darker
|
|
@@ -37,6 +59,8 @@ Columns of cards for a pipeline: a stage per column, a card per thing moving thr
|
|
|
37
59
|
| Variant | What changes |
|
|
38
60
|
| --- | --- |
|
|
39
61
|
| Default | Columns as tall as their cards; the board as tall as its tallest column |
|
|
62
|
+
| Person card | `a.ws-card.__card`: a face, a name and a meta line; the whole card is the link |
|
|
63
|
+
| Standard card | `article.ws-card.__card` with the six slots above; the title is the link and covers the card. Every board in an app uses it, so a ticket board and an onboarding board read alike |
|
|
40
64
|
| `--fill` | The board takes the rest of a height-bound column (`flex: 1; min-height: 0`); every column runs the full height and scrolls on its own, its head stuck at the very top (over the column's 8 px padding, so no card shows above it; the title sits where the plain board's does), so the sideways bar sits at the window's foot (K16). A column that holds more than it shows fades its last 24 px into its ground, lifting at the end; a card reached by Tab scrolls clear of the stuck head |
|
|
41
65
|
| Phone | The same row of 280 columns, the next one peeking in, snapping a stage at a time |
|
|
42
66
|
|
|
@@ -49,10 +73,15 @@ Columns of cards for a pipeline: a stage per column, a card per thing moving thr
|
|
|
49
73
|
darkens a step. The app adds and removes both classes; the kit only draws them.
|
|
50
74
|
- **Dropped**: the card glides to its new place and the cards it leaves close up, never teleports (North's "List rows
|
|
51
75
|
reorder", `springs.reorder` .4 / 0): wrap the move in `reorder()` (below).
|
|
76
|
+
- **Stalled** (a standard card past its stage's time): the time in stage takes the needs words (`--ws-word-needs`,
|
|
77
|
+
medium) and the 12 px pause mark before it, with "stalled" in words for a screen reader and in its `title` ("54 days
|
|
78
|
+
in this stage, stalled"). Nothing else on the card changes: no coloured rail, no tinted card.
|
|
79
|
+
- **Needs you** (something blocks the card and it is the person's to fix): a `needs` chip saying what ("No meeting
|
|
80
|
+
booked", "Due Nov 12"), first in the chips. A card the stage waits on adds the well act under the foot.
|
|
52
81
|
- **Empty**: the column keeps its full width, its head and its count; a muted sentence in place of cards. It never
|
|
53
82
|
hides, which would reflow every column beside it mid-task.
|
|
54
|
-
- **Loading**: ghost cards the height of a card
|
|
55
|
-
once; still under reduced motion.
|
|
83
|
+
- **Loading**: ghost cards the height of a card under the shimmer (`__ghost ws-shimmer`), the heads shown at
|
|
84
|
+
once; still under reduced motion. A board of standard cards takes the taller ghost (`__ghost--card`).
|
|
56
85
|
- **Scrolled**: the far-edge fade while stages are hidden, gone at the end; in `--fill`, a column's foot fades while cards
|
|
57
86
|
are hidden below it, gone at its end.
|
|
58
87
|
- **Touch**: a card is one target (its whole box); dragging on touch needs a press-and-hold, not drawn here.
|
|
@@ -80,6 +109,10 @@ Columns of cards for a pipeline: a stage per column, a card per thing moving thr
|
|
|
80
109
|
- A stage name is the pipeline's own word for it, sentence case, one or two words ("Offer made", not "Stage 2").
|
|
81
110
|
- A card's meta line says the one thing worth knowing at a glance (time in stage, who is waited on), never the whole
|
|
82
111
|
record.
|
|
112
|
+
- A standard card's kind is one sentence-case word ("New", "Expansion", "Dispute"), never an uppercase eyebrow; its
|
|
113
|
+
figure is short ("$28K", "$1,321"). Time in stage is a bare figure and a unit ("54d", "6h"), its meaning in the
|
|
114
|
+
`title`.
|
|
115
|
+
- A chip is a fact, two or three words; a tally is a bare figure ("1/6") that says what it counts to a screen reader.
|
|
83
116
|
- No em dashes; the count is a bare number.
|
|
84
117
|
|
|
85
118
|
## Props
|
|
@@ -87,7 +120,8 @@ Columns of cards for a pipeline: a stage per column, a card per thing moving thr
|
|
|
87
120
|
| Name | Type | Meaning |
|
|
88
121
|
| --- | --- | --- |
|
|
89
122
|
| `columns` | `{ stage, label, count, cards: CardItem[] }[]` | The board's stages, in order |
|
|
90
|
-
| `CardItem` | `{ id, name, initials, meta, href? }` | One card's content |
|
|
123
|
+
| `CardItem` | `{ id, name, initials, meta, href? }` | One person card's content |
|
|
124
|
+
| `BoardCard` | `{ id, kind?, figure?, age?, stalled?, title, href, meta?, chips?: { label, tone }[], people: { name, initials }[], tally?: { value, label }, act?: { label, onClick }, stageAct?: { label, onClick } }` | One standard card: `act` is the foot's icon act, `stageAct` the well button under it |
|
|
91
125
|
| `onMove` | `(cardId, toStage) => void` | Called when a card changes column |
|
|
92
126
|
| `draggingId` | `string \| null` | Which card, if any, is currently lifted |
|
|
93
127
|
| `fill` | `boolean` | The board page form (`--fill`) |
|
|
@@ -101,6 +135,12 @@ Columns of cards for a pipeline: a stage per column, a card per thing moving thr
|
|
|
101
135
|
- **A board page shows its sideways bar** (K16): when the board is the page, use `--fill` so the bar is never below the
|
|
102
136
|
fold, and each stage scrolls on its own.
|
|
103
137
|
- **The head is sentence case** (D6): 13 / 18 medium, no uppercase eyebrow.
|
|
138
|
+
- **One card anatomy per app.** Every board with kinds, figures and owners uses the standard card, its slots in their
|
|
139
|
+
order; a board never draws its own card.
|
|
140
|
+
- **Never** a coloured rail down a card's edge, a hue per column or per stage (a stage's dot, a tinted column), an
|
|
141
|
+
underlined title, a navy button on a card, or more than one act in the foot. Urgency is words: the stalled time, a
|
|
142
|
+
needs chip. Not a salmon rail for critical and a grey one for the rest, not a purple dot on one stage, not one card's
|
|
143
|
+
title in mid blue with an underline, not a navy "Hand over" on every card in the column.
|
|
104
144
|
|
|
105
145
|
## Source
|
|
106
146
|
|
|
@@ -108,7 +148,9 @@ Columns of cards for a pipeline: a stage per column, a card per thing moving thr
|
|
|
108
148
|
flat-except-dragged rules; North's floating shadow family and its edge fade; the K16 audit (Revenue OS: a 6-stage
|
|
109
149
|
board 638 px tall with its sideways bar at y 1,107 in a 900 px window).
|
|
110
150
|
- **Precedent**: an intentional addition decided from the system, 26 Oct 2026, generalised from the first product's
|
|
111
|
-
pipeline board; `--fill` added in 0.5.0 (4 Nov 2026).
|
|
151
|
+
pipeline board; `--fill` added in 0.5.0 (4 Nov 2026). The standard card (0.5.2, 10 Nov 2026) from the field: an app's
|
|
152
|
+
ticket board and onboarding board each drew their own card (rails per column, a stage hue, one title underlined), so
|
|
153
|
+
the two boards did not match.
|
|
112
154
|
|
|
113
155
|
## Markup
|
|
114
156
|
|
|
@@ -132,6 +174,28 @@ Columns of cards for a pipeline: a stage per column, a card per thing moving thr
|
|
|
132
174
|
</div>
|
|
133
175
|
```
|
|
134
176
|
|
|
177
|
+
The standard card (in a column, in place of the person card):
|
|
178
|
+
|
|
179
|
+
```html
|
|
180
|
+
<article class="ws-card ws-kanban-board__card">
|
|
181
|
+
<div class="ws-kanban-board__card-head">
|
|
182
|
+
<span class="ws-kanban-board__kind">Expansion <strong>$28K</strong></span>
|
|
183
|
+
<span class="ws-kanban-board__age ws-kanban-board__age--stalled" title="40 days in this stage, stalled"><svg viewBox="0 0 12 12" fill="currentColor" aria-hidden="true"><rect x="3" y="2.5" width="2" height="7" rx="0.5"/><rect x="7" y="2.5" width="2" height="7" rx="0.5"/></svg>40d<span class="ws-sr"> in this stage, stalled</span></span>
|
|
184
|
+
</div>
|
|
185
|
+
<a class="ws-kanban-board__open" href="#mesa" title="Mesa Group, Felix expansion">Mesa Group, Felix expansion</a>
|
|
186
|
+
<span class="ws-kanban-board__meta">Signed Sep 11</span>
|
|
187
|
+
<div class="ws-kanban-board__chips"><span class="ws-chip ws-chip--live"><span class="ws-chip__label">TTV1 15d</span></span></div>
|
|
188
|
+
<div class="ws-kanban-board__card-foot">
|
|
189
|
+
<span class="ws-avatar-stack" role="img" aria-label="Hal Dorn and Ella Sim"><span class="ws-avatar ws-avatar--sm">HD</span><span class="ws-avatar ws-avatar--sm">ES</span></span>
|
|
190
|
+
<span class="ws-kanban-board__card-end">
|
|
191
|
+
<span class="ws-kanban-board__tally" title="1 of 6 tasks done"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2.5 6.5l2.25 2.25L9.5 3.5"/></svg>1/6<span class="ws-sr"> tasks done</span></span>
|
|
192
|
+
<button type="button" class="ws-icon-btn ws-icon-btn--sm" aria-label="Move to Graduated" title="Move to Graduated"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 8h10M9 4l4 4-4 4"/></svg></button>
|
|
193
|
+
</span>
|
|
194
|
+
</div>
|
|
195
|
+
<button type="button" class="ws-btn ws-btn--well ws-btn--sm ws-kanban-board__card-act">Hand over</button>
|
|
196
|
+
</article>
|
|
197
|
+
```
|
|
198
|
+
|
|
135
199
|
## Styles
|
|
136
200
|
|
|
137
201
|
`design-system/components/KanbanBoard/style.css`, in `bundle.css` and `fello.css`. Dark and the navy ground come from
|
|
@@ -74,6 +74,135 @@
|
|
|
74
74
|
</div>
|
|
75
75
|
</div>
|
|
76
76
|
|
|
77
|
+
<div class="ws-stack">
|
|
78
|
+
<span class="ws-muted">The standard card, one anatomy for every board: the kind and its figure with the time in stage, the title (two lines, the card's link), the meta, the chips, the foot (people, a tally, one icon act), one well act under it when the card waits on the person. A stall is the needs words and the pause mark, never a coloured rail. Tickets</span>
|
|
79
|
+
<div class="ws-kanban-board" role="region" aria-label="Ticket stages" tabindex="0">
|
|
80
|
+
|
|
81
|
+
<section class="ws-kanban-board__column" aria-labelledby="kt-new">
|
|
82
|
+
<div class="ws-kanban-board__head"><h3 class="ws-kanban-board__title" id="kt-new">New <span class="ws-kanban-board__count">70</span></h3></div>
|
|
83
|
+
<article class="ws-card ws-kanban-board__card">
|
|
84
|
+
<div class="ws-kanban-board__card-head">
|
|
85
|
+
<span class="ws-kanban-board__kind">Dispute <strong>$1,321</strong></span>
|
|
86
|
+
<span class="ws-kanban-board__age ws-kanban-board__age--stalled" title="Quiet for 7 days"><svg viewBox="0 0 12 12" fill="currentColor" aria-hidden="true"><rect x="3" y="2.5" width="2" height="7" rx="0.5"/><rect x="7" y="2.5" width="2" height="7" rx="0.5"/></svg>7d<span class="ws-sr"> quiet, stalled</span></span>
|
|
87
|
+
</div>
|
|
88
|
+
<a class="ws-kanban-board__open" href="#t1" title="Dispute raised on the October invoice by the team's broker">Dispute raised on the October invoice by the team's broker</a>
|
|
89
|
+
<span class="ws-kanban-board__meta">Harbor Lane Realty, last activity Nov 2</span>
|
|
90
|
+
<div class="ws-kanban-board__chips"><span class="ws-chip ws-chip--needs"><span class="ws-chip__label">Critical</span></span><span class="ws-chip ws-chip--needs"><span class="ws-chip__label">Due Nov 12</span></span></div>
|
|
91
|
+
<div class="ws-kanban-board__card-foot">
|
|
92
|
+
<span class="ws-kanban-board__who"><span class="ws-avatar ws-avatar--sm" aria-hidden="true">VC</span><span class="ws-kanban-board__name" title="Vasu Chandran">Vasu Chandran</span></span>
|
|
93
|
+
<span class="ws-kanban-board__card-end">
|
|
94
|
+
<button type="button" class="ws-icon-btn ws-icon-btn--sm" aria-label="Move to In progress" title="Move to In progress"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 8h10M9 4l4 4-4 4"/></svg></button>
|
|
95
|
+
</span>
|
|
96
|
+
</div>
|
|
97
|
+
</article>
|
|
98
|
+
<article class="ws-card ws-kanban-board__card">
|
|
99
|
+
<div class="ws-kanban-board__card-head">
|
|
100
|
+
<span class="ws-kanban-board__kind">Churn <strong>$4.2K</strong></span>
|
|
101
|
+
<span class="ws-kanban-board__age" title="6 hours in this stage">6h</span>
|
|
102
|
+
</div>
|
|
103
|
+
<a class="ws-kanban-board__open" href="#t2" title="Wants to cancel after the price change">Wants to cancel after the price change</a>
|
|
104
|
+
<span class="ws-kanban-board__meta">Oakridge Homes Group, last activity today</span>
|
|
105
|
+
<div class="ws-kanban-board__card-foot">
|
|
106
|
+
<span class="ws-kanban-board__who"><span class="ws-avatar ws-avatar--sm" aria-hidden="true">AY</span><span class="ws-kanban-board__name" title="Ayan Roy">Ayan Roy</span></span>
|
|
107
|
+
<span class="ws-kanban-board__card-end">
|
|
108
|
+
<button type="button" class="ws-icon-btn ws-icon-btn--sm" aria-label="Move to In progress" title="Move to In progress"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 8h10M9 4l4 4-4 4"/></svg></button>
|
|
109
|
+
</span>
|
|
110
|
+
</div>
|
|
111
|
+
</article>
|
|
112
|
+
</section>
|
|
113
|
+
|
|
114
|
+
<section class="ws-kanban-board__column" aria-labelledby="kt-progress">
|
|
115
|
+
<div class="ws-kanban-board__head"><h3 class="ws-kanban-board__title" id="kt-progress">In progress <span class="ws-kanban-board__count">339</span></h3></div>
|
|
116
|
+
<article class="ws-card ws-kanban-board__card">
|
|
117
|
+
<div class="ws-kanban-board__card-head">
|
|
118
|
+
<span class="ws-kanban-board__kind">Integration</span>
|
|
119
|
+
<span class="ws-kanban-board__age" title="4 days in this stage">4d</span>
|
|
120
|
+
</div>
|
|
121
|
+
<a class="ws-kanban-board__open" href="#t3" title="Leads stop syncing to the CRM after the second login">Leads stop syncing to the CRM after the second login</a>
|
|
122
|
+
<span class="ws-kanban-board__meta">The Connors Team, last activity Nov 5</span>
|
|
123
|
+
<div class="ws-kanban-board__chips"><span class="ws-chip ws-chip--sunk"><span class="ws-chip__label">Deferred</span></span><span class="ws-chip"><span class="ws-chip__label">Integration setup</span></span></div>
|
|
124
|
+
<div class="ws-kanban-board__card-foot">
|
|
125
|
+
<span class="ws-kanban-board__who"><span class="ws-avatar ws-avatar--sm" aria-hidden="true">TM</span><span class="ws-kanban-board__name" title="Tamara Mendes-Okonkwo">Tamara Mendes-Okonkwo</span></span>
|
|
126
|
+
<span class="ws-kanban-board__card-end">
|
|
127
|
+
<span class="ws-kanban-board__tally" title="2 replies"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="1.5" y="2.5" width="9" height="7" rx="1"/><path d="M2 3.5l4 3 4-3"/></svg>2<span class="ws-sr"> replies</span></span>
|
|
128
|
+
<button type="button" class="ws-icon-btn ws-icon-btn--sm" aria-label="Move to Waiting on customer" title="Move to Waiting on customer"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 8h10M9 4l4 4-4 4"/></svg></button>
|
|
129
|
+
</span>
|
|
130
|
+
</div>
|
|
131
|
+
</article>
|
|
132
|
+
</section>
|
|
133
|
+
|
|
134
|
+
<section class="ws-kanban-board__column" aria-labelledby="kt-wait" aria-busy="true">
|
|
135
|
+
<div class="ws-kanban-board__head"><h3 class="ws-kanban-board__title" id="kt-wait">Waiting on customer</h3></div>
|
|
136
|
+
<div class="ws-kanban-board__ghost ws-kanban-board__ghost--card ws-shimmer"></div>
|
|
137
|
+
</section>
|
|
138
|
+
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
<div class="ws-stack">
|
|
143
|
+
<span class="ws-muted">The same card on an onboarding board: the kind and the deal's figure, the stall, milestones as chips, the team as a stack, tasks as a tally, and the one act this stage waits on (Hand over) as a well button</span>
|
|
144
|
+
<div class="ws-kanban-board" role="region" aria-label="Onboarding stages" tabindex="0">
|
|
145
|
+
|
|
146
|
+
<section class="ws-kanban-board__column" aria-labelledby="ko-sched">
|
|
147
|
+
<div class="ws-kanban-board__head"><h3 class="ws-kanban-board__title" id="ko-sched">Scheduling <span class="ws-kanban-board__count">15</span></h3></div>
|
|
148
|
+
<article class="ws-card ws-kanban-board__card">
|
|
149
|
+
<div class="ws-kanban-board__card-head">
|
|
150
|
+
<span class="ws-kanban-board__kind">New <strong>$26K</strong></span>
|
|
151
|
+
<span class="ws-kanban-board__age ws-kanban-board__age--stalled" title="54 days in this stage, stalled"><svg viewBox="0 0 12 12" fill="currentColor" aria-hidden="true"><rect x="3" y="2.5" width="2" height="7" rx="0.5"/><rect x="7" y="2.5" width="2" height="7" rx="0.5"/></svg>54d<span class="ws-sr"> in this stage, stalled</span></span>
|
|
152
|
+
</div>
|
|
153
|
+
<a class="ws-kanban-board__open" href="#o1" title="Bayview Real Estate Team, Felix expansion with a long name that wraps">Bayview Real Estate Team, Felix expansion with a long name that wraps</a>
|
|
154
|
+
<div class="ws-kanban-board__chips"><span class="ws-chip ws-chip--needs"><span class="ws-chip__label">No meeting booked</span></span></div>
|
|
155
|
+
<div class="ws-kanban-board__card-foot">
|
|
156
|
+
<span class="ws-avatar-stack" role="img" aria-label="Tara Vale and Josh Smith"><span class="ws-avatar ws-avatar--sm">TV</span><span class="ws-avatar ws-avatar--sm">JS</span></span>
|
|
157
|
+
<span class="ws-kanban-board__card-end">
|
|
158
|
+
<span class="ws-kanban-board__tally" title="0 of 1 messages sent"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="1.5" y="2.5" width="9" height="7" rx="1"/><path d="M2 3.5l4 3 4-3"/></svg>0/1<span class="ws-sr"> messages sent</span></span>
|
|
159
|
+
<button type="button" class="ws-icon-btn ws-icon-btn--sm" aria-label="Move to Implementing" title="Move to Implementing"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 8h10M9 4l4 4-4 4"/></svg></button>
|
|
160
|
+
</span>
|
|
161
|
+
</div>
|
|
162
|
+
</article>
|
|
163
|
+
</section>
|
|
164
|
+
|
|
165
|
+
<section class="ws-kanban-board__column" aria-labelledby="ko-opt">
|
|
166
|
+
<div class="ws-kanban-board__head"><h3 class="ws-kanban-board__title" id="ko-opt">Optimizing <span class="ws-kanban-board__count">66</span></h3></div>
|
|
167
|
+
<article class="ws-card ws-kanban-board__card">
|
|
168
|
+
<div class="ws-kanban-board__card-head">
|
|
169
|
+
<span class="ws-kanban-board__kind">New <strong>$39K</strong></span>
|
|
170
|
+
<span class="ws-kanban-board__age" title="27 days in this stage">27d</span>
|
|
171
|
+
</div>
|
|
172
|
+
<a class="ws-kanban-board__open" href="#o2" title="Linden Indy Realty, onboarding">Linden Indy Realty, onboarding</a>
|
|
173
|
+
<div class="ws-kanban-board__chips"><span class="ws-chip ws-chip--live"><span class="ws-chip__label">TTV1 15d</span></span></div>
|
|
174
|
+
<div class="ws-kanban-board__card-foot">
|
|
175
|
+
<span class="ws-avatar-stack" role="img" aria-label="Mia Lund and Ravi Bose"><span class="ws-avatar ws-avatar--sm">ML</span><span class="ws-avatar ws-avatar--sm">RB</span></span>
|
|
176
|
+
<span class="ws-kanban-board__card-end">
|
|
177
|
+
<span class="ws-kanban-board__tally" title="3 of 4 tasks done"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2.5 6.5l2.25 2.25L9.5 3.5"/></svg>3/4<span class="ws-sr"> tasks done</span></span>
|
|
178
|
+
<button type="button" class="ws-icon-btn ws-icon-btn--sm" aria-label="Move to Review and handoff" title="Move to Review and handoff"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 8h10M9 4l4 4-4 4"/></svg></button>
|
|
179
|
+
</span>
|
|
180
|
+
</div>
|
|
181
|
+
</article>
|
|
182
|
+
</section>
|
|
183
|
+
|
|
184
|
+
<section class="ws-kanban-board__column" aria-labelledby="ko-review">
|
|
185
|
+
<div class="ws-kanban-board__head"><h3 class="ws-kanban-board__title" id="ko-review">Review and handoff <span class="ws-kanban-board__count">9</span></h3></div>
|
|
186
|
+
<article class="ws-card ws-kanban-board__card">
|
|
187
|
+
<div class="ws-kanban-board__card-head">
|
|
188
|
+
<span class="ws-kanban-board__kind">Expansion <strong>$28K</strong></span>
|
|
189
|
+
<span class="ws-kanban-board__age ws-kanban-board__age--stalled" title="40 days in this stage, stalled"><svg viewBox="0 0 12 12" fill="currentColor" aria-hidden="true"><rect x="3" y="2.5" width="2" height="7" rx="0.5"/><rect x="7" y="2.5" width="2" height="7" rx="0.5"/></svg>40d<span class="ws-sr"> in this stage, stalled</span></span>
|
|
190
|
+
</div>
|
|
191
|
+
<a class="ws-kanban-board__open" href="#o3" title="Mesa Group, Felix expansion">Mesa Group, Felix expansion</a>
|
|
192
|
+
<div class="ws-kanban-board__chips"><span class="ws-chip ws-chip--live"><span class="ws-chip__label">TTV1 15d</span></span><span class="ws-chip ws-chip--live"><span class="ws-chip__label">TTV2 26d</span></span></div>
|
|
193
|
+
<div class="ws-kanban-board__card-foot">
|
|
194
|
+
<span class="ws-avatar-stack" role="img" aria-label="Hal Dorn, Ella Sim and Josh Smith"><span class="ws-avatar ws-avatar--sm">HD</span><span class="ws-avatar ws-avatar--sm">ES</span><span class="ws-avatar ws-avatar--sm">JS</span></span>
|
|
195
|
+
<span class="ws-kanban-board__card-end">
|
|
196
|
+
<span class="ws-kanban-board__tally" title="1 of 12 tasks done"><svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2.5 6.5l2.25 2.25L9.5 3.5"/></svg>1/12<span class="ws-sr"> tasks done</span></span>
|
|
197
|
+
</span>
|
|
198
|
+
</div>
|
|
199
|
+
<button type="button" class="ws-btn ws-btn--well ws-btn--sm ws-kanban-board__card-act">Hand over</button>
|
|
200
|
+
</article>
|
|
201
|
+
</section>
|
|
202
|
+
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
|
|
77
206
|
<div class="ws-stack">
|
|
78
207
|
<span class="ws-muted">--fill, a board page: the board takes the rest of a height-bound column, each stage scrolls on its own with its head stuck, so the sideways bar stays on screen (shown here in a 320 px frame)</span>
|
|
79
208
|
<div style="height: 320px; display: flex; flex-direction: column;">
|
|
@@ -29,9 +29,40 @@
|
|
|
29
29
|
.ws-kanban-board__card--dragging { box-shadow: var(--ws-shadow-pop); transform: rotate(-1deg); }
|
|
30
30
|
.ws-kanban-board__who { display: flex; align-items: center; gap: var(--space-xs); min-width: 0; }
|
|
31
31
|
.ws-kanban-board__meta { font-size: var(--ws-text-control); line-height: var(--ws-leading-control); color: var(--color-text-muted); }
|
|
32
|
+
/* The standard card: one anatomy for every board (a ticket, an onboarding, a deal), its slots in a fixed order: the head
|
|
33
|
+
(the kind and its figure, the time in stage), the title, the meta, the chips, the foot (people, a tally, one act), an
|
|
34
|
+
optional act under it. A card with acts is an article: its title is the link and covers the whole card (__open), the
|
|
35
|
+
acts sit above that cover. */
|
|
36
|
+
.ws-kanban-board__card { position: relative; }
|
|
37
|
+
.ws-kanban-board__card-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-xs); min-width: 0; font-size: var(--ws-text-control); line-height: var(--ws-leading-control); }
|
|
38
|
+
.ws-kanban-board__kind { min-width: 0; overflow: hidden; font-weight: 500; color: var(--color-text-muted); white-space: nowrap; text-overflow: ellipsis; }
|
|
39
|
+
.ws-kanban-board__kind > strong { font-weight: 600; color: var(--color-text-heading); font-variant-numeric: tabular-nums; }
|
|
40
|
+
/* Time in stage, muted; a stall takes the needs words and the pause mark, never a coloured rail. */
|
|
41
|
+
.ws-kanban-board__age { display: inline-flex; flex: none; align-items: center; gap: var(--space-xxs); margin-left: auto; color: var(--color-text-muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
42
|
+
.ws-kanban-board__age > svg { width: var(--ws-icon-sm); height: var(--ws-icon-sm); }
|
|
43
|
+
.ws-kanban-board__age--stalled { font-weight: 500; color: var(--ws-word-needs); }
|
|
44
|
+
/* The title: 14 / 20 medium navy, two lines at most (whole in its title), never underlined. Its cover is the card's
|
|
45
|
+
target and carries the focus ring, 2 px out as an a.ws-card's does. */
|
|
46
|
+
.ws-kanban-board__open { display: -webkit-box; overflow: hidden; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; font-size: var(--ws-text-body); line-height: var(--ws-leading-row); font-weight: 500; color: var(--color-text-heading); text-decoration: none; overflow-wrap: anywhere; }
|
|
47
|
+
.ws-kanban-board__open::after { content: ""; position: absolute; inset: calc(var(--ws-rim) * -1); border-radius: var(--ws-radius-card); }
|
|
48
|
+
.ws-kanban-board__open:focus-visible { outline: 0; }
|
|
49
|
+
.ws-kanban-board__open:focus-visible::after { outline: var(--ws-focus-width) solid var(--ws-focus-color); outline-offset: var(--ws-focus-offset); }
|
|
50
|
+
.ws-kanban-board__chips { display: flex; flex-wrap: wrap; gap: var(--space-xxs); min-width: 0; margin-top: var(--space-xxs); }
|
|
51
|
+
.ws-kanban-board__chips > .ws-chip { max-width: 100%; }
|
|
52
|
+
.ws-kanban-board__card-foot { display: flex; align-items: center; gap: var(--space-xs); min-width: 0; min-height: var(--ws-control-h-sm); margin-top: var(--space-xxs); }
|
|
53
|
+
.ws-kanban-board__card-foot > .ws-kanban-board__who { flex: 1; }
|
|
54
|
+
.ws-kanban-board__name { min-width: 0; overflow: hidden; font-size: var(--ws-text-control); line-height: var(--ws-leading-control); color: var(--color-text-body); white-space: nowrap; text-overflow: ellipsis; }
|
|
55
|
+
.ws-kanban-board__card-end { display: flex; flex: none; align-items: center; gap: var(--space-xs); margin-left: auto; }
|
|
56
|
+
.ws-kanban-board__tally { display: inline-flex; align-items: center; gap: var(--space-xxs); font-size: var(--ws-text-control); line-height: var(--ws-leading-control); color: var(--color-text-muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
57
|
+
.ws-kanban-board__tally > svg { width: var(--ws-icon-sm); height: var(--ws-icon-sm); }
|
|
58
|
+
/* Acts sit above the title's cover, so a press on one is the act, not the card. */
|
|
59
|
+
.ws-kanban-board__card :is(.ws-icon-btn, .ws-btn) { position: relative; z-index: 1; }
|
|
60
|
+
.ws-kanban-board__card-act { width: 100%; margin-top: var(--space-xxs); }
|
|
32
61
|
.ws-kanban-board__empty { padding: var(--space-md) var(--space-xs); text-align: center; font-size: var(--ws-text-control); line-height: var(--ws-leading-control); color: var(--color-text-muted); }
|
|
33
|
-
/* Loading: a ghost the height of a card
|
|
62
|
+
/* Loading: a ghost the height of a card under the shimmer (add ws-shimmer). */
|
|
34
63
|
.ws-kanban-board__ghost { display: block; height: calc(var(--ws-card-padding) * 2 + var(--ws-control-h-sm) + var(--space-xxs) + var(--ws-leading-control)); border-radius: var(--ws-radius-card); background: var(--ws-track); }
|
|
64
|
+
/* The standard card's ghost: the height of a head, two title lines, a meta line and the foot. */
|
|
65
|
+
.ws-kanban-board__ghost--card { height: calc(var(--ws-card-padding) * 2 + var(--ws-leading-control) * 2 + var(--ws-leading-row) * 2 + var(--ws-control-h-sm) + var(--space-xxs) * 4); }
|
|
35
66
|
.ws-kanban-board__ghost.ws-shimmer { background: var(--ws-shimmer); background-size: 300% 100%; }
|
|
36
67
|
/* A board page (K16): the board takes the rest of a height-bound column (the shell's body, a fill page), every column
|
|
37
68
|
runs its full height and scrolls on its own with its head stuck, so the sideways bar is always on screen. */
|
|
@@ -175,7 +175,7 @@
|
|
|
175
175
|
</div>
|
|
176
176
|
|
|
177
177
|
<div class="ws-stack" style="width: 640px;">
|
|
178
|
-
<span class="ws-muted">Loading: the list's rows and the detail as ghosts
|
|
178
|
+
<span class="ws-muted">Loading: the list's rows and the detail as ghosts under the shimmer (under 48rem the pair stacks, list first)</span>
|
|
179
179
|
<div class="ws-list-detail" aria-busy="true">
|
|
180
180
|
<div class="ws-list-detail__grid">
|
|
181
181
|
<section class="ws-list-detail__list" aria-label="Reviews, loading">
|
|
@@ -55,7 +55,7 @@ answers grid (305:2341): which AI assistants named you, for each of the question
|
|
|
55
55
|
and the foot stay; the hovered row does not change. The whole question shows in the note.
|
|
56
56
|
- **Focus** (keyboard, or a tap that focuses the question): the same dim and the same note. A page script may set
|
|
57
57
|
`aria-expanded="true"` on the question to keep the note open after a tap where the browser does not focus buttons.
|
|
58
|
-
- **Loading**: every cell a ghost
|
|
58
|
+
- **Loading**: every cell a ghost under the shimmer (`__cell ws-shimmer`), the questions and sources shown at
|
|
59
59
|
once; still under reduced motion.
|
|
60
60
|
- **Narrow**: source names drop to their marks in 32 px columns (a screen reader still hears each source through the
|
|
61
61
|
head's label), and the foot's line drops under its title.
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
</div>
|
|
63
63
|
|
|
64
64
|
<div class="ws-stack" style="width: 420px;">
|
|
65
|
-
<span class="ws-muted">Loading: every cell a ghost
|
|
65
|
+
<span class="ws-muted">Loading: every cell a ghost under the shimmer, the questions and sources shown at once</span>
|
|
66
66
|
<div class="ws-matrix" aria-busy="true">
|
|
67
67
|
<div class="ws-matrix__table" role="table" aria-label="Which AI assistants named you, checking">
|
|
68
68
|
<div class="ws-matrix__head" role="rowgroup">
|
|
@@ -71,14 +71,14 @@ Coverage, 206 behaviours (the page counts them in its head):
|
|
|
71
71
|
| 6. Focus | 7 | 4 | 2 | 0 | 1 |
|
|
72
72
|
| 7. Open and close | 23 | 10 | 5 | 2 | 6 |
|
|
73
73
|
| 8. Panels docking | 12 | 6 | 1 | 1 | 4 |
|
|
74
|
-
| 9. Loading, working and loops | 11 |
|
|
74
|
+
| 9. Loading, working and loops | 11 | 3 | 3 | 0 | 5 |
|
|
75
75
|
| 10. Number and chart build-ins | 13 | 5 | 2 | 0 | 6 |
|
|
76
76
|
| 11. Tooltips and hover cards | 6 | 2 | 4 | 0 | 0 |
|
|
77
77
|
| 12. Stagger | 11 | 6 | 2 | 1 | 2 |
|
|
78
78
|
| 13. Reduced motion | 4 | 4 | 0 | 0 | 0 |
|
|
79
79
|
| 14. What never moves | 10 | 6 | 3 | 0 | 1 |
|
|
80
80
|
| 17. Interaction states | 13 | 7 | 2 | 1 | 3 |
|
|
81
|
-
| All | 206 |
|
|
81
|
+
| All | 206 | 113 | 46 | 5 | 42 |
|
|
82
82
|
|
|
83
83
|
## States
|
|
84
84
|
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
<li class="ws-cluster" style="flex-wrap: nowrap; align-items: flex-start; gap: var(--space-xs);"><span class="ws-badge ws-badge--navy" aria-hidden="true" style="flex: none; min-width: var(--space-lg);">11</span><span class="ws-stack ws-stack--tight" style="min-width: 0;"><span class="ws-row-name">Sort the list</span><span class="ws-meta">Pick an order: the rows glide to their new places.</span></span></li>
|
|
37
37
|
<li class="ws-cluster" style="flex-wrap: nowrap; align-items: flex-start; gap: var(--space-xs);"><span class="ws-badge ws-badge--navy" aria-hidden="true" style="flex: none; min-width: var(--space-lg);">12</span><span class="ws-stack ws-stack--tight" style="min-width: 0;"><span class="ws-row-name">Press any button</span><span class="ws-meta">It gives under your finger and springs back.</span></span></li>
|
|
38
38
|
<li class="ws-cluster" style="flex-wrap: nowrap; align-items: flex-start; gap: var(--space-xs);"><span class="ws-badge ws-badge--navy" aria-hidden="true" style="flex: none; min-width: var(--space-lg);">13</span><span class="ws-stack ws-stack--tight" style="min-width: 0;"><span class="ws-row-name">Point at a health square</span><span class="ws-meta">After a breath it grows open in place.</span></span></li>
|
|
39
|
-
<li class="ws-cluster" style="flex-wrap: nowrap; align-items: flex-start; gap: var(--space-xs);"><span class="ws-badge ws-badge--navy" aria-hidden="true" style="flex: none; min-width: var(--space-lg);">14</span><span class="ws-stack ws-stack--tight" style="min-width: 0;"><span class="ws-row-name">Refresh the sources</span><span class="ws-meta">A shimmer
|
|
39
|
+
<li class="ws-cluster" style="flex-wrap: nowrap; align-items: flex-start; gap: var(--space-xs);"><span class="ws-badge ws-badge--navy" aria-hidden="true" style="flex: none; min-width: var(--space-lg);">14</span><span class="ws-stack ws-stack--tight" style="min-width: 0;"><span class="ws-row-name">Refresh the sources</span><span class="ws-meta">A shimmer, one quiet grey, then each line settles in.</span></span></li>
|
|
40
40
|
<li class="ws-cluster" style="flex-wrap: nowrap; align-items: flex-start; gap: var(--space-xs);"><span class="ws-badge ws-badge--navy" aria-hidden="true" style="flex: none; min-width: var(--space-lg);">15</span><span class="ws-stack ws-stack--tight" style="min-width: 0;"><span class="ws-row-name">Replay the arrival</span><span class="ws-meta">The cards rise in, the columns grow in one after another and the goals fill.</span></span></li>
|
|
41
41
|
</ol>
|
|
42
42
|
<p class="ws-muted" data-reduced-readout></p>
|
|
@@ -270,7 +270,7 @@
|
|
|
270
270
|
<div class="ws-stack" style="gap: var(--ws-gap-group); padding-top: var(--ws-gap-component);">
|
|
271
271
|
<div class="ws-stack">
|
|
272
272
|
<p class="ws-body">Each row is one of North's behaviours by Seth's own label (north-motion.md), North's values, a live kit demo to hover, press or play, the kit token or class to build with, and how close the kit is. 206 behaviours: 115 exact, 45 close, 5 through a small JS helper, 41 not in the kit yet.</p>
|
|
273
|
-
<span class="ws-cluster"><span class="ws-chip ws-chip--live" style="min-width: var(--space-6xl); justify-content: center;">Exact
|
|
273
|
+
<span class="ws-cluster"><span class="ws-chip ws-chip--live" style="min-width: var(--space-6xl); justify-content: center;">Exact 113</span><span class="ws-chip ws-chip--middle" style="min-width: var(--space-6xl); justify-content: center;">Close 46</span><span class="ws-chip ws-chip--accent" style="min-width: var(--space-6xl); justify-content: center;">JS helper 5</span><span class="ws-chip" style="min-width: var(--space-6xl); justify-content: center;">Not yet 42</span></span>
|
|
274
274
|
<p class="ws-muted">Slow motion (in Try it) is this page's own: it multiplies every duration, delay and stagger token by --ws-motion-slow (4), so a spring's shape shows. Reduced motion: with prefers-reduced-motion set, every row here is still (instant state changes, no springs, no view transitions, shimmers stop on their ghost), and Slow motion has nothing to slow. A browser without a feature (linear(), anchor positioning, @starting-style, interpolate-size, view transitions) shows the change at once, never a broken layout.</p>
|
|
275
275
|
</div>
|
|
276
276
|
|
|
@@ -1436,21 +1436,21 @@
|
|
|
1436
1436
|
|
|
1437
1437
|
<section class="ws-stack" aria-labelledby="mo-loading">
|
|
1438
1438
|
<h2 class="ws-section" id="mo-loading">9. Loading, working and loops</h2>
|
|
1439
|
-
<p class="ws-muted">Every working signal says in words what it is doing; the shimmer passes
|
|
1439
|
+
<p class="ws-muted">Every working signal says in words what it is doing; the shimmer passes over ghost shapes in one quiet grey.</p>
|
|
1440
1440
|
<div class="ws-board">
|
|
1441
1441
|
<article class="ws-card" aria-label="Shimmer in chart colours"><div class="ws-head">
|
|
1442
1442
|
<div class="ws-stack ws-stack--tight">
|
|
1443
|
-
<span class="ws-cluster"><span class="ws-chip ws-chip--
|
|
1443
|
+
<span class="ws-cluster"><span class="ws-chip ws-chip--middle" style="min-width: var(--space-6xl); justify-content: center;">Close</span><span class="ws-row-name">Shimmer in chart colours</span></span>
|
|
1444
1444
|
<span class="ws-meta">North: text and blocks: a 100deg band of #f6ddd8, #e4dcf3, #d6eaea over #ece8e4, 300% wide, 1.8 s shimmer, infinite (css:1458)</span>
|
|
1445
|
-
<span class="ws-meta">Kit: .ws-skeleton, .ws-shimmer: --ws-shimmer over --ws-loop-shimmer on --ws-ease-shimmer; still ghost under reduced motion</span>
|
|
1445
|
+
<span class="ws-meta">Kit: .ws-skeleton, .ws-shimmer: --ws-shimmer over --ws-loop-shimmer on --ws-ease-shimmer, North's band, width and beat in one quiet grey: the ghost #e3dfdb with a lighter pass #f6f3f0, no chart hues (Amit, 10 Nov 2026); still ghost under reduced motion</span>
|
|
1446
1446
|
</div>
|
|
1447
1447
|
<div class="ws-cluster"><span class="ws-stack" style="width: var(--space-9xl);" aria-hidden="true"><span class="ws-skeleton ws-skeleton--title" style="width: 60%;"></span><span class="ws-skeleton ws-skeleton--line"></span><span class="ws-skeleton ws-skeleton--line" style="width: 66%;"></span></span></div>
|
|
1448
1448
|
</div></article>
|
|
1449
1449
|
<article class="ws-card" aria-label="Empty chart shapes take the colour band"><div class="ws-head">
|
|
1450
1450
|
<div class="ws-stack ws-stack--tight">
|
|
1451
|
-
<span class="ws-cluster"><span class="ws-chip
|
|
1451
|
+
<span class="ws-cluster"><span class="ws-chip" style="min-width: var(--space-6xl); justify-content: center;">Not yet</span><span class="ws-row-name">Empty chart shapes take the colour band</span></span>
|
|
1452
1452
|
<span class="ws-meta">North: #e3dfdb shapes, a coral, purple, teal band at mix-blend color, .85, the same 1.8 s</span>
|
|
1453
|
-
<span class="ws-meta">Kit: .ws-skeleton__chart</span>
|
|
1453
|
+
<span class="ws-meta">Kit: not taken: loaders are one neutral tone (Amit, 10 Nov 2026); .ws-skeleton__chart's bars are ghosts under the same shimmer</span>
|
|
1454
1454
|
</div>
|
|
1455
1455
|
<div class="ws-cluster"><span class="ws-skeleton__chart" style="width: var(--space-9xl);" aria-hidden="true"><i style="height: 35%;"></i><i style="height: 60%;"></i><i style="height: 45%;"></i><i style="height: 75%;"></i><i style="height: 55%;"></i><i style="height: 90%;"></i><i style="height: 70%;"></i><i style="height: 50%;"></i></span></div>
|
|
1456
1456
|
</div></article>
|
|
@@ -2514,7 +2514,7 @@
|
|
|
2514
2514
|
veil.addEventListener('click', closeChat);
|
|
2515
2515
|
chat.addEventListener('keydown', (e) => { if (e.key === 'Escape' && mode === 'bar') closeChat(); });
|
|
2516
2516
|
|
|
2517
|
-
/* 14: the sources load:
|
|
2517
|
+
/* 14: the sources load: the shimmer, one quiet grey, then each line settles in, top to bottom. */
|
|
2518
2518
|
const sources = play.querySelector('[data-play-sources]'), sourcesView = sources.querySelector('[data-sources-view]');
|
|
2519
2519
|
let loading = 0;
|
|
2520
2520
|
const load = () => {
|