mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 14:16:28 +03:00
The default lives in each surface's fallback, not in the markup: show() checks whichever row the fallback names every time a tab opens, so the checked attribute on the option never survived. Persuade, Operate, and Read now open on Full palette. Experience keeps Restrained because four working colors are not on its allow list, and a default pointing at a blocked row would leave that tab with nothing selected. The font screen's boards also take the chosen palette under Restrained, which is the one answer no strategy block rewrites. They were drawing a page the palette had never reached while screen 03 kept its color under the same answer. Prepared with AI assistance (Cursor). Co-authored-by: Cursor <cursoragent@cursor.com>
257 lines
12 KiB
JavaScript
257 lines
12 KiB
JavaScript
/*
|
|
The four kinds of surface a design system can be asked to serve, straight from
|
|
the skill's modes, and the questions that are answered once for each of them.
|
|
|
|
Values travel as `surface-modes` (multi-select) from screen 01b; the agent
|
|
pre-checks what PRODUCT.md suggests via cues.json, and the visitor corrects
|
|
it. Persuade is the markup default so the answer can never arrive empty on
|
|
runs whose cues carry no hint.
|
|
*/
|
|
export const SURFACE_MODES = ['persuade', 'operate', 'read', 'experience'];
|
|
|
|
/*
|
|
A question listed here is answered once per chosen surface rather than once
|
|
per run, because the answer that suits the marketing page rarely suits the
|
|
tool it sells. Each entry carries:
|
|
|
|
- `allow`, the options that surface can take. Anything left out stays in the
|
|
list, turned off, with the option's own `data-blocked-reason` in place of
|
|
its description. Whether an option is out belongs to the option and not to
|
|
the pairing, so the reason is written once beside the copy it replaces.
|
|
Omitted where the question rules nothing out, which is not the same as
|
|
listing nothing, so the attribute is left off rather than left empty.
|
|
- `fallback`, what the surface lands on when nobody ever opens its tab. Every
|
|
chosen surface leaves an answer, so every surface needs one. Omitted where
|
|
the options are not known until the run deals them, and the first row on
|
|
the list stands in.
|
|
- `answered` and `unanswered`, what the tab tells a screen reader. `{}` is
|
|
where the chosen option's own label goes, lowercased so it reads as part of
|
|
the sentence around it. `properName` turns that off for a question whose
|
|
options are named after something rather than described.
|
|
- `flat`, for a question whose tabs are there to look with rather than to
|
|
split. The per-surface reading is still kept while the screen is open, so an
|
|
option a surface rules out cannot be the answer left behind, but only the
|
|
leading surface's choice is written down. A key per surface would promise
|
|
whoever reads the answers a distinction the run has nowhere to spend.
|
|
|
|
A question that leaves a surface out of `surfaces` is not asked of that
|
|
surface at all, which is a stronger statement than withholding an option from
|
|
it. Withholding says the surface would answer this badly; leaving it out says
|
|
the surface has no stake in the question. So the tab strip only offers the
|
|
surfaces the question names, and a run whose surfaces are all left out never
|
|
sees the screen and records no answer for it. Motion and layout are scoped
|
|
that way today; the rest name all four.
|
|
|
|
Persuade is allowed everything on every question: earning attention is the
|
|
whole job of the surface, and no answer here is too much for it. The other
|
|
three are ruled by what the surface is for rather than by how loud an option
|
|
is. A tool has to stay scannable, which rules out the loudest answers and,
|
|
on boundaries, the emptiest one as well. A page read at length is one plane
|
|
and one column. On a portfolio the work leads, so it is the interface that
|
|
has to recede; the page itself is still allowed to be dramatic, which is why
|
|
drenched survives there and four working colors do not.
|
|
*/
|
|
export const SURFACE_ANSWERS = {
|
|
'color-strategy': {
|
|
tablist: 'Surface being colored',
|
|
answered: 'colored {}',
|
|
unanswered: 'no color strategy chosen yet',
|
|
surfaces: {
|
|
persuade: { allow: 'restrained committed full-palette drenched', fallback: 'full-palette' },
|
|
operate: { allow: 'restrained committed full-palette', fallback: 'full-palette' },
|
|
read: { allow: 'restrained committed full-palette', fallback: 'full-palette' },
|
|
// The one surface that cannot take four working colors, so it keeps the
|
|
// quietest answer its allow list has.
|
|
experience: { allow: 'restrained committed drenched', fallback: 'restrained' },
|
|
},
|
|
},
|
|
|
|
/*
|
|
Flat, and the only question here that rules nothing out. There is no pair
|
|
that suits a landing page and is forbidden on a dashboard; a pair that fails
|
|
the dashboard is a bad pair, and the model composing fonts.json is told to
|
|
rank all six against the strictest surface the run names. So no allow list
|
|
is written, and every row stays live on every tab.
|
|
|
|
The tabs earn their place all the same. One pair is chosen and one type
|
|
system comes out of the run, and the thing a visitor cannot otherwise do is
|
|
see that pair set as a dashboard, as a document, and as an index before
|
|
committing to it. The strip here is a way of looking rather than a second
|
|
decision.
|
|
*/
|
|
'font-pair': {
|
|
tablist: 'Surface the pair is shown on',
|
|
answered: 'set in {}',
|
|
unanswered: 'no pair chosen yet',
|
|
properName: true,
|
|
flat: true,
|
|
surfaces: {
|
|
persuade: {},
|
|
operate: {},
|
|
read: {},
|
|
experience: {},
|
|
},
|
|
},
|
|
|
|
/*
|
|
One of the two questions that are not put to every surface. Motion energy is
|
|
a claim on attention, and only two of the four surfaces are in a position to
|
|
make one: a landing page earning a decision and a portfolio presenting work.
|
|
A tool and a document are moved through rather than watched, and their motion
|
|
follows from what the interface is doing rather than from a house style, so
|
|
asking them would collect an answer nothing should act on. An app-UI-only or
|
|
docs-only run therefore never reaches this screen.
|
|
|
|
Both surfaces that are asked can take all three energies, so nothing is
|
|
withheld and no option carries a reason. Their defaults differ, because what
|
|
a surface does with movement when nobody says otherwise is the whole of what
|
|
it is for: a page earning a decision has to answer the pointer it is trying
|
|
to keep, and a portfolio stages the work's arrival, which is what a reveal
|
|
is for.
|
|
*/
|
|
'motion-energy': {
|
|
tablist: 'Surface being moved',
|
|
answered: '{} movement',
|
|
unanswered: 'no motion energy chosen yet',
|
|
surfaces: {
|
|
persuade: { fallback: 'responsive' },
|
|
experience: { fallback: 'choreographed' },
|
|
},
|
|
},
|
|
|
|
/*
|
|
The other question that is not put to every surface, and the only one that is
|
|
scoped and flat at once. How strict a layout feels is a claim about the
|
|
composition of the page itself, so it is asked of the two surfaces whose page
|
|
is the thing being looked at: one earning a decision, one presenting work. A
|
|
tool and a document are composed by what they have to hold instead, a rail
|
|
against a working column against a panel, one column at one measure, so the
|
|
question has nowhere to move on them and an answer taken there would be a
|
|
decision nobody made. An app-UI-only or docs-only run therefore never reaches
|
|
this screen either.
|
|
|
|
Both surfaces that are asked can take all three answers, so nothing is
|
|
withheld and no option carries a reason. Their defaults differ: a page
|
|
earning a decision needs one block to lead, which is what the breaks in the
|
|
aligned grid are for, and a gallery hangs work square so the only irregular
|
|
edges on the page are the work's own.
|
|
|
|
Flat, because one answer is all the run has to spend. The twelve-column ruler
|
|
on this screen describes a page, and it describes one whether the run ships a
|
|
dashboard or a gallery, so what the two of them leave is previewed on every
|
|
board the screens after it draw, the shell and the document included. A key
|
|
per surface would promise whoever reads the answers a distinction with
|
|
nowhere to land, and the tabs are left as a way of looking at the one answer
|
|
on both pages that asked for it.
|
|
*/
|
|
'layout-structure': {
|
|
tablist: 'Surface being laid out',
|
|
answered: '{} layout',
|
|
unanswered: 'no layout structure chosen yet',
|
|
flat: true,
|
|
surfaces: {
|
|
persuade: { fallback: 'balanced' },
|
|
experience: { fallback: 'simple-grid' },
|
|
},
|
|
},
|
|
|
|
/*
|
|
Open space is out on a tool. Operate asks for a ground of its own under
|
|
sidebars, toolbars, and panels, and the open answer is defined by every
|
|
ground being the same one. Its default is that second ground rather than
|
|
panels, which spend an edge and an inset on every object and buy density
|
|
back at the price of it.
|
|
|
|
Cards and panels are out on the two surfaces whose content is the point.
|
|
A page read at length is one column, and a page of work is the work.
|
|
*/
|
|
'boundary-style': {
|
|
tablist: 'Surface being separated',
|
|
answered: 'separated by {}',
|
|
unanswered: 'no boundary style chosen yet',
|
|
surfaces: {
|
|
persuade: { allow: 'open-space thin-dividers surface-changes cards-and-panels', fallback: 'open-space' },
|
|
operate: { allow: 'thin-dividers surface-changes cards-and-panels', fallback: 'surface-changes' },
|
|
read: { allow: 'open-space thin-dividers surface-changes', fallback: 'open-space' },
|
|
experience: { allow: 'open-space thin-dividers surface-changes', fallback: 'open-space' },
|
|
},
|
|
},
|
|
|
|
/*
|
|
The least mode-sensitive question in the run, and the matrix says so. A
|
|
radius is not a claim on the reader's attention the way a color or a shadow
|
|
is, and the two surfaces that look like candidates both survive scrutiny:
|
|
fully round controls are the house style of an entire mobile platform, so
|
|
ruling them out of app UI would put this screen against Material rather
|
|
than against a mistake, and a document has too few shapes for the answer to
|
|
reach. Only the portfolio blocks anything, and its default is sharp, since
|
|
a gallery hangs work square and the work's own edges are the shapes on the
|
|
page.
|
|
*/
|
|
'corner-style': {
|
|
tablist: 'Surface being shaped',
|
|
answered: '{} corners',
|
|
unanswered: 'no corner style chosen yet',
|
|
surfaces: {
|
|
persuade: { allow: 'sharp slightly-soft friendly pill', fallback: 'slightly-soft' },
|
|
operate: { allow: 'sharp slightly-soft friendly pill', fallback: 'slightly-soft' },
|
|
read: { allow: 'sharp slightly-soft friendly pill', fallback: 'slightly-soft' },
|
|
experience: { allow: 'sharp slightly-soft friendly', fallback: 'sharp' },
|
|
},
|
|
},
|
|
|
|
/*
|
|
Floating is out wherever the page is worked in or read at length, which is
|
|
the line color strategy already drew for drenched. Lift that deep is how an
|
|
overlay says it is temporary; spent on every resting panel it stops meaning
|
|
anything and leaves a dense screen harder to scan. The portfolio keeps it:
|
|
lifting the work off the page is a way of presenting the work.
|
|
*/
|
|
'depth-style': {
|
|
tablist: 'Surface being lifted',
|
|
answered: '{} depth',
|
|
unanswered: 'no depth style chosen yet',
|
|
surfaces: {
|
|
persuade: { allow: 'flat soft-lift floating', fallback: 'soft-lift' },
|
|
operate: { allow: 'flat soft-lift', fallback: 'flat' },
|
|
read: { allow: 'flat soft-lift', fallback: 'flat' },
|
|
experience: { allow: 'flat soft-lift floating', fallback: 'flat' },
|
|
},
|
|
},
|
|
};
|
|
|
|
/* The matrix reaches the browser on the surface tiles, which is already where
|
|
the script looks for everything a surface knows about itself. One pair of
|
|
attributes per question, read by name rather than by dataset key so the
|
|
question's own value is the lookup. A question this surface is not asked
|
|
contributes nothing, and the absent per-surface field is what the script
|
|
reads that from. */
|
|
export const surfaceAttrs = (mode) => Object.fromEntries(
|
|
Object.entries(SURFACE_ANSWERS).flatMap(([name, question]) => {
|
|
const { allow, fallback } = question.surfaces[mode] ?? {};
|
|
return [
|
|
...(allow ? [[`data-allow-${name}`, allow]] : []),
|
|
...(fallback ? [[`data-default-${name}`, fallback]] : []),
|
|
];
|
|
}),
|
|
);
|
|
|
|
/* Which surfaces a question is put to at all, in tile order. The per-surface
|
|
fields a screen renders are the browser's copy of this, and the leading
|
|
applicable surface is the one whose answer the bare key carries. */
|
|
export const surfacesAsked = (name) => SURFACE_MODES.filter(
|
|
(mode) => mode in SURFACE_ANSWERS[name].surfaces,
|
|
);
|
|
|
|
/* Everything the script needs to run a per-surface question is on its tab
|
|
strip, so a screen opts in by rendering one of these and nothing else. */
|
|
export const surfaceTabsAttrs = (name) => ({
|
|
'data-surface-tabs': name,
|
|
'data-surface-flat': SURFACE_ANSWERS[name].flat ? '' : undefined,
|
|
'data-surface-proper-name': SURFACE_ANSWERS[name].properName ? '' : undefined,
|
|
'data-surface-answered': SURFACE_ANSWERS[name].answered,
|
|
'data-surface-unanswered': SURFACE_ANSWERS[name].unanswered,
|
|
role: 'tablist',
|
|
'aria-label': SURFACE_ANSWERS[name].tablist,
|
|
});
|