Files
pbakaus_impeccable/picker/data/surfaces.js
T
2026-09-01 10:02:40 +05:00

215 lines
10 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 the tabs exist to protect 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.
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: 'committed' },
operate: { allow: 'restrained committed full-palette', fallback: 'restrained' },
read: { allow: 'restrained committed full-palette', fallback: 'restrained' },
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: {},
},
},
/*
Flat, and the tabs here are worth having anyway. The twelve-column ruler on
this screen describes a page, and it describes one whether the run ships a
dashboard or a gallery, so the answer stays one value that every screen
after it inherits. What the tabs buy is the chance to say that a surface
cannot take an answer before it is chosen for it.
Freeform is out on the two surfaces you come back to. A block that has
left the grid is found by looking rather than by habit, and habit is what
a tool and a long document are read with: the sidebar was there last time,
the callout sat against the same measure a page ago. Persuade and the
portfolio keep it, because a page seen once has nothing to remember.
The two disciplined answers are kept by all four, even though the probe
showed them only a few points apart on a dashboard. What is being chosen
between them is which spans the page spends its columns on, and that is a
decision a tool makes as much as a landing page does; ruling one out for
being quiet would buy nothing.
Persuade lands on the aligned grid with breaks in it: a page earning a
decision needs one block to lead, and that is what the breaks are for.
The other three land on the even grid, for the reason each of them is
ruled by. A tool wants the layout predictable, a document is one column
and one rhythm, and a gallery hangs work square so the only irregular
edges on the page are the work's own.
*/
'layout-structure': {
tablist: 'Surface being laid out',
answered: '{} layout',
unanswered: 'no layout structure chosen yet',
flat: true,
surfaces: {
persuade: { allow: 'simple-grid balanced freeform', fallback: 'balanced' },
operate: { allow: 'simple-grid balanced', fallback: 'simple-grid' },
read: { allow: 'simple-grid balanced', fallback: 'simple-grid' },
experience: { allow: 'simple-grid balanced freeform', 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. */
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]] : []),
];
}),
);
/* 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,
});