Rework typography screen to match the questionnaire pattern.

Screen 04 drops the kicker and the text eyebrow, renders six large
scrollable specimen cards (heading and body each labeled and set in
their own face), and reuses screen 03's dual desktop-plus-phone
artboards with both hero copy blocks typeset live. The type hero
widens its copy column so wide display faces clear the image block.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Abdul Wahab
2026-09-01 10:02:39 +05:00
co-authored by Cursor
parent 626bfbb0a8
commit 51ce043130
4 changed files with 215 additions and 146 deletions
+18 -8
View File
@@ -203,8 +203,8 @@ function syncFontPair(pair) {
typePreview.style.setProperty('--pt-heading', fontStack(pair.heading.family));
typePreview.style.setProperty('--pt-body', fontStack(pair.body.family));
typePreview.style.setProperty('--pt-heading-weight', pair.heading.weight);
document.querySelector('[data-type-headline]').textContent = specimen.headline;
document.querySelector('[data-type-body]').textContent = specimen.body;
for (const node of document.querySelectorAll('[data-type-headline]')) node.textContent = specimen.headline;
for (const node of document.querySelectorAll('[data-type-body]')) node.textContent = specimen.body;
document.querySelector('[name="font-heading"]').value = pair.heading.family;
document.querySelector('[name="font-body"]').value = pair.body.family;
}
@@ -218,11 +218,12 @@ function renderFontPairs(manifest, fallback) {
const input = node.querySelector('input');
input.value = pair.id;
input.checked = index === 0;
input.setAttribute('aria-label', `${pair.heading.family} with ${pair.body.family}`);
node.style.setProperty('--pair-heading', fontStack(pair.heading.family));
node.style.setProperty('--pair-body', fontStack(pair.body.family));
node.style.setProperty('--pair-heading-weight', pair.heading.weight);
node.style.setProperty('--pair-body-weight', pair.body.weight);
node.querySelector('[data-pair-name]').textContent = pair.name;
node.querySelector('[data-pair-heading]').textContent = pair.heading.family;
node.querySelector('[data-pair-body]').textContent = pair.body.family;
node.querySelector('[data-pair-why]').textContent = pair.why;
fragment.append(node);
@@ -403,15 +404,24 @@ function openTints(role) {
setActiveRole(role);
const item = $(`[data-band-item="${role}"]`, panel);
const strip = $('[data-tints]', item);
const [currentL, C, H] = hexToOklch(state().colors[role]);
const nearest = Math.max(0, Math.min(6, Math.round((0.92 - currentL) / (0.74 / 6))));
const current = state().colors[role];
const [L, C, H] = hexToOklch(current);
// The strip pivots on the band's color: the middle swatch is that color
// exactly, the three to its left step toward near-white, the three to its
// right toward near-black. Chroma eases off toward the ends so the
// extremes stay in gamut instead of clipping to a different hue.
$$('[data-tint]', strip).forEach((button, index) => {
const L = 0.92 - index * (0.74 / 6);
const hex = oklchToHex([L, C * (0.55 + 0.45 * Math.sin(Math.PI * index / 6)), H]);
const offset = index - 3;
const tintL = offset < 0
? L + (0.96 - L) * (-offset / 3)
: L - (L - 0.16) * (offset / 3);
const hex = offset === 0
? current
: oklchToHex([tintL, C * (1 - 0.3 * (Math.abs(offset) / 3)), H]);
button.dataset.tint = hex;
button.style.setProperty('--tint-color', hex);
button.setAttribute('aria-label', hex);
button.toggleAttribute('data-current', index === nearest);
button.toggleAttribute('data-current', offset === 0);
});
strip.hidden = false;
item.dataset.tintOpen = '';