mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 06:06:37 +03:00
Update
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
---
|
||||
/*
|
||||
The wireframe landing page every option screen previews its answer on. One
|
||||
copy so a change to the page's anatomy lands on every screen at once.
|
||||
|
||||
mode: 'bars' fills the hero with bar stacks, which is what a screen judging
|
||||
color, layout, or depth needs. 'type' swaps them for real text elements and
|
||||
adds the data-type-* hooks the font screen writes specimen copy into; that
|
||||
screen is judging glyphs, so bars would tell it nothing.
|
||||
|
||||
phone: false drops the handset and leaves the desktop artboard alone in the
|
||||
frame, for a screen whose answer only reads at desktop width.
|
||||
*/
|
||||
interface Props {
|
||||
mode?: 'bars' | 'type';
|
||||
phone?: boolean;
|
||||
class?: string;
|
||||
strategyHook?: boolean;
|
||||
typeHook?: boolean;
|
||||
cursor?: boolean;
|
||||
/*
|
||||
Marks an artboard as one that accumulates the structural answers. The
|
||||
committed layout, boundary, corner, and depth selectors key on it, which is
|
||||
how each screen shows the page as decided so far without those answers
|
||||
reaching back to the screens that came before them.
|
||||
*/
|
||||
carry?: boolean;
|
||||
}
|
||||
|
||||
const {
|
||||
mode = 'bars',
|
||||
phone = true,
|
||||
class: className = '',
|
||||
strategyHook = false,
|
||||
typeHook = false,
|
||||
cursor = false,
|
||||
carry = false,
|
||||
} = Astro.props;
|
||||
const type = mode === 'type';
|
||||
const classes = ['picker-artboard', className].filter(Boolean).join(' ');
|
||||
// Empty string renders the bare attribute the font screen's selectors expect;
|
||||
// undefined drops it entirely on the other screens.
|
||||
const hook = type ? '' : undefined;
|
||||
---
|
||||
|
||||
<div
|
||||
class={classes}
|
||||
data-artboard=""
|
||||
data-carry={carry ? '' : undefined}
|
||||
data-strategy-preview={strategyHook ? '' : undefined}
|
||||
data-type-preview={typeHook ? '' : undefined}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="ps-desktop">
|
||||
<div class="ps-nav">
|
||||
<div class="ps-brand-block" data-type-brand={hook}></div>
|
||||
{type ? (
|
||||
<div class="ps-nav-bars"><i data-type-nav></i><i data-type-nav></i><i data-type-nav></i><i data-type-nav></i></div>
|
||||
) : (
|
||||
<div class="ps-nav-bars"><i></i><i></i><i></i><i></i></div>
|
||||
)}
|
||||
<div class="ps-nav-action" data-type-nav-action={hook}></div>
|
||||
</div>
|
||||
<div class="ps-hero">
|
||||
<div class="ps-hero-copy">
|
||||
{type ? (
|
||||
<Fragment>
|
||||
<h2 class="pt-headline" data-type-headline></h2>
|
||||
<p class="pt-body" data-type-body></p>
|
||||
<div class="ps-actions"><i data-type-cta-primary></i><i data-type-cta-secondary></i></div>
|
||||
</Fragment>
|
||||
) : (
|
||||
<Fragment>
|
||||
<div class="ps-eyebrow"></div>
|
||||
<div class="ps-headline"><i></i><i></i></div>
|
||||
<div class="ps-copy"><i></i></div>
|
||||
<div class="ps-actions"><i></i><i></i></div>
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
<div class="ps-image"></div>
|
||||
</div>
|
||||
<div class="ps-proof">
|
||||
<div class="ps-proof-item"><i></i><span data-type-proof={hook}></span></div>
|
||||
<i class="ps-proof-divider" aria-hidden="true"></i>
|
||||
<div class="ps-proof-item"><i></i><span data-type-proof={hook}></span></div>
|
||||
<i class="ps-proof-divider" aria-hidden="true"></i>
|
||||
<div class="ps-proof-item"><i></i><span data-type-proof={hook}></span></div>
|
||||
<i class="ps-proof-divider" aria-hidden="true"></i>
|
||||
<div class="ps-proof-item"><i></i><span data-type-proof={hook}></span></div>
|
||||
</div>
|
||||
<div class="ps-editorial">
|
||||
<div class="ps-editorial-copy">
|
||||
<strong data-type-section-title={hook}></strong>
|
||||
{type
|
||||
? <p class="pt-section-body" data-type-section-body></p>
|
||||
: <span><i></i><i></i></span>}
|
||||
<em data-type-section-link={hook}></em>
|
||||
</div>
|
||||
<div class="ps-gallery">
|
||||
{[0, 1, 2, 3].map(() => (
|
||||
<div class="ps-gallery-item">
|
||||
<i></i>
|
||||
<span><b data-type-gallery-title={hook}></b><b data-type-gallery-meta={hook}></b></span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div class="ps-footer">
|
||||
{type ? (
|
||||
<div class="ps-footer-links"><i data-type-footer-link></i><i data-type-footer-link></i><i data-type-footer-link></i><i data-type-footer-link></i></div>
|
||||
) : (
|
||||
<div class="ps-footer-links"><i></i><i></i><i></i><i></i></div>
|
||||
)}
|
||||
<div class="ps-footer-mark" data-type-footer-mark={hook}></div>
|
||||
</div>
|
||||
{cursor && <i class="ps-cursor" data-cursor aria-hidden="true"></i>}
|
||||
</div>
|
||||
|
||||
{phone && (
|
||||
<div class="ps-phone">
|
||||
<div class="ps-phone-top">
|
||||
<div class="ps-brand-block" data-type-brand={hook}></div>
|
||||
<div class="ps-nav-action" data-type-menu-action={hook}></div>
|
||||
</div>
|
||||
<div class="ps-phone-body">
|
||||
<div class="ps-image"></div>
|
||||
{type ? (
|
||||
<Fragment>
|
||||
<h2 class="pt-headline" data-type-headline></h2>
|
||||
<p class="pt-body" data-type-body></p>
|
||||
</Fragment>
|
||||
) : (
|
||||
<Fragment>
|
||||
<div class="ps-headline"><i></i><i></i></div>
|
||||
<div class="ps-copy"><i></i><i></i></div>
|
||||
</Fragment>
|
||||
)}
|
||||
<div class="ps-actions">
|
||||
<i data-type-cta-primary={hook}></i><i data-type-cta-secondary={hook}></i>
|
||||
</div>
|
||||
<div class="ps-proof">
|
||||
<div class="ps-proof-item"><i></i><span data-type-proof={hook}></span></div>
|
||||
<i class="ps-proof-divider" aria-hidden="true"></i>
|
||||
<div class="ps-proof-item"><i></i><span data-type-proof={hook}></span></div>
|
||||
</div>
|
||||
<div class="ps-editorial-copy">
|
||||
<strong data-type-section-title={hook}></strong>
|
||||
{type
|
||||
? <p class="pt-section-body" data-type-section-body></p>
|
||||
: <span><i></i><i></i></span>}
|
||||
</div>
|
||||
<div class="ps-gallery">
|
||||
{[0, 1].map(() => (
|
||||
<div class="ps-gallery-item">
|
||||
<i></i>
|
||||
<span><b data-type-gallery-title={hook}></b><b data-type-gallery-meta={hook}></b></span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div class="ps-phone-footer">
|
||||
{type ? (
|
||||
<Fragment><i data-type-footer-link></i><i data-type-footer-link></i><i data-type-footer-link></i></Fragment>
|
||||
) : (
|
||||
<Fragment><i></i><i></i><i></i></Fragment>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -0,0 +1,79 @@
|
||||
---
|
||||
/*
|
||||
One question, one artboard: the shape screens 03 onward all share. A screen
|
||||
that only remaps artboard slots needs nothing of its own here, so it arrives
|
||||
as data and its design lives in its own stylesheet.
|
||||
*/
|
||||
import Artboard from './Artboard.astro';
|
||||
|
||||
interface Option {
|
||||
value: string;
|
||||
title: string;
|
||||
desc: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
id: string;
|
||||
step: string;
|
||||
slug: string;
|
||||
name: string;
|
||||
title: string;
|
||||
legend: string;
|
||||
cta: string;
|
||||
options: Option[];
|
||||
previewClass: string;
|
||||
phone?: boolean;
|
||||
}
|
||||
|
||||
const { id, step, slug, name, title, legend, cta, options, previewClass, phone = true } = Astro.props;
|
||||
const titleId = `picker-${slug}-title`;
|
||||
const artboardClass = phone ? previewClass : `${previewClass} picker-artboard--solo`;
|
||||
---
|
||||
|
||||
<section class="picker-screen" data-screen={id} data-step={step} aria-hidden="true" aria-labelledby={titleId}>
|
||||
<div class="picker-container">
|
||||
<div class="picker-strategy" data-question={slug}>
|
||||
<h2 id={titleId} class="picker-question-title">{title}</h2>
|
||||
|
||||
<div class="picker-strategy-grid">
|
||||
<div class="picker-type-rail">
|
||||
<fieldset
|
||||
class="picker-strategy-options picker-strategy-choices picker-type-options"
|
||||
style={`--pk-choices:${options.length}`}
|
||||
>
|
||||
<legend>{legend}</legend>
|
||||
|
||||
{options.map((option, index) => (
|
||||
<label class="picker-strategy-option">
|
||||
<input type="radio" name={name} value={option.value} checked={index === 0} />
|
||||
<span class="picker-strategy-copy">
|
||||
<span class="picker-strategy-title">{option.title}</span>
|
||||
<span class="picker-strategy-desc">{option.desc}</span>
|
||||
</span>
|
||||
</label>
|
||||
))}
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<Artboard
|
||||
class={artboardClass}
|
||||
phone={phone}
|
||||
cursor={slug === 'motion'}
|
||||
carry={slug !== 'motion'}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="picker-actions-stack">
|
||||
<button class="ks-button ks-button-primary" type="button" data-advance="next">
|
||||
{cta}
|
||||
<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>
|
||||
@@ -1,5 +1,13 @@
|
||||
---
|
||||
import '../styles/picker.css';
|
||||
// Loaded after the shared sheet so a screen's own answers win on source order
|
||||
// as well as specificity. One file per question, each owning only the slots
|
||||
// its options rewrite.
|
||||
import '../styles/screens/motion.css';
|
||||
import '../styles/screens/layout.css';
|
||||
import '../styles/screens/boundaries.css';
|
||||
import '../styles/screens/corners.css';
|
||||
import '../styles/screens/depth.css';
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
|
||||
+172
-142
@@ -1,5 +1,7 @@
|
||||
---
|
||||
import Picker from '../layouts/Picker.astro';
|
||||
import Artboard from '../components/Artboard.astro';
|
||||
import QuestionScreen from '../components/QuestionScreen.astro';
|
||||
|
||||
const roles = [
|
||||
['primary', 'Primary'],
|
||||
@@ -24,6 +26,169 @@ const scales = [
|
||||
|
||||
// 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]];
|
||||
|
||||
// The five questions that are answered by looking at the same 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.
|
||||
const questions = [
|
||||
{
|
||||
id: '06',
|
||||
step: 'Motion',
|
||||
slug: 'motion',
|
||||
name: 'motion-energy',
|
||||
title: 'How much movement should this design have?',
|
||||
legend: 'Motion energy',
|
||||
cta: 'Select motion energy',
|
||||
previewClass: 'picker-preview-motion',
|
||||
phone: false,
|
||||
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-preview-layout',
|
||||
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 in it.',
|
||||
},
|
||||
{
|
||||
value: 'editorial',
|
||||
title: 'Editorial',
|
||||
desc: 'Uneven columns, with one of them clearly leading.',
|
||||
},
|
||||
{
|
||||
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',
|
||||
options: [
|
||||
{
|
||||
value: 'open-space',
|
||||
title: 'Open space',
|
||||
desc: 'Nothing between sections but room. Spacing does the dividing.',
|
||||
},
|
||||
{
|
||||
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.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
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',
|
||||
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.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
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',
|
||||
options: [
|
||||
{
|
||||
value: 'flat',
|
||||
title: 'Flat',
|
||||
desc: 'One plane. Nothing casts a shadow.',
|
||||
},
|
||||
{
|
||||
value: 'layered',
|
||||
title: 'Layered',
|
||||
desc: 'Tone stacks the page. Still no shadows anywhere.',
|
||||
},
|
||||
{
|
||||
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.',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
---
|
||||
|
||||
<Picker>
|
||||
@@ -265,76 +430,7 @@ const scaleRows = [['H1', 6], ['H2', 5], ['H3', 4], ['H4', 3], ['H5', 2], ['H6',
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="picker-strategy-preview" data-strategy-preview aria-hidden="true">
|
||||
<div class="ps-desktop">
|
||||
<div class="ps-nav">
|
||||
<div class="ps-brand-block"></div>
|
||||
<div class="ps-nav-bars"><i></i><i></i><i></i><i></i></div>
|
||||
<div class="ps-nav-action"></div>
|
||||
</div>
|
||||
<div class="ps-hero">
|
||||
<div class="ps-hero-copy">
|
||||
<div class="ps-eyebrow"></div>
|
||||
<div class="ps-headline"><i></i><i></i></div>
|
||||
<div class="ps-copy"><i></i></div>
|
||||
<div class="ps-actions"><i></i><i></i></div>
|
||||
</div>
|
||||
<div class="ps-image"></div>
|
||||
</div>
|
||||
<div class="ps-proof">
|
||||
<div class="ps-proof-item"><i></i><span></span></div>
|
||||
<i class="ps-proof-divider" aria-hidden="true"></i>
|
||||
<div class="ps-proof-item"><i></i><span></span></div>
|
||||
<i class="ps-proof-divider" aria-hidden="true"></i>
|
||||
<div class="ps-proof-item"><i></i><span></span></div>
|
||||
<i class="ps-proof-divider" aria-hidden="true"></i>
|
||||
<div class="ps-proof-item"><i></i><span></span></div>
|
||||
</div>
|
||||
<div class="ps-editorial">
|
||||
<div class="ps-editorial-copy">
|
||||
<strong></strong>
|
||||
<span><i></i><i></i></span>
|
||||
<em></em>
|
||||
</div>
|
||||
<div class="ps-gallery">
|
||||
<div class="ps-gallery-item"><i></i><span><b></b><b></b></span></div>
|
||||
<div class="ps-gallery-item"><i></i><span><b></b><b></b></span></div>
|
||||
<div class="ps-gallery-item"><i></i><span><b></b><b></b></span></div>
|
||||
<div class="ps-gallery-item"><i></i><span><b></b><b></b></span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ps-footer">
|
||||
<div class="ps-footer-links"><i></i><i></i><i></i><i></i></div>
|
||||
<div class="ps-footer-mark"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ps-phone">
|
||||
<div class="ps-phone-top">
|
||||
<div class="ps-brand-block"></div>
|
||||
<div class="ps-nav-action"></div>
|
||||
</div>
|
||||
<div class="ps-phone-body">
|
||||
<div class="ps-image"></div>
|
||||
<div class="ps-headline"><i></i><i></i></div>
|
||||
<div class="ps-copy"><i></i><i></i></div>
|
||||
<div class="ps-actions"><i></i><i></i></div>
|
||||
<div class="ps-proof">
|
||||
<div class="ps-proof-item"><i></i><span></span></div>
|
||||
<i class="ps-proof-divider" aria-hidden="true"></i>
|
||||
<div class="ps-proof-item"><i></i><span></span></div>
|
||||
</div>
|
||||
<div class="ps-editorial-copy">
|
||||
<strong></strong>
|
||||
<span><i></i><i></i></span>
|
||||
</div>
|
||||
<div class="ps-gallery">
|
||||
<div class="ps-gallery-item"><i></i><span><b></b><b></b></span></div>
|
||||
<div class="ps-gallery-item"><i></i><span><b></b><b></b></span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ps-phone-footer"><i></i><i></i><i></i></div>
|
||||
</div>
|
||||
</div>
|
||||
<Artboard class="picker-strategy-preview" strategyHook />
|
||||
</div>
|
||||
|
||||
<div class="picker-actions-stack">
|
||||
@@ -380,75 +476,7 @@ const scaleRows = [['H1', 6], ['H2', 5], ['H3', 4], ['H4', 3], ['H5', 2], ['H6',
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="picker-strategy-preview picker-preview-type" data-type-preview aria-hidden="true">
|
||||
<div class="ps-desktop">
|
||||
<div class="ps-nav">
|
||||
<div class="ps-brand-block" data-type-brand></div>
|
||||
<div class="ps-nav-bars"><i data-type-nav></i><i data-type-nav></i><i data-type-nav></i><i data-type-nav></i></div>
|
||||
<div class="ps-nav-action" data-type-nav-action></div>
|
||||
</div>
|
||||
<div class="ps-hero">
|
||||
<div class="ps-hero-copy">
|
||||
<h2 class="pt-headline" data-type-headline></h2>
|
||||
<p class="pt-body" data-type-body></p>
|
||||
<div class="ps-actions"><i data-type-cta-primary></i><i data-type-cta-secondary></i></div>
|
||||
</div>
|
||||
<div class="ps-image"></div>
|
||||
</div>
|
||||
<div class="ps-proof">
|
||||
<div class="ps-proof-item"><i></i><span data-type-proof></span></div>
|
||||
<i class="ps-proof-divider" aria-hidden="true"></i>
|
||||
<div class="ps-proof-item"><i></i><span data-type-proof></span></div>
|
||||
<i class="ps-proof-divider" aria-hidden="true"></i>
|
||||
<div class="ps-proof-item"><i></i><span data-type-proof></span></div>
|
||||
<i class="ps-proof-divider" aria-hidden="true"></i>
|
||||
<div class="ps-proof-item"><i></i><span data-type-proof></span></div>
|
||||
</div>
|
||||
<div class="ps-editorial">
|
||||
<div class="ps-editorial-copy">
|
||||
<strong data-type-section-title></strong>
|
||||
<p class="pt-section-body" data-type-section-body></p>
|
||||
<em data-type-section-link></em>
|
||||
</div>
|
||||
<div class="ps-gallery">
|
||||
<div class="ps-gallery-item"><i></i><span><b data-type-gallery-title></b><b data-type-gallery-meta></b></span></div>
|
||||
<div class="ps-gallery-item"><i></i><span><b data-type-gallery-title></b><b data-type-gallery-meta></b></span></div>
|
||||
<div class="ps-gallery-item"><i></i><span><b data-type-gallery-title></b><b data-type-gallery-meta></b></span></div>
|
||||
<div class="ps-gallery-item"><i></i><span><b data-type-gallery-title></b><b data-type-gallery-meta></b></span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ps-footer">
|
||||
<div class="ps-footer-links"><i data-type-footer-link></i><i data-type-footer-link></i><i data-type-footer-link></i><i data-type-footer-link></i></div>
|
||||
<div class="ps-footer-mark" data-type-footer-mark></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ps-phone">
|
||||
<div class="ps-phone-top">
|
||||
<div class="ps-brand-block" data-type-brand></div>
|
||||
<div class="ps-nav-action" data-type-menu-action></div>
|
||||
</div>
|
||||
<div class="ps-phone-body">
|
||||
<div class="ps-image"></div>
|
||||
<h2 class="pt-headline" data-type-headline></h2>
|
||||
<p class="pt-body" data-type-body></p>
|
||||
<div class="ps-actions"><i data-type-cta-primary></i><i data-type-cta-secondary></i></div>
|
||||
<div class="ps-proof">
|
||||
<div class="ps-proof-item"><i></i><span data-type-proof></span></div>
|
||||
<i class="ps-proof-divider" aria-hidden="true"></i>
|
||||
<div class="ps-proof-item"><i></i><span data-type-proof></span></div>
|
||||
</div>
|
||||
<div class="ps-editorial-copy">
|
||||
<strong data-type-section-title></strong>
|
||||
<p class="pt-section-body" data-type-section-body></p>
|
||||
</div>
|
||||
<div class="ps-gallery">
|
||||
<div class="ps-gallery-item"><i></i><span><b data-type-gallery-title></b><b data-type-gallery-meta></b></span></div>
|
||||
<div class="ps-gallery-item"><i></i><span><b data-type-gallery-title></b><b data-type-gallery-meta></b></span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ps-phone-footer"><i data-type-footer-link></i><i data-type-footer-link></i><i data-type-footer-link></i></div>
|
||||
</div>
|
||||
</div>
|
||||
<Artboard mode="type" class="picker-strategy-preview picker-preview-type" typeHook />
|
||||
</div>
|
||||
|
||||
<template data-pair-card>
|
||||
@@ -563,7 +591,9 @@ const scaleRows = [['H1', 6], ['H2', 5], ['H3', 4], ['H4', 3], ['H5', 2], ['H6',
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="picker-screen" data-screen="06" data-step="Review" aria-hidden="true" aria-labelledby="picker-finish-copy">
|
||||
{questions.map((question) => <QuestionScreen {...question} />)}
|
||||
|
||||
<section class="picker-screen" data-screen="11" data-step="Review" aria-hidden="true" aria-labelledby="picker-finish-copy">
|
||||
<div class="picker-container">
|
||||
<div class="picker-placeholder">
|
||||
<p id="picker-finish-copy">Your visual direction is ready.</p>
|
||||
@@ -584,10 +614,10 @@ const scaleRows = [['H1', 6], ['H2', 5], ['H3', 4], ['H4', 3], ['H5', 2], ['H6',
|
||||
|
||||
<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><i></i><i></i><i></i>
|
||||
</div>
|
||||
<p class="picker-progress-label">
|
||||
<span class="picker-progress-index"><b data-progress-index>1</b> / 5</span>
|
||||
<span class="picker-progress-index"><b data-progress-index>1</b> / 10</span>
|
||||
<span class="picker-progress-name" data-progress-name></span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -13,7 +13,6 @@ const hint = $('[data-palette-hint]');
|
||||
const ringGuide = $('[data-ring-guide]');
|
||||
const loupe = $('[data-loupe]');
|
||||
const preview = $('.picker-preview');
|
||||
const strategyPreview = document.querySelector('[data-strategy-preview]');
|
||||
const typePreview = document.querySelector('[data-type-preview]');
|
||||
const fontOptions = document.querySelector('[data-font-options]');
|
||||
const pairTemplate = document.querySelector('[data-pair-card]');
|
||||
@@ -1032,10 +1031,13 @@ scroller.addEventListener('scroll', () => {
|
||||
}, { passive: true });
|
||||
document.addEventListener('picker:screenchange', (event) => {
|
||||
activate(event.detail.screen === '02');
|
||||
// The scale sheet is deliberately absent: it is picker chrome in the
|
||||
// picker's own theme, not a third artboard painted in the user's palette.
|
||||
const target = { '03': strategyPreview, '04': typePreview }[event.detail.screen];
|
||||
if (target) syncCommittedPalette(target);
|
||||
// Every artboard on the screen being shown, whatever question it belongs to,
|
||||
// gets the committed palette. The scale sheet is deliberately not one: it is
|
||||
// picker chrome in the picker's own theme, not a page in the user's palette.
|
||||
const screenNode = document.querySelector(`.picker-screen[data-screen="${event.detail.screen}"]`);
|
||||
for (const artboard of screenNode?.querySelectorAll('[data-artboard]') ?? []) {
|
||||
syncCommittedPalette(artboard);
|
||||
}
|
||||
// Arriving is the quietest moment there is, so the rail settles here even
|
||||
// if it is already in order: the chosen pair is the row you land on.
|
||||
if (event.detail.screen === '04') {
|
||||
|
||||
+182
-78
@@ -272,10 +272,18 @@ body.picker-page {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* The art belongs to the start screen and the finish. Every question in
|
||||
between is judged against a plain ground, because a photograph behind a
|
||||
palette or a wireframe is a second opinion nobody asked for. */
|
||||
.picker-shell:has(#picker-form[data-current="02"]) .picker-hero-art,
|
||||
.picker-shell:has(#picker-form[data-current="03"]) .picker-hero-art,
|
||||
.picker-shell:has(#picker-form[data-current="04"]) .picker-hero-art,
|
||||
.picker-shell:has(#picker-form[data-current="05"]) .picker-hero-art {
|
||||
.picker-shell:has(#picker-form[data-current="05"]) .picker-hero-art,
|
||||
.picker-shell:has(#picker-form[data-current="06"]) .picker-hero-art,
|
||||
.picker-shell:has(#picker-form[data-current="07"]) .picker-hero-art,
|
||||
.picker-shell:has(#picker-form[data-current="08"]) .picker-hero-art,
|
||||
.picker-shell:has(#picker-form[data-current="09"]) .picker-hero-art,
|
||||
.picker-shell:has(#picker-form[data-current="10"]) .picker-hero-art {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
@@ -291,10 +299,14 @@ body.picker-page {
|
||||
transition: opacity 180ms var(--ks-ease);
|
||||
}
|
||||
|
||||
/* Ten steps hold the same total width five did: the row is a measure of how
|
||||
far along the interview is, and a wider one would start competing with the
|
||||
question. The ticks divide whatever that width is. */
|
||||
.picker-progress-track {
|
||||
width: 232px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 34px);
|
||||
gap: 6px;
|
||||
grid-template-columns: repeat(10, minmax(0, 1fr));
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.picker-progress-track i {
|
||||
@@ -307,7 +319,12 @@ body.picker-page {
|
||||
.picker-progress[data-step="2"] .picker-progress-track i:nth-child(-n + 2),
|
||||
.picker-progress[data-step="3"] .picker-progress-track i:nth-child(-n + 3),
|
||||
.picker-progress[data-step="4"] .picker-progress-track i:nth-child(-n + 4),
|
||||
.picker-progress[data-step="5"] .picker-progress-track i {
|
||||
.picker-progress[data-step="5"] .picker-progress-track i:nth-child(-n + 5),
|
||||
.picker-progress[data-step="6"] .picker-progress-track i:nth-child(-n + 6),
|
||||
.picker-progress[data-step="7"] .picker-progress-track i:nth-child(-n + 7),
|
||||
.picker-progress[data-step="8"] .picker-progress-track i:nth-child(-n + 8),
|
||||
.picker-progress[data-step="9"] .picker-progress-track i:nth-child(-n + 9),
|
||||
.picker-progress[data-step="10"] .picker-progress-track i {
|
||||
background: var(--ks-kinpaku);
|
||||
}
|
||||
|
||||
@@ -1354,7 +1371,7 @@ body.picker-page {
|
||||
.picker-strategy-choices.picker-type-options {
|
||||
height: 100%;
|
||||
align-content: stretch;
|
||||
grid-template-rows: repeat(4, minmax(0, 1fr));
|
||||
grid-template-rows: repeat(var(--pk-choices, 4), minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.picker-strategy-choices.picker-type-options .picker-strategy-option {
|
||||
@@ -1381,16 +1398,18 @@ body.picker-page {
|
||||
max-width: 34ch;
|
||||
}
|
||||
|
||||
/* Screen 03 shows one editorial landing page at desktop and phone sizes.
|
||||
Geometry stays fixed while the strategy radios remap its color slots.
|
||||
Screen 04's artboards read the same slots, so the strategy travels there
|
||||
with the palette instead of being re-decided.
|
||||
/* The wireframe landing page every option screen previews its answer on, at
|
||||
desktop and phone sizes. Each screen remaps the slots its own question owns:
|
||||
color on 03, glyphs on 04, then motion, grid, dividers, radius, and shadow.
|
||||
Answers travel forward, so by the last screen the artboard is showing every
|
||||
decision made so far rather than one at a time.
|
||||
|
||||
Two of the slots name an ink rather than a fill: a filled button's label
|
||||
has to come from the fill it sits on, and which fill that is changes with
|
||||
the strategy. --pv-t-ink is set alongside the palette by the picker script;
|
||||
the fallback here matches the gold that stands in before a palette lands. */
|
||||
.picker-strategy-preview {
|
||||
Two of the color slots name an ink rather than a fill: a filled button's
|
||||
label has to come from the fill it sits on, and which fill that is changes
|
||||
with the strategy. --pv-t-ink is set alongside the palette by the picker
|
||||
script; the fallback here matches the gold that stands in before a palette
|
||||
lands. */
|
||||
.picker-artboard {
|
||||
--pv-primary: var(--ks-champagne);
|
||||
--pv-secondary: var(--ks-patina);
|
||||
--pv-tertiary: var(--ks-kinpaku);
|
||||
@@ -1447,6 +1466,58 @@ body.picker-page {
|
||||
--pvs-accent-c: var(--pv-primary);
|
||||
--pvs-accent-d: var(--pv-bone);
|
||||
--ps-proof-gap: 25px;
|
||||
|
||||
/* Geometry slots. Every default below is the value the artboard already had,
|
||||
so screens 03 and 04 are unchanged by their existence. A later screen
|
||||
answers its question by rewriting a handful of them.
|
||||
|
||||
Radius is four slots rather than one because a wireframe bar standing in
|
||||
for a line of text must never round like a button: at pill radius it stops
|
||||
reading as text and starts reading as a tag. So controls, surfaces, and
|
||||
text bars move independently, and the dot stays a dot unless a screen
|
||||
says otherwise. */
|
||||
--pvs-radius-frame: 4px;
|
||||
--pvs-radius-frame-phone: 5px;
|
||||
--pvs-radius-surface: 2px;
|
||||
--pvs-radius-control: 2px;
|
||||
--pvs-radius-bar: 2px;
|
||||
--pvs-radius-dot: 50%;
|
||||
|
||||
/* Section separation. The frame is the browser window and stays put; the
|
||||
dividers are the page's own, and are what a boundary answer removes. */
|
||||
--pvs-frame-w: 1px;
|
||||
--pvs-frame-c: var(--pvs-rule);
|
||||
--pvs-divider-w: 1px;
|
||||
--pvs-divider-c: var(--pvs-rule);
|
||||
|
||||
/* Section grounds and element containers. Transparent by default, which is
|
||||
why they cost nothing until a screen fills them. */
|
||||
--pvs-band: transparent;
|
||||
--pvs-band-alt: transparent;
|
||||
--pvs-panel: transparent;
|
||||
--pvs-panel-edge: transparent;
|
||||
--pvs-panel-edge-w: 0px;
|
||||
--pvs-panel-pad: 0px;
|
||||
|
||||
--pvs-shadow-card: none;
|
||||
--pvs-shadow-control: none;
|
||||
--pvs-shadow-surface: none;
|
||||
--pvs-shadow-chrome: none;
|
||||
|
||||
/* Page grid. Gutters are percentages of the artboard, so they hold at both
|
||||
sizes and at any zoom. */
|
||||
--pvs-gutter: 5.5%;
|
||||
--pvs-gutter-end: 3.2%;
|
||||
--pvs-rows: 9.4% 50.6% 9% 22.7% 8.3%;
|
||||
--pvs-nav-cols: auto 1fr auto;
|
||||
--pvs-hero-cols: 35.8% minmax(0, 1fr);
|
||||
--pvs-hero-gap: 11.3%;
|
||||
--pvs-editorial-cols: 27.9% minmax(0, 1fr);
|
||||
--pvs-editorial-gap: 6%;
|
||||
--pvs-gallery-cols: 1.12fr 0.9fr 0.9fr 0.9fr;
|
||||
--pvs-gallery-gap: 3%;
|
||||
--pvs-section-align: center;
|
||||
|
||||
width: 100%;
|
||||
aspect-ratio: 1.865 / 1;
|
||||
display: grid;
|
||||
@@ -1454,19 +1525,33 @@ body.picker-page {
|
||||
gap: 5.25%;
|
||||
}
|
||||
|
||||
/* One artboard filling the frame, for a screen whose answer only reads at
|
||||
desktop width. It keeps the paired frame's ratio rather than taking a
|
||||
taller one of its own: the preview then occupies the same block on every
|
||||
screen, and the CTA underneath does not move as you page through. */
|
||||
.picker-artboard--solo {
|
||||
aspect-ratio: 1.865 / 1;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
/* Hovering an option previews its strategy before the click commits it.
|
||||
While any strategy option is hovered, the hovered branch drives the
|
||||
preview and the checked branch stands down; hovering Restrained needs
|
||||
no block of its own because standing down lands on the defaults.
|
||||
|
||||
The third selector in each block carries the committed answer onto screen
|
||||
04's type artboards. Only the committed answer travels: a hover preview
|
||||
belongs to the screen whose options are under the cursor. Its :where()
|
||||
holds that selector at one class of specificity on purpose, so the
|
||||
The last two selectors in each block carry the committed answer onto every
|
||||
later screen's artboard, so a screen deciding motion or radius shows it on
|
||||
the page the visitor actually chose. Only the committed answer travels: a
|
||||
hover preview belongs to the screen whose options are under the cursor,
|
||||
which is what the no-hover guard on the [data-artboard] selector is for.
|
||||
Both hold themselves at one class of specificity on purpose, so the
|
||||
artboard's own defaults are still beaten on source order while the
|
||||
reading-contrast slots the type preview declares further down still win. */
|
||||
#picker-form:has(.picker-strategy-option:hover input[name="color-strategy"][value="committed"]) .picker-strategy-preview:not(.picker-preview-type),
|
||||
#picker-form:not(:has(.picker-strategy-option:hover input[name="color-strategy"])):has(input[name="color-strategy"][value="committed"]:checked) .picker-strategy-preview:not(.picker-preview-type),
|
||||
:where(#picker-form:has(input[name="color-strategy"][value="committed"]:checked)) .picker-preview-type {
|
||||
:where(#picker-form:has(input[name="color-strategy"][value="committed"]:checked)) .picker-preview-type,
|
||||
:where(#picker-form:not(:has(.picker-strategy-option:hover input[name="color-strategy"])):has(input[name="color-strategy"][value="committed"]:checked)) [data-artboard] {
|
||||
--pvs-headline: color-mix(in oklab, var(--pv-primary) 80%, var(--pv-strong));
|
||||
--pvs-headline-text: var(--pv-p-on-n);
|
||||
--pvs-eyebrow: var(--pv-primary);
|
||||
@@ -1487,7 +1572,8 @@ body.picker-page {
|
||||
signal, and accents; neutral stays the ground. */
|
||||
#picker-form:has(.picker-strategy-option:hover input[name="color-strategy"][value="full-palette"]) .picker-strategy-preview:not(.picker-preview-type),
|
||||
#picker-form:not(:has(.picker-strategy-option:hover input[name="color-strategy"])):has(input[name="color-strategy"][value="full-palette"]:checked) .picker-strategy-preview:not(.picker-preview-type),
|
||||
:where(#picker-form:has(input[name="color-strategy"][value="full-palette"]:checked)) .picker-preview-type {
|
||||
:where(#picker-form:has(input[name="color-strategy"][value="full-palette"]:checked)) .picker-preview-type,
|
||||
:where(#picker-form:not(:has(.picker-strategy-option:hover input[name="color-strategy"])):has(input[name="color-strategy"][value="full-palette"]:checked)) [data-artboard] {
|
||||
--pvs-headline: color-mix(in oklab, var(--pv-primary) 80%, var(--pv-strong));
|
||||
--pvs-headline-text: var(--pv-p-on-n);
|
||||
--pvs-accent-text: var(--pv-t-on-n);
|
||||
@@ -1512,7 +1598,8 @@ body.picker-page {
|
||||
work; nothing is left bare white and the hairlines tint with the ground. */
|
||||
#picker-form:has(.picker-strategy-option:hover input[name="color-strategy"][value="drenched"]) .picker-strategy-preview:not(.picker-preview-type),
|
||||
#picker-form:not(:has(.picker-strategy-option:hover input[name="color-strategy"])):has(input[name="color-strategy"][value="drenched"]:checked) .picker-strategy-preview:not(.picker-preview-type),
|
||||
:where(#picker-form:has(input[name="color-strategy"][value="drenched"]:checked)) .picker-preview-type {
|
||||
:where(#picker-form:has(input[name="color-strategy"][value="drenched"]:checked)) .picker-preview-type,
|
||||
:where(#picker-form:not(:has(.picker-strategy-option:hover input[name="color-strategy"])):has(input[name="color-strategy"][value="drenched"]:checked)) [data-artboard] {
|
||||
--pvs-ground: var(--pv-primary);
|
||||
--pvs-ink: var(--pv-p-ink);
|
||||
--pvs-surface: color-mix(in oklab, var(--pv-p-ink) 10%, var(--pv-primary));
|
||||
@@ -1553,13 +1640,13 @@ body.picker-page {
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
background-color: var(--pvs-ground);
|
||||
border: 1px solid var(--pvs-rule);
|
||||
border: var(--pvs-frame-w) solid var(--pvs-frame-c);
|
||||
}
|
||||
|
||||
.ps-desktop {
|
||||
display: grid;
|
||||
grid-template-rows: 9.4% 50.6% 9% 22.7% 8.3%;
|
||||
border-radius: 4px;
|
||||
grid-template-rows: var(--pvs-rows);
|
||||
border-radius: var(--pvs-radius-frame);
|
||||
}
|
||||
|
||||
.ps-phone {
|
||||
@@ -1568,16 +1655,18 @@ body.picker-page {
|
||||
align-self: stretch;
|
||||
display: grid;
|
||||
grid-template-rows: 7.2% 1fr 7.5%;
|
||||
border-radius: 5px;
|
||||
border-radius: var(--pvs-radius-frame-phone);
|
||||
}
|
||||
|
||||
.ps-nav {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
grid-template-columns: var(--pvs-nav-cols);
|
||||
gap: 24px;
|
||||
align-items: center;
|
||||
padding: 0 5.5%;
|
||||
border-bottom: 1px solid var(--pvs-rule);
|
||||
padding: 0 var(--pvs-gutter);
|
||||
background-color: var(--pvs-band);
|
||||
border-bottom: var(--pvs-divider-w) solid var(--pvs-divider-c);
|
||||
box-shadow: var(--pvs-shadow-chrome);
|
||||
}
|
||||
|
||||
.ps-brand-block {
|
||||
@@ -1585,7 +1674,7 @@ body.picker-page {
|
||||
height: auto;
|
||||
aspect-ratio: 1;
|
||||
background-color: var(--pvs-cta);
|
||||
border-radius: 50%;
|
||||
border-radius: var(--pvs-radius-dot);
|
||||
}
|
||||
|
||||
.ps-nav-bars {
|
||||
@@ -1599,23 +1688,24 @@ body.picker-page {
|
||||
width: clamp(18px, 7%, 34px);
|
||||
height: 4px;
|
||||
background-color: var(--pvs-bars);
|
||||
border-radius: 2px;
|
||||
border-radius: var(--pvs-radius-bar);
|
||||
}
|
||||
|
||||
.ps-nav-action {
|
||||
width: clamp(34px, 3.4vw, 48px);
|
||||
height: clamp(7px, 26%, 13px);
|
||||
background-color: var(--pvs-signal);
|
||||
border-radius: 2px;
|
||||
border-radius: var(--pvs-radius-control);
|
||||
box-shadow: var(--pvs-shadow-control);
|
||||
}
|
||||
|
||||
.ps-hero {
|
||||
min-height: 0;
|
||||
display: grid;
|
||||
grid-template-columns: 35.8% minmax(0, 1fr);
|
||||
gap: 11.3%;
|
||||
align-items: center;
|
||||
padding: 2% 3.2% 2% 5.5%;
|
||||
grid-template-columns: var(--pvs-hero-cols);
|
||||
gap: var(--pvs-hero-gap);
|
||||
align-items: var(--pvs-section-align);
|
||||
padding: 2% var(--pvs-gutter-end) 2% var(--pvs-gutter);
|
||||
}
|
||||
|
||||
.ps-hero-copy {
|
||||
@@ -1628,7 +1718,7 @@ body.picker-page {
|
||||
height: 10px;
|
||||
margin-bottom: 14px;
|
||||
background-color: var(--pvs-eyebrow);
|
||||
border-radius: 2px;
|
||||
border-radius: var(--pvs-radius-bar);
|
||||
}
|
||||
|
||||
.ps-headline {
|
||||
@@ -1642,7 +1732,7 @@ body.picker-page {
|
||||
width: 100%;
|
||||
height: 15px;
|
||||
background-color: var(--pvs-headline);
|
||||
border-radius: 2px;
|
||||
border-radius: var(--pvs-radius-bar);
|
||||
}
|
||||
|
||||
.ps-headline i:last-child {
|
||||
@@ -1660,7 +1750,7 @@ body.picker-page {
|
||||
width: 64%;
|
||||
height: 8px;
|
||||
background-color: var(--pvs-copy);
|
||||
border-radius: 2px;
|
||||
border-radius: var(--pvs-radius-bar);
|
||||
}
|
||||
|
||||
.ps-copy i:last-child {
|
||||
@@ -1677,7 +1767,8 @@ body.picker-page {
|
||||
width: clamp(42px, 42%, 86px);
|
||||
height: clamp(12px, 2.35vw, 29px);
|
||||
background-color: var(--pvs-cta);
|
||||
border-radius: 2px;
|
||||
border-radius: var(--pvs-radius-control);
|
||||
box-shadow: var(--pvs-shadow-control);
|
||||
}
|
||||
|
||||
.ps-actions i:last-child {
|
||||
@@ -1693,7 +1784,8 @@ body.picker-page {
|
||||
background-color: var(--pvs-image);
|
||||
background-image: linear-gradient(135deg, var(--pvs-image-a), var(--pvs-image-b));
|
||||
border: 1px solid var(--pvs-image-edge);
|
||||
border-radius: 2px;
|
||||
border-radius: var(--pvs-radius-surface);
|
||||
box-shadow: var(--pvs-shadow-surface);
|
||||
}
|
||||
|
||||
.ps-image::after {
|
||||
@@ -1704,7 +1796,7 @@ body.picker-page {
|
||||
width: clamp(6px, 1.1vw, 10px);
|
||||
aspect-ratio: 1;
|
||||
background-color: var(--pvs-accent-d);
|
||||
border-radius: 50%;
|
||||
border-radius: var(--pvs-radius-dot);
|
||||
}
|
||||
|
||||
.ps-proof {
|
||||
@@ -1713,7 +1805,8 @@ body.picker-page {
|
||||
grid-template-columns: 1.05fr 1fr 1fr 1.15fr;
|
||||
align-items: stretch;
|
||||
column-gap: var(--ps-proof-gap);
|
||||
padding: 2% 3.2% 2% 5.5%;
|
||||
padding: 2% var(--pvs-gutter-end) 2% var(--pvs-gutter);
|
||||
background-color: var(--pvs-band-alt);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -1721,10 +1814,10 @@ body.picker-page {
|
||||
.ps-proof::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 5.5%;
|
||||
right: 3.2%;
|
||||
height: 1px;
|
||||
background-color: var(--pvs-rule);
|
||||
left: var(--pvs-gutter);
|
||||
right: var(--pvs-gutter-end);
|
||||
height: var(--pvs-divider-w);
|
||||
background-color: var(--pvs-divider-c);
|
||||
}
|
||||
|
||||
.ps-proof::before {
|
||||
@@ -1745,7 +1838,7 @@ body.picker-page {
|
||||
}
|
||||
|
||||
.ps-proof-item + .ps-proof-item {
|
||||
border-left: 1px solid var(--pvs-rule);
|
||||
border-left: var(--pvs-divider-w) solid var(--pvs-divider-c);
|
||||
}
|
||||
|
||||
.ps-proof-item i {
|
||||
@@ -1753,10 +1846,10 @@ body.picker-page {
|
||||
width: clamp(7px, 1.5vw, 13px);
|
||||
aspect-ratio: 1;
|
||||
background-color: var(--pvs-accent-a);
|
||||
border-radius: 50%;
|
||||
border-radius: var(--pvs-radius-dot);
|
||||
}
|
||||
|
||||
.picker-strategy-preview:not(.picker-preview-type) .ps-proof {
|
||||
.picker-artboard:not(.picker-preview-type) .ps-proof {
|
||||
grid-template-columns:
|
||||
minmax(0, 1fr) auto
|
||||
minmax(0, 1fr) auto
|
||||
@@ -1764,29 +1857,29 @@ body.picker-page {
|
||||
minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.picker-strategy-preview:not(.picker-preview-type) .ps-proof-item + .ps-proof-item {
|
||||
.picker-artboard:not(.picker-preview-type) .ps-proof-item + .ps-proof-item {
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
.picker-strategy-preview:not(.picker-preview-type) .ps-proof-divider {
|
||||
width: 1px;
|
||||
.picker-artboard:not(.picker-preview-type) .ps-proof-divider {
|
||||
width: var(--pvs-divider-w);
|
||||
align-self: stretch;
|
||||
background-color: var(--pvs-rule);
|
||||
background-color: var(--pvs-divider-c);
|
||||
}
|
||||
|
||||
.picker-strategy-preview:not(.picker-preview-type) .ps-phone-body .ps-proof {
|
||||
.picker-artboard:not(.picker-preview-type) .ps-phone-body .ps-proof {
|
||||
grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.picker-strategy-preview:not(.picker-preview-type) .ps-proof-item:nth-of-type(2) i {
|
||||
.picker-artboard:not(.picker-preview-type) .ps-proof-item:nth-of-type(2) i {
|
||||
background-color: var(--pvs-accent-b);
|
||||
}
|
||||
|
||||
.picker-strategy-preview:not(.picker-preview-type) .ps-proof-item:nth-of-type(3) i {
|
||||
.picker-artboard:not(.picker-preview-type) .ps-proof-item:nth-of-type(3) i {
|
||||
background-color: var(--pvs-accent-c);
|
||||
}
|
||||
|
||||
.picker-strategy-preview:not(.picker-preview-type) .ps-proof-item:nth-of-type(4) i {
|
||||
.picker-artboard:not(.picker-preview-type) .ps-proof-item:nth-of-type(4) i {
|
||||
background-color: var(--pvs-accent-d);
|
||||
}
|
||||
|
||||
@@ -1796,16 +1889,16 @@ body.picker-page {
|
||||
flex: 1;
|
||||
height: 6px;
|
||||
background-color: var(--pvs-bars);
|
||||
border-radius: 2px;
|
||||
border-radius: var(--pvs-radius-bar);
|
||||
}
|
||||
|
||||
.ps-editorial {
|
||||
min-height: 0;
|
||||
display: grid;
|
||||
grid-template-columns: 27.9% minmax(0, 1fr);
|
||||
gap: 6%;
|
||||
align-items: center;
|
||||
padding: 2.2% 5.5%;
|
||||
grid-template-columns: var(--pvs-editorial-cols);
|
||||
gap: var(--pvs-editorial-gap);
|
||||
align-items: var(--pvs-section-align);
|
||||
padding: 2.2% var(--pvs-gutter);
|
||||
}
|
||||
|
||||
.ps-editorial-copy {
|
||||
@@ -1817,7 +1910,7 @@ body.picker-page {
|
||||
width: 78%;
|
||||
height: 11px;
|
||||
background-color: var(--pvs-title);
|
||||
border-radius: 2px;
|
||||
border-radius: var(--pvs-radius-bar);
|
||||
}
|
||||
|
||||
.ps-editorial-copy span {
|
||||
@@ -1830,7 +1923,7 @@ body.picker-page {
|
||||
width: 90%;
|
||||
height: 6px;
|
||||
background-color: var(--pvs-bars);
|
||||
border-radius: 2px;
|
||||
border-radius: var(--pvs-radius-bar);
|
||||
}
|
||||
|
||||
.ps-editorial-copy span i:last-child {
|
||||
@@ -1842,15 +1935,15 @@ body.picker-page {
|
||||
height: 6px;
|
||||
margin-top: 3px;
|
||||
background-color: var(--pvs-signal);
|
||||
border-radius: 2px;
|
||||
border-radius: var(--pvs-radius-bar);
|
||||
}
|
||||
|
||||
.ps-gallery {
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 1.12fr 0.9fr 0.9fr 0.9fr;
|
||||
gap: 3%;
|
||||
grid-template-columns: var(--pvs-gallery-cols);
|
||||
gap: var(--pvs-gallery-gap);
|
||||
}
|
||||
|
||||
.ps-gallery-item {
|
||||
@@ -1858,6 +1951,11 @@ body.picker-page {
|
||||
display: grid;
|
||||
grid-template-rows: minmax(0, 1fr) auto;
|
||||
gap: 12px;
|
||||
padding: var(--pvs-panel-pad);
|
||||
background-color: var(--pvs-panel);
|
||||
border: var(--pvs-panel-edge-w) solid var(--pvs-panel-edge);
|
||||
border-radius: var(--pvs-radius-surface);
|
||||
box-shadow: var(--pvs-shadow-card);
|
||||
}
|
||||
|
||||
.ps-desktop .ps-editorial-copy {
|
||||
@@ -1877,7 +1975,7 @@ body.picker-page {
|
||||
display: block;
|
||||
min-height: 0;
|
||||
background-color: var(--pvs-accent-a);
|
||||
border-radius: 2px;
|
||||
border-radius: var(--pvs-radius-surface);
|
||||
}
|
||||
|
||||
.ps-gallery-item:nth-child(2) > i {
|
||||
@@ -1902,7 +2000,7 @@ body.picker-page {
|
||||
width: 76%;
|
||||
height: 6px;
|
||||
background-color: var(--pvs-bars);
|
||||
border-radius: 2px;
|
||||
border-radius: var(--pvs-radius-bar);
|
||||
}
|
||||
|
||||
.ps-gallery-item b:last-child {
|
||||
@@ -1913,8 +2011,9 @@ body.picker-page {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-inline: 5.5%;
|
||||
border-top: 1px solid var(--pvs-rule);
|
||||
padding-inline: var(--pvs-gutter);
|
||||
background-color: var(--pvs-band);
|
||||
border-top: var(--pvs-divider-w) solid var(--pvs-divider-c);
|
||||
}
|
||||
|
||||
.ps-footer-links {
|
||||
@@ -1927,14 +2026,14 @@ body.picker-page {
|
||||
width: clamp(12px, 3vw, 28px);
|
||||
height: 4px;
|
||||
background-color: var(--pvs-bars);
|
||||
border-radius: 2px;
|
||||
border-radius: var(--pvs-radius-bar);
|
||||
}
|
||||
|
||||
.ps-footer-mark {
|
||||
width: 7%;
|
||||
height: 5px;
|
||||
background-color: var(--pvs-bars);
|
||||
border-radius: 2px;
|
||||
border-radius: var(--pvs-radius-bar);
|
||||
}
|
||||
|
||||
/* Phone artboard: the same site on mobile. */
|
||||
@@ -1945,7 +2044,8 @@ body.picker-page {
|
||||
min-height: 0;
|
||||
padding: 0 6%;
|
||||
background-color: var(--pvs-surface);
|
||||
border-bottom: 1px solid var(--pvs-rule);
|
||||
border-bottom: var(--pvs-divider-w) solid var(--pvs-divider-c);
|
||||
box-shadow: var(--pvs-shadow-chrome);
|
||||
}
|
||||
|
||||
.ps-phone-top .ps-brand-block {
|
||||
@@ -2082,10 +2182,10 @@ body.picker-page {
|
||||
min-height: 0;
|
||||
padding-inline: 6%;
|
||||
background-color: var(--pvs-surface);
|
||||
border-top: 1px solid var(--pvs-rule);
|
||||
border-top: var(--pvs-divider-w) solid var(--pvs-divider-c);
|
||||
}
|
||||
|
||||
.picker-strategy-preview:not(.picker-preview-type) :is(
|
||||
.picker-artboard:not(.picker-preview-type) :is(
|
||||
.ps-desktop,
|
||||
.ps-phone,
|
||||
.ps-nav,
|
||||
@@ -2110,6 +2210,8 @@ body.picker-page {
|
||||
.ps-editorial-copy strong,
|
||||
.ps-editorial-copy span i,
|
||||
.ps-editorial-copy > em,
|
||||
.ps-gallery,
|
||||
.ps-gallery-item,
|
||||
.ps-gallery-item > i,
|
||||
.ps-gallery-item b,
|
||||
.ps-footer-links i,
|
||||
@@ -2119,7 +2221,9 @@ body.picker-page {
|
||||
transition:
|
||||
background-color 280ms var(--ks-ease),
|
||||
background-image 280ms var(--ks-ease),
|
||||
border-color 280ms var(--ks-ease);
|
||||
border-color 280ms var(--ks-ease),
|
||||
border-radius 280ms var(--ks-ease),
|
||||
box-shadow 280ms var(--ks-ease);
|
||||
}
|
||||
|
||||
.picker-preview-type :is(
|
||||
@@ -3366,7 +3470,7 @@ body.picker-page {
|
||||
.picker-tip::after,
|
||||
.picker-palette-hint,
|
||||
.picker-strategy-option,
|
||||
.picker-strategy-preview:not(.picker-preview-type) :is(
|
||||
.picker-artboard:not(.picker-preview-type) :is(
|
||||
.ps-desktop,
|
||||
.ps-phone,
|
||||
.ps-nav,
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
/* ============================================================
|
||||
Screen 08: boundary style.
|
||||
|
||||
What tells the reader one section has ended and the next has begun. Four
|
||||
answers, in order of how much material they spend: nothing, a line, a
|
||||
change of ground, an edge you can see.
|
||||
|
||||
Bands are mixed from the artboard's own ink and ground rather than named
|
||||
colors, so a drenched page separates with a lighter wash of its primary
|
||||
instead of turning grey.
|
||||
|
||||
The tokens moved here are section-scale, and they are the grounds and edges
|
||||
themselves. Depth works at element scale and only ever adds to what is set
|
||||
here, as a shadow or as a tonal overlay, so the two answers compose rather
|
||||
than overwrite each other.
|
||||
============================================================ */
|
||||
|
||||
/* ── Open space ──────────────────────────────────────────────
|
||||
Every rule off and every ground the same, so the only thing left holding
|
||||
the sections apart is the air between them. The band the proof sits in
|
||||
gets more of it, because with nothing drawn the page has to spend room to
|
||||
say the same thing. */
|
||||
#picker-form:has(.picker-strategy-option:hover input[name="boundary-style"][value="open-space"]) .picker-preview-boundaries,
|
||||
:where(#picker-form:not(:has(.picker-strategy-option:hover input[name="boundary-style"])):has(input[name="boundary-style"][value="open-space"]:checked)) [data-carry] {
|
||||
--pvs-divider-w: 0px;
|
||||
--pvs-band: transparent;
|
||||
--pvs-band-alt: transparent;
|
||||
--pvs-band-top: transparent;
|
||||
--pvs-panel: transparent;
|
||||
--pvs-panel-edge-w: 0px;
|
||||
--pvs-panel-pad: 0px;
|
||||
--pvs-proof-pad-y: 4.2%;
|
||||
--pvs-proof-inset: 0px;
|
||||
}
|
||||
|
||||
/* ── Thin dividers ───────────────────────────────────────────
|
||||
One hairline per boundary and nothing else: at the nav, above and below
|
||||
the proof band, between its columns, and over the footer. */
|
||||
#picker-form:has(.picker-strategy-option:hover input[name="boundary-style"][value="thin-dividers"]) .picker-preview-boundaries,
|
||||
:where(#picker-form:not(:has(.picker-strategy-option:hover input[name="boundary-style"])):has(input[name="boundary-style"][value="thin-dividers"]:checked)) [data-carry] {
|
||||
--pvs-divider-w: 1px;
|
||||
--pvs-divider-c: color-mix(in oklab, var(--pvs-ink) 24%, var(--pvs-ground));
|
||||
--pvs-band: transparent;
|
||||
--pvs-band-alt: transparent;
|
||||
--pvs-band-top: transparent;
|
||||
--pvs-panel: transparent;
|
||||
--pvs-panel-edge-w: 0px;
|
||||
--pvs-panel-pad: 0px;
|
||||
--pvs-proof-pad-y: 2%;
|
||||
--pvs-proof-inset: 0px;
|
||||
}
|
||||
|
||||
/* ── Surface changes ─────────────────────────────────────────
|
||||
No lines at all. Chrome sits on one tone, the proof band on another, and
|
||||
the boundary is wherever the ground changes under your eye. */
|
||||
#picker-form:has(.picker-strategy-option:hover input[name="boundary-style"][value="surface-changes"]) .picker-preview-boundaries,
|
||||
:where(#picker-form:not(:has(.picker-strategy-option:hover input[name="boundary-style"])):has(input[name="boundary-style"][value="surface-changes"]:checked)) [data-carry] {
|
||||
--pvs-divider-w: 0px;
|
||||
--pvs-band: color-mix(in oklab, var(--pvs-ink) 7%, var(--pvs-ground));
|
||||
--pvs-band-alt: color-mix(in oklab, var(--pvs-ink) 13%, var(--pvs-ground));
|
||||
--pvs-band-top: color-mix(in oklab, var(--pvs-ink) 7%, var(--pvs-ground));
|
||||
--pvs-panel: transparent;
|
||||
--pvs-panel-edge-w: 0px;
|
||||
--pvs-panel-pad: 0px;
|
||||
--pvs-proof-pad-y: 2.8%;
|
||||
--pvs-proof-inset: 0px;
|
||||
}
|
||||
|
||||
/* ── Cards and panels ────────────────────────────────────────
|
||||
Content moves inside containers. The proof band pulls in off the page
|
||||
margins and grows an edge, the cards do the same, and the page reads as a
|
||||
set of objects on a ground rather than a run of sections. */
|
||||
#picker-form:has(.picker-strategy-option:hover input[name="boundary-style"][value="cards-and-panels"]) .picker-preview-boundaries,
|
||||
:where(#picker-form:not(:has(.picker-strategy-option:hover input[name="boundary-style"])):has(input[name="boundary-style"][value="cards-and-panels"]:checked)) [data-carry] {
|
||||
--pvs-divider-w: 0px;
|
||||
--pvs-band: transparent;
|
||||
--pvs-band-alt: color-mix(in oklab, var(--pvs-ink) 5%, var(--pvs-ground));
|
||||
--pvs-band-top: transparent;
|
||||
--pvs-panel: color-mix(in oklab, var(--pvs-ink) 5%, var(--pvs-ground));
|
||||
--pvs-panel-edge: color-mix(in oklab, var(--pvs-ink) 20%, var(--pvs-ground));
|
||||
--pvs-panel-edge-w: 1px;
|
||||
--pvs-panel-pad: 5%;
|
||||
--pvs-proof-pad-y: 2.4%;
|
||||
--pvs-proof-inset: var(--pvs-gutter);
|
||||
}
|
||||
|
||||
/* The proof band is the one section that can become a container without the
|
||||
page being restructured, so it carries the difference between a tinted
|
||||
band and a panel: the inset pulls it off the page margins and the edge
|
||||
only exists where an inset was asked for. */
|
||||
[data-carry] .ps-desktop > .ps-proof {
|
||||
margin-inline: var(--pvs-proof-inset, 0px);
|
||||
padding-block: var(--pvs-proof-pad-y, 2%);
|
||||
border: var(--pvs-panel-edge-w) solid var(--pvs-panel-edge);
|
||||
border-radius: var(--pvs-radius-surface);
|
||||
}
|
||||
|
||||
/* The handset's chrome carries its own tint in the base artboard, which is a
|
||||
boundary the open answers did not ask for. Both ends take the band. */
|
||||
[data-carry] .ps-phone-top,
|
||||
[data-carry] .ps-phone-footer {
|
||||
background-color: var(--pvs-band-top, var(--pvs-surface));
|
||||
}
|
||||
|
||||
/* Percentage padding resolves against the card's own width, so one value
|
||||
holds at both artboard sizes instead of swamping the handset. */
|
||||
[data-carry] .ps-gallery-item {
|
||||
padding: var(--pvs-panel-pad, 0px);
|
||||
}
|
||||
|
||||
/* The handset's card row is already the block closest to the footer, so it
|
||||
takes a shorter version of the same padding rather than growing into it. */
|
||||
[data-carry] .ps-phone-body .ps-gallery-item {
|
||||
padding: calc(var(--pvs-panel-pad, 0px) * 0.5);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/* ============================================================
|
||||
Screen 09: corner style.
|
||||
|
||||
One question, four radii, and a deliberate limit: the text bars barely move
|
||||
and the dots do not move at all.
|
||||
|
||||
A bar in this wireframe stands for a line of type. Round it like a button
|
||||
and it stops reading as text and starts reading as a tag, which would make
|
||||
the pill answer look like a different page rather than the same page with
|
||||
rounder corners. So bars top out a few pixels short of their own height.
|
||||
|
||||
The dots stay circular in every answer, Sharp included. A dot is a mark
|
||||
rather than a shape with corners; squaring it changes what it is, not how
|
||||
round it is, and the Sharp page reads as sharp without it.
|
||||
============================================================ */
|
||||
|
||||
/* ── Sharp ───────────────────────────────────────────────────
|
||||
Nothing rounded. Every corner is drafted. */
|
||||
#picker-form:has(.picker-strategy-option:hover input[name="corner-style"][value="sharp"]) .picker-preview-corners,
|
||||
:where(#picker-form:not(:has(.picker-strategy-option:hover input[name="corner-style"])):has(input[name="corner-style"][value="sharp"]:checked)) [data-carry] {
|
||||
--pvs-radius-frame: 0px;
|
||||
--pvs-radius-frame-phone: 0px;
|
||||
--pvs-radius-surface: 0px;
|
||||
--pvs-radius-control: 0px;
|
||||
--pvs-radius-bar: 0px;
|
||||
}
|
||||
|
||||
/* ── Slightly soft ───────────────────────────────────────────
|
||||
Enough radius to take the edge off and not enough to be remarked on. */
|
||||
#picker-form:has(.picker-strategy-option:hover input[name="corner-style"][value="slightly-soft"]) .picker-preview-corners,
|
||||
:where(#picker-form:not(:has(.picker-strategy-option:hover input[name="corner-style"])):has(input[name="corner-style"][value="slightly-soft"]:checked)) [data-carry] {
|
||||
--pvs-radius-frame: 4px;
|
||||
--pvs-radius-frame-phone: 5px;
|
||||
--pvs-radius-surface: 3px;
|
||||
--pvs-radius-control: 3px;
|
||||
--pvs-radius-bar: 2px;
|
||||
}
|
||||
|
||||
/* ── Friendly ────────────────────────────────────────────────
|
||||
Cards, pictures, and buttons are clearly rounded, and the handset rounds
|
||||
with them. */
|
||||
#picker-form:has(.picker-strategy-option:hover input[name="corner-style"][value="friendly"]) .picker-preview-corners,
|
||||
:where(#picker-form:not(:has(.picker-strategy-option:hover input[name="corner-style"])):has(input[name="corner-style"][value="friendly"]:checked)) [data-carry] {
|
||||
--pvs-radius-frame: 7px;
|
||||
--pvs-radius-frame-phone: 11px;
|
||||
--pvs-radius-surface: 8px;
|
||||
--pvs-radius-control: 8px;
|
||||
--pvs-radius-bar: 3px;
|
||||
}
|
||||
|
||||
/* ── Pill-like ───────────────────────────────────────────────
|
||||
Controls go fully round. Surfaces round hard but stay rectangles, because
|
||||
a card at half its own height is no longer a card. */
|
||||
#picker-form:has(.picker-strategy-option:hover input[name="corner-style"][value="pill"]) .picker-preview-corners,
|
||||
:where(#picker-form:not(:has(.picker-strategy-option:hover input[name="corner-style"])):has(input[name="corner-style"][value="pill"]:checked)) [data-carry] {
|
||||
--pvs-radius-frame: 9px;
|
||||
--pvs-radius-frame-phone: 15px;
|
||||
--pvs-radius-surface: 12px;
|
||||
--pvs-radius-control: 999px;
|
||||
--pvs-radius-bar: 3px;
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/* ============================================================
|
||||
Screen 10: depth style.
|
||||
|
||||
How far off the page an element sits. Boundaries fills section grounds;
|
||||
this file lifts individual elements, and the two answers compose instead
|
||||
of arguing.
|
||||
|
||||
Two kinds of value, because a shadow and a tonal step are not the same
|
||||
material. Cast shadows go in the --pvs-shadow-* slots and need something
|
||||
solid under them, which is why the block at the bottom moves the card's
|
||||
lift onto the picture inside it wherever the boundary answer left the card
|
||||
transparent: a shadow around an invisible rectangle reads as a bug. Tonal
|
||||
steps go in --pvs-tint-* and are painted as a ground, so they land on any
|
||||
element whether or not it already has one.
|
||||
|
||||
Shadows are sized for the artboard rather than for a real page. The frame
|
||||
is about a third of browser width, so a blur that would read as gentle at
|
||||
full size disappears here; the offsets and blurs below are already scaled.
|
||||
|
||||
They are also black rather than mixed from the artboard's ink. Ink follows
|
||||
the ground, so on a drenched page an ink shadow turns pale and the lift
|
||||
comes out as a glow. Light falls the same way whatever color the page is.
|
||||
============================================================ */
|
||||
|
||||
/* ── Flat ────────────────────────────────────────────────────
|
||||
One plane. Nothing casts, nothing steps. */
|
||||
#picker-form:has(.picker-strategy-option:hover input[name="depth-style"][value="flat"]) .picker-preview-depth,
|
||||
:where(#picker-form:not(:has(.picker-strategy-option:hover input[name="depth-style"])):has(input[name="depth-style"][value="flat"]:checked)) [data-carry] {
|
||||
--pvs-shadow-chrome: none;
|
||||
--pvs-shadow-card: none;
|
||||
--pvs-shadow-control: none;
|
||||
--pvs-shadow-surface: none;
|
||||
--pvs-tint-chrome: none;
|
||||
--pvs-tint-card: none;
|
||||
}
|
||||
|
||||
/* ── Layered ─────────────────────────────────────────────────
|
||||
Two tonal steps off the ground, cards further up than chrome, and not one
|
||||
shadow on the page. */
|
||||
#picker-form:has(.picker-strategy-option:hover input[name="depth-style"][value="layered"]) .picker-preview-depth,
|
||||
:where(#picker-form:not(:has(.picker-strategy-option:hover input[name="depth-style"])):has(input[name="depth-style"][value="layered"]:checked)) [data-carry] {
|
||||
--pvs-shadow-chrome: none;
|
||||
--pvs-shadow-card: none;
|
||||
--pvs-shadow-control: none;
|
||||
--pvs-shadow-surface: none;
|
||||
--pvs-tint-chrome: linear-gradient(
|
||||
color-mix(in oklab, var(--pvs-ink) 6%, transparent),
|
||||
color-mix(in oklab, var(--pvs-ink) 6%, transparent)
|
||||
);
|
||||
--pvs-tint-card: linear-gradient(
|
||||
color-mix(in oklab, var(--pvs-ink) 12%, transparent),
|
||||
color-mix(in oklab, var(--pvs-ink) 12%, transparent)
|
||||
);
|
||||
}
|
||||
|
||||
/* ── Soft lift ───────────────────────────────────────────────
|
||||
Cards and controls come off the page by a millimetre. Two shadows on the
|
||||
cards: a tight one for the contact edge, a wider one for the ambient. */
|
||||
#picker-form:has(.picker-strategy-option:hover input[name="depth-style"][value="soft-lift"]) .picker-preview-depth,
|
||||
:where(#picker-form:not(:has(.picker-strategy-option:hover input[name="depth-style"])):has(input[name="depth-style"][value="soft-lift"]:checked)) [data-carry] {
|
||||
--pvs-shadow-chrome: 0 1px 2px rgb(0 0 0 / 0.1);
|
||||
--pvs-shadow-card:
|
||||
0 1px 1px rgb(0 0 0 / 0.15),
|
||||
0 3px 7px rgb(0 0 0 / 0.14);
|
||||
--pvs-shadow-control: 0 1px 2px rgb(0 0 0 / 0.24);
|
||||
--pvs-shadow-surface: 0 2px 5px rgb(0 0 0 / 0.14);
|
||||
--pvs-tint-chrome: none;
|
||||
--pvs-tint-card: none;
|
||||
}
|
||||
|
||||
/* ── Floating ────────────────────────────────────────────────
|
||||
A clear gap between the panels and the page. Offsets grow faster than
|
||||
blurs, so the lift reads as height rather than as fog. */
|
||||
#picker-form:has(.picker-strategy-option:hover input[name="depth-style"][value="floating"]) .picker-preview-depth,
|
||||
:where(#picker-form:not(:has(.picker-strategy-option:hover input[name="depth-style"])):has(input[name="depth-style"][value="floating"]:checked)) [data-carry] {
|
||||
--pvs-shadow-chrome: 0 2px 6px rgb(0 0 0 / 0.16);
|
||||
--pvs-shadow-card:
|
||||
0 2px 3px rgb(0 0 0 / 0.2),
|
||||
0 9px 18px rgb(0 0 0 / 0.24);
|
||||
--pvs-shadow-control: 0 3px 6px rgb(0 0 0 / 0.32);
|
||||
--pvs-shadow-surface: 0 6px 14px rgb(0 0 0 / 0.22);
|
||||
--pvs-tint-chrome: none;
|
||||
--pvs-tint-card: none;
|
||||
}
|
||||
|
||||
/* Tonal steps paint as a ground, over whatever ground the boundary answer
|
||||
already chose. */
|
||||
[data-carry] .ps-gallery-item {
|
||||
background-image: var(--pvs-tint-card, none);
|
||||
}
|
||||
|
||||
[data-carry] :is(.ps-nav, .ps-footer, .ps-phone-top, .ps-phone-footer) {
|
||||
background-image: var(--pvs-tint-chrome, none);
|
||||
}
|
||||
|
||||
/* ── Where a cast shadow is allowed to land ──────────────────
|
||||
Only off something solid. Under the two boundary answers that give the
|
||||
card and the proof band a ground, the section itself lifts. Under the two
|
||||
that leave them transparent, the lift moves inward to the picture, which
|
||||
is the only part of the card that is a surface in its own right. */
|
||||
#picker-form:has(input[name="boundary-style"][value="cards-and-panels"]:checked) [data-carry] :is(.ps-gallery-item, .ps-desktop > .ps-proof),
|
||||
#picker-form:has(input[name="boundary-style"][value="surface-changes"]:checked) [data-carry] .ps-desktop > .ps-proof {
|
||||
box-shadow: var(--pvs-shadow-card);
|
||||
}
|
||||
|
||||
#picker-form:not(:has(input[name="boundary-style"][value="cards-and-panels"]:checked)) [data-carry] .ps-gallery-item {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
#picker-form:not(:has(input[name="boundary-style"][value="cards-and-panels"]:checked)) [data-carry] .ps-gallery-item > i {
|
||||
box-shadow: var(--pvs-shadow-card);
|
||||
}
|
||||
|
||||
/* Chrome is a ground only under the surface answer, so that is the only one
|
||||
where the bar at the top of the page casts. The footers are left out: a
|
||||
footer's shadow would fall out of the frame and be clipped away, so the
|
||||
only trace of it would be a hard line. */
|
||||
#picker-form:not(:has(input[name="boundary-style"][value="surface-changes"]:checked)) [data-carry] :is(.ps-nav, .ps-phone-top) {
|
||||
box-shadow: none;
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/* ============================================================
|
||||
Screen 07: layout structure.
|
||||
|
||||
Four grids on the same page. The answer is read off the columns, the page
|
||||
margins, the vertical bands, and whether a block is allowed to leave its
|
||||
cell, so each option rewrites all four rather than nudging one number.
|
||||
|
||||
Two selectors per option, the shape screen 03 established. The first is the
|
||||
hover preview and belongs to this screen alone. The second is the committed
|
||||
answer and reaches every artboard marked data-carry, which is this screen
|
||||
plus boundaries, corners, and depth, so each later question is asked about
|
||||
the page as it has been chosen so far.
|
||||
============================================================ */
|
||||
|
||||
/* ── Simple grid ─────────────────────────────────────────────
|
||||
Halves and quarters, both margins equal, bands of near-equal weight. The
|
||||
thing to notice is that there is nothing to notice. */
|
||||
#picker-form:has(.picker-strategy-option:hover input[name="layout-structure"][value="simple-grid"]) .picker-preview-layout,
|
||||
:where(#picker-form:not(:has(.picker-strategy-option:hover input[name="layout-structure"])):has(input[name="layout-structure"][value="simple-grid"]:checked)) [data-carry] {
|
||||
--pvs-gutter: 5.5%;
|
||||
--pvs-gutter-end: 5.5%;
|
||||
--pvs-hero-cols: minmax(0, 1fr) minmax(0, 1fr);
|
||||
--pvs-hero-gap: 5.5%;
|
||||
--pvs-editorial-cols: 22.4% minmax(0, 1fr);
|
||||
--pvs-editorial-gap: 5.5%;
|
||||
--pvs-gallery-cols: repeat(4, minmax(0, 1fr));
|
||||
--pvs-gallery-gap: 5.5%;
|
||||
--pvs-section-align: start;
|
||||
--pvs-rows: 9.4% 45.5% 9% 27.8% 8.3%;
|
||||
--pvs-phone-gutter: 6%;
|
||||
}
|
||||
|
||||
/* ── Balanced ────────────────────────────────────────────────
|
||||
The grid still runs the page, but the hero leans and the first card is
|
||||
given more room than its neighbours. Two breaks, both on purpose. */
|
||||
#picker-form:has(.picker-strategy-option:hover input[name="layout-structure"][value="balanced"]) .picker-preview-layout,
|
||||
:where(#picker-form:not(:has(.picker-strategy-option:hover input[name="layout-structure"])):has(input[name="layout-structure"][value="balanced"]:checked)) [data-carry] {
|
||||
--pvs-gutter: 5.5%;
|
||||
--pvs-gutter-end: 4.5%;
|
||||
--pvs-hero-cols: 42% minmax(0, 1fr);
|
||||
--pvs-hero-gap: 9%;
|
||||
--pvs-editorial-cols: 29% minmax(0, 1fr);
|
||||
--pvs-editorial-gap: 6%;
|
||||
--pvs-gallery-cols: 1.35fr 0.88fr 0.88fr 0.88fr;
|
||||
--pvs-gallery-gap: 3.5%;
|
||||
--pvs-section-align: center;
|
||||
--pvs-rows: 9.4% 50.6% 9% 22.7% 8.3%;
|
||||
}
|
||||
|
||||
/* ── Editorial ───────────────────────────────────────────────
|
||||
A wide standing column against a narrow portrait, uneven margins, a hero
|
||||
band that takes most of the page, and a lead card at twice its neighbours.
|
||||
Copy hangs from the top of each band the way a column of text hangs from a
|
||||
rule. */
|
||||
#picker-form:has(.picker-strategy-option:hover input[name="layout-structure"][value="editorial"]) .picker-preview-layout,
|
||||
:where(#picker-form:not(:has(.picker-strategy-option:hover input[name="layout-structure"])):has(input[name="layout-structure"][value="editorial"]:checked)) [data-carry] {
|
||||
--pvs-gutter: 9.5%;
|
||||
--pvs-gutter-end: 2.5%;
|
||||
--pvs-hero-cols: 56% minmax(0, 1fr);
|
||||
--pvs-hero-gap: 11%;
|
||||
--pvs-editorial-cols: 14% minmax(0, 1fr);
|
||||
--pvs-editorial-gap: 7%;
|
||||
--pvs-gallery-cols: 2.2fr 0.8fr 0.8fr 0.8fr;
|
||||
--pvs-gallery-gap: 3%;
|
||||
--pvs-section-align: start;
|
||||
--pvs-rows: 8% 57.5% 8.5% 18.5% 7.5%;
|
||||
--pvs-phone-gutter: 11%;
|
||||
--pvs-phone-bleed: -9%;
|
||||
--pvs-phone-bleed-w: 118%;
|
||||
}
|
||||
|
||||
/* ── Freeform ────────────────────────────────────────────────
|
||||
Columns of no particular relation, and blocks that leave their cells. */
|
||||
#picker-form:has(.picker-strategy-option:hover input[name="layout-structure"][value="freeform"]) .picker-preview-layout,
|
||||
:where(#picker-form:not(:has(.picker-strategy-option:hover input[name="layout-structure"])):has(input[name="layout-structure"][value="freeform"]:checked)) [data-carry] {
|
||||
--pvs-gutter: 4%;
|
||||
--pvs-gutter-end: 7%;
|
||||
--pvs-hero-cols: 46% minmax(0, 1fr);
|
||||
--pvs-hero-gap: 2%;
|
||||
--pvs-editorial-cols: 37% minmax(0, 1fr);
|
||||
--pvs-editorial-gap: 2.5%;
|
||||
--pvs-gallery-cols: 1.45fr 0.72fr 1.1fr 0.83fr;
|
||||
--pvs-gallery-gap: 2.5%;
|
||||
--pvs-section-align: end;
|
||||
--pvs-rows: 9.4% 48% 8.4% 26.2% 8%;
|
||||
--pvs-phone-gutter: 4%;
|
||||
--pvs-drift: 1;
|
||||
}
|
||||
|
||||
/* Drift is declared on every artboard and multiplied by a flag only freeform
|
||||
raises, so the other three options pay nothing for the rule existing. The
|
||||
offsets are transforms: a block that steps out of its cell does not push
|
||||
the rest of the page around to make room, which is what makes the overlap
|
||||
read as intent rather than as a broken grid. */
|
||||
[data-carry] .ps-hero-copy {
|
||||
position: relative;
|
||||
z-index: var(--pvs-drift, 0);
|
||||
translate: calc(var(--pvs-drift, 0) * 4%) calc(var(--pvs-drift, 0) * 9%);
|
||||
}
|
||||
|
||||
[data-carry] .ps-hero > .ps-image {
|
||||
translate: calc(var(--pvs-drift, 0) * -5%) calc(var(--pvs-drift, 0) * -7%);
|
||||
}
|
||||
|
||||
/* Drift is a desktop move. The handset has one column and a stack sized in
|
||||
pixels, so a block stepping out of line there has nowhere to go but through
|
||||
the bottom of the frame. */
|
||||
[data-carry] .ps-desktop .ps-gallery-item:nth-child(2n) {
|
||||
translate: 0 calc(var(--pvs-drift, 0) * 14%);
|
||||
}
|
||||
|
||||
[data-carry] .ps-desktop .ps-editorial-copy {
|
||||
translate: 0 calc(var(--pvs-drift, 0) * -12%);
|
||||
}
|
||||
|
||||
/* ── The handset ─────────────────────────────────────────────
|
||||
One column still answers the question, in the margins and in whether
|
||||
anything is allowed to break the column: the picture is the one block given
|
||||
that permission, full bleed under Editorial. The rhythm between blocks is
|
||||
left alone, because the handset's stack is sized in pixels and a couple of
|
||||
points of extra gap pushes the card row through the bottom of the frame at
|
||||
the narrowest window the picker supports. */
|
||||
[data-carry] .ps-phone-body {
|
||||
padding-inline: var(--pvs-phone-gutter, 6%);
|
||||
}
|
||||
|
||||
[data-carry] .ps-phone-body > .ps-image {
|
||||
margin-inline: var(--pvs-phone-bleed, 0);
|
||||
width: var(--pvs-phone-bleed-w, auto);
|
||||
}
|
||||
@@ -0,0 +1,480 @@
|
||||
/* ============================================================
|
||||
Screen 06: motion energy.
|
||||
|
||||
One scene, three energies. A synthetic pointer walks the same route through
|
||||
the same wireframe on all three options and clicks the same four things: a
|
||||
nav item, the second of two hero buttons, a choice in the proof row, and a
|
||||
card. What differs is what the page does about it.
|
||||
|
||||
The whole scene runs on one 8s timeline with every beat written as a
|
||||
percentage of it, and nothing is offset by animation-delay. That is what
|
||||
keeps a four-element orchestrated response in step after an hour of looping,
|
||||
and it is why a frozen frame of this screen is always coherent.
|
||||
|
||||
The three options are not three sets of keyframes. They set the amplitude of
|
||||
the shared ones: how far a press travels, whether an entrance starts from
|
||||
nothing, whether siblings dim, and above all whether a state change steps or
|
||||
eases. Restrained's step-start puts every change on the frame the click
|
||||
lands, which is what "state changes only" means in practice.
|
||||
============================================================ */
|
||||
.picker-preview-motion {
|
||||
--mt: 8s;
|
||||
--mt-ease: cubic-bezier(0.16, 1, 0.3, 1);
|
||||
|
||||
/* Restrained is the default state of every slot below: changes land whole,
|
||||
nothing travels, nothing arrives. */
|
||||
--mt-tf: steps(1, start);
|
||||
--mt-press-scale: 1;
|
||||
--mt-lift: 0px;
|
||||
--mt-dim: 1;
|
||||
--mt-in-o: 1;
|
||||
--mt-in-y: 0px;
|
||||
--mt-in-clip: 0%;
|
||||
}
|
||||
|
||||
/* Hover previews the energy before the click commits it, same as the color
|
||||
strategy rows. Motion is the one answer that does not travel to the screens
|
||||
after it: a page still moving is a page you cannot judge a grid on. */
|
||||
/* State changes get a gentler curve than movement does. The deceleration curve
|
||||
below is right for something arriving from somewhere, but it puts half a
|
||||
color cross-fade in the first 25ms, which is another way of spelling
|
||||
instant. */
|
||||
#picker-form:has(.picker-strategy-option:hover input[name="motion-energy"][value="responsive"]) .picker-preview-motion,
|
||||
#picker-form:not(:has(.picker-strategy-option:hover input[name="motion-energy"])):has(input[name="motion-energy"][value="responsive"]:checked) .picker-preview-motion {
|
||||
--mt-tf: ease;
|
||||
--mt-press-scale: 0.94;
|
||||
}
|
||||
|
||||
#picker-form:has(.picker-strategy-option:hover input[name="motion-energy"][value="choreographed"]) .picker-preview-motion,
|
||||
#picker-form:not(:has(.picker-strategy-option:hover input[name="motion-energy"])):has(input[name="motion-energy"][value="choreographed"]:checked) .picker-preview-motion {
|
||||
--mt-tf: ease;
|
||||
--mt-press-scale: 0.94;
|
||||
--mt-lift: -4px;
|
||||
--mt-dim: 0.45;
|
||||
--mt-in-o: 0;
|
||||
--mt-in-y: 14px;
|
||||
--mt-in-clip: 100%;
|
||||
}
|
||||
|
||||
/* Size containment turns the frame into the coordinate system: the pointer's
|
||||
route is written in cqw and cqh, so it lands on the same button at every
|
||||
viewport width without a line of script. */
|
||||
.picker-preview-motion .ps-desktop {
|
||||
position: relative;
|
||||
container-type: size;
|
||||
}
|
||||
|
||||
/* ── The pointer ─────────────────────────────────────────────── */
|
||||
|
||||
.picker-preview-motion .ps-cursor {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
background-color: var(--pvs-ink);
|
||||
clip-path: polygon(0 0, 0 76%, 24% 58%, 41% 100%, 58% 92%, 41% 50%, 74% 42%);
|
||||
filter: drop-shadow(0.5px 0.5px 0 var(--pvs-ground));
|
||||
animation: mt-path var(--mt) ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* The click has to be visible or the state change looks unprovoked. This ring
|
||||
belongs to the pointer rather than to the page, so it reads the same on all
|
||||
three options: it is the visitor's hand, not the design's motion. */
|
||||
.picker-preview-motion .ps-cursor::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
left: -4px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border: 1.5px solid var(--pvs-ink);
|
||||
border-radius: 50%;
|
||||
opacity: 0;
|
||||
animation: mt-click var(--mt) linear infinite;
|
||||
}
|
||||
|
||||
@keyframes mt-path {
|
||||
0%, 6% { translate: 34cqw 96cqh; }
|
||||
16%, 19% { translate: 76.9cqw 4.8cqh; }
|
||||
31%, 33% { translate: 21.8cqw 45.8cqh; }
|
||||
45%, 47% { translate: 54.9cqw 65.1cqh; }
|
||||
58%, 60.5% { translate: 73.8cqw 80.2cqh; }
|
||||
72%, 100% { translate: 34cqw 96cqh; }
|
||||
}
|
||||
|
||||
@keyframes mt-click {
|
||||
0%, 17.6% { scale: 0.35; opacity: 0; }
|
||||
18% { scale: 0.35; opacity: 0.55; }
|
||||
20.4% { scale: 1.5; opacity: 0; }
|
||||
|
||||
31.6% { scale: 0.35; opacity: 0; }
|
||||
32% { scale: 0.35; opacity: 0.55; }
|
||||
34.4% { scale: 1.5; opacity: 0; }
|
||||
|
||||
45.6% { scale: 0.35; opacity: 0; }
|
||||
46% { scale: 0.35; opacity: 0.55; }
|
||||
48.4% { scale: 1.5; opacity: 0; }
|
||||
|
||||
58.6% { scale: 0.35; opacity: 0; }
|
||||
59% { scale: 0.35; opacity: 0.55; }
|
||||
61.4%, 100% { scale: 1.5; opacity: 0; }
|
||||
}
|
||||
|
||||
/* ── Beat one: the nav item at 18% ───────────────────────────── */
|
||||
|
||||
.picker-preview-motion .ps-nav-bars i {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.picker-preview-motion .ps-nav-bars i::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: auto 0 -4px 0;
|
||||
height: 2px;
|
||||
background-color: var(--pvs-signal);
|
||||
transform: scaleX(0);
|
||||
}
|
||||
|
||||
.picker-preview-motion .ps-nav-bars i:first-child {
|
||||
animation: mt-nav-off var(--mt) var(--mt-tf) infinite;
|
||||
}
|
||||
|
||||
/* The outgoing marker collapses toward the item taking over and the incoming
|
||||
one grows from the side it came from, so the pair reads as one marker
|
||||
changing places rather than two independent bars. */
|
||||
.picker-preview-motion .ps-nav-bars i:first-child::after {
|
||||
transform-origin: right;
|
||||
animation: mt-rule-out var(--mt) var(--mt-tf) infinite;
|
||||
}
|
||||
|
||||
.picker-preview-motion .ps-nav-bars i:nth-child(3) {
|
||||
animation:
|
||||
mt-nav-on var(--mt) var(--mt-tf) infinite,
|
||||
mt-press-nav var(--mt) linear infinite;
|
||||
}
|
||||
|
||||
.picker-preview-motion .ps-nav-bars i:nth-child(3)::after {
|
||||
transform-origin: left;
|
||||
animation: mt-rule-in var(--mt) var(--mt-tf) infinite;
|
||||
}
|
||||
|
||||
@keyframes mt-nav-off {
|
||||
0%, 18% { background-color: var(--pvs-cta); }
|
||||
21%, 88% { background-color: var(--pvs-bars); }
|
||||
91%, 100% { background-color: var(--pvs-cta); }
|
||||
}
|
||||
|
||||
@keyframes mt-nav-on {
|
||||
0%, 18% { background-color: var(--pvs-bars); }
|
||||
21%, 88% { background-color: var(--pvs-cta); }
|
||||
91%, 100% { background-color: var(--pvs-bars); }
|
||||
}
|
||||
|
||||
@keyframes mt-rule-out {
|
||||
0%, 18% { transform: scaleX(1); }
|
||||
21%, 88% { transform: scaleX(0); }
|
||||
91%, 100% { transform: scaleX(1); }
|
||||
}
|
||||
|
||||
@keyframes mt-rule-in {
|
||||
0%, 18% { transform: scaleX(0); }
|
||||
21%, 88% { transform: scaleX(1); }
|
||||
91%, 100% { transform: scaleX(0); }
|
||||
}
|
||||
|
||||
@keyframes mt-press-nav {
|
||||
0%, 17.6% { scale: 1; }
|
||||
18.4% { scale: var(--mt-press-scale); }
|
||||
20% { scale: 1; }
|
||||
100% { scale: 1; }
|
||||
}
|
||||
|
||||
/* ── Beat two: the hero buttons as one control, at 32% ───────── */
|
||||
|
||||
.picker-preview-motion .ps-actions i:first-child {
|
||||
animation: mt-seg-off var(--mt) var(--mt-tf) infinite;
|
||||
}
|
||||
|
||||
.picker-preview-motion .ps-actions i:last-child {
|
||||
animation:
|
||||
mt-seg-on var(--mt) var(--mt-tf) infinite,
|
||||
mt-press-seg var(--mt) linear infinite;
|
||||
}
|
||||
|
||||
@keyframes mt-seg-off {
|
||||
0%, 32% { background-color: var(--pvs-cta); }
|
||||
35%, 88% { background-color: var(--pvs-ghost); }
|
||||
91%, 100% { background-color: var(--pvs-cta); }
|
||||
}
|
||||
|
||||
@keyframes mt-seg-on {
|
||||
0%, 32% { background-color: var(--pvs-ghost); }
|
||||
35%, 88% { background-color: var(--pvs-cta); }
|
||||
91%, 100% { background-color: var(--pvs-ghost); }
|
||||
}
|
||||
|
||||
@keyframes mt-press-seg {
|
||||
0%, 31.6% { scale: 1; }
|
||||
32.4% { scale: var(--mt-press-scale); }
|
||||
34% { scale: 1; }
|
||||
100% { scale: 1; }
|
||||
}
|
||||
|
||||
/* ── Beat three: the proof row as a choice, at 46% ───────────── */
|
||||
|
||||
/* The row's four claims become four options, which is what gives the pointer
|
||||
something to choose. Selection is a halo on the marker and the claim beside
|
||||
it darkening: the dots keep the accent colors the palette gave them, because
|
||||
this screen is not the one relitigating color. */
|
||||
.picker-preview-motion .ps-proof-item:nth-of-type(1) i {
|
||||
animation: mt-halo-off var(--mt) var(--mt-tf) infinite;
|
||||
}
|
||||
|
||||
.picker-preview-motion .ps-proof-item:nth-of-type(1) span {
|
||||
animation: mt-claim-off var(--mt) var(--mt-tf) infinite;
|
||||
}
|
||||
|
||||
.picker-preview-motion .ps-proof-item:nth-of-type(3) i {
|
||||
animation:
|
||||
mt-halo-on var(--mt) var(--mt-tf) infinite,
|
||||
mt-press-choice var(--mt) linear infinite;
|
||||
}
|
||||
|
||||
.picker-preview-motion .ps-proof-item:nth-of-type(3) span {
|
||||
animation: mt-claim-on var(--mt) var(--mt-tf) infinite;
|
||||
}
|
||||
|
||||
@keyframes mt-halo-off {
|
||||
0%, 46% { box-shadow: 0 0 0 3px color-mix(in oklab, var(--pvs-cta) 30%, transparent); }
|
||||
49%, 88% { box-shadow: 0 0 0 0 color-mix(in oklab, var(--pvs-cta) 0%, transparent); }
|
||||
91%, 100% { box-shadow: 0 0 0 3px color-mix(in oklab, var(--pvs-cta) 30%, transparent); }
|
||||
}
|
||||
|
||||
@keyframes mt-halo-on {
|
||||
0%, 46% { box-shadow: 0 0 0 0 color-mix(in oklab, var(--pvs-cta) 0%, transparent); }
|
||||
49%, 88% { box-shadow: 0 0 0 3px color-mix(in oklab, var(--pvs-cta) 30%, transparent); }
|
||||
91%, 100% { box-shadow: 0 0 0 0 color-mix(in oklab, var(--pvs-cta) 0%, transparent); }
|
||||
}
|
||||
|
||||
@keyframes mt-claim-off {
|
||||
0%, 46% { background-color: var(--pvs-headline); }
|
||||
49%, 88% { background-color: var(--pvs-bars); }
|
||||
91%, 100% { background-color: var(--pvs-headline); }
|
||||
}
|
||||
|
||||
@keyframes mt-claim-on {
|
||||
0%, 46% { background-color: var(--pvs-bars); }
|
||||
49%, 88% { background-color: var(--pvs-headline); }
|
||||
91%, 100% { background-color: var(--pvs-bars); }
|
||||
}
|
||||
|
||||
@keyframes mt-press-choice {
|
||||
0%, 45.6% { scale: 1; }
|
||||
46.4% { scale: var(--mt-press-scale); }
|
||||
48% { scale: 1; }
|
||||
100% { scale: 1; }
|
||||
}
|
||||
|
||||
/* ── Beat four: the card at 59% ──────────────────────────────── */
|
||||
|
||||
/* Where the three energies separate most. All three mark the card as chosen.
|
||||
Responsive stops there. Choreographed lifts it off the page and takes its
|
||||
neighbours down 80ms later, which is the difference between feedback and
|
||||
something staged. */
|
||||
.picker-preview-motion .ps-gallery-item:nth-child(3) {
|
||||
animation:
|
||||
mt-card-on var(--mt) var(--mt-tf) infinite,
|
||||
mt-card-lift var(--mt) var(--mt-tf) infinite;
|
||||
}
|
||||
|
||||
.picker-preview-motion .ps-gallery-item:not(:nth-child(3)) {
|
||||
animation: mt-card-dim var(--mt) var(--mt-tf) infinite;
|
||||
}
|
||||
|
||||
@keyframes mt-card-on {
|
||||
0%, 59% {
|
||||
background-color: transparent;
|
||||
box-shadow: inset 0 0 0 0 var(--pvs-cta);
|
||||
}
|
||||
62%, 88% {
|
||||
background-color: var(--pvs-surface);
|
||||
box-shadow: inset 0 0 0 1.5px var(--pvs-cta);
|
||||
}
|
||||
91%, 100% {
|
||||
background-color: transparent;
|
||||
box-shadow: inset 0 0 0 0 var(--pvs-cta);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes mt-card-lift {
|
||||
0%, 59% { transform: translateY(0); }
|
||||
62%, 88% { transform: translateY(var(--mt-lift)); }
|
||||
91%, 100% { transform: translateY(0); }
|
||||
}
|
||||
|
||||
@keyframes mt-card-dim {
|
||||
0%, 60% { filter: opacity(1); }
|
||||
63%, 88% { filter: opacity(var(--mt-dim)); }
|
||||
91%, 100% { filter: opacity(1); }
|
||||
}
|
||||
|
||||
/* ── The entrance, for the option that has one ───────────────── */
|
||||
|
||||
/* Six bands down the page, 80ms apart, each arriving over 360ms: 760ms from
|
||||
the first movement to the last, inside the band an authored entrance is
|
||||
allowed to take. Restrained and Responsive run the same animations with the
|
||||
travel set to zero, so the page is simply already there.
|
||||
|
||||
The bands are containers rather than leaves. Every element that changes
|
||||
state during the loop is inside one, so an entrance never has to share a
|
||||
property with an interaction. */
|
||||
.picker-preview-motion .ps-nav {
|
||||
animation: mt-in-1 var(--mt) var(--mt-ease) infinite;
|
||||
}
|
||||
|
||||
.picker-preview-motion .ps-hero-copy > * {
|
||||
animation: mt-in-2 var(--mt) var(--mt-ease) infinite;
|
||||
}
|
||||
|
||||
.picker-preview-motion .ps-hero > .ps-image {
|
||||
animation: mt-in-image var(--mt) var(--mt-ease) infinite;
|
||||
}
|
||||
|
||||
.picker-preview-motion .ps-proof-item {
|
||||
animation: mt-in-4 var(--mt) var(--mt-ease) infinite;
|
||||
}
|
||||
|
||||
.picker-preview-motion .ps-editorial-copy > * {
|
||||
animation: mt-in-5 var(--mt) var(--mt-ease) infinite;
|
||||
}
|
||||
|
||||
.picker-preview-motion .ps-gallery {
|
||||
animation: mt-in-5 var(--mt) var(--mt-ease) infinite;
|
||||
}
|
||||
|
||||
.picker-preview-motion .ps-footer {
|
||||
animation: mt-in-6 var(--mt) var(--mt-ease) infinite;
|
||||
}
|
||||
|
||||
@keyframes mt-in-1 {
|
||||
0% { opacity: var(--mt-in-o); translate: 0 var(--mt-in-y); }
|
||||
4.5%, 95% { opacity: 1; translate: 0 0; }
|
||||
100% { opacity: var(--mt-in-o); translate: 0 0; }
|
||||
}
|
||||
|
||||
@keyframes mt-in-2 {
|
||||
0%, 1% { opacity: var(--mt-in-o); translate: 0 var(--mt-in-y); }
|
||||
5.5%, 95% { opacity: 1; translate: 0 0; }
|
||||
100% { opacity: var(--mt-in-o); translate: 0 0; }
|
||||
}
|
||||
|
||||
@keyframes mt-in-4 {
|
||||
0%, 3% { opacity: var(--mt-in-o); translate: 0 var(--mt-in-y); }
|
||||
7.5%, 95% { opacity: 1; translate: 0 0; }
|
||||
100% { opacity: var(--mt-in-o); translate: 0 0; }
|
||||
}
|
||||
|
||||
@keyframes mt-in-5 {
|
||||
0%, 4% { opacity: var(--mt-in-o); translate: 0 var(--mt-in-y); }
|
||||
8.5%, 95% { opacity: 1; translate: 0 0; }
|
||||
100% { opacity: var(--mt-in-o); translate: 0 0; }
|
||||
}
|
||||
|
||||
@keyframes mt-in-6 {
|
||||
0%, 5% { opacity: var(--mt-in-o); translate: 0 var(--mt-in-y); }
|
||||
9.5%, 95% { opacity: 1; translate: 0 0; }
|
||||
100% { opacity: var(--mt-in-o); translate: 0 0; }
|
||||
}
|
||||
|
||||
/* The picture is the one element that arrives on its own terms: it uncovers
|
||||
rather than rises, because a photograph revealing itself is the moment a
|
||||
staged entrance is built around. */
|
||||
@keyframes mt-in-image {
|
||||
0%, 2% {
|
||||
opacity: var(--mt-in-o);
|
||||
clip-path: inset(0 0 var(--mt-in-clip) 0);
|
||||
}
|
||||
6.5%, 95% {
|
||||
opacity: 1;
|
||||
clip-path: inset(0 0 0% 0);
|
||||
}
|
||||
100% {
|
||||
opacity: var(--mt-in-o);
|
||||
clip-path: inset(0 0 var(--mt-in-clip) 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* A loop nobody asked for is the definition of a nonessential one. Reduced
|
||||
motion gets the end of the scene instead: the pointer parked over the card
|
||||
it chose, with every state already changed, which is the same information
|
||||
without the movement. */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
/* Pseudo-elements are not valid inside :is() and get dropped from the list
|
||||
rather than failing loudly, so the two that animate are cancelled on their
|
||||
own lines. */
|
||||
.picker-preview-motion .ps-cursor::after,
|
||||
.picker-preview-motion .ps-nav-bars i::after,
|
||||
.picker-preview-motion :is(
|
||||
.ps-cursor,
|
||||
.ps-nav,
|
||||
.ps-nav-bars i,
|
||||
.ps-hero-copy > *,
|
||||
.ps-image,
|
||||
.ps-actions i,
|
||||
.ps-proof-item,
|
||||
.ps-proof-item i,
|
||||
.ps-proof-item span,
|
||||
.ps-editorial-copy > *,
|
||||
.ps-gallery,
|
||||
.ps-gallery-item,
|
||||
.ps-footer
|
||||
) {
|
||||
/* The declarations being cancelled are per-element and more specific than
|
||||
any list can be without repeating all fifteen selectors. A preference
|
||||
the visitor set in their operating system outranks them. */
|
||||
animation: none !important;
|
||||
}
|
||||
|
||||
.picker-preview-motion .ps-cursor {
|
||||
translate: 73.8cqw 80.2cqh;
|
||||
}
|
||||
|
||||
.picker-preview-motion .ps-nav-bars i:first-child {
|
||||
background-color: var(--pvs-bars);
|
||||
}
|
||||
|
||||
.picker-preview-motion .ps-nav-bars i:nth-child(3) {
|
||||
background-color: var(--pvs-cta);
|
||||
}
|
||||
|
||||
.picker-preview-motion .ps-nav-bars i:nth-child(3)::after {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
|
||||
.picker-preview-motion .ps-actions i:first-child {
|
||||
background-color: var(--pvs-ghost);
|
||||
}
|
||||
|
||||
.picker-preview-motion .ps-actions i:last-child {
|
||||
background-color: var(--pvs-cta);
|
||||
}
|
||||
|
||||
.picker-preview-motion .ps-proof-item:nth-of-type(3) i {
|
||||
box-shadow: 0 0 0 3px color-mix(in oklab, var(--pvs-cta) 30%, transparent);
|
||||
}
|
||||
|
||||
.picker-preview-motion .ps-proof-item:nth-of-type(3) span {
|
||||
background-color: var(--pvs-headline);
|
||||
}
|
||||
|
||||
.picker-preview-motion .ps-gallery-item:nth-child(3) {
|
||||
background-color: var(--pvs-surface);
|
||||
box-shadow: inset 0 0 0 1.5px var(--pvs-cta);
|
||||
transform: translateY(var(--mt-lift));
|
||||
}
|
||||
|
||||
.picker-preview-motion .ps-gallery-item:not(:nth-child(3)) {
|
||||
filter: opacity(var(--mt-dim));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user