mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-18 00:56:30 +03:00
Mirror the screen 02 neutral contrast badge on the strategy strip, share alert painting between both strips, and render richer PRODUCT.md context in the design document with matching visual-cues schema guidance. AI-assisted commit. Co-authored-by: Cursor <cursoragent@cursor.com>
1709 lines
90 KiB
Plaintext
1709 lines
90 KiB
Plaintext
---
|
||
import Picker from '../layouts/Picker.astro';
|
||
import Artboard from '../components/Artboard.astro';
|
||
import QuestionScreen from '../components/QuestionScreen.astro';
|
||
import iconPacks from '../data/icon-packs.json';
|
||
import { SURFACE_MODES, surfaceAttrs, surfaceTabsAttrs } from '../data/surfaces.js';
|
||
|
||
const roles = [
|
||
['primary', 'Primary'],
|
||
['secondary', 'Secondary'],
|
||
['tertiary', 'Tertiary'],
|
||
['neutral', 'Neutral'],
|
||
];
|
||
|
||
// Musical intervals, smallest step first. The ratio is the multiplier between
|
||
// one size and the next; the description says what that much contrast is for,
|
||
// since the number alone tells nobody which to pick.
|
||
const scales = [
|
||
['minor-second', 1.067, 'Minor Second', 'Almost no contrast. For dense tools where weight and color carry hierarchy instead of size.'],
|
||
['major-second', 1.125, 'Major Second', 'A quiet step. Long documents and data screens that need many levels close together.'],
|
||
['minor-third', 1.2, 'Minor Third', 'The workhorse. Clear order without shouting, and it survives on small screens.'],
|
||
['major-third', 1.25, 'Major Third', 'Confident separation. A safe default when the page has both marketing and interface on it.'],
|
||
['perfect-fourth', 1.333, 'Perfect Fourth', 'Real jumps between levels. Landing pages where the headline has to lead.'],
|
||
['augmented-fourth', 1.414, 'Augmented Fourth', 'Sharp contrast. Editorial layouts built from few sizes used decisively.'],
|
||
['perfect-fifth', 1.5, 'Perfect Fifth', 'Display type against small text, with little in between. Poster thinking.'],
|
||
['golden-ratio', 1.618, 'Golden Ratio', 'The widest range here. Beautiful with one big statement, awkward with six levels.'],
|
||
];
|
||
|
||
// Exponent above the 16px base: P sits at the base, H1 six steps up.
|
||
const scaleRows = [['H1', 6], ['H2', 5], ['H3', 4], ['H4', 3], ['H5', 2], ['H6', 1], ['P', 0]];
|
||
|
||
// Only the row copy is rendered here. The glyphs are 109KB of markup for sets
|
||
// the run will use one of, so they are fetched when the screen first opens.
|
||
const iconRows = iconPacks.packs.map((pack) => ({
|
||
id: pack.id,
|
||
name: pack.name,
|
||
license: pack.license,
|
||
url: pack.url,
|
||
note: pack.note,
|
||
meta: `${pack.grid} grid / ${pack.weight} / ${pack.license}`,
|
||
}));
|
||
|
||
// The surface tiles and the per-surface answer matrix both live in
|
||
// data/surfaces.js, so the tiles carry what each surface allows without the
|
||
// matrix being restated on every screen that reads it.
|
||
|
||
// The five questions that are answered by looking at a wireframe page. Each one
|
||
// only rewrites the artboard slots it owns, so by the last screen the page is
|
||
// showing every decision made before it. Motion is the one question whose answer
|
||
// cannot be read on a handset, so it takes the frame alone.
|
||
//
|
||
// All five draw a board per surface they are put to, which is four on
|
||
// boundaries, corners, and depth and a pair each on the two scoped questions,
|
||
// motion and layout. A structural answer is a claim about a kind of page, and the
|
||
// kinds are not the same shape: an app shell divides into a rail, a working
|
||
// column, and a panel, a document is a rail and a measure, an index is a run of
|
||
// entries. Repainting one landing page under four labels showed the same page
|
||
// four times and asked the visitor to imagine the rest.
|
||
//
|
||
// Which board is showing is read off `data-surface`, the attribute the component
|
||
// already publishes and the one the tab strip toggles, rather than a class per
|
||
// screen per surface. Screen 06 takes a class because its scenes are written per
|
||
// board; the rest ask the same question of every board and only need to know
|
||
// which anatomy is under them.
|
||
const boards = [
|
||
{ surface: 'persuade' },
|
||
{ surface: 'operate' },
|
||
{ surface: 'read' },
|
||
{ surface: 'experience' },
|
||
];
|
||
|
||
const questions = [
|
||
{
|
||
id: '06',
|
||
step: 'Motion',
|
||
slug: 'motion',
|
||
name: 'motion-energy',
|
||
title: 'How much movement should it have?',
|
||
legend: 'Motion energy',
|
||
cta: 'Select motion energy',
|
||
previewClass: 'picker-preview-motion',
|
||
phone: false,
|
||
// The two surfaces this question is put to, and the only screen where a
|
||
// surface answers on a page of its own kind: an energy is demonstrated by
|
||
// watching a page use it, and a landing page and a portfolio use movement
|
||
// for different ends. Repainting one wireframe would show the same scene
|
||
// twice under two labels.
|
||
boards: [
|
||
{ surface: 'persuade' },
|
||
{ surface: 'experience', class: 'picker-preview-motion--index' },
|
||
],
|
||
options: [
|
||
{
|
||
value: 'restrained',
|
||
title: 'Restrained',
|
||
desc: 'State changes only. Things are already changed by the time you see them.',
|
||
},
|
||
{
|
||
value: 'responsive',
|
||
title: 'Responsive',
|
||
desc: 'Feedback and transitions on whatever you touch. Nothing is orchestrated.',
|
||
},
|
||
{
|
||
value: 'choreographed',
|
||
title: 'Choreographed',
|
||
desc: 'Entrances are staged and scrolling drives sequences.',
|
||
},
|
||
],
|
||
},
|
||
{
|
||
id: '07',
|
||
step: 'Layout',
|
||
slug: 'layout',
|
||
name: 'layout-structure',
|
||
title: 'How strict should the layout feel?',
|
||
legend: 'Layout structure',
|
||
cta: 'Select layout structure',
|
||
previewClass: 'picker-strategy-preview picker-preview-layout',
|
||
// The two surfaces this question is put to, for the reason in
|
||
// data/surfaces.js: a landing page answers in the spans its hero and its
|
||
// card row take and a portfolio in how each entry splits between the work
|
||
// and its caption, where a shell and a document are composed by what they
|
||
// hold. The answer is one value all the same, and the boards on the screens
|
||
// after this one show it on all four pages.
|
||
boards: [
|
||
{ surface: 'persuade' },
|
||
{ surface: 'experience' },
|
||
],
|
||
options: [
|
||
{
|
||
value: 'simple-grid',
|
||
title: 'Simple grid',
|
||
desc: 'Everything lines up. Equal columns and one rhythm down the page.',
|
||
},
|
||
{
|
||
value: 'balanced',
|
||
title: 'Balanced',
|
||
desc: 'An aligned grid with a few deliberate breaks that carry the emphasis.',
|
||
},
|
||
{
|
||
value: 'freeform',
|
||
title: 'Freeform',
|
||
desc: 'Blocks step out of the grid and overlap where it pays off.',
|
||
},
|
||
],
|
||
},
|
||
{
|
||
id: '08',
|
||
step: 'Boundaries',
|
||
slug: 'boundaries',
|
||
name: 'boundary-style',
|
||
title: 'How should sections be separated?',
|
||
legend: 'Boundary style',
|
||
cta: 'Select boundary style',
|
||
previewClass: 'picker-preview-boundaries',
|
||
boards,
|
||
options: [
|
||
{
|
||
value: 'open-space',
|
||
title: 'Open space',
|
||
desc: 'Nothing between sections but room. Spacing does the dividing.',
|
||
blocked: 'Sidebars and toolbars need a ground of their own, which is the one thing air cannot give them.',
|
||
},
|
||
{
|
||
value: 'thin-dividers',
|
||
title: 'Thin dividers',
|
||
desc: 'A hairline marks where one section ends and the next starts.',
|
||
},
|
||
{
|
||
value: 'surface-changes',
|
||
title: 'Surface changes',
|
||
desc: 'Bands of tone. Sections are told apart by the ground they sit on.',
|
||
},
|
||
{
|
||
value: 'cards-and-panels',
|
||
title: 'Cards and panels',
|
||
desc: 'Content sits inside containers with edges you can see.',
|
||
blocked: 'An edge around every block comes between people and the thing they came for.',
|
||
},
|
||
],
|
||
},
|
||
{
|
||
id: '09',
|
||
step: 'Corners',
|
||
slug: 'corners',
|
||
name: 'corner-style',
|
||
title: 'How round should shapes be?',
|
||
legend: 'Corner style',
|
||
cta: 'Select corner style',
|
||
previewClass: 'picker-preview-corners',
|
||
boards,
|
||
options: [
|
||
{
|
||
value: 'sharp',
|
||
title: 'Sharp',
|
||
desc: 'Square corners throughout. Precise, drafted, architectural.',
|
||
},
|
||
{
|
||
value: 'slightly-soft',
|
||
title: 'Slightly soft',
|
||
desc: 'Just enough radius to take the edge off without being noticed.',
|
||
},
|
||
{
|
||
value: 'friendly',
|
||
title: 'Friendly',
|
||
desc: 'Cards, buttons, and images are clearly rounded.',
|
||
},
|
||
{
|
||
value: 'pill',
|
||
title: 'Pill-like',
|
||
desc: 'Buttons and tags go fully round.',
|
||
blocked: 'Fully round controls are the loudest shape on a page where the work should lead.',
|
||
},
|
||
],
|
||
},
|
||
{
|
||
id: '10',
|
||
step: 'Depth',
|
||
slug: 'depth',
|
||
name: 'depth-style',
|
||
title: 'How much depth should the surface have?',
|
||
legend: 'Depth style',
|
||
cta: 'Select depth style',
|
||
previewClass: 'picker-preview-depth',
|
||
boards,
|
||
options: [
|
||
{
|
||
value: 'flat',
|
||
title: 'Flat',
|
||
desc: 'One plane. Nothing casts a shadow.',
|
||
},
|
||
{
|
||
value: 'soft-lift',
|
||
title: 'Soft lift',
|
||
desc: 'Cards and controls sit a little off the page.',
|
||
},
|
||
{
|
||
value: 'floating',
|
||
title: 'Floating',
|
||
desc: 'Panels are well above the surface, with the lift to prove it.',
|
||
blocked: 'Lift this deep belongs to menus and dialogs, not to a page people work in or read at length.',
|
||
},
|
||
],
|
||
},
|
||
];
|
||
---
|
||
|
||
<Picker>
|
||
<main class="picker-shell">
|
||
<div class="picker-hero-art" style="background-image: url('assets/hero-dark.jpg')" aria-hidden="true"></div>
|
||
|
||
<form id="picker-form">
|
||
<section class="picker-screen" data-screen="01" data-active aria-labelledby="picker-start-title">
|
||
<div class="picker-container">
|
||
<div class="picker-start">
|
||
<div class="picker-brand">
|
||
<span class="picker-brand-logo" aria-hidden="true">
|
||
<svg viewBox="0 0 24 24" fill="currentColor">
|
||
<path d="M5 2.5 L13.5 2.5 L5.5 21.5 L5 21.5 Q2.5 21.5 2.5 19 L2.5 5 Q2.5 2.5 5 2.5 Z"></path>
|
||
<path d="M16.5 2.5 L19 2.5 Q21.5 2.5 21.5 5 L21.5 19 Q21.5 21.5 19 21.5 L8.5 21.5 Z"></path>
|
||
</svg>
|
||
</span>
|
||
<span class="picker-brand-name">Impeccable</span>
|
||
</div>
|
||
|
||
<h1 id="picker-start-title" class="picker-title">Let's build your<br />design system.</h1>
|
||
|
||
<div class="picker-actions">
|
||
<button class="ks-button ks-button-primary" type="button" data-advance="next">
|
||
Start
|
||
<span class="ks-button-arrow" aria-hidden="true">
|
||
<svg viewBox="0 0 16 8" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="square">
|
||
<path d="M0 4h14M10 0l4 4-4 4"></path>
|
||
</svg>
|
||
</span>
|
||
</button>
|
||
</div>
|
||
|
||
<div class="picker-key-hint">
|
||
<div class="picker-key-cluster" aria-hidden="true">
|
||
<div class="picker-key-row picker-key-row-top">
|
||
<kbd class="picker-keycap">↑</kbd>
|
||
</div>
|
||
<div class="picker-key-row">
|
||
<kbd class="picker-keycap">←</kbd>
|
||
<kbd class="picker-keycap">↓</kbd>
|
||
<kbd class="picker-keycap">→</kbd>
|
||
</div>
|
||
</div>
|
||
<p class="picker-key-caption">Arrow keys to move</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="picker-screen" data-screen="01b" data-step="Surfaces" aria-hidden="true" aria-labelledby="picker-modes-title">
|
||
<div class="picker-container">
|
||
<div class="picker-modes">
|
||
<h2 id="picker-modes-title" class="picker-question-title">What are we designing?</h2>
|
||
|
||
<fieldset class="picker-modes-grid">
|
||
<legend>Surfaces</legend>
|
||
|
||
<label class="picker-mode-tile">
|
||
<input type="checkbox" name="surface-modes" value="persuade" aria-label="Landing pages, marketing, campaigns, and pricing" data-surface-label="Landing page" {...surfaceAttrs('persuade')} checked />
|
||
<span class="picker-mode-preview picker-mode-preview--exact" aria-hidden="true">
|
||
<span class="picker-preview">
|
||
<span class="pv-desktop">
|
||
<span class="pv-nav">
|
||
<span class="pv-logo"></span>
|
||
<span class="pv-nav-bars"><i></i><i></i><i></i></span>
|
||
<span class="pv-pill"></span>
|
||
</span>
|
||
<span class="pv-hero">
|
||
<span class="pv-hero-copy">
|
||
<span class="pv-eyebrow"></span>
|
||
<span class="pv-headline"><i></i><i></i></span>
|
||
<span class="pv-copy-bar"></span>
|
||
<span class="pv-actions"><i></i><i></i></span>
|
||
</span>
|
||
<span class="pv-image"></span>
|
||
</span>
|
||
</span>
|
||
<span class="pv-phone">
|
||
<span class="pv-phone-top">
|
||
<span class="pv-logo"></span>
|
||
<span class="pv-avatar"></span>
|
||
</span>
|
||
<span class="pv-phone-body">
|
||
<span class="pv-image"></span>
|
||
<span class="pv-mini-card"><i></i><span></span></span>
|
||
<span class="pv-mini-card"><i></i><span></span></span>
|
||
</span>
|
||
<span class="pv-tabbar"><i></i><i></i><i></i><i></i></span>
|
||
</span>
|
||
</span>
|
||
</span>
|
||
<span class="picker-mode-copy">
|
||
<span class="picker-mode-pills"><i>Landing pages</i><i>Marketing</i><i>Campaigns</i><i>Pricing</i></span>
|
||
<span class="picker-mode-goal">You're <strong>convincing</strong> someone to act. Lead with proof and a clear next step.</span>
|
||
</span>
|
||
</label>
|
||
|
||
<label class="picker-mode-tile">
|
||
<input type="checkbox" name="surface-modes" value="operate" aria-label="App UI, dashboards, admin, and settings" data-surface-label="App UI" {...surfaceAttrs('operate')} />
|
||
<span class="picker-mode-preview picker-mode-preview--exact" aria-hidden="true">
|
||
<span class="picker-preview picker-preview--ops">
|
||
<span class="pv-desktop">
|
||
<span class="pv-nav">
|
||
<span class="pv-logo"></span>
|
||
<span class="pv-nav-bars"><i></i><i></i><i></i></span>
|
||
<span class="pv-pill"></span>
|
||
</span>
|
||
<span class="po-body">
|
||
<span class="po-side">
|
||
<span class="po-item"><i class="po-dot"></i><i class="po-line"></i></span>
|
||
<span class="po-item po-item--on"><i class="po-dot"></i><i class="po-line"></i></span>
|
||
<span class="po-item"><i class="po-dot"></i><i class="po-line"></i></span>
|
||
</span>
|
||
<span class="po-main">
|
||
<span class="po-chart">
|
||
<i style="--h: 19.23cqh"></i>
|
||
<i style="--h: 26.17cqh"></i>
|
||
<i style="--h: 16cqh"></i>
|
||
<i class="po-peak" style="--h: 30.7cqh"></i>
|
||
<i style="--h: 22.3cqh"></i>
|
||
</span>
|
||
<span class="po-divider"></span>
|
||
<span class="po-rows">
|
||
<span class="po-row">
|
||
<i class="po-dot"></i>
|
||
<i class="po-cell" style="--w: 15.24"></i>
|
||
<i class="po-cell" style="--w: 12.76"></i>
|
||
<i class="po-cell" style="--w: 12.76"></i>
|
||
</span>
|
||
<span class="po-row po-row--on">
|
||
<i class="po-dot"></i>
|
||
<i class="po-cell" style="--w: 15.24"></i>
|
||
<i class="po-cell" style="--w: 12.76"></i>
|
||
<i class="po-cell" style="--w: 12.61"></i>
|
||
</span>
|
||
<span class="po-row">
|
||
<i class="po-dot"></i>
|
||
<i class="po-cell" style="--w: 15.24"></i>
|
||
<i class="po-cell" style="--w: 12.76"></i>
|
||
<i class="po-cell" style="--w: 12.61"></i>
|
||
</span>
|
||
</span>
|
||
</span>
|
||
<span class="po-panel">
|
||
<span class="po-heading"></span>
|
||
<span class="po-field"><i class="po-chev"></i></span>
|
||
<span class="po-switch"><i class="po-label"></i><i class="po-toggle po-toggle--on"></i></span>
|
||
<span class="po-switch"><i class="po-label"></i><i class="po-toggle"></i></span>
|
||
</span>
|
||
</span>
|
||
</span>
|
||
<span class="pv-phone">
|
||
<span class="pv-phone-top">
|
||
<span class="pv-logo"></span>
|
||
<span class="pv-avatar"></span>
|
||
</span>
|
||
<span class="po-phone-body">
|
||
<span class="po-chart">
|
||
<i style="--h: 17.34cqh"></i>
|
||
<i style="--h: 25.42cqh"></i>
|
||
<i style="--h: 15.33cqh"></i>
|
||
<i class="po-peak" style="--h: 33.57cqh"></i>
|
||
<i style="--h: 23.93cqh"></i>
|
||
</span>
|
||
<span class="po-field"><i class="po-chev"></i></span>
|
||
<span class="po-prow">
|
||
<i class="po-dot"></i>
|
||
<i class="po-label"></i>
|
||
<i class="po-toggle po-toggle--on"></i>
|
||
</span>
|
||
<span class="po-prow">
|
||
<i class="po-dot"></i>
|
||
<i class="po-label"></i>
|
||
<i class="po-chev po-chev--next"></i>
|
||
</span>
|
||
</span>
|
||
<span class="pv-tabbar"><i></i><i></i><i></i><i></i></span>
|
||
</span>
|
||
</span>
|
||
</span>
|
||
<span class="picker-mode-copy">
|
||
<span class="picker-mode-pills"><i>App UI</i><i>Dashboards</i><i>Admin</i><i>Settings</i></span>
|
||
<span class="picker-mode-goal"><strong>Familiarity</strong> comes first. Keep paths obvious and the layout predictable.</span>
|
||
</span>
|
||
</label>
|
||
|
||
<label class="picker-mode-tile">
|
||
<input type="checkbox" name="surface-modes" value="read" aria-label="Docs, articles, guides, and changelogs" data-surface-label="Docs" {...surfaceAttrs('read')} />
|
||
<span class="picker-mode-preview picker-mode-preview--exact" aria-hidden="true">
|
||
<span class="picker-preview picker-preview--docs">
|
||
<span class="pv-desktop">
|
||
<span class="pv-nav">
|
||
<span class="pv-logo"></span>
|
||
<span class="pv-nav-bars"><i></i><i></i><i></i></span>
|
||
<span class="pv-pill"></span>
|
||
</span>
|
||
<span class="pd-body">
|
||
<span class="pd-side">
|
||
<span class="pd-item"><i></i><span></span></span>
|
||
<span class="pd-item pd-item--on"><i></i><span></span></span>
|
||
<span class="pd-item"><i></i><span></span></span>
|
||
<span class="pd-item"><i></i><span></span></span>
|
||
</span>
|
||
<span class="pd-main">
|
||
<span class="pd-heading" style="--w:95%"></span>
|
||
<span class="pd-para pd-para--lede"><i style="--w:71%"></i><i style="--w:41%"></i></span>
|
||
<span class="pd-para"><i></i><i></i><i style="--w:72%"></i></span>
|
||
<span class="pd-para"><i></i><i></i><i style="--w:43%"></i></span>
|
||
<span class="pd-note">
|
||
<i class="pd-note-dot"></i>
|
||
<span class="pd-note-lines"><i style="--w:85%"></i><i style="--w:62%"></i></span>
|
||
</span>
|
||
</span>
|
||
</span>
|
||
</span>
|
||
<span class="pv-phone">
|
||
<span class="pv-phone-top">
|
||
<span class="pv-logo"></span>
|
||
<span class="pv-avatar"></span>
|
||
</span>
|
||
<span class="pd-phone-body">
|
||
<span class="pd-crumb"></span>
|
||
<span class="pd-heading" style="--w:98.5%"></span>
|
||
<span class="pd-para pd-para--lede"><i style="--w:71%"></i><i style="--w:55%"></i></span>
|
||
<span class="pd-para"><i></i><i></i><i></i><i style="--w:69%"></i></span>
|
||
<span class="pd-para"><i></i><i></i><i style="--w:57%"></i></span>
|
||
<span class="pd-note">
|
||
<i class="pd-note-dot"></i>
|
||
<span class="pd-note-lines"><i style="--w:92%"></i><i style="--w:63%"></i></span>
|
||
</span>
|
||
</span>
|
||
<span class="pv-tabbar"><i></i><i></i><i></i><i></i></span>
|
||
</span>
|
||
</span>
|
||
</span>
|
||
<span class="picker-mode-copy">
|
||
<span class="picker-mode-pills"><i>Docs</i><i>Articles</i><i>Guides</i><i>Changelogs</i></span>
|
||
<span class="picker-mode-goal">The job is to <strong>read and understand</strong>. Let type and structure do the work.</span>
|
||
</span>
|
||
</label>
|
||
|
||
<label class="picker-mode-tile">
|
||
<input type="checkbox" name="surface-modes" value="experience" aria-label="Portfolios, galleries, and showcases" data-surface-label="Portfolio" {...surfaceAttrs('experience')} />
|
||
<span class="picker-mode-preview picker-mode-preview--exact" aria-hidden="true">
|
||
<span class="picker-preview picker-preview--gallery">
|
||
<span class="pv-desktop">
|
||
<span class="pv-nav">
|
||
<span class="pv-logo"></span>
|
||
<span class="pv-nav-bars"><i></i><i></i><i></i></span>
|
||
<span class="pv-pill"></span>
|
||
</span>
|
||
<span class="pg-body">
|
||
<span class="pg-row">
|
||
<span class="pv-image"></span>
|
||
<span class="pg-cap">
|
||
<i class="pg-cap-title" style="--w:33.61%"></i>
|
||
<i></i>
|
||
<i style="--w:65.3%"></i>
|
||
</span>
|
||
</span>
|
||
<span class="pg-row pg-row--flip">
|
||
<span class="pg-cap">
|
||
<i class="pg-cap-title" style="--w:36.64%"></i>
|
||
<i></i>
|
||
<i style="--w:66.07%"></i>
|
||
</span>
|
||
<span class="pv-image"></span>
|
||
</span>
|
||
<span class="pg-rail">
|
||
<i class="pg-arrow"></i>
|
||
<span class="pg-track">
|
||
<i class="pg-track-on" style="--w: 20.31"></i>
|
||
<i style="--w: 9.14"></i>
|
||
<i style="--w: 8.98"></i>
|
||
<i style="--w: 12.44"></i>
|
||
</span>
|
||
<i class="pg-arrow pg-arrow--next"></i>
|
||
</span>
|
||
</span>
|
||
</span>
|
||
<span class="pv-phone">
|
||
<span class="pv-phone-top">
|
||
<span class="pv-logo"></span>
|
||
<span class="pv-avatar"></span>
|
||
</span>
|
||
<span class="pg-phone-body">
|
||
<span class="pv-image"></span>
|
||
<span class="pg-cap">
|
||
<i class="pg-cap-title" style="--w:30.18%"></i>
|
||
<i style="--w:79.68%"></i>
|
||
<i style="--w:55.6%"></i>
|
||
</span>
|
||
<span class="pv-image"></span>
|
||
<span class="pg-cap">
|
||
<i class="pg-cap-title" style="--w:30.18%"></i>
|
||
<i style="--w:79.68%"></i>
|
||
<i style="--w:55.6%"></i>
|
||
</span>
|
||
</span>
|
||
<span class="pv-tabbar"><i></i><i></i><i></i></span>
|
||
</span>
|
||
</span>
|
||
</span>
|
||
<span class="picker-mode-copy">
|
||
<span class="picker-mode-pills"><i>Portfolios</i><i>Galleries</i><i>Showcases</i></span>
|
||
<span class="picker-mode-goal">You're <strong>showcasing</strong> work, not pitching it. Let browsing feel like exploring the work up close.</span>
|
||
</span>
|
||
</label>
|
||
</fieldset>
|
||
|
||
<p class="picker-modes-note">
|
||
Previews only help you decide. They will <strong>not be the final design</strong>.
|
||
</p>
|
||
|
||
<div class="picker-actions-stack">
|
||
<button class="ks-button ks-button-primary" type="button" data-modes-next data-advance="next">
|
||
Continue
|
||
<span class="ks-button-arrow" aria-hidden="true">
|
||
<svg viewBox="0 0 16 8" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="square">
|
||
<path d="M0 4h14M10 0l4 4-4 4"></path>
|
||
</svg>
|
||
</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="picker-screen" data-screen="02" data-step="Palette" aria-hidden="true" aria-labelledby="picker-palette-title">
|
||
<div class="picker-container">
|
||
<div class="picker-palette">
|
||
<h2 id="picker-palette-title" class="picker-question-title">Choose a color palette</h2>
|
||
|
||
<div class="picker-palette-grid">
|
||
<div class="picker-deck-column" aria-label="Palette sources">
|
||
<div class="picker-deck-stage">
|
||
<div class="picker-deck-scroller" data-deck-scroll aria-label="Browse palette cards">
|
||
<div data-deck-points></div>
|
||
</div>
|
||
<div class="picker-card-layer" data-deck-cards></div>
|
||
<div class="picker-loupe" data-loupe aria-hidden="true">
|
||
<canvas width="80" height="80"></canvas>
|
||
</div>
|
||
</div>
|
||
|
||
<template data-cue-card>
|
||
<article class="picker-card">
|
||
<div class="picker-card-face picker-cue-face">
|
||
<img alt="" />
|
||
{roles.map(([role, label]) => (
|
||
<button class="picker-ring" type="button" data-role={role} role="slider" aria-label={`${label} color picker`}></button>
|
||
))}
|
||
</div>
|
||
</article>
|
||
</template>
|
||
|
||
<template data-seed-card>
|
||
<article class="picker-card">
|
||
<div class="picker-card-face picker-seed-face">
|
||
{roles.map(() => <span></span>)}
|
||
</div>
|
||
</article>
|
||
</template>
|
||
|
||
<div class="picker-deck-controls">
|
||
<button class="picker-icon-button" type="button" data-deck-prev aria-label="Previous card">
|
||
<svg viewBox="0 0 20 20" aria-hidden="true"><path d="m12.5 4.5-5 5.5 5 5.5"></path></svg>
|
||
</button>
|
||
<p class="picker-deck-count" data-deck-count aria-live="polite">Loading palettes</p>
|
||
<button class="picker-icon-button" type="button" data-deck-next aria-label="Next card">
|
||
<svg viewBox="0 0 20 20" aria-hidden="true"><path d="m7.5 4.5 5 5.5-5 5.5"></path></svg>
|
||
</button>
|
||
</div>
|
||
<p class="picker-ring-guide" data-ring-guide>
|
||
<span>Drag the color rings to sample from the image.</span>
|
||
<span>Use arrow keys for precise adjustments.</span>
|
||
</p>
|
||
</div>
|
||
|
||
<div class="picker-palette-subgrid">
|
||
<fieldset class="picker-palette-panel">
|
||
<legend>Chosen colors</legend>
|
||
<button class="picker-icon-button picker-tip picker-reset" type="button" data-reset data-tip="Reset to this card's colors" aria-label="Reset to this card's colors">
|
||
<svg viewBox="0 0 20 20" aria-hidden="true"><path d="M4.6 7.2A6.2 6.2 0 1 1 4 11.4M4.6 7.2V3.8m0 3.4H8"></path></svg>
|
||
</button>
|
||
|
||
<p
|
||
class="picker-palette-hint"
|
||
data-palette-hint
|
||
data-idle="Drag the rings on the image to pull colors from it, or edit any swatch directly."
|
||
data-primary="Your main brand color: buttons, links, the color people remember. Pick something with presence."
|
||
data-secondary="Supports the primary: section accents, hovers, secondary buttons. Adjacent, not identical."
|
||
data-tertiary="The rare accent: badges, highlights, one detail per screen. The most saturated of the four."
|
||
data-neutral="Backgrounds and large surfaces: most of every page. Quiet, near-white or near-black."
|
||
aria-live="polite"
|
||
>Drag the rings on the image to pull colors from it, or edit any swatch directly.</p>
|
||
|
||
<div class="picker-bands">
|
||
{roles.map(([role, label]) => (
|
||
<div class="picker-band-item" data-band-item={role}>
|
||
<div class="picker-band" data-band={role} tabindex="0">
|
||
<button class="picker-band-grip picker-tip" type="button" data-grip={role} data-tip="Drag to reorder" aria-label={`Reorder the ${label} color`}>
|
||
<svg viewBox="0 0 20 20" aria-hidden="true">
|
||
<circle cx="7" cy="5" r="1.5"></circle><circle cx="13" cy="5" r="1.5"></circle>
|
||
<circle cx="7" cy="10" r="1.5"></circle><circle cx="13" cy="10" r="1.5"></circle>
|
||
<circle cx="7" cy="15" r="1.5"></circle><circle cx="13" cy="15" r="1.5"></circle>
|
||
</svg>
|
||
</button>
|
||
<output data-band-hex={role}>#000000</output>
|
||
<div class="picker-band-tools" role="group" aria-label={`${label} color actions`}>
|
||
<button class="picker-band-tool picker-tip" type="button" data-copy-color={role} data-tip="Copy hex + OKLCH" aria-label={`Copy ${label} hex and OKLCH`}>
|
||
<svg viewBox="0 0 20 20" aria-hidden="true"><rect x="6.5" y="6.5" width="9" height="9"></rect><path d="M4.5 13.5h-1v-10h10v1"></path></svg>
|
||
</button>
|
||
<button class="picker-band-tool picker-tip" type="button" data-edit-tints={role} data-tip="Edit tints" aria-expanded="false" aria-label={`Edit ${label} tints`}>
|
||
<svg viewBox="0 0 20 20" aria-hidden="true"><path d="m4 14.5-.4 2 2-.4L15 6.7 13.3 5 4 14.5Z"></path><path d="m11.9 6.4 1.7 1.7"></path></svg>
|
||
</button>
|
||
<button class="picker-band-tool picker-tip" type="button" data-custom-color={role} data-tip="Custom color" aria-label={`Choose custom ${label} color`}>
|
||
<svg viewBox="0 0 20 20" aria-hidden="true"><path d="M10 4v12M4 10h12"></path></svg>
|
||
</button>
|
||
</div>
|
||
<input class="picker-native-color" type="color" value="#000000" data-color-input={role} tabindex="-1" aria-label={`Custom ${label} color`} />
|
||
{role === 'neutral' && (
|
||
<>
|
||
<span class="picker-band-alert" data-contrast-alert role="img" tabindex="0" aria-label="Contrast warning" aria-describedby="picker-contrast-tip" hidden>
|
||
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path fill="currentColor" fill-rule="evenodd" d="M12 2.5 22.5 20.5H1.5L12 2.5Z M11 9h2l-.3 5.4h-1.4L11 9Z M12 16.2a1.2 1.2 0 1 0 0 2.4 1.2 1.2 0 0 0 0-2.4Z"></path></svg>
|
||
</span>
|
||
<span class="picker-band-alert-tip" role="tooltip" id="picker-contrast-tip" data-contrast-tip></span>
|
||
</>
|
||
)}
|
||
</div>
|
||
<div class="picker-band-foot">
|
||
<h2>{label}</h2>
|
||
<div class="picker-tint-strip" data-tints={role} hidden>
|
||
{Array.from({ length: 7 }, () => <button class="picker-tint" type="button" data-tint></button>)}
|
||
<button class="picker-tint-close" type="button" data-close-tints aria-label="Close tints">×</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
<p class="picker-band-status" data-reorder-status role="status" aria-live="polite"></p>
|
||
|
||
<div class="picker-preview" aria-hidden="true">
|
||
<div class="pv-desktop">
|
||
<div class="pv-nav">
|
||
<div class="pv-logo"></div>
|
||
<div class="pv-nav-bars"><i></i><i></i><i></i></div>
|
||
<div class="pv-pill"></div>
|
||
</div>
|
||
<div class="pv-hero">
|
||
<div class="pv-hero-copy">
|
||
<div class="pv-eyebrow"></div>
|
||
<div class="pv-headline"><i></i><i></i></div>
|
||
<div class="pv-copy-bar"></div>
|
||
<div class="pv-actions"><i></i><i></i></div>
|
||
</div>
|
||
<div class="pv-image"></div>
|
||
</div>
|
||
</div>
|
||
<div class="pv-phone">
|
||
<div class="pv-phone-top">
|
||
<div class="pv-logo"></div>
|
||
<div class="pv-avatar"></div>
|
||
</div>
|
||
<div class="pv-phone-body">
|
||
<div class="pv-image"></div>
|
||
<div class="pv-mini-card"><i></i><span></span></div>
|
||
<div class="pv-mini-card"><i></i><span></span></div>
|
||
</div>
|
||
<div class="pv-tabbar"><i></i><i></i><i></i><i></i></div>
|
||
</div>
|
||
</div>
|
||
</fieldset>
|
||
|
||
<div class="picker-actions-stack">
|
||
<button class="ks-button ks-button-primary picker-select" type="button" data-select-palette data-advance="next" disabled>
|
||
Select this palette
|
||
<span class="ks-button-arrow" aria-hidden="true">
|
||
<svg viewBox="0 0 16 8" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="square">
|
||
<path d="M0 4h14M10 0l4 4-4 4"></path>
|
||
</svg>
|
||
</span>
|
||
</button>
|
||
<button class="picker-back" type="button" data-advance="prev">Back</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<input type="hidden" name="palette-source" />
|
||
{roles.map(([role]) => <input type="hidden" name={`palette-${role}`} />)}
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="picker-screen" data-screen="03" data-step="Color strategy" aria-hidden="true" aria-labelledby="picker-strategy-title">
|
||
<div class="picker-container">
|
||
<div class="picker-strategy">
|
||
<h2 id="picker-strategy-title" class="picker-question-title">How much color should this design use?</h2>
|
||
|
||
<div class="picker-strategy-grid">
|
||
<div class="picker-type-rail">
|
||
<fieldset class="picker-strategy-options picker-strategy-choices picker-type-options">
|
||
<legend>Color strategy</legend>
|
||
|
||
<label class="picker-strategy-option">
|
||
<input type="radio" name="color-strategy" value="restrained" />
|
||
<span class="picker-strategy-copy">
|
||
<span class="picker-strategy-title">Restrained</span>
|
||
<span class="picker-strategy-desc">Mostly neutral. The primary appears in a few deliberate places.</span>
|
||
</span>
|
||
</label>
|
||
|
||
<label class="picker-strategy-option">
|
||
<input type="radio" name="color-strategy" value="committed" />
|
||
<span class="picker-strategy-copy">
|
||
<span class="picker-strategy-title">Committed</span>
|
||
<span class="picker-strategy-desc">One strong color does real work across the page.</span>
|
||
</span>
|
||
</label>
|
||
|
||
{/* The reason a surface cannot take an option replaces the
|
||
description on the row, so it is written here beside the
|
||
copy it stands in for rather than in the script. */}
|
||
<label class="picker-strategy-option" data-blocked-reason="Four colors on duty compete with the work on show.">
|
||
<input type="radio" name="color-strategy" value="full-palette" checked />
|
||
<span class="picker-strategy-copy">
|
||
<span class="picker-strategy-title">Full palette</span>
|
||
<span class="picker-strategy-desc">All four colors on duty, each with a clear job.</span>
|
||
</span>
|
||
</label>
|
||
|
||
<label class="picker-strategy-option" data-blocked-reason="Too loud for a page people work in or read at length.">
|
||
<input type="radio" name="color-strategy" value="drenched" />
|
||
<span class="picker-strategy-copy">
|
||
<span class="picker-strategy-title">Drenched</span>
|
||
<span class="picker-strategy-desc">The page itself is the color. Everything else sits on top.</span>
|
||
</span>
|
||
</label>
|
||
</fieldset>
|
||
|
||
{/* Every chosen surface is colored separately and leaves its own
|
||
answer, so each has a field to leave it in. They stay disabled
|
||
until their tile is chosen, which is what keeps a surface
|
||
nobody asked for out of the answers. */}
|
||
{SURFACE_MODES.map((mode) => (
|
||
<input type="hidden" data-surface-field={`color-strategy-${mode}`} name={`color-strategy-${mode}`} disabled />
|
||
))}
|
||
|
||
<div class="picker-bands picker-bands--compact" data-band-scope="strategy">
|
||
{roles.map(([role, label]) => (
|
||
<div class="picker-band-item" data-band-item={role}>
|
||
<div class="picker-band" data-band={role}>
|
||
<button class="picker-band-grip picker-tip" type="button" data-grip={role} data-tip="Drag to reorder" aria-label={`Reorder the ${label} color`}>
|
||
<svg viewBox="0 0 20 20" aria-hidden="true">
|
||
<circle cx="7" cy="5" r="1.5"></circle><circle cx="13" cy="5" r="1.5"></circle>
|
||
<circle cx="7" cy="10" r="1.5"></circle><circle cx="13" cy="10" r="1.5"></circle>
|
||
<circle cx="7" cy="15" r="1.5"></circle><circle cx="13" cy="15" r="1.5"></circle>
|
||
</svg>
|
||
</button>
|
||
<output data-band-hex={role}>#000000</output>
|
||
{role === 'neutral' && (
|
||
<>
|
||
<span class="picker-band-alert" data-contrast-alert role="img" tabindex="0" aria-label="Contrast warning" aria-describedby="picker-strategy-contrast-tip" hidden>
|
||
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path fill="currentColor" fill-rule="evenodd" d="M12 2.5 22.5 20.5H1.5L12 2.5Z M11 9h2l-.3 5.4h-1.4L11 9Z M12 16.2a1.2 1.2 0 1 0 0 2.4 1.2 1.2 0 0 0 0-2.4Z"></path></svg>
|
||
</span>
|
||
<span class="picker-band-alert-tip" role="tooltip" id="picker-strategy-contrast-tip" data-contrast-tip></span>
|
||
</>
|
||
)}
|
||
</div>
|
||
<div class="picker-band-foot">
|
||
<h2>{label}</h2>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
<p class="picker-band-status" data-reorder-status role="status" aria-live="polite"></p>
|
||
</div>
|
||
|
||
<div class="picker-strategy-stage" data-surface-stage>
|
||
<div class="picker-surface-tabs" {...surfaceTabsAttrs('color-strategy')} hidden></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="picker-actions-stack">
|
||
<button class="ks-button ks-button-primary" type="button" data-advance="next">
|
||
Select this strategy
|
||
<span class="ks-button-arrow" aria-hidden="true">
|
||
<svg viewBox="0 0 16 8" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="square">
|
||
<path d="M0 4h14M10 0l4 4-4 4"></path>
|
||
</svg>
|
||
</span>
|
||
</button>
|
||
<button class="picker-back" type="button" data-advance="prev">Back</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="picker-screen" data-screen="04" data-step="Typeface" aria-hidden="true" aria-labelledby="picker-type-title">
|
||
<div class="picker-container">
|
||
<div class="picker-type">
|
||
<h2 id="picker-type-title" class="picker-question-title">Choose a font pair</h2>
|
||
|
||
<div class="picker-type-grid">
|
||
<div class="picker-type-rail">
|
||
<fieldset class="picker-strategy-options picker-type-options" data-font-options>
|
||
<legend>Font pairs</legend>
|
||
</fieldset>
|
||
|
||
<div class="picker-type-controls">
|
||
<div class="picker-type-scroll">
|
||
<button class="picker-icon-button" type="button" data-list-scroll="-1" aria-label="Scroll font pairs up">
|
||
<svg viewBox="0 0 20 20" aria-hidden="true"><path d="m4.5 12.5 5.5-5 5.5 5"></path></svg>
|
||
</button>
|
||
<button class="picker-icon-button" type="button" data-list-scroll="1" aria-label="Scroll font pairs down">
|
||
<svg viewBox="0 0 20 20" aria-hidden="true"><path d="m4.5 7.5 5.5 5 5.5-5"></path></svg>
|
||
</button>
|
||
</div>
|
||
|
||
<button class="picker-type-custom" type="button" data-font-custom-open>
|
||
<svg viewBox="0 0 20 20" aria-hidden="true"><path d="M10 4.5v11M4.5 10h11"></path></svg>
|
||
Custom font
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* One board per surface, all mounted and one shown. The stage is
|
||
the box the strip docks on and the node the chosen pair's
|
||
faces are written to, so every board is set in them at once. */}
|
||
<div class="picker-type-stage" data-type-stage>
|
||
<div class="picker-surface-tabs" {...surfaceTabsAttrs('font-pair')} hidden></div>
|
||
<Artboard mode="type" surface="persuade" class="picker-strategy-preview picker-preview-type" typeHook />
|
||
<Artboard mode="type" surface="operate" class="picker-strategy-preview picker-preview-type picker-preview-type--ops" typeHook />
|
||
<Artboard mode="type" surface="read" class="picker-strategy-preview picker-preview-type picker-preview-type--read" typeHook />
|
||
<Artboard mode="type" surface="experience" class="picker-strategy-preview picker-preview-type picker-preview-type--experience" typeHook />
|
||
</div>
|
||
</div>
|
||
|
||
<template data-pair-card>
|
||
<label class="picker-strategy-option picker-type-option">
|
||
<input type="radio" name="font-pair" />
|
||
<span class="picker-type-copy">
|
||
<span class="picker-type-sample-heading"><span class="picker-strategy-title" data-pair-heading></span><span class="picker-type-role">headings</span></span>
|
||
<span class="picker-type-sample-body"><span data-pair-body></span><span class="picker-type-role">body text</span></span>
|
||
<span class="picker-type-desc picker-strategy-desc" data-pair-why></span>
|
||
</span>
|
||
</label>
|
||
</template>
|
||
|
||
{/* The pair is one answer for the whole run, so these fields carry
|
||
no name and nothing but the strip reads them. They are what
|
||
stops a tab from putting its own surface's reading of the
|
||
question into the answers. */}
|
||
{SURFACE_MODES.map((mode) => (
|
||
<input type="hidden" data-surface-field={`font-pair-${mode}`} disabled />
|
||
))}
|
||
|
||
<input type="hidden" name="font-heading" />
|
||
<input type="hidden" name="font-body" />
|
||
<input type="hidden" name="font-heading-source" />
|
||
<input type="hidden" name="font-body-source" />
|
||
|
||
<div class="picker-actions-stack">
|
||
<button class="ks-button ks-button-primary" type="button" data-advance="next">
|
||
Select this pair
|
||
<span class="ks-button-arrow" aria-hidden="true">
|
||
<svg viewBox="0 0 16 8" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="square">
|
||
<path d="M0 4h14M10 0l4 4-4 4"></path>
|
||
</svg>
|
||
</span>
|
||
</button>
|
||
<button class="picker-back" type="button" data-advance="prev">Back</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="picker-screen" data-screen="05" data-step="Type scale" aria-hidden="true" aria-labelledby="picker-scale-title">
|
||
<div class="picker-container">
|
||
<div class="picker-scale">
|
||
<div class="picker-question-head">
|
||
<h2 id="picker-scale-title" class="picker-question-title">Choose a type scale</h2>
|
||
<p class="picker-question-lede">
|
||
Create a polished, professional experience with text that stays consistent and easy to read everywhere.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="picker-scale-grid">
|
||
<div class="picker-type-rail">
|
||
<fieldset class="picker-strategy-options picker-type-options picker-scale-options" data-scale-options>
|
||
<legend>Type scale</legend>
|
||
|
||
{scales.map(([slug, ratio, name, description]) => (
|
||
<label class="picker-strategy-option">
|
||
<input
|
||
type="radio"
|
||
name="type-scale"
|
||
value={slug}
|
||
data-ratio={ratio}
|
||
data-scale-name={name}
|
||
checked={slug === 'major-third'}
|
||
/>
|
||
<span class="picker-strategy-copy">
|
||
<span class="picker-strategy-title">
|
||
<b class="picker-scale-ratio">{ratio.toFixed(3)}</b>
|
||
{name}
|
||
</span>
|
||
<span class="picker-strategy-desc">{description}</span>
|
||
</span>
|
||
</label>
|
||
))}
|
||
</fieldset>
|
||
|
||
<div class="picker-type-controls">
|
||
<div class="picker-type-scroll">
|
||
<button class="picker-icon-button" type="button" data-list-scroll="-1" aria-label="Scroll type scales up">
|
||
<svg viewBox="0 0 20 20" aria-hidden="true"><path d="m4.5 12.5 5.5-5 5.5 5"></path></svg>
|
||
</button>
|
||
<button class="picker-icon-button" type="button" data-list-scroll="1" aria-label="Scroll type scales down">
|
||
<svg viewBox="0 0 20 20" aria-hidden="true"><path d="m4.5 7.5 5.5 5 5.5-5"></path></svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="picker-scale-column">
|
||
<div class="picker-scale-sheet" data-scale-sheet aria-hidden="true">
|
||
{scaleRows.map(([tag, step]) => (
|
||
<div class="ts-row" data-scale-row={step}>
|
||
<p class="ts-sample" data-scale-sample></p>
|
||
<span class="ts-tag">{tag}</span>
|
||
<span class="ts-values">
|
||
<b data-scale-px></b>
|
||
<i data-scale-rem></i>
|
||
</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
<div class="picker-type-controls">
|
||
<p class="picker-scale-note">Values at a 16px base, specimen scaled to fit</p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* The same scale on the components a page is actually built from.
|
||
Headings are placeholders and running text is lorem, for the
|
||
reason the wireframe copy is: a face and a ratio are judged on
|
||
texture, and real prose pulls the eye into reading it instead.
|
||
Every step the sheet lists has a component here, which is why
|
||
the lede and the quote exist at all: they carry the two steps
|
||
no editorial page would spend a heading level on. */}
|
||
<div class="picker-scale-column">
|
||
<div class="picker-scale-specimen" data-scale-specimen aria-hidden="true">
|
||
<p class="sp-h1" data-sp-label="H1">A generic display heading</p>
|
||
<p class="sp-lede" data-sp-label="Lede">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt.</p>
|
||
<p class="sp-p" data-sp-label="P">
|
||
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
|
||
consequat. Duis aute <em>irure dolor in reprehenderit</em> in voluptate velit esse cillum dolore eu
|
||
fugiat nulla pariatur, <u>excepteur sint occaecat</u> cupidatat non proident.
|
||
</p>
|
||
|
||
<p class="sp-h2" data-sp-label="H2">A section heading</p>
|
||
<p class="sp-p" data-sp-label="P">
|
||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium,
|
||
totam rem aperiam <strong>eaque ipsa quae</strong> ab illo inventore veritatis.
|
||
</p>
|
||
<ul class="sp-list" data-sp-label="List">
|
||
<li>Lorem ipsum dolor sit amet, consectetur</li>
|
||
<li>Adipiscing elit, sed do eiusmod tempor incididunt ut labore</li>
|
||
<li>Magna aliqua ut enim ad minim veniam</li>
|
||
</ul>
|
||
|
||
<blockquote class="sp-quote" data-sp-label="Quote">
|
||
<p>Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae.</p>
|
||
<cite class="sp-cite">Attribution line</cite>
|
||
</blockquote>
|
||
|
||
<p class="sp-h3" data-sp-label="H3">A subsection heading</p>
|
||
<p class="sp-p" data-sp-label="P">
|
||
At vero eos et accusamus et iusto odio dignissimos ducimus, qui blanditiis praesentium voluptatum
|
||
deleniti atque, held together by <code class="sp-code">a run of inline code</code> mid sentence.
|
||
</p>
|
||
<ol class="sp-list sp-list-ordered" data-sp-label="List">
|
||
<li>Nam libero tempore, cum soluta nobis</li>
|
||
<li>Eligendi optio cumque nihil impedit</li>
|
||
<li>Quo minus id quod maxime placeat</li>
|
||
</ol>
|
||
<pre class="sp-pre" data-sp-label="Code"><code>function step(base, ratio, n) {
|
||
return base * ratio ** n;
|
||
}</code></pre>
|
||
|
||
<p class="sp-h4" data-sp-label="H4">A minor heading</p>
|
||
<p class="sp-p" data-sp-label="P">
|
||
Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet, ut et
|
||
voluptates repudiandae sint et molestiae non recusandae.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="picker-type-scroll">
|
||
<button class="picker-icon-button" type="button" data-list-scroll="-1" aria-label="Scroll the specimen up">
|
||
<svg viewBox="0 0 20 20" aria-hidden="true"><path d="m4.5 12.5 5.5-5 5.5 5"></path></svg>
|
||
</button>
|
||
<button class="picker-icon-button" type="button" data-list-scroll="1" aria-label="Scroll the specimen down">
|
||
<svg viewBox="0 0 20 20" aria-hidden="true"><path d="m4.5 7.5 5.5 5 5.5-5"></path></svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<input type="hidden" name="type-scale-ratio" />
|
||
|
||
<div class="picker-actions-stack">
|
||
<button class="ks-button ks-button-primary" type="button" data-advance="next">
|
||
Select this scale
|
||
<span class="ks-button-arrow" aria-hidden="true">
|
||
<svg viewBox="0 0 16 8" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="square">
|
||
<path d="M0 4h14M10 0l4 4-4 4"></path>
|
||
</svg>
|
||
</span>
|
||
</button>
|
||
<button class="picker-back" type="button" data-advance="prev">Back</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
{questions.filter((question) => question.id === '07').map((question) => <QuestionScreen {...question} />)}
|
||
|
||
{/* Screen 04b: the configure hub. Every remaining question already
|
||
holds an answer (defaults are pre-filled the moment a surface tile
|
||
is checked), so the run can end here. The cards are skeletons;
|
||
summaries and edited marks are written by renderHub() in
|
||
scripts/palette-picker.js on every arrival. Placement is the
|
||
approved masonry: tall per-surface cards anchor the outer columns. */}
|
||
<section class="picker-screen" data-screen="04b" data-step="Configure" aria-hidden="true" aria-labelledby="picker-hub-title">
|
||
<div class="picker-container">
|
||
<div class="picker-hub">
|
||
<div class="picker-question-head">
|
||
<h2 id="picker-hub-title" class="picker-question-title">What else would you like to configure?</h2>
|
||
<p class="picker-question-lede">
|
||
Every choice below already holds a sensible default for your surfaces. Open any card to adjust it, or create your design context right away.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="picker-hub-grid">
|
||
<div class="picker-hub-col">
|
||
<button class="picker-hub-card" type="button" data-hub-target="06" data-hub-category="material">
|
||
<span class="picker-hub-eyebrow">Material</span>
|
||
<span class="picker-hub-title">Motion
|
||
<svg class="picker-hub-arrow" viewBox="0 0 16 8" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="square" aria-hidden="true"><path d="M0 4h14M10 0l4 4-4 4"></path></svg>
|
||
<span class="picker-hub-mark" data-hub-mark hidden>Edited</span>
|
||
</span>
|
||
<span class="picker-hub-summary" data-hub-summary></span>
|
||
</button>
|
||
<button class="picker-hub-card" type="button" data-hub-target="08" data-hub-category="material">
|
||
<span class="picker-hub-eyebrow">Material</span>
|
||
<span class="picker-hub-title">Boundaries
|
||
<svg class="picker-hub-arrow" viewBox="0 0 16 8" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="square" aria-hidden="true"><path d="M0 4h14M10 0l4 4-4 4"></path></svg>
|
||
<span class="picker-hub-mark" data-hub-mark hidden>Edited</span>
|
||
</span>
|
||
<span class="picker-hub-summary" data-hub-summary></span>
|
||
</button>
|
||
</div>
|
||
|
||
<div class="picker-hub-col">
|
||
<button class="picker-hub-card" type="button" data-hub-target="11" data-hub-category="iconography">
|
||
<span class="picker-hub-eyebrow">Iconography</span>
|
||
<span class="picker-hub-title">Icon set
|
||
<svg class="picker-hub-arrow" viewBox="0 0 16 8" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="square" aria-hidden="true"><path d="M0 4h14M10 0l4 4-4 4"></path></svg>
|
||
<span class="picker-hub-mark" data-hub-mark hidden>Edited</span>
|
||
</span>
|
||
<span class="picker-hub-summary" data-hub-summary></span>
|
||
</button>
|
||
<button class="picker-hub-card" type="button" data-hub-target="09" data-hub-category="material">
|
||
<span class="picker-hub-eyebrow">Material</span>
|
||
<span class="picker-hub-title">Corners
|
||
<svg class="picker-hub-arrow" viewBox="0 0 16 8" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="square" aria-hidden="true"><path d="M0 4h14M10 0l4 4-4 4"></path></svg>
|
||
<span class="picker-hub-mark" data-hub-mark hidden>Edited</span>
|
||
</span>
|
||
<span class="picker-hub-summary" data-hub-summary></span>
|
||
</button>
|
||
</div>
|
||
|
||
<div class="picker-hub-col">
|
||
<button class="picker-hub-card" type="button" data-hub-target="10" data-hub-category="material">
|
||
<span class="picker-hub-eyebrow">Material</span>
|
||
<span class="picker-hub-title">Depth
|
||
<svg class="picker-hub-arrow" viewBox="0 0 16 8" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="square" aria-hidden="true"><path d="M0 4h14M10 0l4 4-4 4"></path></svg>
|
||
<span class="picker-hub-mark" data-hub-mark hidden>Edited</span>
|
||
</span>
|
||
<span class="picker-hub-summary" data-hub-summary></span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="picker-actions-stack">
|
||
<button class="ks-button ks-button-primary" type="button" data-hub-finish data-hub-target="12">
|
||
Create my design context
|
||
<span class="ks-button-arrow" aria-hidden="true">
|
||
<svg viewBox="0 0 16 8" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="square">
|
||
<path d="M0 4h14M10 0l4 4-4 4"></path>
|
||
</svg>
|
||
</span>
|
||
</button>
|
||
<button class="picker-back" type="button" data-advance="prev">Back</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
{questions.filter((question) => question.id !== '07').map((question) => <QuestionScreen {...question} />)}
|
||
|
||
<section class="picker-screen" data-screen="11" data-step="Icons" aria-hidden="true" aria-labelledby="picker-icons-title">
|
||
<div class="picker-container">
|
||
<div class="picker-icons">
|
||
<h2 id="picker-icons-title" class="picker-question-title">Choose an icon set</h2>
|
||
|
||
<div class="picker-icons-grid">
|
||
<div class="picker-type-rail">
|
||
<fieldset class="picker-strategy-options picker-type-options picker-icon-options" data-icon-options>
|
||
<legend>Icon set</legend>
|
||
|
||
{iconRows.map((pack, index) => (
|
||
<div class="picker-icon-row">
|
||
<label class="picker-strategy-option">
|
||
<input
|
||
type="radio"
|
||
name="icon-pack"
|
||
value={pack.id}
|
||
data-pack-name={pack.name}
|
||
data-pack-license={pack.license}
|
||
data-pack-url={pack.url}
|
||
checked={index === 0}
|
||
/>
|
||
<span class="picker-strategy-copy">
|
||
<span class="picker-strategy-title">{pack.name}</span>
|
||
<span class="picker-icon-meta">{pack.meta}</span>
|
||
<span class="picker-strategy-desc">{pack.note}</span>
|
||
</span>
|
||
</label>
|
||
<a
|
||
class="picker-icon-out"
|
||
href={pack.url}
|
||
target="_blank"
|
||
rel="noreferrer"
|
||
aria-label={`Open the ${pack.name} site in a new tab`}
|
||
>
|
||
<svg viewBox="0 0 20 20" aria-hidden="true">
|
||
<path d="M11 4.5h4.5V9M15.5 4.5 9.5 10.5M13 11.5v3.5a.5.5 0 0 1-.5.5h-7a.5.5 0 0 1-.5-.5v-7a.5.5 0 0 1 .5-.5H9"></path>
|
||
</svg>
|
||
</a>
|
||
</div>
|
||
))}
|
||
</fieldset>
|
||
|
||
<div class="picker-type-scroll">
|
||
<button class="picker-icon-button" type="button" data-list-scroll="-1" aria-label="Scroll icon sets up">
|
||
<svg viewBox="0 0 20 20" aria-hidden="true"><path d="m4.5 12.5 5.5-5 5.5 5"></path></svg>
|
||
</button>
|
||
<button class="picker-icon-button" type="button" data-list-scroll="1" aria-label="Scroll icon sets down">
|
||
<svg viewBox="0 0 20 20" aria-hidden="true"><path d="m4.5 7.5 5.5 5 5.5-5"></path></svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="picker-icon-sheet" data-icon-sheet aria-hidden="true">
|
||
<div class="picker-icon-field" data-icon-field></div>
|
||
<div class="picker-icon-strip">
|
||
<span class="picker-icon-strip-label">At 16px</span>
|
||
<div class="picker-icon-strip-row" data-icon-strip></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<input type="hidden" name="icon-pack-name" />
|
||
<input type="hidden" name="icon-pack-license" />
|
||
<input type="hidden" name="icon-pack-url" />
|
||
|
||
<div class="picker-actions-stack">
|
||
<button class="ks-button ks-button-primary" type="button" data-advance="next">
|
||
Select this set
|
||
<span class="ks-button-arrow" aria-hidden="true">
|
||
<svg viewBox="0 0 16 8" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="square">
|
||
<path d="M0 4h14M10 0l4 4-4 4"></path>
|
||
</svg>
|
||
</span>
|
||
</button>
|
||
<button class="picker-back" type="button" data-advance="prev">Back</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="picker-screen" data-screen="12" data-step="Review" aria-hidden="true" aria-labelledby="picker-finish-copy">
|
||
<div class="picker-container">
|
||
<div class="picker-placeholder picker-finish">
|
||
<h2 id="picker-finish-copy" class="picker-title">Building your design context</h2>
|
||
<div class="picker-finish-loader" data-doc-loader aria-hidden="true"><i></i></div>
|
||
<div class="picker-actions-stack" data-doc-error hidden>
|
||
<p class="picker-finish-error">Your answers could not be saved — the picker server may have closed.</p>
|
||
<button class="ks-button ks-button-primary" type="button" data-doc-retry>
|
||
Try again
|
||
<span class="ks-button-arrow" aria-hidden="true">
|
||
<svg viewBox="0 0 16 8" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="square">
|
||
<path d="M0 4h14M10 0l4 4-4 4"></path>
|
||
</svg>
|
||
</span>
|
||
</button>
|
||
<button class="picker-back" type="button" data-advance="prev">Back</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<div class="picker-progress" aria-hidden="true">
|
||
<div class="picker-progress-track">
|
||
<i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i>
|
||
</div>
|
||
<p class="picker-progress-label">
|
||
<span class="picker-progress-index"><b data-progress-index>1</b> / <span data-progress-total>12</span></span>
|
||
<span class="picker-progress-name" data-progress-name></span>
|
||
</p>
|
||
</div>
|
||
</form>
|
||
</main>
|
||
|
||
{/* Design context document — hidden until the review screen saves the
|
||
answers. Mosaic, morph, and article vocabulary ported unchanged from
|
||
docs/design-context-categorization/design-context.html; the detail
|
||
templates start empty and are filled from the interview by
|
||
scripts/design-context.js. */}
|
||
<section class="dcx-shell" data-dcx-shell hidden aria-label="Design context categories">
|
||
<div class="dcx-grid">
|
||
|
||
<div class="dcx-badge" data-reveal style="--reveal-delay: 0.45s" aria-hidden="true">
|
||
<svg viewBox="0 0 24 24" focusable="false">
|
||
<path d="M5 2.5 L13.5 2.5 L5.5 21.5 L5 21.5 Q2.5 21.5 2.5 19 L2.5 5 Q2.5 2.5 5 2.5 Z"></path>
|
||
<path d="M16.5 2.5 L19 2.5 Q21.5 2.5 21.5 5 L21.5 19 Q21.5 21.5 19 21.5 L8.5 21.5 Z"></path>
|
||
</svg>
|
||
</div>
|
||
|
||
<button class="dcx-tile dcx-tile--audience" type="button" data-category="audience" data-name="Audience" data-reveal style="--reveal-delay: 0s" aria-label="Open Audience">
|
||
<span class="dcx-tile-viz" aria-hidden="true">
|
||
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg" class="dcx-viz-svg">
|
||
<circle cx="15" cy="14" r="4" stroke="var(--viz-ink)" class="vz-stroke" vector-effect="non-scaling-stroke"></circle>
|
||
<path d="M7 30c0-5 3.6-8 8-8s8 3 8 8" stroke="var(--viz-ink)" stroke-linecap="round" class="vz-stroke" vector-effect="non-scaling-stroke"></path>
|
||
<g class="anim-join">
|
||
<circle cx="28" cy="16" r="3.4" stroke="var(--viz-accent)" class="vz-stroke" vector-effect="non-scaling-stroke"></circle>
|
||
<path d="M21.5 30c0-4.4 3-7 6.5-7s6.5 2.6 6.5 7" stroke="var(--viz-accent)" stroke-linecap="round" class="anim-draw vz-stroke" vector-effect="non-scaling-stroke"></path>
|
||
</g>
|
||
</svg>
|
||
</span>
|
||
<h2 class="dcx-tile-title">Audience</h2>
|
||
</button>
|
||
|
||
<button class="dcx-tile dcx-tile--product" type="button" data-category="product" data-name="Product" data-reveal style="--reveal-delay: 0.05s" aria-label="Open Product">
|
||
<span class="dcx-tile-viz" aria-hidden="true">
|
||
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg" class="dcx-viz-svg">
|
||
<line x1="8" y1="13" x2="26" y2="13" stroke="var(--viz-ink)" stroke-linecap="round" class="vz-stroke" vector-effect="non-scaling-stroke"></line>
|
||
<line x1="8" y1="19" x2="30" y2="19" stroke="var(--viz-mist)" stroke-linecap="round" class="vz-stroke" vector-effect="non-scaling-stroke"></line>
|
||
<line x1="8" y1="25" x2="22" y2="25" stroke="var(--viz-accent)" stroke-linecap="round" class="anim-join vz-stroke" vector-effect="non-scaling-stroke"></line>
|
||
<line x1="26" y1="23" x2="26" y2="27" stroke="var(--viz-accent)" class="anim-blink anim-join vz-stroke" vector-effect="non-scaling-stroke"></line>
|
||
</svg>
|
||
</span>
|
||
<h2 class="dcx-tile-title">Product</h2>
|
||
</button>
|
||
|
||
<button class="dcx-tile dcx-tile--brand" type="button" data-category="brand" data-name="Brand" data-reveal style="--reveal-delay: 0.1s" aria-label="Open Brand">
|
||
<span class="dcx-tile-viz" aria-hidden="true">
|
||
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg" class="dcx-viz-svg">
|
||
<path d="M6 17 L34 17" stroke="var(--viz-mist)" class="vz-ghost" vector-effect="non-scaling-stroke"></path>
|
||
<path d="M12 10 L16 17 L20 33 M28 10 L24 17 L20 33" stroke="var(--viz-ink)" stroke-linejoin="round" class="vz-stroke" vector-effect="non-scaling-stroke"></path>
|
||
<path d="M12 10 L28 10 L34 17 L20 33 L6 17 Z" stroke="var(--viz-accent)" stroke-linejoin="round" class="anim-draw anim-join vz-stroke" vector-effect="non-scaling-stroke"></path>
|
||
<circle cx="20" cy="13.5" r="1.4" fill="var(--viz-accent)" class="anim-join"></circle>
|
||
</svg>
|
||
</span>
|
||
<h2 class="dcx-tile-title">Brand</h2>
|
||
</button>
|
||
|
||
<button class="dcx-tile dcx-tile--color" type="button" data-category="color" data-name="Color" data-reveal style="--reveal-delay: 0.15s" aria-label="Open Color">
|
||
<span class="dcx-tile-viz" aria-hidden="true">
|
||
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg" class="dcx-viz-svg">
|
||
<circle cx="16" cy="20" r="8" stroke="var(--viz-ink)" class="anim-move-x vz-stroke" vector-effect="non-scaling-stroke"></circle>
|
||
<circle cx="24" cy="20" r="8" stroke="var(--viz-accent)" class="anim-move-x-opp anim-join vz-stroke" vector-effect="non-scaling-stroke"></circle>
|
||
<path d="M20 14.5C21.5 16 22.5 18 22.5 20C22.5 22 21.5 24 20 25.5C18.5 24 17.5 22 17.5 20C17.5 18 18.5 16 20 14.5Z" fill="var(--viz-accent)" class="anim-fade-in"></path>
|
||
</svg>
|
||
</span>
|
||
<h2 class="dcx-tile-title">Color</h2>
|
||
</button>
|
||
|
||
<button class="dcx-tile dcx-tile--typography" type="button" data-category="typography" data-name="Typography" data-reveal style="--reveal-delay: 0.2s" aria-label="Open Typography">
|
||
<span class="dcx-tile-viz" aria-hidden="true">
|
||
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg" class="dcx-viz-svg">
|
||
<path d="M10 30L20 10L30 30" stroke="var(--viz-ink)" stroke-linecap="round" stroke-linejoin="round" class="vz-stroke" vector-effect="non-scaling-stroke"></path>
|
||
<path d="M10 30L20 10L30 30" stroke="var(--viz-accent)" stroke-linecap="round" stroke-linejoin="round" class="anim-draw anim-join vz-stroke" vector-effect="non-scaling-stroke"></path>
|
||
<path d="M15 22H25" stroke="var(--viz-accent)" stroke-linecap="round" class="anim-draw-delay anim-join vz-stroke" vector-effect="non-scaling-stroke"></path>
|
||
</svg>
|
||
</span>
|
||
<h2 class="dcx-tile-title">Typography</h2>
|
||
</button>
|
||
|
||
<button class="dcx-tile dcx-tile--iconography" type="button" data-category="iconography" data-name="Iconography" data-reveal style="--reveal-delay: 0.25s" aria-label="Open Iconography">
|
||
<span class="dcx-tile-viz" aria-hidden="true">
|
||
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg" class="dcx-viz-svg">
|
||
<circle cx="14" cy="14" r="4.5" stroke="var(--viz-ink)" class="anim-pop anim-pop-1 vz-stroke" vector-effect="non-scaling-stroke"></circle>
|
||
<rect x="22" y="9.5" width="9" height="9" rx="1.5" stroke="var(--viz-ink)" class="anim-pop anim-pop-2 vz-stroke" vector-effect="non-scaling-stroke"></rect>
|
||
<path d="M14 22.5L18.5 30.5H9.5L14 22.5Z" stroke="var(--viz-ink)" stroke-linejoin="round" class="anim-pop anim-pop-3 vz-stroke" vector-effect="non-scaling-stroke"></path>
|
||
<path d="M26.5 22.5V30.5M22.5 26.5H30.5" stroke="var(--viz-accent)" stroke-linecap="round" class="anim-pop anim-pop-4 vz-stroke" vector-effect="non-scaling-stroke"></path>
|
||
</svg>
|
||
</span>
|
||
<h2 class="dcx-tile-title">Iconography</h2>
|
||
</button>
|
||
|
||
<button class="dcx-tile dcx-tile--material" type="button" data-category="material" data-name="Material" data-reveal style="--reveal-delay: 0.3s" aria-label="Open Material">
|
||
<span class="dcx-tile-viz" aria-hidden="true">
|
||
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg" class="dcx-viz-svg">
|
||
<rect x="5" y="10" width="30" height="18.5" stroke="var(--viz-ink)" class="vz-stroke" vector-effect="non-scaling-stroke"></rect>
|
||
<line x1="23.5" y1="10" x2="23.5" y2="28.5" stroke="var(--viz-ink)" class="vz-stroke" vector-effect="non-scaling-stroke"></line>
|
||
<line x1="23.5" y1="21.5" x2="35" y2="21.5" stroke="var(--viz-ink)" class="vz-stroke" vector-effect="non-scaling-stroke"></line>
|
||
<path d="M5 28.5C5 18.28 13.28 10 23.5 10C29.85 10 35 15.15 35 21.5C35 25.42 31.82 28.5 27.9 28.5" fill="none" stroke="var(--viz-accent)" stroke-linecap="round" class="anim-draw anim-join vz-stroke" vector-effect="non-scaling-stroke"></path>
|
||
</svg>
|
||
</span>
|
||
<h2 class="dcx-tile-title">Material</h2>
|
||
</button>
|
||
|
||
<button class="dcx-tile dcx-tile--interface" type="button" data-category="interface" data-name="Interface" data-reveal style="--reveal-delay: 0.35s" aria-label="Open Interface">
|
||
<span class="dcx-tile-viz" aria-hidden="true">
|
||
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg" class="dcx-viz-svg">
|
||
<rect x="10" y="14" width="20" height="12" rx="6" stroke="var(--viz-ink)" class="vz-stroke" vector-effect="non-scaling-stroke"></rect>
|
||
<circle cx="16" cy="20" r="4" fill="var(--viz-mist)" class="anim-toggle-move"></circle>
|
||
</svg>
|
||
</span>
|
||
<h2 class="dcx-tile-title">Interface</h2>
|
||
</button>
|
||
|
||
</div>
|
||
</section>
|
||
|
||
{/* Detail articles: empty shells filled per run by design-context.js. */}
|
||
<template id="dcx-detail-audience"></template>
|
||
<template id="dcx-detail-product"></template>
|
||
<template id="dcx-detail-brand"></template>
|
||
<template id="dcx-detail-color"></template>
|
||
<template id="dcx-detail-typography"></template>
|
||
<template id="dcx-detail-iconography"></template>
|
||
<template id="dcx-detail-material"></template>
|
||
<template id="dcx-detail-interface"></template>
|
||
|
||
{/* Expander shell: sidebar + topbar + main, cloned per open. */}
|
||
<template id="dcx-shell-template">
|
||
<div class="dcx-expander-inner">
|
||
<aside class="dcx-sidebar">
|
||
<a class="dcx-sidebar-brand" href="https://impeccable.style/" target="_blank" rel="noopener" aria-label="Impeccable home (opens the live site)">
|
||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||
<path d="M5 2.5 L13.5 2.5 L5.5 21.5 L5 21.5 Q2.5 21.5 2.5 19 L2.5 5 Q2.5 2.5 5 2.5 Z"></path>
|
||
<path d="M16.5 2.5 L19 2.5 Q21.5 2.5 21.5 5 L21.5 19 Q21.5 21.5 19 21.5 L8.5 21.5 Z"></path>
|
||
</svg>
|
||
<span class="dcx-sidebar-brand-name">Impeccable</span>
|
||
</a>
|
||
<nav class="dcx-nav" aria-label="Design context categories">
|
||
<span class="dcx-nav-label">Design context</span>
|
||
<ul class="dcx-nav-list">
|
||
<li data-category="audience"><a class="dcx-nav-link" href="#audience" data-dcx-nav="audience">Audience</a><div class="dcx-subnav"></div></li>
|
||
<li data-category="product"><a class="dcx-nav-link" href="#product" data-dcx-nav="product">Product</a><div class="dcx-subnav"></div></li>
|
||
<li data-category="brand"><a class="dcx-nav-link" href="#brand" data-dcx-nav="brand">Brand</a><div class="dcx-subnav"></div></li>
|
||
<li data-category="color"><a class="dcx-nav-link" href="#color" data-dcx-nav="color">Color</a><div class="dcx-subnav"></div></li>
|
||
<li data-category="typography"><a class="dcx-nav-link" href="#typography" data-dcx-nav="typography">Typography</a><div class="dcx-subnav"></div></li>
|
||
<li data-category="iconography"><a class="dcx-nav-link" href="#iconography" data-dcx-nav="iconography">Iconography</a><div class="dcx-subnav"></div></li>
|
||
<li data-category="material"><a class="dcx-nav-link" href="#material" data-dcx-nav="material">Material</a><div class="dcx-subnav"></div></li>
|
||
<li data-category="interface"><a class="dcx-nav-link" href="#interface" data-dcx-nav="interface">Interface</a><div class="dcx-subnav"></div></li>
|
||
</ul>
|
||
</nav>
|
||
</aside>
|
||
<div class="dcx-panel">
|
||
<header class="dcx-topbar">
|
||
<div class="dcx-current"></div>
|
||
<button class="dcx-request" type="button" data-dcx-request>Request a change</button>
|
||
<button class="dcx-close" type="button" aria-label="Close"></button>
|
||
</header>
|
||
<div class="dcx-main" tabindex="-1"></div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
{/* Edit-request modal: complex changes the document cannot apply itself
|
||
are written up here and queued for the agent over the doc session. */}
|
||
<dialog class="picker-modal dcx-request-modal" data-dcx-request-modal aria-labelledby="dcx-request-title">
|
||
<div class="picker-modal-inner">
|
||
<header class="picker-modal-head">
|
||
<h2 id="dcx-request-title">Request a change</h2>
|
||
<p data-dcx-request-lede>Describe the change; the agent applies it to the design document and the page updates here.</p>
|
||
</header>
|
||
<fieldset class="picker-modal-field">
|
||
<legend data-dcx-request-scope>Change</legend>
|
||
<label class="picker-modal-label" for="dcx-request-prompt">What should change?</label>
|
||
<textarea class="picker-modal-input dcx-request-prompt" id="dcx-request-prompt" rows="4" maxlength="4000"
|
||
placeholder="e.g. Swap the heading font for something with more contrast at display sizes."></textarea>
|
||
<div class="picker-modal-file-block" data-dcx-request-fonts hidden>
|
||
<label class="picker-modal-label" for="dcx-request-font-files">Font files (optional)</label>
|
||
<input class="picker-modal-file-input" id="dcx-request-font-files" type="file" multiple
|
||
accept=".woff2,.woff,.ttf,.otf" data-dcx-request-files />
|
||
<p class="picker-modal-or" data-dcx-request-upload-note></p>
|
||
</div>
|
||
</fieldset>
|
||
<footer class="picker-modal-actions">
|
||
<button class="picker-back" type="button" data-dcx-request-cancel>Cancel</button>
|
||
<button class="ks-button ks-button-primary" type="button" data-dcx-request-send>Send to the agent</button>
|
||
</footer>
|
||
</div>
|
||
</dialog>
|
||
|
||
{/* Live edit tray: every queued request with its status, fed by polling. */}
|
||
<div class="dcx-tray" data-dcx-tray hidden aria-live="polite"></div>
|
||
|
||
<dialog class="picker-modal" data-font-modal aria-labelledby="picker-font-modal-title">
|
||
<div class="picker-modal-inner">
|
||
<header class="picker-modal-head">
|
||
<h2 id="picker-font-modal-title">Add a custom font</h2>
|
||
<p>Link a hosted stylesheet or upload a face. Nothing is checked until you finish.</p>
|
||
</header>
|
||
|
||
{[['heading', 'Heading'], ['body', 'Body']].map(([role, label]) => (
|
||
<fieldset class="picker-modal-field">
|
||
<legend>{label}</legend>
|
||
<label class="picker-modal-label" for={`custom-${role}-url`}>Stylesheet or font URL</label>
|
||
<input
|
||
class="picker-modal-input"
|
||
id={`custom-${role}-url`}
|
||
type="url"
|
||
inputmode="url"
|
||
placeholder="https://example.com/typeface.css"
|
||
data-custom-url={role}
|
||
/>
|
||
<p class="picker-modal-or">or</p>
|
||
<div class="picker-modal-file-block">
|
||
<div class="picker-modal-file">
|
||
<input
|
||
class="picker-modal-file-input"
|
||
id={`custom-${role}-file`}
|
||
type="file"
|
||
accept=".woff2,.woff,.ttf,.otf,font/woff2,font/woff,font/ttf,font/otf,application/font-woff,application/font-woff2,application/x-font-ttf,application/x-font-otf"
|
||
multiple
|
||
data-custom-file={role}
|
||
/>
|
||
<label class="picker-modal-file-button" for={`custom-${role}-file`}>Choose font files</label>
|
||
</div>
|
||
<ul class="picker-modal-file-list" data-custom-file-list={role} hidden></ul>
|
||
</div>
|
||
</fieldset>
|
||
))}
|
||
|
||
<p class="picker-modal-status" data-custom-status aria-live="polite"></p>
|
||
|
||
<p class="picker-modal-hint" id="picker-font-modal-hint" data-custom-hint aria-live="polite"></p>
|
||
|
||
<div class="picker-modal-actions">
|
||
<button class="picker-back" type="button" data-font-custom-close>Cancel</button>
|
||
<button
|
||
class="ks-button ks-button-secondary"
|
||
type="button"
|
||
data-font-custom-save
|
||
aria-describedby="picker-font-modal-hint"
|
||
disabled
|
||
>Use these fonts</button>
|
||
</div>
|
||
</div>
|
||
</dialog>
|
||
|
||
<aside class="picker-width-gate" aria-labelledby="picker-width-title">
|
||
<div>
|
||
<h2 id="picker-width-title">This window is too narrow.</h2>
|
||
<p>Open this link in your browser at least 1200px wide.</p>
|
||
<div class="picker-copy-link">
|
||
<code data-copy-url-value aria-label="Picker URL"></code>
|
||
<button class="picker-copy-link-button" type="button" aria-label="Copy link" data-copy-url>
|
||
<svg class="picker-copy-link-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true">
|
||
<rect x="9" y="9" width="13" height="13" rx="2"></rect>
|
||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
||
</svg>
|
||
<svg class="picker-copy-link-check" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||
<path d="M20 6 9 17l-5-5"></path>
|
||
</svg>
|
||
<span>Copy link</span>
|
||
</button>
|
||
</div>
|
||
<p class="picker-copy-status" data-copy-status aria-live="polite">Paste it into your browser to continue.</p>
|
||
</div>
|
||
</aside>
|
||
|
||
<script>
|
||
import '../scripts/palette-picker.js';
|
||
import '../scripts/design-context.js';
|
||
|
||
const $ = (selector, root = document) => root.querySelector(selector);
|
||
const form = $('#picker-form');
|
||
const copyButton = $('[data-copy-url]');
|
||
const status = $('[data-copy-status]');
|
||
const screens = [...form.querySelectorAll('.picker-screen')];
|
||
let current = screens.findIndex((screen) => screen.hasAttribute('data-active'));
|
||
|
||
const url = new URL(location.href);
|
||
url.hostname = 'localhost';
|
||
const link = url.href.replace(/\/$/, '');
|
||
$('[data-copy-url-value]').textContent = link;
|
||
|
||
const controls = () => [...screens[current].querySelectorAll(
|
||
'button:not([disabled]), input:not([disabled]):not([type="hidden"]):not([tabindex="-1"]), select:not([disabled]), textarea:not([disabled]), a[href]',
|
||
)];
|
||
|
||
// Neither the start screen nor the review is a question, so the stepper
|
||
// counts from the surfaces screen and fills on the last one, icons.
|
||
// Review holds it full rather than counting past the end.
|
||
//
|
||
// A question no chosen surface takes is not a step either, so the count and
|
||
// the row of ticks come from the screens this run actually asks rather than
|
||
// from the markup: a docs-only run skips motion and layout, so it is nine
|
||
// questions long and says so.
|
||
const progress = $('.picker-progress');
|
||
const track = $('.picker-progress-track');
|
||
const steps = () => screens.filter((screen, index) => (
|
||
index > 0 && index < screens.length - 1 && !screen.hasAttribute('data-skip')
|
||
));
|
||
const setProgress = (target) => {
|
||
const list = steps();
|
||
const total = list.length;
|
||
const at = list.indexOf(target);
|
||
const step = at >= 0 ? at + 1 : (target === screens[0] ? 1 : total);
|
||
progress.dataset.step = String(step);
|
||
progress.style.setProperty('--pk-steps', String(total));
|
||
[...track.children].forEach((tick, index) => tick.toggleAttribute('data-off', index >= total));
|
||
$('[data-progress-index]').textContent = String(step);
|
||
$('[data-progress-total]').textContent = String(total);
|
||
$('[data-progress-name]').textContent = target.dataset.step || '';
|
||
};
|
||
|
||
// The surfaces, palette, and strategy screens are the one stretch of the run
|
||
// that shows the same objects more than once, so they are where a change is
|
||
// worth animating: the test page travels from the leading surface tile into
|
||
// the palette panel and on to the strategy stage, and the four colors travel
|
||
// with it over that second step, instead of each being redrawn. Everywhere
|
||
// else there is nothing to carry, and capturing a screen that is
|
||
// mid-animation reads worse than a plain swap does. Only adjacent screens
|
||
// are ever swapped, so listing the three ids names exactly the two pairs.
|
||
const MORPH = new Set(['01b', '02', '03']);
|
||
const morphs = (from, to) => (
|
||
typeof document.startViewTransition === 'function'
|
||
&& !matchMedia('(prefers-reduced-motion: reduce)').matches
|
||
&& MORPH.has(from?.dataset.screen)
|
||
&& MORPH.has(to?.dataset.screen)
|
||
);
|
||
|
||
/* A question the run does not ask is not a screen the run has, so the step
|
||
carries on in the direction it was already travelling. That is what makes
|
||
Back from the screen after a skipped one land on the screen before it
|
||
rather than on the one nobody was shown. */
|
||
const goTo = (index, step = 1) => {
|
||
let at = index;
|
||
while (screens[at]?.hasAttribute('data-skip')) at += step;
|
||
const target = screens[at];
|
||
if (!target) return;
|
||
if (morphs(screens[current], target)) {
|
||
document.startViewTransition(() => swap(at));
|
||
return;
|
||
}
|
||
swap(at);
|
||
};
|
||
|
||
const swap = (index) => {
|
||
const target = screens[index];
|
||
screens.forEach((screen, screenIndex) => {
|
||
const active = screenIndex === index;
|
||
screen.toggleAttribute('data-active', active);
|
||
if (active) screen.removeAttribute('aria-hidden');
|
||
else screen.setAttribute('aria-hidden', 'true');
|
||
});
|
||
current = index;
|
||
const id = target.dataset.screen;
|
||
form.dataset.current = id;
|
||
setProgress(target);
|
||
document.dispatchEvent(new CustomEvent('picker:screenchange', {
|
||
detail: { screen: id },
|
||
}));
|
||
controls()[0]?.focus();
|
||
};
|
||
|
||
/* Motion, boundaries, corners, depth, and icons are reachable only
|
||
from the configure hub, so both their CTA and their Back return
|
||
there rather than to the DOM neighbor. Type scale (05) and layout
|
||
(07) sit on the linear path ahead of the hub and follow DOM order.
|
||
The hub itself and the review screen keep the ordinary rule: Back
|
||
on the hub is the layout screen (or type scale when layout is
|
||
skipped), Back on the review's error box is the icons screen. */
|
||
const HUB_SCREEN = '04b';
|
||
const HUB_RETURNS = new Set(['06', '08', '09', '10', '11']);
|
||
const screenIndex = (id) => screens.findIndex((screen) => screen.dataset.screen === id);
|
||
|
||
form.onclick = (e) => {
|
||
const control = e.target.closest('[data-advance]');
|
||
if (!control) return;
|
||
if (HUB_RETURNS.has(screens[current].dataset.screen)) {
|
||
goTo(screenIndex(HUB_SCREEN));
|
||
return;
|
||
}
|
||
const step = control.dataset.advance === 'prev' ? -1 : 1;
|
||
goTo(current + step, step);
|
||
};
|
||
|
||
/* The hub's cards and its finish CTA jump by screen id. goTo() keeps
|
||
its skip rule, so a jump aimed at a skipped screen walks off it. */
|
||
document.addEventListener('picker:goto', (e) => {
|
||
const index = screenIndex(e.detail.screen);
|
||
if (index !== -1) goTo(index);
|
||
});
|
||
|
||
document.addEventListener('keydown', (e) => {
|
||
if (e.defaultPrevented) return;
|
||
if (e.target instanceof Element && e.target.closest('input, textarea, select, [role="slider"]')) return;
|
||
|
||
if (e.key === 'Enter') {
|
||
if (current === 0 && matchMedia('(max-width: 1199px)').matches) return;
|
||
const control = current === 0 ? $('[data-advance="next"]', screens[0]) : document.activeElement;
|
||
if (controls().includes(control)) {
|
||
e.preventDefault();
|
||
control.click();
|
||
}
|
||
return;
|
||
}
|
||
const d = { ArrowUp: -1, ArrowLeft: -1, ArrowDown: 1, ArrowRight: 1 };
|
||
if (!(e.key in d)) return;
|
||
e.preventDefault();
|
||
const items = controls();
|
||
const focused = items.indexOf(document.activeElement);
|
||
const index = focused < 0 ? 0 : (focused + d[e.key] + items.length) % items.length;
|
||
items[index]?.focus();
|
||
});
|
||
|
||
// Submission belongs to the review screen now: arriving on it triggers
|
||
// design-context.js's finish sequence (save, then reveal the design
|
||
// context document), so a stray native submit has nothing left to do.
|
||
form.onsubmit = (e) => e.preventDefault();
|
||
|
||
copyButton.onclick = async () => {
|
||
try {
|
||
await navigator.clipboard.writeText(link);
|
||
copyButton.classList.add('is-copied');
|
||
copyButton.setAttribute('aria-label', 'Link copied');
|
||
status.textContent = 'Copied. Paste it into your browser to continue.';
|
||
setTimeout(() => {
|
||
copyButton.classList.remove('is-copied');
|
||
copyButton.setAttribute('aria-label', 'Copy link');
|
||
}, 1200);
|
||
} catch {
|
||
status.textContent = 'Copy failed. Select the link above and copy it instead.';
|
||
}
|
||
};
|
||
|
||
form.dataset.current = screens[current].dataset.screen;
|
||
setProgress(screens[current]);
|
||
</script>
|
||
</Picker>
|