mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-15 07:36:50 +03:00
update
This commit is contained in:
+34
-13
@@ -1077,9 +1077,10 @@ const questions = [
|
||||
{/* Screen 04b: the configure hub. Every remaining question already
|
||||
holds an answer (defaults are pre-filled the moment a surface tile
|
||||
is checked), so the run can end here. The cards are skeletons;
|
||||
summaries and edited marks are written by renderHub() in
|
||||
scripts/palette-picker.js on every arrival. Placement is the
|
||||
approved masonry: tall per-surface cards anchor the outer columns. */}
|
||||
previews, tabs, tooltip summaries, and edited marks are written by
|
||||
renderHub() in scripts/palette-picker.js on every arrival. The
|
||||
column wrappers are display: contents, so the five near-uniform
|
||||
cards flow the grid three over two. */}
|
||||
<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">
|
||||
@@ -1092,51 +1093,71 @@ const questions = [
|
||||
|
||||
<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">
|
||||
<button class="picker-hub-card" type="button" data-hub-target="06" data-hub-category="material" aria-describedby="picker-hub-tip-06">
|
||||
<span class="picker-hub-eyebrow">Material</span>
|
||||
<span class="picker-hub-title">Motion
|
||||
<svg class="picker-hub-arrow" viewBox="0 0 16 8" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="square" aria-hidden="true"><path d="M0 4h14M10 0l4 4-4 4"></path></svg>
|
||||
<span class="picker-hub-mark" data-hub-mark hidden>Edited</span>
|
||||
</span>
|
||||
<span class="picker-hub-summary" data-hub-summary></span>
|
||||
<span class="picker-hub-preview" data-hub-preview aria-hidden="true"></span>
|
||||
<span class="picker-hub-tabs" data-hub-tabs hidden></span>
|
||||
<span class="picker-hub-tip" role="tooltip" id="picker-hub-tip-06" data-hub-tip>
|
||||
<span class="picker-hub-summary" data-hub-summary></span>
|
||||
</span>
|
||||
</button>
|
||||
<button class="picker-hub-card" type="button" data-hub-target="08" data-hub-category="material">
|
||||
<button class="picker-hub-card" type="button" data-hub-target="08" data-hub-category="material" aria-describedby="picker-hub-tip-08">
|
||||
<span class="picker-hub-eyebrow">Material</span>
|
||||
<span class="picker-hub-title">Boundaries
|
||||
<svg class="picker-hub-arrow" viewBox="0 0 16 8" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="square" aria-hidden="true"><path d="M0 4h14M10 0l4 4-4 4"></path></svg>
|
||||
<span class="picker-hub-mark" data-hub-mark hidden>Edited</span>
|
||||
</span>
|
||||
<span class="picker-hub-summary" data-hub-summary></span>
|
||||
<span class="picker-hub-preview" data-hub-preview aria-hidden="true"></span>
|
||||
<span class="picker-hub-tabs" data-hub-tabs hidden></span>
|
||||
<span class="picker-hub-tip" role="tooltip" id="picker-hub-tip-08" data-hub-tip>
|
||||
<span class="picker-hub-summary" data-hub-summary></span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="picker-hub-col">
|
||||
<button class="picker-hub-card" type="button" data-hub-target="11" data-hub-category="iconography">
|
||||
<button class="picker-hub-card" type="button" data-hub-target="11" data-hub-category="iconography" aria-describedby="picker-hub-tip-11">
|
||||
<span class="picker-hub-eyebrow">Iconography</span>
|
||||
<span class="picker-hub-title">Icon set
|
||||
<svg class="picker-hub-arrow" viewBox="0 0 16 8" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="square" aria-hidden="true"><path d="M0 4h14M10 0l4 4-4 4"></path></svg>
|
||||
<span class="picker-hub-mark" data-hub-mark hidden>Edited</span>
|
||||
</span>
|
||||
<span class="picker-hub-summary" data-hub-summary></span>
|
||||
<span class="picker-hub-preview" data-hub-preview aria-hidden="true"></span>
|
||||
<span class="picker-hub-tabs" data-hub-tabs hidden></span>
|
||||
<span class="picker-hub-tip" role="tooltip" id="picker-hub-tip-11" data-hub-tip>
|
||||
<span class="picker-hub-summary" data-hub-summary></span>
|
||||
</span>
|
||||
</button>
|
||||
<button class="picker-hub-card" type="button" data-hub-target="09" data-hub-category="material">
|
||||
<button class="picker-hub-card" type="button" data-hub-target="09" data-hub-category="material" aria-describedby="picker-hub-tip-09">
|
||||
<span class="picker-hub-eyebrow">Material</span>
|
||||
<span class="picker-hub-title">Corners
|
||||
<svg class="picker-hub-arrow" viewBox="0 0 16 8" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="square" aria-hidden="true"><path d="M0 4h14M10 0l4 4-4 4"></path></svg>
|
||||
<span class="picker-hub-mark" data-hub-mark hidden>Edited</span>
|
||||
</span>
|
||||
<span class="picker-hub-summary" data-hub-summary></span>
|
||||
<span class="picker-hub-preview" data-hub-preview aria-hidden="true"></span>
|
||||
<span class="picker-hub-tabs" data-hub-tabs hidden></span>
|
||||
<span class="picker-hub-tip" role="tooltip" id="picker-hub-tip-09" data-hub-tip>
|
||||
<span class="picker-hub-summary" data-hub-summary></span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="picker-hub-col">
|
||||
<button class="picker-hub-card" type="button" data-hub-target="10" data-hub-category="material">
|
||||
<button class="picker-hub-card" type="button" data-hub-target="10" data-hub-category="material" aria-describedby="picker-hub-tip-10">
|
||||
<span class="picker-hub-eyebrow">Material</span>
|
||||
<span class="picker-hub-title">Depth
|
||||
<svg class="picker-hub-arrow" viewBox="0 0 16 8" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="square" aria-hidden="true"><path d="M0 4h14M10 0l4 4-4 4"></path></svg>
|
||||
<span class="picker-hub-mark" data-hub-mark hidden>Edited</span>
|
||||
</span>
|
||||
<span class="picker-hub-summary" data-hub-summary></span>
|
||||
<span class="picker-hub-preview" data-hub-preview aria-hidden="true"></span>
|
||||
<span class="picker-hub-tabs" data-hub-tabs hidden></span>
|
||||
<span class="picker-hub-tip" role="tooltip" id="picker-hub-tip-10" data-hub-tip>
|
||||
<span class="picker-hub-summary" data-hub-summary></span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2347,61 +2347,239 @@ function hubOptionDesc(group, value) {
|
||||
return (desc.dataset.copy ?? desc.textContent).trim();
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Hub previews: each card's body is a clone of its question's own
|
||||
artboard, painted with the committed palette and the previewed
|
||||
surface's answers.
|
||||
|
||||
The material clones borrow design-context.css's proof frame: the
|
||||
committed bodies there key off data-dcx-* attributes on the frame
|
||||
because a clone cannot ride the #picker-form:has() selectors its
|
||||
original took its rendition from once the answer has to differ per
|
||||
card. The stylesheet is already on this page, so nothing is
|
||||
restated. The motion clone is the one exception: its scenes exist
|
||||
only as #picker-form:has() rules with no dcx mirror, so it keeps
|
||||
its scene classes, stays keyed to the live motion-energy radio,
|
||||
and the renderer parks that radio on the previewed surface's
|
||||
answer the same way the question screens park it. The keyframes
|
||||
are infinite, so the clone loops on its own; the hover-restart
|
||||
wiring never reaches it (replayMotion holds the boards it listed
|
||||
at load, and screen 06's hover rows are display: none here).
|
||||
============================================================ */
|
||||
const HUB_PREVIEW_MARK_GROUPS = {
|
||||
strategy: 'color-strategy',
|
||||
boundary: 'boundary-style',
|
||||
corner: 'corner-style',
|
||||
depth: 'depth-style',
|
||||
layout: 'layout-structure',
|
||||
};
|
||||
|
||||
/* One surface's committed structural answers, read off the same hidden
|
||||
fields the summary rows read; the checked radio covers a flat group
|
||||
whose field this surface never rendered. */
|
||||
function hubPreviewMarks(mode) {
|
||||
const marks = { surface: mode };
|
||||
for (const [mark, group] of Object.entries(HUB_PREVIEW_MARK_GROUPS)) {
|
||||
marks[mark] = document.querySelector(`input[type="hidden"][data-surface-field="${group}-${mode}"]`)?.value
|
||||
|| document.querySelector(`input[name="${group}"]:checked`)?.value || '';
|
||||
}
|
||||
return marks;
|
||||
}
|
||||
|
||||
/* Clone one question's mounted artboard into a proof frame, the same
|
||||
lift design-context.js's proofHtml performs: ids do not survive (they
|
||||
would collide with the originals), the surface attribute moves to the
|
||||
frame, and the committed palette is painted on the frame under the
|
||||
--pkc-* names the dcx bodies read. */
|
||||
function hubProof(source, marks = null) {
|
||||
const wrap = document.createElement('span');
|
||||
wrap.className = 'picker-hub-proof';
|
||||
if (marks) {
|
||||
wrap.classList.add('dcx-proof--board');
|
||||
for (const [key, value] of Object.entries(marks)) {
|
||||
if (value) wrap.setAttribute(`data-dcx-${key}`, value);
|
||||
}
|
||||
syncCommittedPalette(wrap, 'pkc');
|
||||
}
|
||||
const clone = source.cloneNode(true);
|
||||
clone.hidden = false;
|
||||
clone.removeAttribute('data-surface');
|
||||
for (const node of [clone, ...clone.querySelectorAll('[id]')]) node.removeAttribute('id');
|
||||
/* The icon sheet's lookup hooks must not survive either: the hub card
|
||||
sits before screen 11 in the DOM, so a clone still carrying them
|
||||
would shadow the originals for every later querySelector, including
|
||||
the document's own icon proof. */
|
||||
for (const node of [clone, ...clone.querySelectorAll('[data-icon-field], [data-icon-strip]')]) {
|
||||
node.removeAttribute('data-icon-sheet');
|
||||
node.removeAttribute('data-icon-field');
|
||||
node.removeAttribute('data-icon-strip');
|
||||
}
|
||||
wrap.append(clone);
|
||||
return wrap;
|
||||
}
|
||||
|
||||
const hubQuestionBoard = (screen, mode) => document.querySelector(
|
||||
`.picker-screen[data-screen="${screen}"] .picker-artboard[data-surface="${mode}"]`,
|
||||
);
|
||||
|
||||
/* The preview and its tab strip for one card. The active tab carries
|
||||
the patina dot and the surface's answer, so the choice reads without
|
||||
hovering; the tooltip carries the summary rows. */
|
||||
function renderHubPreview(cardNode, rows) {
|
||||
const previewSlot = cardNode.querySelector('[data-hub-preview]');
|
||||
const tabsSlot = cardNode.querySelector('[data-hub-tabs]');
|
||||
const screen = cardNode.dataset.hubTarget;
|
||||
const group = HUB_GROUPS[screen];
|
||||
if (group === 'icon-pack') {
|
||||
tabsSlot.hidden = true;
|
||||
tabsSlot.replaceChildren();
|
||||
previewSlot.replaceChildren(hubProof(iconSheet));
|
||||
/* The glyphs are fetched on screen 11's first arrival, which a run
|
||||
that stops at the hub never makes; fetch them here and re-render
|
||||
once. The empty marker is the failed state, which must not refetch
|
||||
on every arrival. */
|
||||
if (!iconField.querySelector('.picker-icon-cell') && !('empty' in iconField.dataset)) {
|
||||
loadIconPacks().then(renderHub);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const modes = rows.map((row) => row.mode);
|
||||
const mode = modes.includes(cardNode.dataset.hubSurface) ? cardNode.dataset.hubSurface : modes[0];
|
||||
cardNode.dataset.hubSurface = mode ?? '';
|
||||
const board = mode ? hubQuestionBoard(screen, mode) : null;
|
||||
if (!board) {
|
||||
previewSlot.replaceChildren();
|
||||
tabsSlot.hidden = true;
|
||||
tabsSlot.replaceChildren();
|
||||
return;
|
||||
}
|
||||
if (group === 'motion-energy') {
|
||||
surfaceQuestions.find((question) => question.screen === screen)?.park(mode);
|
||||
const proof = hubProof(board);
|
||||
syncCommittedPalette(proof.firstChild);
|
||||
previewSlot.replaceChildren(proof);
|
||||
/* The route stops are measured off the clone's own layout, exactly
|
||||
what screen 06 does for the originals on arrival. */
|
||||
requestAnimationFrame(() => plotMotionRoute(proof.firstChild));
|
||||
} else {
|
||||
previewSlot.replaceChildren(hubProof(board, hubPreviewMarks(mode)));
|
||||
}
|
||||
tabsSlot.hidden = rows.length === 0;
|
||||
tabsSlot.replaceChildren(...rows.map((row) => {
|
||||
const tab = document.createElement('span');
|
||||
tab.className = 'picker-hub-tab';
|
||||
tab.dataset.hubTab = row.mode;
|
||||
const dot = document.createElement('i');
|
||||
dot.className = 'picker-hub-tab-dot';
|
||||
dot.setAttribute('aria-hidden', 'true');
|
||||
tab.append(dot, hubSurfaceLabel(row.mode));
|
||||
if (row.mode === mode) {
|
||||
tab.dataset.on = '';
|
||||
const value = document.createElement('span');
|
||||
value.className = 'picker-hub-tab-value';
|
||||
value.textContent = hubOptionTitle(group, row.value);
|
||||
tab.append(value);
|
||||
}
|
||||
return tab;
|
||||
}));
|
||||
}
|
||||
|
||||
function renderHubCard(cardNode) {
|
||||
const group = HUB_GROUPS[cardNode.dataset.hubTarget];
|
||||
if (!group) return;
|
||||
const summary = cardNode.querySelector('[data-hub-summary]');
|
||||
const mark = cardNode.querySelector('[data-hub-mark]');
|
||||
const previewSlot = cardNode.querySelector('[data-hub-preview]');
|
||||
const tabsSlot = cardNode.querySelector('[data-hub-tabs]');
|
||||
const target = document.querySelector(`.picker-screen[data-screen="${cardNode.dataset.hubTarget}"]`);
|
||||
const skipped = Boolean(target?.hasAttribute('data-skip'));
|
||||
cardNode.classList.toggle('is-skipped', skipped);
|
||||
cardNode.disabled = skipped;
|
||||
cardNode.setAttribute('aria-disabled', skipped ? 'true' : 'false');
|
||||
if (skipped) {
|
||||
summary.replaceChildren();
|
||||
previewSlot.replaceChildren(hubPlainLine('Not asked of these surfaces'));
|
||||
tabsSlot.hidden = true;
|
||||
tabsSlot.replaceChildren();
|
||||
cardNode.classList.remove('is-edited');
|
||||
mark.hidden = true;
|
||||
return;
|
||||
}
|
||||
const rows = hubSurfaceRows(group);
|
||||
let edited;
|
||||
let lines;
|
||||
if (rows.length === 0) {
|
||||
const checked = document.querySelector(`input[name="${group}"]:checked`);
|
||||
edited = hubEdited.has(group);
|
||||
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 = [hubRow('All surfaces', hubOptionTitle(group, rows[0].value), group, rows[0].value)];
|
||||
} else if (rows.length === 1) {
|
||||
lines = [hubRow(hubSurfaceLabel(rows[0].mode), hubOptionTitle(group, rows[0].value), group, rows[0].value)];
|
||||
} else {
|
||||
lines = rows.map((row) => hubRow(
|
||||
hubSurfaceLabel(row.mode),
|
||||
hubOptionTitle(group, row.value),
|
||||
group,
|
||||
row.value,
|
||||
));
|
||||
}
|
||||
}
|
||||
summary.replaceChildren(...lines);
|
||||
cardNode.classList.toggle('is-edited', edited);
|
||||
mark.hidden = !edited;
|
||||
renderHubPreview(cardNode, rows);
|
||||
}
|
||||
|
||||
function renderHub() {
|
||||
if (!hubScreen) return;
|
||||
for (const cardNode of hubScreen.querySelectorAll('.picker-hub-card')) {
|
||||
const group = HUB_GROUPS[cardNode.dataset.hubTarget];
|
||||
if (!group) continue;
|
||||
const summary = cardNode.querySelector('[data-hub-summary]');
|
||||
const mark = cardNode.querySelector('[data-hub-mark]');
|
||||
const target = document.querySelector(`.picker-screen[data-screen="${cardNode.dataset.hubTarget}"]`);
|
||||
const skipped = Boolean(target?.hasAttribute('data-skip'));
|
||||
cardNode.classList.toggle('is-skipped', skipped);
|
||||
cardNode.disabled = skipped;
|
||||
cardNode.setAttribute('aria-disabled', skipped ? 'true' : 'false');
|
||||
if (skipped) {
|
||||
summary.replaceChildren(hubPlainLine('Not asked of these surfaces'));
|
||||
cardNode.classList.remove('is-edited');
|
||||
mark.hidden = true;
|
||||
continue;
|
||||
}
|
||||
const rows = hubSurfaceRows(group);
|
||||
let edited;
|
||||
let lines;
|
||||
if (rows.length === 0) {
|
||||
const checked = document.querySelector(`input[name="${group}"]:checked`);
|
||||
edited = hubEdited.has(group);
|
||||
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 = [hubRow('All surfaces', hubOptionTitle(group, rows[0].value), group, rows[0].value)];
|
||||
} else if (rows.length === 1) {
|
||||
lines = [hubRow(hubSurfaceLabel(rows[0].mode), hubOptionTitle(group, rows[0].value), group, rows[0].value)];
|
||||
} else {
|
||||
lines = rows.map((row) => hubRow(
|
||||
hubSurfaceLabel(row.mode),
|
||||
hubOptionTitle(group, row.value),
|
||||
group,
|
||||
row.value,
|
||||
));
|
||||
}
|
||||
}
|
||||
summary.replaceChildren(...lines);
|
||||
cardNode.classList.toggle('is-edited', edited);
|
||||
mark.hidden = !edited;
|
||||
}
|
||||
for (const cardNode of hubScreen.querySelectorAll('.picker-hub-card')) renderHubCard(cardNode);
|
||||
}
|
||||
|
||||
/* The tooltip floats above the card and flips below when the card sits
|
||||
too near the viewport top to hold it; when neither side holds the
|
||||
whole panel, the roomier side takes it. Measured when the pointer or
|
||||
focus arrives, before the reveal transition starts; the tip is
|
||||
opacity-hidden rather than display-hidden, so its height is real. */
|
||||
function placeHubTip(cardNode) {
|
||||
const tip = cardNode.querySelector('[data-hub-tip]');
|
||||
if (!tip) return;
|
||||
const rect = cardNode.getBoundingClientRect();
|
||||
const need = tip.offsetHeight + 20;
|
||||
const above = rect.top;
|
||||
const below = window.innerHeight - rect.bottom;
|
||||
cardNode.dataset.tipAt = (above >= need || above >= below) ? 'above' : 'below';
|
||||
}
|
||||
hubScreen?.addEventListener('pointerover', (event) => {
|
||||
const cardNode = event.target.closest('.picker-hub-card');
|
||||
if (cardNode) placeHubTip(cardNode);
|
||||
});
|
||||
hubScreen?.addEventListener('focusin', (event) => {
|
||||
const cardNode = event.target.closest('.picker-hub-card');
|
||||
if (cardNode) placeHubTip(cardNode);
|
||||
});
|
||||
|
||||
/* Cards and the finish CTA jump by screen id. The inline nav script
|
||||
owns goTo(); it listens for this event, so no swap logic is
|
||||
duplicated here. A disabled card never reaches this handler. */
|
||||
duplicated here. A disabled card never reaches this handler. A mini
|
||||
tab hit re-clones its own card instead and never travels: the card
|
||||
click must stay the jump to the question. */
|
||||
hubScreen?.addEventListener('click', (event) => {
|
||||
const tab = event.target.closest('[data-hub-tab]');
|
||||
if (tab) {
|
||||
event.stopPropagation();
|
||||
const cardNode = tab.closest('.picker-hub-card');
|
||||
cardNode.dataset.hubSurface = tab.dataset.hubTab;
|
||||
renderHubCard(cardNode);
|
||||
return;
|
||||
}
|
||||
const cardNode = event.target.closest('[data-hub-target]');
|
||||
if (!cardNode || cardNode.disabled) return;
|
||||
document.dispatchEvent(new CustomEvent('picker:goto', {
|
||||
|
||||
+153
-8
@@ -7909,10 +7909,12 @@ body.picker-page {
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Screen 04b: the configure hub. Three packed columns of cards;
|
||||
the tall per-surface cards (motion, depth) anchor the outer
|
||||
tracks. Sharp corners and hairline rules, the same vocabulary
|
||||
as the option rows. Summaries are written by renderHub().
|
||||
Screen 04b: the configure hub. Five near-uniform preview cards
|
||||
flow a three-column grid, three over two; the column wrappers
|
||||
are display: contents so the markup's masonry grouping costs
|
||||
nothing. Sharp corners and hairline rules, the same vocabulary
|
||||
as the option rows. Previews, tabs, and the tooltip's summary
|
||||
rows are written by renderHub().
|
||||
============================================================ */
|
||||
.picker-hub {
|
||||
display: grid;
|
||||
@@ -7930,14 +7932,12 @@ body.picker-page {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 14px;
|
||||
align-items: start;
|
||||
align-items: stretch;
|
||||
width: min(100%, 1080px);
|
||||
}
|
||||
|
||||
.picker-hub-col {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
align-content: start;
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.picker-hub-card {
|
||||
@@ -8075,6 +8075,151 @@ body.picker-page {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* The card body: a clone of the question's own artboard, written by
|
||||
renderHubPreview(). The frame borrows design-context.css's
|
||||
.dcx-proof--board hooks for the committed bodies, so no rendition
|
||||
rule is restated here. Pointer events stay off: everything inside
|
||||
the card except a mini tab is the jump to the question. */
|
||||
.picker-hub-preview {
|
||||
display: block;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.picker-hub-proof {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* The mini tab strip: the surface-tab vocabulary at card scale. Spans
|
||||
rather than buttons because the card itself is one, and a button
|
||||
cannot hold another. */
|
||||
.picker-hub-tabs {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 3px;
|
||||
width: fit-content;
|
||||
max-width: 100%;
|
||||
padding: 3px;
|
||||
background: var(--ks-lacquer-raised);
|
||||
border: 1px solid var(--ks-rule);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.picker-hub-tabs[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.picker-hub-tab {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 10px;
|
||||
color: var(--ks-text-muted);
|
||||
font-size: 12.5px;
|
||||
letter-spacing: 0.015em;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background-color 180ms var(--ks-ease),
|
||||
color 180ms var(--ks-ease);
|
||||
}
|
||||
|
||||
.picker-hub-tab:hover {
|
||||
color: var(--ks-champagne);
|
||||
background: color-mix(in oklab, var(--ks-patina) 9%, var(--ks-lacquer-raised));
|
||||
}
|
||||
|
||||
.picker-hub-tab[data-on] {
|
||||
color: var(--ks-champagne);
|
||||
background: color-mix(in oklab, var(--ks-patina) 14%, var(--ks-lacquer-raised));
|
||||
}
|
||||
|
||||
/* The same report the full-size strip's dot gives: filled on the tab
|
||||
being previewed, hollow on the rest. */
|
||||
.picker-hub-tab-dot {
|
||||
flex: none;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border: 1px solid color-mix(in oklab, var(--ks-patina) 55%, transparent);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.picker-hub-tab[data-on] .picker-hub-tab-dot {
|
||||
background: var(--ks-patina);
|
||||
border-color: var(--ks-patina);
|
||||
}
|
||||
|
||||
.picker-hub-tab-value {
|
||||
color: color-mix(in oklab, var(--ks-patina) 85%, var(--ks-champagne));
|
||||
}
|
||||
|
||||
/* The summary rows' new home: a floating panel on the card's own
|
||||
hover and focus, the contrast alert tip's pattern at card width.
|
||||
Above by default; placeHubTip() flips it below when the card sits
|
||||
too near the viewport top. The hovered card rises over its
|
||||
neighbours so the panel is never clipped by the masonry. */
|
||||
.picker-hub-card:is(:hover, :focus-within) {
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.picker-hub-tip {
|
||||
position: absolute;
|
||||
left: -1px;
|
||||
right: -1px;
|
||||
bottom: calc(100% + 10px);
|
||||
z-index: 12;
|
||||
padding: 16px 18px;
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
opacity: 0;
|
||||
background: var(--ks-lacquer-raised);
|
||||
border: 1px solid var(--ks-rule);
|
||||
border-radius: 2px;
|
||||
pointer-events: none;
|
||||
transform: translateY(3px);
|
||||
transition:
|
||||
opacity 180ms var(--ks-ease),
|
||||
transform 180ms var(--ks-ease);
|
||||
}
|
||||
|
||||
.picker-hub-tip::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: -5px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: var(--ks-lacquer-raised);
|
||||
border-right: 1px solid var(--ks-rule);
|
||||
border-bottom: 1px solid var(--ks-rule);
|
||||
transform: translateX(-50%) rotate(45deg);
|
||||
}
|
||||
|
||||
.picker-hub-card[data-tip-at="below"] .picker-hub-tip {
|
||||
top: calc(100% + 10px);
|
||||
bottom: auto;
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
|
||||
.picker-hub-card[data-tip-at="below"] .picker-hub-tip::after {
|
||||
top: -5px;
|
||||
bottom: auto;
|
||||
border: 0;
|
||||
border-left: 1px solid var(--ks-rule);
|
||||
border-top: 1px solid var(--ks-rule);
|
||||
}
|
||||
|
||||
.picker-hub-card:not(.is-skipped):is(:hover, :focus-within) .picker-hub-tip {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.picker-hub-tip,
|
||||
.picker-hub-tab {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* A question no chosen surface takes: present so the menu keeps its
|
||||
shape, muted so nobody is invited into a screen the run skips. */
|
||||
.picker-hub-card.is-skipped,
|
||||
|
||||
@@ -292,7 +292,7 @@ figure.cell iframe { display: block; border: 0; background: transparent; positio
|
||||
<figcaption><span class="cell-num">26</span> Selected: Landing page + Restrained</figcaption>
|
||||
<div class="scaler" data-width="918" data-height="492.21875">
|
||||
<div class="clip" style="width:918px;height:492.21875px">
|
||||
<iframe title="26. Selected: Landing page + Restrained" data-cell="motion--persuade--restrained" data-cell-num="26" data-section="motion" scrolling="no" style="width:1600px;height:1000px"></iframe>
|
||||
<iframe title="26. Selected: Landing page + Restrained" data-cell="motion--persuade--restrained" data-cell-num="26" data-section="motion" data-premium-motion scrolling="no" style="width:1600px;height:1000px"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</figure>
|
||||
@@ -300,7 +300,7 @@ figure.cell iframe { display: block; border: 0; background: transparent; positio
|
||||
<figcaption><span class="cell-num">27</span> Selected: Landing page + Responsive</figcaption>
|
||||
<div class="scaler" data-width="918" data-height="492.21875">
|
||||
<div class="clip" style="width:918px;height:492.21875px">
|
||||
<iframe title="27. Selected: Landing page + Responsive" data-cell="motion--persuade--responsive" data-cell-num="27" data-section="motion" scrolling="no" style="width:1600px;height:1000px"></iframe>
|
||||
<iframe title="27. Selected: Landing page + Responsive" data-cell="motion--persuade--responsive" data-cell-num="27" data-section="motion" data-premium-motion scrolling="no" style="width:1600px;height:1000px"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</figure>
|
||||
@@ -308,7 +308,7 @@ figure.cell iframe { display: block; border: 0; background: transparent; positio
|
||||
<figcaption><span class="cell-num">28</span> Selected: Landing page + Choreographed</figcaption>
|
||||
<div class="scaler" data-width="918" data-height="492.21875">
|
||||
<div class="clip" style="width:918px;height:492.21875px">
|
||||
<iframe title="28. Selected: Landing page + Choreographed" data-cell="motion--persuade--choreographed" data-cell-num="28" data-section="motion" scrolling="no" style="width:1600px;height:1000px"></iframe>
|
||||
<iframe title="28. Selected: Landing page + Choreographed" data-cell="motion--persuade--choreographed" data-cell-num="28" data-section="motion" data-premium-motion scrolling="no" style="width:1600px;height:1000px"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</figure>
|
||||
@@ -316,7 +316,7 @@ figure.cell iframe { display: block; border: 0; background: transparent; positio
|
||||
<figcaption><span class="cell-num">29</span> Selected: Portfolio + Restrained</figcaption>
|
||||
<div class="scaler" data-width="918" data-height="492.21875">
|
||||
<div class="clip" style="width:918px;height:492.21875px">
|
||||
<iframe title="29. Selected: Portfolio + Restrained" data-cell="motion--experience--restrained" data-cell-num="29" data-section="motion" scrolling="no" style="width:1600px;height:1000px"></iframe>
|
||||
<iframe title="29. Selected: Portfolio + Restrained" data-cell="motion--experience--restrained" data-cell-num="29" data-section="motion" data-premium-motion scrolling="no" style="width:1600px;height:1000px"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</figure>
|
||||
@@ -324,7 +324,7 @@ figure.cell iframe { display: block; border: 0; background: transparent; positio
|
||||
<figcaption><span class="cell-num">30</span> Selected: Portfolio + Responsive</figcaption>
|
||||
<div class="scaler" data-width="918" data-height="492.21875">
|
||||
<div class="clip" style="width:918px;height:492.21875px">
|
||||
<iframe title="30. Selected: Portfolio + Responsive" data-cell="motion--experience--responsive" data-cell-num="30" data-section="motion" scrolling="no" style="width:1600px;height:1000px"></iframe>
|
||||
<iframe title="30. Selected: Portfolio + Responsive" data-cell="motion--experience--responsive" data-cell-num="30" data-section="motion" data-premium-motion scrolling="no" style="width:1600px;height:1000px"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</figure>
|
||||
@@ -332,7 +332,7 @@ figure.cell iframe { display: block; border: 0; background: transparent; positio
|
||||
<figcaption><span class="cell-num">31</span> Selected: Portfolio + Choreographed</figcaption>
|
||||
<div class="scaler" data-width="918" data-height="492.21875">
|
||||
<div class="clip" style="width:918px;height:492.21875px">
|
||||
<iframe title="31. Selected: Portfolio + Choreographed" data-cell="motion--experience--choreographed" data-cell-num="31" data-section="motion" scrolling="no" style="width:1600px;height:1000px"></iframe>
|
||||
<iframe title="31. Selected: Portfolio + Choreographed" data-cell="motion--experience--choreographed" data-cell-num="31" data-section="motion" data-premium-motion scrolling="no" style="width:1600px;height:1000px"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</figure>
|
||||
@@ -855,6 +855,7 @@ function paletteVars() {
|
||||
};
|
||||
}
|
||||
function applyToFrame(iframe) {
|
||||
if (iframe.hasAttribute('data-premium-motion')) return;
|
||||
var win = iframe.contentWindow;
|
||||
if (!win || !win.__applyPalette) return;
|
||||
win.__applyPalette(paletteVars());
|
||||
@@ -873,6 +874,11 @@ var cellById = {};
|
||||
PAYLOAD.cells.forEach(function (cell) { cellById[cell.id] = cell; });
|
||||
Array.prototype.forEach.call(document.querySelectorAll('iframe[data-cell]'), function (iframe) {
|
||||
var cell = cellById[iframe.getAttribute('data-cell')];
|
||||
var cellNumber = Number(iframe.getAttribute('data-cell-num'));
|
||||
if (iframe.hasAttribute('data-premium-motion')) {
|
||||
iframe.src = '../motion-previews-premium.html?embed=' + cellNumber;
|
||||
return;
|
||||
}
|
||||
iframe.addEventListener('load', function () { applyToFrame(iframe); });
|
||||
iframe.srcdoc = buildDoc(cell);
|
||||
if (cell.section === 'motion') {
|
||||
@@ -1297,6 +1303,7 @@ Array.prototype.forEach.call(document.querySelectorAll('.scaler'), function (sca
|
||||
if (!targetNumbers.has(cell.number)) return;
|
||||
var frame = document.querySelector('iframe[data-cell-num="' + cell.number + '"]');
|
||||
if (!frame) return;
|
||||
if (frame.hasAttribute('data-premium-motion')) return;
|
||||
frame.srcdoc = buildDerivedDoc(cell);
|
||||
});
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user