mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 06:06:37 +03:00
Refine configure hub cards with pills, notes, and layout polish.
Add the intro lede, per-row patina answer marks with DOM-sourced notes, mode-tile hover, and tighter card typography including inline pills and title arrows. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -916,23 +916,28 @@ const questions = [
|
||||
<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">
|
||||
<h2 id="picker-hub-title" class="picker-question-title">What else would you like to configure?</h2>
|
||||
<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>
|
||||
<svg class="picker-hub-chevron" viewBox="0 0 20 20" aria-hidden="true"><path d="m7.5 4.5 5 5.5-5 5.5"></path></svg>
|
||||
</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>
|
||||
<svg class="picker-hub-chevron" viewBox="0 0 20 20" aria-hidden="true"><path d="m7.5 4.5 5 5.5-5 5.5"></path></svg>
|
||||
</span>
|
||||
<span class="picker-hub-summary" data-hub-summary></span>
|
||||
</button>
|
||||
@@ -942,24 +947,24 @@ const questions = [
|
||||
<button class="picker-hub-card" type="button" data-hub-target="05" data-hub-category="typography">
|
||||
<span class="picker-hub-eyebrow">Typography</span>
|
||||
<span class="picker-hub-title">Type scale
|
||||
<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>
|
||||
<svg class="picker-hub-chevron" viewBox="0 0 20 20" aria-hidden="true"><path d="m7.5 4.5 5 5.5-5 5.5"></path></svg>
|
||||
</span>
|
||||
<span class="picker-hub-summary" data-hub-summary></span>
|
||||
</button>
|
||||
<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>
|
||||
<svg class="picker-hub-chevron" viewBox="0 0 20 20" aria-hidden="true"><path d="m7.5 4.5 5 5.5-5 5.5"></path></svg>
|
||||
</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>
|
||||
<svg class="picker-hub-chevron" viewBox="0 0 20 20" aria-hidden="true"><path d="m7.5 4.5 5 5.5-5 5.5"></path></svg>
|
||||
</span>
|
||||
<span class="picker-hub-summary" data-hub-summary></span>
|
||||
</button>
|
||||
@@ -969,16 +974,16 @@ const questions = [
|
||||
<button class="picker-hub-card" type="button" data-hub-target="07" data-hub-category="material">
|
||||
<span class="picker-hub-eyebrow">Material</span>
|
||||
<span class="picker-hub-title">Layout
|
||||
<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>
|
||||
<svg class="picker-hub-chevron" viewBox="0 0 20 20" aria-hidden="true"><path d="m7.5 4.5 5 5.5-5 5.5"></path></svg>
|
||||
</span>
|
||||
<span class="picker-hub-summary" data-hub-summary></span>
|
||||
</button>
|
||||
<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>
|
||||
<svg class="picker-hub-chevron" viewBox="0 0 20 20" aria-hidden="true"><path d="m7.5 4.5 5 5.5-5 5.5"></path></svg>
|
||||
</span>
|
||||
<span class="picker-hub-summary" data-hub-summary></span>
|
||||
</button>
|
||||
|
||||
@@ -2279,18 +2279,52 @@ function hubSurfaceRows(group) {
|
||||
}));
|
||||
}
|
||||
|
||||
function hubLine(text, surface) {
|
||||
/* A summary row is a surface pill on the left and the value docked
|
||||
right behind a patina dot, the same signal the surface tabs and the
|
||||
option rows spend on a committed choice. The plain line survives
|
||||
only for the skipped-card message. */
|
||||
function hubPlainLine(text) {
|
||||
const line = document.createElement('span');
|
||||
line.className = 'picker-hub-line';
|
||||
if (surface) {
|
||||
const name = document.createElement('b');
|
||||
name.textContent = surface;
|
||||
line.append(name);
|
||||
}
|
||||
line.append(text);
|
||||
return line;
|
||||
}
|
||||
|
||||
function hubRow(surfaceLabel, valueTitle, group, value) {
|
||||
const entry = document.createElement('span');
|
||||
entry.className = 'picker-hub-entry';
|
||||
const pill = document.createElement('span');
|
||||
pill.className = 'picker-hub-pill';
|
||||
const label = document.createElement('span');
|
||||
label.className = 'picker-hub-pill-label';
|
||||
label.textContent = surfaceLabel;
|
||||
const val = document.createElement('span');
|
||||
val.className = 'picker-hub-value';
|
||||
const dot = document.createElement('i');
|
||||
dot.className = 'picker-hub-dot';
|
||||
dot.setAttribute('aria-hidden', 'true');
|
||||
val.append(dot, valueTitle);
|
||||
pill.append(label, val);
|
||||
const note = document.createElement('span');
|
||||
note.className = 'picker-hub-note';
|
||||
const why = value ? hubOptionDesc(group, value) : '';
|
||||
note.textContent = why;
|
||||
note.hidden = !why;
|
||||
entry.append(pill, note);
|
||||
return entry;
|
||||
}
|
||||
|
||||
/* The one-line reason the current answer is a sound default, read off
|
||||
the option row's own description so the hub never restates copy.
|
||||
dataset.copy is read first because applyAllows swaps textContent for
|
||||
a blocked reason on surfaces that rule the option out. */
|
||||
function hubOptionDesc(group, value) {
|
||||
const input = document.querySelector(`input[name="${group}"][value="${value}"]`);
|
||||
const desc = input?.closest('.picker-strategy-option')?.querySelector('.picker-strategy-desc');
|
||||
if (!desc) return '';
|
||||
return (desc.dataset.copy ?? desc.textContent).trim();
|
||||
}
|
||||
|
||||
function renderHub() {
|
||||
if (!hubScreen) return;
|
||||
for (const cardNode of hubScreen.querySelectorAll('.picker-hub-card')) {
|
||||
@@ -2304,7 +2338,7 @@ function renderHub() {
|
||||
cardNode.disabled = skipped;
|
||||
cardNode.setAttribute('aria-disabled', skipped ? 'true' : 'false');
|
||||
if (skipped) {
|
||||
summary.replaceChildren(hubLine('Not asked of these surfaces'));
|
||||
summary.replaceChildren(hubPlainLine('Not asked of these surfaces'));
|
||||
cardNode.classList.remove('is-edited');
|
||||
mark.hidden = true;
|
||||
continue;
|
||||
@@ -2315,15 +2349,25 @@ function renderHub() {
|
||||
if (rows.length === 0) {
|
||||
const checked = document.querySelector(`input[name="${group}"]:checked`);
|
||||
edited = hubEdited.has(group);
|
||||
lines = [hubLine(checked ? hubOptionTitle(group, checked.value) : '')];
|
||||
lines = [hubRow(
|
||||
'All surfaces',
|
||||
checked ? hubOptionTitle(group, checked.value) : '',
|
||||
group,
|
||||
checked?.value,
|
||||
)];
|
||||
} else {
|
||||
edited = rows.some((row) => row.chosen);
|
||||
if (rows.length > 1 && rows.every((row) => row.value === rows[0].value)) {
|
||||
lines = [hubLine(`${hubOptionTitle(group, rows[0].value)} · all`)];
|
||||
lines = [hubRow('All surfaces', hubOptionTitle(group, rows[0].value), group, rows[0].value)];
|
||||
} else if (rows.length === 1) {
|
||||
lines = [hubLine(hubOptionTitle(group, rows[0].value))];
|
||||
lines = [hubRow(hubSurfaceLabel(rows[0].mode), hubOptionTitle(group, rows[0].value), group, rows[0].value)];
|
||||
} else {
|
||||
lines = rows.map((row) => hubLine(hubOptionTitle(group, row.value), hubSurfaceLabel(row.mode)));
|
||||
lines = rows.map((row) => hubRow(
|
||||
hubSurfaceLabel(row.mode),
|
||||
hubOptionTitle(group, row.value),
|
||||
group,
|
||||
row.value,
|
||||
));
|
||||
}
|
||||
}
|
||||
summary.replaceChildren(...lines);
|
||||
|
||||
+75
-23
@@ -329,6 +329,7 @@ body.picker-page {
|
||||
.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="04b"]) .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,
|
||||
@@ -4109,7 +4110,7 @@ body.picker-page {
|
||||
gap: clamp(12px, 2svh, 18px);
|
||||
}
|
||||
|
||||
/* Screen 05 is the only question that carries a lede, and one sentence at
|
||||
/* Screen 05 and the configure hub carry a lede, and one sentence at
|
||||
caption size under a display headline reads as a footnote. Set above the
|
||||
body size instead, with the measure pulled in so the sentence breaks into
|
||||
two balanced lines rather than one long ruler. */
|
||||
@@ -7566,6 +7567,11 @@ body.picker-page {
|
||||
padding-block: clamp(32px, 5svh, 48px) clamp(24px, 3.5svh, 48px);
|
||||
}
|
||||
|
||||
.picker-hub .picker-question-lede {
|
||||
max-width: 800px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.picker-hub-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
@@ -7581,10 +7587,11 @@ body.picker-page {
|
||||
}
|
||||
|
||||
.picker-hub-card {
|
||||
position: relative;
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
gap: 16px;
|
||||
width: 100%;
|
||||
padding: 20px 24px;
|
||||
padding: 24px 28px;
|
||||
text-align: left;
|
||||
background: var(--ks-lacquer-raised);
|
||||
border: 1px solid var(--ks-rule);
|
||||
@@ -7596,16 +7603,19 @@ body.picker-page {
|
||||
}
|
||||
|
||||
.picker-hub-card:hover {
|
||||
background: var(--ks-graphite);
|
||||
border-color: var(--ks-kinpaku-deep);
|
||||
border-color: color-mix(in oklab, var(--ks-patina) 45%, var(--ks-rule));
|
||||
}
|
||||
|
||||
.picker-hub-card:focus-visible {
|
||||
outline: 1px solid var(--ks-kinpaku);
|
||||
outline-offset: 3px;
|
||||
outline: 2px solid var(--ks-patina-deep);
|
||||
outline-offset: -2px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.picker-hub-eyebrow {
|
||||
position: absolute;
|
||||
top: 24px;
|
||||
right: 28px;
|
||||
color: var(--ks-text-faint);
|
||||
font-size: var(--ks-type-eyebrow-size);
|
||||
font-weight: 500;
|
||||
@@ -7616,31 +7626,29 @@ body.picker-page {
|
||||
.picker-hub-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
gap: 8px;
|
||||
padding-right: 72px;
|
||||
color: var(--ks-champagne);
|
||||
font-size: var(--ks-type-title-size);
|
||||
font-weight: var(--ks-type-title-weight);
|
||||
line-height: var(--ks-type-title-line);
|
||||
}
|
||||
|
||||
.picker-hub-chevron {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-left: auto;
|
||||
.picker-hub-arrow {
|
||||
width: 14px;
|
||||
height: 8px;
|
||||
flex: 0 0 auto;
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-width: 1.5;
|
||||
color: var(--ks-text-faint);
|
||||
transition: color 180ms var(--ks-ease), transform 180ms var(--ks-ease);
|
||||
}
|
||||
|
||||
.picker-hub-card:hover .picker-hub-chevron {
|
||||
color: var(--ks-kinpaku);
|
||||
.picker-hub-card:hover .picker-hub-arrow {
|
||||
color: var(--ks-patina);
|
||||
transform: translateX(2px);
|
||||
}
|
||||
|
||||
.picker-hub-mark {
|
||||
margin-left: auto;
|
||||
padding: 2px 8px;
|
||||
border: 1px solid var(--ks-kinpaku-deep);
|
||||
color: var(--ks-kinpaku);
|
||||
@@ -7652,20 +7660,64 @@ body.picker-page {
|
||||
|
||||
.picker-hub-summary {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
gap: 14px;
|
||||
color: var(--ks-text-muted);
|
||||
font-size: 0.86rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.picker-hub-line {
|
||||
.picker-hub-entry {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.picker-hub-line b {
|
||||
.picker-hub-line {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Surface pill wraps the label and the committed value; patina dot
|
||||
plus the text mix mirror the surface tabs' answered state. */
|
||||
.picker-hub-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
max-width: 100%;
|
||||
padding: 4px 9px;
|
||||
color: var(--ks-text-faint);
|
||||
font-weight: 500;
|
||||
font-size: 12.5px;
|
||||
letter-spacing: 0.015em;
|
||||
border: 1px solid var(--ks-rule);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.picker-hub-pill-label {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.picker-hub-value {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: color-mix(in oklab, var(--ks-patina) 85%, var(--ks-champagne));
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.picker-hub-dot {
|
||||
flex: 0 0 auto;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
background: var(--ks-patina);
|
||||
}
|
||||
|
||||
/* Why this answer is a sound default, read off the option row's own
|
||||
description by renderHub(). One note per answer row. */
|
||||
.picker-hub-note {
|
||||
padding-inline: 2px;
|
||||
color: var(--ks-text-faint);
|
||||
font-size: 0.84rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* A question no chosen surface takes: present so the menu keeps its
|
||||
@@ -7681,7 +7733,7 @@ body.picker-page {
|
||||
.picker-hub-card.is-skipped .picker-hub-title,
|
||||
.picker-hub-card.is-skipped .picker-hub-eyebrow,
|
||||
.picker-hub-card.is-skipped .picker-hub-summary,
|
||||
.picker-hub-card.is-skipped .picker-hub-chevron {
|
||||
.picker-hub-card.is-skipped .picker-hub-arrow {
|
||||
color: var(--ks-text-mute-deep);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user