mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 06:06:37 +03:00
Ship the browsable mosaic document as an isolated HTML file and document the build intent in WHAT.md so the categorization work has a concrete UI target. Co-authored-by: Cursor <cursoragent@cursor.com>
2448 lines
87 KiB
HTML
2448 lines
87 KiB
HTML
<!doctype html>
|
|
<html lang="en" class="dark" data-theme-pref="auto">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>Design Context — Hanazono Atelier</title>
|
|
<meta name="description" content="The 8-category design context map, rendered with real data from the Hanazono Atelier example project.">
|
|
<meta name="robots" content="noindex">
|
|
|
|
<!-- FOUC guard: resolve theme before first paint. Mirrors the site's theme.js. -->
|
|
<script>
|
|
(function () {
|
|
var pref = localStorage.getItem('impeccable-theme');
|
|
if (pref !== 'light' && pref !== 'dark') pref = 'auto';
|
|
var resolved = pref === 'auto'
|
|
? (window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark')
|
|
: pref;
|
|
document.documentElement.classList.remove('light', 'dark');
|
|
document.documentElement.classList.add(resolved);
|
|
document.documentElement.setAttribute('data-theme-pref', pref);
|
|
})();
|
|
</script>
|
|
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Albert+Sans:wght@300;400;500;600;700&family=Alumni+Sans:wght@100;300;400;500;600;700&family=Shippori+Mincho:wght@400;500&family=Source+Sans+3:wght@400;600&display=swap" rel="stylesheet">
|
|
|
|
<style>
|
|
/* ============================================================
|
|
Tokens — copied from the site's kinpaku-tokens.css so this file
|
|
stands alone. Dark is the default; html.light remaps.
|
|
============================================================ */
|
|
:root {
|
|
--ks-kinpaku: oklch(84% 0.19 80.46);
|
|
--ks-kinpaku-pale: oklch(86% 0.07 84);
|
|
--ks-kinpaku-rich: oklch(77% 0.13 82);
|
|
--ks-kinpaku-deep: oklch(61% 0.085 78);
|
|
--ks-patina: oklch(70% 0.12 188);
|
|
--ks-patina-deep: oklch(49% 0.08 188);
|
|
|
|
--ks-lacquer: oklch(7% 0.006 95);
|
|
--ks-lacquer-deep: oklch(4% 0.004 95);
|
|
--ks-lacquer-raised: oklch(11% 0.006 95);
|
|
--ks-graphite: oklch(15% 0.008 95);
|
|
--ks-graphite-2: oklch(19% 0.008 95);
|
|
|
|
--ks-champagne: oklch(91% 0 0);
|
|
--ks-text: oklch(88% 0 0);
|
|
--ks-text-muted: oklch(72% 0 0);
|
|
--ks-text-faint: oklch(62% 0 0);
|
|
--ks-rule: oklch(78% 0 0 / 0.16);
|
|
|
|
--ks-code-fg: oklch(91% 0 0);
|
|
--ks-code-bg: var(--ks-graphite-2);
|
|
|
|
--ks-font-display: "Alumni Sans", "Albert Sans", Arial, sans-serif;
|
|
--ks-font: "Albert Sans", "Avenir Next", "Helvetica Neue", Arial, system-ui, sans-serif;
|
|
--ks-mono: "SFMono-Regular", "Roboto Mono", "JetBrains Mono", Consolas, monospace;
|
|
--ks-ease: cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
|
|
/* Section accent (mirrors docs-kinpaku's --docs-* vars). */
|
|
--accent: var(--ks-kinpaku);
|
|
--accent-hover: var(--ks-kinpaku-pale);
|
|
--accent-wash: oklch(78% 0.12 82 / 0.1);
|
|
--accent-line: oklch(78% 0.12 82 / 0.32);
|
|
--panel-bg: oklch(11% 0.006 95);
|
|
--shadow-color: oklch(0% 0 0 / 0.4);
|
|
|
|
/* Viz vignettes inside mosaic tiles. */
|
|
--viz-ink: var(--ks-text);
|
|
--viz-mist: oklch(78% 0 0 / 0.3);
|
|
--viz-accent: var(--ks-kinpaku);
|
|
}
|
|
|
|
html.dark { color-scheme: dark; }
|
|
|
|
html.light {
|
|
color-scheme: light;
|
|
|
|
--ks-lacquer: oklch(97% 0.012 95);
|
|
--ks-lacquer-deep: oklch(94% 0.014 95);
|
|
--ks-lacquer-raised: oklch(99% 0.008 95);
|
|
--ks-graphite: oklch(91% 0.012 95);
|
|
--ks-graphite-2: oklch(88% 0.014 95);
|
|
|
|
--ks-champagne: oklch(18% 0.02 95);
|
|
--ks-text: oklch(25% 0.018 95);
|
|
--ks-text-muted: oklch(45% 0.015 95);
|
|
--ks-text-faint: oklch(55% 0.012 95);
|
|
--ks-rule: oklch(25% 0.02 95 / 0.12);
|
|
|
|
--ks-code-fg: oklch(28% 0.02 95);
|
|
--ks-code-bg: var(--ks-graphite);
|
|
|
|
--ks-nav-active: var(--ks-patina-deep);
|
|
|
|
--accent: var(--ks-patina-deep);
|
|
--accent-hover: var(--ks-patina);
|
|
--accent-wash: oklch(49% 0.08 188 / 0.08);
|
|
--accent-line: oklch(49% 0.08 188 / 0.3);
|
|
--panel-bg: oklch(99% 0.008 95);
|
|
--shadow-color: oklch(25% 0.02 95 / 0.16);
|
|
|
|
--viz-ink: oklch(25% 0.018 95);
|
|
--viz-mist: oklch(25% 0.02 95 / 0.24);
|
|
--viz-accent: var(--ks-patina-deep);
|
|
}
|
|
|
|
/* ============================================================
|
|
Base
|
|
============================================================ */
|
|
* { box-sizing: border-box; }
|
|
|
|
html, body { margin: 0; padding: 0; }
|
|
|
|
body {
|
|
font-family: var(--ks-font);
|
|
font-size: 1rem;
|
|
line-height: 1.6;
|
|
color: var(--ks-text);
|
|
background: linear-gradient(180deg, var(--ks-lacquer), var(--ks-lacquer-deep));
|
|
min-height: 100svh;
|
|
-webkit-font-smoothing: antialiased;
|
|
}
|
|
|
|
body.is-locked { overflow: hidden; }
|
|
|
|
.visually-hidden {
|
|
position: absolute;
|
|
width: 1px; height: 1px;
|
|
padding: 0; margin: -1px;
|
|
overflow: hidden;
|
|
clip: rect(0, 0, 0, 0);
|
|
border: 0;
|
|
}
|
|
|
|
code {
|
|
font-family: var(--ks-mono);
|
|
font-size: 0.82em;
|
|
color: var(--ks-code-fg);
|
|
background: var(--ks-code-bg);
|
|
border-radius: 3px;
|
|
padding: 0.14em 0.4em;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* ============================================================
|
|
Header — kinpaku chrome, every link opens the live site
|
|
============================================================ */
|
|
/* Exact port of the site's header: sub-pages.css base + kinpaku-kit.css
|
|
`.kinpaku-chrome` chrome + light-mode.css overrides, with the site's
|
|
markup (Header.astro). Selectors kept 1:1 so both themes match. */
|
|
|
|
.site-header {
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 100;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 24px;
|
|
padding: 28px clamp(22px, 4vw, 56px);
|
|
background: linear-gradient(180deg, oklch(5% 0.004 95 / 0.92), oklch(5% 0.004 95 / 0.68));
|
|
border-bottom: 1px solid oklch(78% 0.12 82 / 0.18);
|
|
-webkit-backdrop-filter: blur(18px) saturate(1.25);
|
|
backdrop-filter: blur(18px) saturate(1.25);
|
|
}
|
|
|
|
html.light .site-header {
|
|
background: linear-gradient(180deg, oklch(99% 0.008 95 / 0.96), oklch(97% 0.012 95 / 0.88));
|
|
border-bottom-color: var(--ks-rule);
|
|
}
|
|
|
|
.site-header a,
|
|
.site-header a:hover {
|
|
text-decoration: none;
|
|
}
|
|
|
|
.site-header-brand {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
color: var(--ks-kinpaku);
|
|
font-family: var(--ks-font);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
html.light .site-header-brand,
|
|
html.light .site-header-brand-logo,
|
|
html.light .site-header-brand-name {
|
|
color: var(--ks-champagne);
|
|
}
|
|
|
|
.site-header-brand-logo {
|
|
width: 38px;
|
|
height: 38px;
|
|
display: grid;
|
|
place-items: center;
|
|
color: var(--ks-kinpaku);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.site-header-brand-logo svg {
|
|
width: 32px;
|
|
height: 32px;
|
|
display: block;
|
|
fill: currentColor;
|
|
}
|
|
|
|
.site-header-brand-name {
|
|
display: inline-block;
|
|
color: var(--ks-kinpaku);
|
|
font-family: var(--ks-font-display);
|
|
font-weight: 400;
|
|
font-size: 1.3rem;
|
|
letter-spacing: 0.15em;
|
|
text-transform: uppercase;
|
|
line-height: 1;
|
|
-webkit-font-smoothing: auto;
|
|
-moz-osx-font-smoothing: auto;
|
|
}
|
|
|
|
.site-header-right {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: clamp(12px, 2vw, 24px);
|
|
min-width: 0;
|
|
}
|
|
|
|
.site-header-nav {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: clamp(22px, 3vw, 48px);
|
|
font-family: var(--ks-font);
|
|
font-size: 0.95rem;
|
|
font-weight: 400;
|
|
min-width: 0;
|
|
}
|
|
|
|
.site-header-nav a {
|
|
position: relative;
|
|
display: inline-block;
|
|
white-space: nowrap;
|
|
color: var(--ks-champagne);
|
|
padding: 4px 0;
|
|
transition: color 160ms var(--ks-ease);
|
|
}
|
|
|
|
.site-header-nav a:hover {
|
|
color: var(--ks-kinpaku);
|
|
}
|
|
|
|
html.light .site-header-nav a:hover {
|
|
color: var(--ks-nav-active);
|
|
}
|
|
|
|
/* Theme toggle — hairline chip, icon keyed on data-theme-pref. */
|
|
.theme-toggle {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 36px;
|
|
height: 36px;
|
|
min-height: 36px;
|
|
padding: 0;
|
|
border-radius: 999px;
|
|
border: 1px solid oklch(78% 0.12 82 / 0.28);
|
|
background: transparent;
|
|
color: var(--ks-champagne);
|
|
cursor: pointer;
|
|
flex: none;
|
|
box-sizing: border-box;
|
|
transition: color 160ms var(--ks-ease), border-color 160ms var(--ks-ease), background 160ms var(--ks-ease);
|
|
}
|
|
|
|
.theme-toggle:hover {
|
|
color: var(--ks-kinpaku);
|
|
border-color: var(--ks-kinpaku-deep);
|
|
background: oklch(77% 0.13 82 / 0.08);
|
|
}
|
|
|
|
html.light .theme-toggle {
|
|
border-color: var(--ks-rule);
|
|
color: var(--ks-text-muted);
|
|
}
|
|
|
|
html.light .theme-toggle:hover {
|
|
color: var(--ks-kinpaku);
|
|
border-color: var(--ks-kinpaku-deep);
|
|
background: oklch(77% 0.13 82 / 0.10);
|
|
}
|
|
|
|
.theme-toggle svg {
|
|
width: 18px;
|
|
height: 18px;
|
|
display: block;
|
|
}
|
|
|
|
.theme-toggle .theme-toggle-icon--dark {
|
|
transform: translate(-1.5px, 1.5px);
|
|
}
|
|
|
|
.theme-toggle .theme-toggle-icon--auto,
|
|
.theme-toggle .theme-toggle-icon--light,
|
|
.theme-toggle .theme-toggle-icon--dark {
|
|
display: none;
|
|
}
|
|
|
|
html[data-theme-pref="auto"] .theme-toggle .theme-toggle-icon--auto,
|
|
html[data-theme-pref="light"] .theme-toggle .theme-toggle-icon--light,
|
|
html[data-theme-pref="dark"] .theme-toggle .theme-toggle-icon--dark {
|
|
display: block;
|
|
}
|
|
|
|
/* Hamburger toggle — hidden on desktop, shown on mobile. */
|
|
.site-header-menu {
|
|
display: none;
|
|
width: 36px;
|
|
height: 36px;
|
|
background: transparent;
|
|
border: 1px solid oklch(78% 0.12 82 / 0.32);
|
|
border-radius: 4px;
|
|
padding: 0;
|
|
cursor: pointer;
|
|
align-items: center;
|
|
justify-content: center;
|
|
flex-direction: column;
|
|
gap: 6px;
|
|
flex: none;
|
|
margin-left: auto;
|
|
transition: border-color 160ms ease, background 160ms ease;
|
|
}
|
|
|
|
.site-header-menu:hover {
|
|
border-color: oklch(78% 0.12 82 / 0.55);
|
|
background: oklch(78% 0.12 82 / 0.06);
|
|
}
|
|
|
|
html.light .site-header-menu {
|
|
border-color: var(--ks-rule);
|
|
color: var(--ks-text);
|
|
}
|
|
|
|
html.light .site-header-menu:hover {
|
|
border-color: var(--ks-kinpaku-deep);
|
|
background: oklch(77% 0.13 82 / 0.08);
|
|
}
|
|
|
|
.site-header-menu-line {
|
|
display: block;
|
|
width: 16px;
|
|
height: 1.5px;
|
|
background: var(--ks-kinpaku);
|
|
transition: transform 200ms ease, opacity 200ms ease;
|
|
}
|
|
|
|
html.light .site-header-menu-line {
|
|
background: var(--ks-text);
|
|
}
|
|
|
|
[data-site-header][data-nav-open="true"] .site-header-menu-line:nth-child(1) {
|
|
transform: translateY(3.75px) rotate(45deg);
|
|
}
|
|
|
|
[data-site-header][data-nav-open="true"] .site-header-menu-line:nth-child(2) {
|
|
transform: translateY(-3.75px) rotate(-45deg);
|
|
}
|
|
|
|
/* Mobile nav drawer: the right cluster collapses below the header. */
|
|
@media (max-width: 760px) {
|
|
.site-header {
|
|
flex-wrap: wrap;
|
|
padding: 16px clamp(16px, 4vw, 24px);
|
|
row-gap: 12px;
|
|
}
|
|
|
|
.site-header-menu {
|
|
display: inline-flex;
|
|
}
|
|
|
|
[data-site-header] .site-header-right {
|
|
order: 3;
|
|
flex-basis: 100%;
|
|
display: none;
|
|
grid-template-columns: max-content 1fr;
|
|
column-gap: 10px;
|
|
flex-direction: column;
|
|
align-items: stretch;
|
|
row-gap: 0;
|
|
margin: 12px 0 -16px;
|
|
padding: 6px;
|
|
background: oklch(4% 0.004 95);
|
|
border-radius: 4px;
|
|
border-top: 1px solid oklch(78% 0.12 82 / 0.18);
|
|
}
|
|
|
|
html.light [data-site-header] .site-header-right {
|
|
background: var(--ks-lacquer-raised);
|
|
border-top-color: var(--ks-rule);
|
|
}
|
|
|
|
[data-site-header][data-nav-open="true"] .site-header-right {
|
|
display: grid;
|
|
}
|
|
|
|
.site-header-nav {
|
|
grid-column: 1 / -1;
|
|
flex-direction: column;
|
|
align-items: stretch;
|
|
gap: 0;
|
|
font-size: 1rem;
|
|
overflow: visible;
|
|
}
|
|
|
|
[data-site-header] .site-header-nav a {
|
|
display: block;
|
|
padding: 14px clamp(16px, 4vw, 24px);
|
|
border-bottom: 1px solid oklch(78% 0.12 82 / 0.06);
|
|
}
|
|
|
|
html.light [data-site-header] .site-header-nav a {
|
|
border-bottom-color: var(--ks-rule);
|
|
}
|
|
|
|
[data-site-header] .site-header-nav a:last-child {
|
|
border-bottom: 0;
|
|
}
|
|
|
|
.theme-toggle {
|
|
grid-column: 1;
|
|
margin: 12px 0 6px;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 480px) {
|
|
.site-header-brand-name {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
/* ============================================================
|
|
Mosaic landing
|
|
============================================================ */
|
|
.dcx-shell {
|
|
--dcx-gap: clamp(10px, 0.9vw, 14px);
|
|
padding: var(--dcx-gap) clamp(14px, 2vw, 26px) clamp(16px, 2.2vw, 28px);
|
|
}
|
|
|
|
.dcx-grid {
|
|
position: relative;
|
|
height: calc(100svh - 130px);
|
|
min-height: 560px;
|
|
display: grid;
|
|
grid-template-columns: repeat(18, minmax(0, 1fr));
|
|
grid-template-rows: repeat(12, minmax(0, 1fr));
|
|
gap: var(--dcx-gap);
|
|
}
|
|
|
|
.dcx-tile--product { grid-column: 1 / 5; grid-row: 1 / 7; --vz-delay: -1.1s; }
|
|
.dcx-tile--brand { grid-column: 5 / 11; grid-row: 1 / 6; --vz-delay: -1.9s; }
|
|
.dcx-tile--audience { grid-column: 11 / 15; grid-row: 1 / 7; --vz-delay: -0.4s; }
|
|
.dcx-tile--typography { grid-column: 15 / 19; grid-row: 1 / 6; --vz-delay: -2.6s; }
|
|
.dcx-tile--color { grid-column: 5 / 10; grid-row: 6 / 13; --vz-delay: -0.7s; }
|
|
.dcx-tile--iconography { grid-column: 1 / 5; grid-row: 7 / 13; --vz-delay: -1.3s; }
|
|
.dcx-tile--interface { grid-column: 10 / 15; grid-row: 7 / 13; --vz-delay: -1.6s; }
|
|
.dcx-tile--material { grid-column: 15 / 19; grid-row: 6 / 13; --vz-delay: -3.2s; }
|
|
|
|
.dcx-tile {
|
|
position: relative;
|
|
display: grid;
|
|
grid-template-rows: auto minmax(0, 1fr);
|
|
gap: clamp(12px, 1.6vw, 24px);
|
|
min-width: 0;
|
|
min-height: 0;
|
|
padding: clamp(18px, 2.2vw, 36px);
|
|
background: var(--panel-bg);
|
|
border: 1px solid var(--ks-rule);
|
|
border-radius: 8px;
|
|
overflow: hidden;
|
|
color: var(--ks-champagne);
|
|
cursor: pointer;
|
|
text-align: left;
|
|
isolation: isolate;
|
|
container-type: inline-size;
|
|
font-family: inherit;
|
|
transition:
|
|
transform 220ms var(--ks-ease),
|
|
border-color 220ms var(--ks-ease),
|
|
background 220ms var(--ks-ease);
|
|
}
|
|
|
|
/* Hover follows the kit's pagination treatment: the surface stays put,
|
|
only the border (and title) take the accent. */
|
|
.dcx-tile:hover,
|
|
.dcx-tile:focus-visible {
|
|
transform: translateY(-2px);
|
|
border-color: var(--accent);
|
|
outline: none;
|
|
}
|
|
|
|
.dcx-tile:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
|
|
|
|
/* Staggered reveal on load — the homepage foundation-grid treatment. */
|
|
.dcx-tile[data-reveal],
|
|
.dcx-badge[data-reveal] {
|
|
opacity: 0;
|
|
transform: translateY(30px);
|
|
transition:
|
|
opacity 0.8s var(--ks-ease),
|
|
transform 0.8s var(--ks-ease);
|
|
transition-delay: var(--reveal-delay, 0s);
|
|
}
|
|
|
|
.dcx-badge[data-reveal] { transform: translateY(0) scale(0.6); }
|
|
|
|
.dcx-tile[data-reveal].revealed {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
/* Hand hover transforms back to the fast transition once revealed. */
|
|
transition:
|
|
transform 220ms var(--ks-ease),
|
|
border-color 220ms var(--ks-ease),
|
|
background 220ms var(--ks-ease),
|
|
opacity 0.8s var(--ks-ease);
|
|
transition-delay: 0s;
|
|
}
|
|
|
|
.dcx-badge[data-reveal].revealed {
|
|
opacity: 1;
|
|
transform: translateY(0) scale(1);
|
|
}
|
|
|
|
.dcx-tile-title {
|
|
position: relative;
|
|
z-index: 2;
|
|
margin: 0;
|
|
font-family: var(--ks-font-display);
|
|
font-size: clamp(2.3rem, 4.8vw, 4.4rem);
|
|
font-weight: 100;
|
|
line-height: 1.02;
|
|
letter-spacing: 0.01em;
|
|
color: inherit;
|
|
text-wrap: balance;
|
|
transition: color 220ms var(--ks-ease);
|
|
}
|
|
|
|
.dcx-tile:hover .dcx-tile-title,
|
|
.dcx-tile:focus-visible .dcx-tile-title { color: var(--accent); }
|
|
|
|
.dcx-tile--typography .dcx-tile-title,
|
|
.dcx-tile--iconography .dcx-tile-title {
|
|
font-size: min(clamp(2.3rem, 4.8vw, 4.4rem), 20cqw);
|
|
}
|
|
|
|
/* Center mark on the gutter seam — the bare glyph in kinpaku gold,
|
|
inset by the same gap the mosaic tiles use. The padding ring is
|
|
painted page-background so it reads as seam, not as a frame. */
|
|
.dcx-badge {
|
|
position: relative;
|
|
z-index: 0;
|
|
grid-column: 10 / 11;
|
|
grid-row: 6 / 7;
|
|
width: clamp(82px, 7vw, 118px);
|
|
height: clamp(82px, 7vw, 118px);
|
|
display: grid;
|
|
place-items: center;
|
|
align-self: center;
|
|
justify-self: center;
|
|
padding: var(--dcx-gap);
|
|
background: var(--ks-lacquer);
|
|
/* glyph corner radius (~9% of the box) plus the gap, so the ring hugs the mark */
|
|
border-radius: calc(9% + var(--dcx-gap));
|
|
color: var(--ks-kinpaku);
|
|
pointer-events: none;
|
|
}
|
|
|
|
.dcx-badge svg { width: 100%; height: 100%; display: block; fill: currentColor; }
|
|
|
|
/* ============================================================
|
|
Tile vignettes — foundation-card animation system.
|
|
The title renders first; the vignette sits below it and scales
|
|
to fill whatever space the tile has left.
|
|
============================================================ */
|
|
.dcx-tile-title { order: 1; }
|
|
|
|
.dcx-tile-viz {
|
|
order: 2;
|
|
min-width: 0;
|
|
min-height: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: var(--viz-ink);
|
|
}
|
|
|
|
.dcx-viz-svg {
|
|
width: 100%;
|
|
height: 100%;
|
|
max-width: 100%;
|
|
max-height: 100%;
|
|
}
|
|
|
|
/* Every vignette stroke uses vector-effect: non-scaling-stroke, so the
|
|
line weight is a fixed pixel value no matter how large the tile lets
|
|
the SVG scale. One thinness across all eight tiles. */
|
|
.dcx-viz-svg .vz-stroke { stroke-width: 3.5px; }
|
|
.dcx-viz-svg .vz-ghost { stroke-width: 2.5px; }
|
|
|
|
@media (max-width: 920px) {
|
|
.dcx-viz-svg .vz-stroke { stroke-width: 1.5px; }
|
|
.dcx-viz-svg .vz-ghost { stroke-width: 1px; }
|
|
}
|
|
|
|
/* Draw (Typography, Material, Brand, Audience figure).
|
|
Same draw-breathe / draw-in choreography as the impeccable homepage.
|
|
The homepage uses stroke-dasharray: 100 in user units, but with
|
|
vector-effect: non-scaling-stroke Chromium measures dashes in screen
|
|
pixels, so a fixed 100 chops scaled-up paths into segments. A script
|
|
sets --pl on each vignette SVG to "100 user units in screen px" and
|
|
the dash keyframes work from that, giving the exact site animation
|
|
at any tile size. */
|
|
.anim-draw {
|
|
stroke-dasharray: var(--pl, 999px);
|
|
stroke-dashoffset: var(--pl, 999px);
|
|
animation: draw-breathe 4s ease-in-out var(--vz-delay, 0s) infinite;
|
|
}
|
|
.dcx-tile:hover .anim-draw { animation: draw-in 0.8s var(--ks-ease) forwards; }
|
|
.anim-draw-delay {
|
|
stroke-dasharray: var(--pl, 999px);
|
|
stroke-dashoffset: var(--pl, 999px);
|
|
}
|
|
.dcx-tile:hover .anim-draw-delay { animation: draw-in 1s var(--ks-ease) 0.2s forwards; }
|
|
@keyframes draw-breathe {
|
|
0%, 100% { stroke-dashoffset: var(--pl, 999px); }
|
|
50% { stroke-dashoffset: calc(var(--pl, 999px) * 0.4); }
|
|
}
|
|
@keyframes draw-in {
|
|
from { stroke-dashoffset: var(--pl, 999px); }
|
|
to { stroke-dashoffset: 0px; }
|
|
}
|
|
|
|
/* Color venn (breathing pulse, spread on hover) */
|
|
.anim-move-x { animation: pulse-x 3s ease-in-out var(--vz-delay, 0s) infinite; }
|
|
.dcx-tile:hover .anim-move-x { animation: spread-x 0.6s ease-in-out forwards; }
|
|
.anim-move-x-opp { animation: pulse-x-opp 3s ease-in-out var(--vz-delay, 0s) infinite; }
|
|
.dcx-tile:hover .anim-move-x-opp { animation: spread-x-opp 0.6s ease-in-out forwards; }
|
|
.anim-fade-in { opacity: 0; transition: opacity 0.6s ease-in-out; }
|
|
.dcx-tile:hover .anim-fade-in { opacity: 1; }
|
|
@keyframes pulse-x { 0%, 100% { transform: translateX(0); } 50% { transform: translateX(1.5px); } }
|
|
@keyframes pulse-x-opp { 0%, 100% { transform: translateX(0); } 50% { transform: translateX(-1.5px); } }
|
|
@keyframes spread-x { from { transform: translateX(0); } to { transform: translateX(4px); } }
|
|
@keyframes spread-x-opp { from { transform: translateX(0); } to { transform: translateX(-4px); } }
|
|
|
|
/* Interface toggle (gentle drift, full snap on hover) */
|
|
.anim-toggle-move { animation: toggle-drift 3s ease-in-out var(--vz-delay, 0s) infinite; }
|
|
.dcx-tile:hover .anim-toggle-move { animation: toggle-snap 0.35s ease-in-out forwards; }
|
|
@keyframes toggle-drift { 0%, 100% { transform: translateX(0); } 50% { transform: translateX(2px); } }
|
|
@keyframes toggle-snap {
|
|
from { transform: translateX(0); fill: var(--viz-mist); }
|
|
to { transform: translateX(8px); fill: var(--viz-accent); }
|
|
}
|
|
|
|
/* Product pitch lines (cursor always blinks) */
|
|
.anim-blink { animation: blink-key 1s step-end var(--vz-delay, 0s) infinite; }
|
|
@keyframes blink-key { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }
|
|
|
|
/* Iconography glyph grid. At rest the four glyphs brighten one by one on
|
|
a shared 4.8s cycle (1.2s apart). Opacity only — scaling the glyphs
|
|
blurs the strokes into a halo and makes them balloon. Hovering kills
|
|
the cycle and lights all four with a small stagger. */
|
|
.anim-pop {
|
|
opacity: 0.35;
|
|
transition: opacity 0.35s var(--ks-ease);
|
|
animation: pop-glow 4.8s ease-in-out infinite;
|
|
}
|
|
.anim-pop-1 { transition-delay: 0s; animation-delay: calc(var(--vz-delay, 0s) + 0s); }
|
|
.anim-pop-2 { transition-delay: 0.07s; animation-delay: calc(var(--vz-delay, 0s) + 1.2s); }
|
|
.anim-pop-3 { transition-delay: 0.14s; animation-delay: calc(var(--vz-delay, 0s) + 2.4s); }
|
|
.anim-pop-4 { transition-delay: 0.21s; animation-delay: calc(var(--vz-delay, 0s) + 3.6s); }
|
|
.dcx-tile:hover .anim-pop { animation: none; opacity: 1; }
|
|
@keyframes pop-glow {
|
|
0%, 25%, 100% { opacity: 0.35; }
|
|
10% { opacity: 1; }
|
|
}
|
|
|
|
/* Audience second figure joins on hover */
|
|
.anim-join { opacity: 0.25; transition: opacity 0.5s var(--ks-ease); }
|
|
.dcx-tile:hover .anim-join { opacity: 1; }
|
|
|
|
/* ============================================================
|
|
Expander — tile-to-fullscreen morph
|
|
============================================================ */
|
|
.dcx-expander {
|
|
position: fixed;
|
|
z-index: 500;
|
|
overflow: hidden;
|
|
border-radius: 8px;
|
|
background: var(--ks-lacquer);
|
|
color: var(--ks-text);
|
|
box-shadow: 0 18px 46px var(--shadow-color);
|
|
transition:
|
|
top 520ms var(--ks-ease),
|
|
left 520ms var(--ks-ease),
|
|
width 520ms var(--ks-ease),
|
|
height 520ms var(--ks-ease),
|
|
border-radius 520ms var(--ks-ease);
|
|
}
|
|
|
|
.dcx-expander.is-full {
|
|
top: 0 !important;
|
|
left: 0 !important;
|
|
width: 100vw !important;
|
|
height: 100svh !important;
|
|
border-radius: 0;
|
|
}
|
|
|
|
.dcx-expander-inner {
|
|
width: 100%;
|
|
height: 100svh;
|
|
min-height: 0;
|
|
display: grid;
|
|
grid-template-columns: 264px minmax(0, 1fr);
|
|
opacity: 0;
|
|
transform: translateY(44px);
|
|
transition: opacity 420ms var(--ks-ease), transform 520ms var(--ks-ease);
|
|
transition-delay: 260ms;
|
|
}
|
|
|
|
.dcx-expander.is-ready .dcx-expander-inner { opacity: 1; transform: translateY(0); }
|
|
|
|
/* ============================================================
|
|
Expander sidebar — DocsSidebar visual language
|
|
============================================================ */
|
|
.dcx-sidebar {
|
|
min-height: 0;
|
|
overflow: hidden auto;
|
|
padding: 26px 22px 32px;
|
|
border-right: 1px solid var(--ks-rule);
|
|
background: var(--ks-lacquer-deep);
|
|
scrollbar-width: thin;
|
|
scrollbar-color: var(--ks-rule) transparent;
|
|
}
|
|
|
|
.dcx-sidebar-brand {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
margin: 2px 0 28px;
|
|
color: var(--ks-kinpaku);
|
|
text-decoration: none;
|
|
}
|
|
|
|
html.light .dcx-sidebar-brand { color: var(--ks-champagne); }
|
|
|
|
.dcx-sidebar-brand svg { width: 26px; height: 26px; fill: currentColor; flex-shrink: 0; }
|
|
|
|
.dcx-sidebar-brand-name {
|
|
font-family: var(--ks-font-display);
|
|
font-weight: 400;
|
|
font-size: 1.02rem;
|
|
letter-spacing: 0.15em;
|
|
text-transform: uppercase;
|
|
line-height: 1;
|
|
-webkit-font-smoothing: auto;
|
|
}
|
|
|
|
.dcx-nav-label {
|
|
display: block;
|
|
font-size: 0.72rem;
|
|
font-weight: 650;
|
|
letter-spacing: 0.14em;
|
|
text-transform: uppercase;
|
|
color: var(--accent);
|
|
margin-bottom: 10px;
|
|
padding-left: 14px;
|
|
}
|
|
|
|
.dcx-nav-list { list-style: none; padding: 0; margin: 0; }
|
|
.dcx-nav-list li { margin: 0; }
|
|
|
|
.dcx-nav-link {
|
|
display: block;
|
|
width: 100%;
|
|
padding: 5px 0 5px 12px;
|
|
border: 0;
|
|
border-left: 2px solid transparent;
|
|
background: transparent;
|
|
font-family: var(--ks-font);
|
|
font-size: 0.94rem;
|
|
font-weight: 400;
|
|
line-height: 1.5;
|
|
color: var(--ks-text);
|
|
text-align: left;
|
|
text-decoration: none;
|
|
cursor: pointer;
|
|
transition: color 160ms var(--ks-ease), border-color 160ms var(--ks-ease);
|
|
}
|
|
|
|
.dcx-nav-link:hover,
|
|
.dcx-nav-link:focus-visible { color: var(--accent-hover); outline: none; }
|
|
|
|
.dcx-nav-link[aria-current="page"] {
|
|
color: var(--accent);
|
|
font-weight: 600;
|
|
border-left-color: var(--accent);
|
|
}
|
|
|
|
.dcx-subnav { display: none; padding: 2px 0 8px 24px; }
|
|
|
|
.dcx-nav-list li.is-active .dcx-subnav {
|
|
display: grid;
|
|
gap: 2px;
|
|
animation: subnav-in 180ms var(--ks-ease) both;
|
|
}
|
|
|
|
@keyframes subnav-in {
|
|
from { opacity: 0; transform: translateY(-4px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
|
|
.dcx-sub-link {
|
|
display: block;
|
|
width: 100%;
|
|
padding: 3px 0;
|
|
border: 0;
|
|
background: transparent;
|
|
font-family: var(--ks-font);
|
|
font-size: 0.85rem;
|
|
color: var(--ks-text-muted);
|
|
text-align: left;
|
|
cursor: pointer;
|
|
transition: color 160ms var(--ks-ease);
|
|
}
|
|
|
|
.dcx-sub-link:hover,
|
|
.dcx-sub-link:focus-visible { color: var(--accent-hover); outline: none; }
|
|
|
|
/* ============================================================
|
|
Expander topbar + main
|
|
============================================================ */
|
|
.dcx-panel {
|
|
position: relative;
|
|
min-width: 0;
|
|
min-height: 0;
|
|
display: grid;
|
|
grid-template-rows: 64px minmax(0, 1fr);
|
|
}
|
|
|
|
.dcx-topbar {
|
|
min-width: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 16px;
|
|
padding: 0 clamp(22px, 4vw, 48px);
|
|
border-bottom: 1px solid var(--ks-rule);
|
|
}
|
|
|
|
.dcx-current {
|
|
min-width: 0;
|
|
font-family: var(--ks-mono);
|
|
font-size: 0.7rem;
|
|
letter-spacing: 0.18em;
|
|
text-transform: uppercase;
|
|
color: var(--ks-text-muted);
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.dcx-close {
|
|
flex-shrink: 0;
|
|
width: 40px;
|
|
height: 40px;
|
|
display: grid;
|
|
place-items: center;
|
|
border: 1px solid var(--ks-rule);
|
|
border-radius: 999px;
|
|
background: transparent;
|
|
color: var(--ks-text);
|
|
cursor: pointer;
|
|
transition:
|
|
color 160ms var(--ks-ease),
|
|
border-color 160ms var(--ks-ease),
|
|
transform 220ms var(--ks-ease);
|
|
}
|
|
|
|
.dcx-close:hover,
|
|
.dcx-close:focus-visible {
|
|
color: var(--accent);
|
|
border-color: var(--accent-line);
|
|
transform: rotate(90deg);
|
|
outline: none;
|
|
}
|
|
|
|
.dcx-close::before,
|
|
.dcx-close::after {
|
|
content: "";
|
|
grid-area: 1 / 1;
|
|
width: 16px;
|
|
height: 1.5px;
|
|
background: currentColor;
|
|
}
|
|
|
|
.dcx-close::before { transform: rotate(45deg); }
|
|
.dcx-close::after { transform: rotate(-45deg); }
|
|
|
|
.dcx-main {
|
|
min-height: 0;
|
|
overflow: auto;
|
|
scrollbar-width: thin;
|
|
scrollbar-color: var(--ks-rule) transparent;
|
|
}
|
|
|
|
.dcx-main:focus { outline: none; }
|
|
|
|
/* ============================================================
|
|
Article
|
|
============================================================ */
|
|
.dcx-article {
|
|
max-width: 880px;
|
|
margin: 0 auto;
|
|
padding: clamp(36px, 5vw, 64px) clamp(22px, 4vw, 56px) 110px;
|
|
}
|
|
|
|
.dcx-eyebrow {
|
|
display: block;
|
|
font-family: var(--ks-mono);
|
|
font-size: 0.7rem;
|
|
letter-spacing: 0.18em;
|
|
text-transform: uppercase;
|
|
color: var(--accent);
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.dcx-title {
|
|
margin: 0 0 18px;
|
|
font-family: var(--ks-font-display);
|
|
font-weight: 100;
|
|
font-size: clamp(3rem, 6vw, 5.2rem);
|
|
line-height: 1.02;
|
|
letter-spacing: -0.01em;
|
|
color: var(--ks-kinpaku);
|
|
text-wrap: balance;
|
|
}
|
|
|
|
html.light .dcx-title { color: var(--ks-champagne); }
|
|
|
|
.dcx-lede {
|
|
margin: 0;
|
|
max-width: 56ch;
|
|
font-size: 1.08rem;
|
|
line-height: 1.65;
|
|
color: var(--ks-text);
|
|
}
|
|
|
|
.dcx-block { margin-top: clamp(34px, 5vw, 52px); }
|
|
|
|
.dcx-block-label {
|
|
display: block;
|
|
font-family: var(--ks-mono);
|
|
font-size: 0.68rem;
|
|
letter-spacing: 0.16em;
|
|
text-transform: uppercase;
|
|
color: var(--ks-text-faint);
|
|
margin-bottom: 14px;
|
|
}
|
|
|
|
/* Definition rows: term + description. */
|
|
.dcx-defs { border-top: 1px solid var(--ks-rule); }
|
|
|
|
.dcx-def {
|
|
display: grid;
|
|
grid-template-columns: minmax(180px, 0.44fr) minmax(0, 1fr);
|
|
gap: 6px 26px;
|
|
align-items: baseline;
|
|
padding: 15px 0;
|
|
border-bottom: 1px solid var(--ks-rule);
|
|
}
|
|
|
|
.dcx-def dt {
|
|
margin: 0;
|
|
font-size: 0.98rem;
|
|
font-weight: 600;
|
|
line-height: 1.4;
|
|
color: var(--ks-champagne);
|
|
}
|
|
|
|
.dcx-def dd {
|
|
margin: 0;
|
|
font-size: 0.92rem;
|
|
line-height: 1.55;
|
|
color: var(--ks-text-muted);
|
|
}
|
|
|
|
/* Chips. */
|
|
.dcx-chips { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
|
|
.dcx-chip {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
padding: 6px 13px;
|
|
border: 1px solid var(--accent-line);
|
|
border-radius: 999px;
|
|
background: var(--accent-wash);
|
|
color: var(--accent-hover);
|
|
font-size: 0.85rem;
|
|
font-weight: 500;
|
|
}
|
|
|
|
html.light .dcx-chip { color: var(--ks-patina-deep); }
|
|
|
|
.dcx-chip--muted {
|
|
border-color: var(--ks-rule);
|
|
background: transparent;
|
|
color: var(--ks-text-muted);
|
|
}
|
|
|
|
/* Callout card (north star, named rules). */
|
|
.dcx-callout {
|
|
padding: clamp(20px, 3vw, 30px);
|
|
border: 1px solid var(--ks-rule);
|
|
border-radius: 10px;
|
|
background: var(--panel-bg);
|
|
}
|
|
|
|
.dcx-callout + .dcx-callout { margin-top: 12px; }
|
|
|
|
.dcx-callout-name {
|
|
margin: 0 0 8px;
|
|
font-size: 1.02rem;
|
|
font-weight: 600;
|
|
color: var(--ks-champagne);
|
|
}
|
|
|
|
.dcx-callout p:not(.dcx-callout-name) {
|
|
margin: 0;
|
|
font-size: 0.92rem;
|
|
line-height: 1.6;
|
|
color: var(--ks-text-muted);
|
|
}
|
|
|
|
.dcx-callout--accent { border-color: var(--accent-line); background: var(--accent-wash); }
|
|
|
|
/* Numbered principle list. */
|
|
.dcx-principles {
|
|
list-style: none;
|
|
counter-reset: principle;
|
|
margin: 0;
|
|
padding: 0;
|
|
border-top: 1px solid var(--ks-rule);
|
|
}
|
|
|
|
.dcx-principles li {
|
|
counter-increment: principle;
|
|
display: grid;
|
|
grid-template-columns: 44px minmax(0, 1fr);
|
|
gap: 20px;
|
|
align-items: baseline;
|
|
padding: 14px 0;
|
|
border-bottom: 1px solid var(--ks-rule);
|
|
font-size: 0.94rem;
|
|
line-height: 1.55;
|
|
color: var(--ks-text-muted);
|
|
}
|
|
|
|
.dcx-principles li::before {
|
|
content: counter(principle, decimal-leading-zero);
|
|
font-family: var(--ks-mono);
|
|
font-size: 0.78rem;
|
|
color: var(--accent);
|
|
}
|
|
|
|
.dcx-principles strong { color: var(--ks-champagne); font-weight: 600; }
|
|
|
|
/* Plain bullet list. */
|
|
.dcx-list {
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
display: grid;
|
|
gap: 9px;
|
|
}
|
|
|
|
.dcx-list li {
|
|
position: relative;
|
|
padding-left: 20px;
|
|
font-size: 0.94rem;
|
|
line-height: 1.55;
|
|
color: var(--ks-text-muted);
|
|
}
|
|
|
|
.dcx-list li::before {
|
|
content: "";
|
|
position: absolute;
|
|
left: 2px;
|
|
top: 0.62em;
|
|
width: 7px;
|
|
height: 1.5px;
|
|
background: var(--accent);
|
|
}
|
|
|
|
.dcx-list strong { color: var(--ks-champagne); font-weight: 600; }
|
|
|
|
/* Swatch fan (Color) — hover fans up, click copies. */
|
|
.dcx-fan {
|
|
position: relative;
|
|
height: clamp(150px, 16vw, 195px);
|
|
overflow: hidden;
|
|
isolation: isolate;
|
|
border-radius: 10px 10px 0 0;
|
|
}
|
|
|
|
.dcx-fan::after {
|
|
content: "";
|
|
position: absolute;
|
|
left: 0; right: 0; bottom: 0;
|
|
z-index: 30;
|
|
height: 1px;
|
|
background: var(--ks-rule);
|
|
pointer-events: none;
|
|
}
|
|
|
|
.dcx-fan-panel {
|
|
position: absolute;
|
|
inset-block: 0;
|
|
left: var(--panel-left);
|
|
z-index: var(--panel-z);
|
|
width: var(--panel-width, 40%);
|
|
border: 0;
|
|
border-radius: 22px 22px 0 0;
|
|
padding: 0;
|
|
background: var(--panel-swatch);
|
|
box-shadow: inset 0 0 0 1px oklch(78% 0 0 / 0.14);
|
|
color: var(--panel-ink);
|
|
cursor: copy;
|
|
text-align: left;
|
|
transform: translate3d(0, 58%, 0);
|
|
transform-origin: 50% 100%;
|
|
transition:
|
|
transform 740ms cubic-bezier(0.16, 1, 0.3, 1),
|
|
filter 460ms cubic-bezier(0.16, 1, 0.3, 1);
|
|
will-change: transform;
|
|
backface-visibility: hidden;
|
|
}
|
|
|
|
.dcx-fan.is-engaged .dcx-fan-panel { transform: translate3d(0, 54%, 0); }
|
|
.dcx-fan.is-engaged .dcx-fan-panel.is-neighbor { transform: translate3d(0, 48%, 0); }
|
|
|
|
.dcx-fan.is-engaged .dcx-fan-panel.is-active,
|
|
.dcx-fan-panel:focus-visible,
|
|
.dcx-fan-panel.is-copied {
|
|
outline: none;
|
|
filter: saturate(1.04);
|
|
transform: translate3d(0, 10%, 0) rotate(-0.48deg);
|
|
}
|
|
|
|
.dcx-fan-info {
|
|
position: absolute;
|
|
top: clamp(20px, 2.6vw, 34px);
|
|
left: clamp(20px, 2.6vw, 34px);
|
|
right: clamp(20px, 2.6vw, 34px);
|
|
display: flex;
|
|
align-items: baseline;
|
|
justify-content: space-between;
|
|
gap: 14px;
|
|
opacity: 0;
|
|
transform: translateY(10px);
|
|
transition: opacity 320ms var(--ks-ease), transform 420ms var(--ks-ease);
|
|
pointer-events: none;
|
|
}
|
|
|
|
.dcx-fan-panel.is-active .dcx-fan-info,
|
|
.dcx-fan-panel:focus-visible .dcx-fan-info,
|
|
.dcx-fan-panel.is-copied .dcx-fan-info { opacity: 1; transform: translateY(0); }
|
|
|
|
.dcx-fan-name {
|
|
margin: 0;
|
|
font-family: var(--ks-font);
|
|
font-size: clamp(0.95rem, 1.6vw, 1.3rem);
|
|
font-weight: 700;
|
|
line-height: 1.1;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.dcx-fan-value {
|
|
margin: 0;
|
|
flex: 0 0 auto;
|
|
font-family: var(--ks-mono);
|
|
font-size: clamp(0.6rem, 1vw, 0.72rem);
|
|
letter-spacing: 0.04em;
|
|
opacity: 0.82;
|
|
}
|
|
|
|
.dcx-fan-note {
|
|
margin: 10px 0 0;
|
|
font-size: 0.8rem;
|
|
color: var(--ks-text-faint);
|
|
}
|
|
|
|
/* Type specimen (Typography) — set in the project's real faces. */
|
|
.dcx-specimen {
|
|
display: grid;
|
|
gap: 18px;
|
|
padding: clamp(24px, 3.4vw, 40px);
|
|
border: 1px solid var(--ks-rule);
|
|
border-radius: 10px;
|
|
background: var(--panel-bg);
|
|
}
|
|
|
|
.dcx-specimen-display {
|
|
margin: 0;
|
|
font-family: "Shippori Mincho", "Hiragino Mincho ProN", "Yu Mincho", Georgia, serif;
|
|
font-weight: 400;
|
|
font-size: clamp(2.2rem, 4.6vw, 3.6rem);
|
|
line-height: 1.15;
|
|
color: var(--ks-champagne);
|
|
text-wrap: balance;
|
|
}
|
|
|
|
.dcx-specimen-body {
|
|
margin: 0;
|
|
max-width: 62ch;
|
|
font-family: "Source Sans 3", system-ui, sans-serif;
|
|
font-size: 1rem;
|
|
line-height: 1.6;
|
|
color: var(--ks-text-muted);
|
|
}
|
|
|
|
.dcx-specimen-label {
|
|
margin: 0;
|
|
font-family: "Source Sans 3", system-ui, sans-serif;
|
|
font-size: 0.875rem;
|
|
font-weight: 600;
|
|
letter-spacing: 0.1em;
|
|
text-transform: uppercase;
|
|
color: var(--ks-text-faint);
|
|
}
|
|
|
|
/* Radius samples (Material). */
|
|
.dcx-radius-row { display: flex; flex-wrap: wrap; gap: 18px; }
|
|
|
|
.dcx-radius-sample { display: grid; gap: 8px; justify-items: center; }
|
|
|
|
.dcx-radius-box {
|
|
width: 76px;
|
|
height: 76px;
|
|
border: 1.5px solid var(--accent-line);
|
|
background: var(--accent-wash);
|
|
}
|
|
|
|
.dcx-radius-caption {
|
|
font-family: var(--ks-mono);
|
|
font-size: 0.68rem;
|
|
color: var(--ks-text-muted);
|
|
}
|
|
|
|
/* Spacing scale bars (Material). */
|
|
.dcx-space-rows { display: grid; gap: 8px; }
|
|
|
|
.dcx-space-row {
|
|
display: grid;
|
|
grid-template-columns: 74px minmax(0, 1fr) 84px;
|
|
align-items: center;
|
|
gap: 14px;
|
|
}
|
|
|
|
.dcx-space-name,
|
|
.dcx-space-value {
|
|
font-family: var(--ks-mono);
|
|
font-size: 0.7rem;
|
|
color: var(--ks-text-muted);
|
|
}
|
|
|
|
.dcx-space-value { text-align: right; color: var(--ks-text-faint); }
|
|
|
|
.dcx-space-bar {
|
|
height: 12px;
|
|
width: var(--bar, 20%);
|
|
min-width: 6px;
|
|
border-radius: 3px;
|
|
background: var(--accent-wash);
|
|
border: 1px solid var(--accent-line);
|
|
}
|
|
|
|
/* Empty state (Iconography). */
|
|
.dcx-empty {
|
|
padding: clamp(26px, 4vw, 40px);
|
|
border: 1px dashed var(--ks-rule);
|
|
border-radius: 10px;
|
|
text-align: left;
|
|
}
|
|
|
|
.dcx-empty-title {
|
|
margin: 0 0 8px;
|
|
font-size: 1rem;
|
|
font-weight: 600;
|
|
color: var(--ks-champagne);
|
|
}
|
|
|
|
.dcx-empty p:not(.dcx-empty-title) {
|
|
margin: 0;
|
|
font-size: 0.92rem;
|
|
line-height: 1.6;
|
|
color: var(--ks-text-muted);
|
|
}
|
|
|
|
/* ============================================================
|
|
Responsive fallback
|
|
============================================================ */
|
|
@media (max-width: 920px) {
|
|
.dcx-shell { padding: 10px; }
|
|
|
|
.dcx-grid {
|
|
height: auto;
|
|
min-height: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
}
|
|
|
|
.dcx-tile {
|
|
grid-column: auto;
|
|
grid-row: auto;
|
|
grid-template-columns: minmax(0, 1fr) auto;
|
|
grid-template-rows: auto;
|
|
align-items: center;
|
|
align-content: center;
|
|
gap: 16px;
|
|
min-height: clamp(72px, 9svh, 104px);
|
|
padding: 16px 20px;
|
|
}
|
|
|
|
/* Compact row: title left, small vignette right. */
|
|
.dcx-tile-viz { width: 44px; height: 44px; }
|
|
|
|
.dcx-tile-title,
|
|
.dcx-tile--typography .dcx-tile-title,
|
|
.dcx-tile--iconography .dcx-tile-title {
|
|
font-size: clamp(1.9rem, 8vw, 2.6rem);
|
|
}
|
|
|
|
.dcx-tile--product { order: 1; }
|
|
.dcx-tile--audience { order: 2; }
|
|
.dcx-tile--brand { order: 3; }
|
|
.dcx-tile--color { order: 4; }
|
|
.dcx-tile--typography { order: 5; }
|
|
.dcx-tile--iconography { order: 6; }
|
|
.dcx-tile--material { order: 7; }
|
|
.dcx-tile--interface { order: 8; }
|
|
|
|
.dcx-badge { display: none; }
|
|
|
|
.dcx-expander-inner {
|
|
grid-template-columns: 1fr;
|
|
grid-template-rows: auto minmax(0, 1fr);
|
|
}
|
|
|
|
.dcx-sidebar {
|
|
display: grid;
|
|
grid-template-columns: auto minmax(0, 1fr);
|
|
align-items: center;
|
|
gap: 12px;
|
|
padding: 10px 12px;
|
|
border-right: 0;
|
|
border-bottom: 1px solid var(--ks-rule);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.dcx-sidebar-brand { margin: 0; }
|
|
.dcx-sidebar-brand-name { display: none; }
|
|
.dcx-nav-label { display: none; }
|
|
|
|
.dcx-nav { min-width: 0; overflow-x: auto; scrollbar-width: none; }
|
|
.dcx-nav::-webkit-scrollbar { display: none; }
|
|
.dcx-nav-list { display: flex; gap: 4px; }
|
|
|
|
.dcx-nav-link {
|
|
width: auto;
|
|
padding: 8px 10px;
|
|
border-left: 0;
|
|
border-bottom: 2px solid transparent;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.dcx-nav-link[aria-current="page"] {
|
|
border-left-color: transparent;
|
|
border-bottom-color: var(--accent);
|
|
}
|
|
|
|
.dcx-nav-list li.is-active .dcx-subnav { display: none; }
|
|
|
|
.dcx-panel { grid-template-rows: 56px minmax(0, 1fr); }
|
|
.dcx-topbar { padding: 0 16px; }
|
|
.dcx-article { padding: 30px 18px 84px; }
|
|
|
|
.dcx-def { grid-template-columns: 1fr; gap: 4px; }
|
|
|
|
.dcx-fan { height: 210px; }
|
|
|
|
.dcx-fan-info {
|
|
top: 18px; left: 16px; right: 16px;
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
gap: 4px;
|
|
}
|
|
|
|
.dcx-space-row { grid-template-columns: 58px minmax(0, 1fr) 70px; }
|
|
}
|
|
|
|
/* ============================================================
|
|
Reduced motion
|
|
============================================================ */
|
|
@media (prefers-reduced-motion: reduce) {
|
|
*, *::before, *::after {
|
|
animation-duration: 0.01ms !important;
|
|
animation-iteration-count: 1 !important;
|
|
transition-duration: 0.01ms !important;
|
|
transition-delay: 0ms !important;
|
|
}
|
|
|
|
.dcx-tile[data-reveal],
|
|
.dcx-badge[data-reveal] {
|
|
opacity: 1;
|
|
transform: none;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<header class="site-header" data-site-header data-nav-open="false">
|
|
<a href="https://impeccable.style/" class="site-header-brand" target="_blank" rel="noopener" aria-label="Impeccable home (opens the live site)">
|
|
<span class="site-header-brand-logo" aria-hidden="true">
|
|
<svg viewBox="0 0 24 24" fill="currentColor">
|
|
<path d="M5 2.5 L13.5 2.5 L5.5 21.5 L5 21.5 Q2.5 21.5 2.5 19 L2.5 5 Q2.5 2.5 5 2.5 Z"/>
|
|
<path d="M16.5 2.5 L19 2.5 Q21.5 2.5 21.5 5 L21.5 19 Q21.5 21.5 19 21.5 L8.5 21.5 Z"/>
|
|
</svg>
|
|
</span>
|
|
<span class="site-header-brand-name">Impeccable</span>
|
|
</a>
|
|
<button
|
|
type="button"
|
|
class="site-header-menu"
|
|
aria-label="Toggle navigation"
|
|
aria-expanded="false"
|
|
aria-controls="site-header-nav-panel"
|
|
data-site-header-menu
|
|
>
|
|
<span class="site-header-menu-line" aria-hidden="true"></span>
|
|
<span class="site-header-menu-line" aria-hidden="true"></span>
|
|
</button>
|
|
<div class="site-header-right" id="site-header-nav-panel">
|
|
<nav class="site-header-nav" aria-label="Primary">
|
|
<a href="https://impeccable.style/designing" data-nav="designing" target="_blank" rel="noopener">Designing</a>
|
|
<a href="https://impeccable.style/docs" data-nav="docs" target="_blank" rel="noopener">Docs</a>
|
|
<a href="https://impeccable.style/slop" data-nav="slop" target="_blank" rel="noopener">Slop</a>
|
|
<a href="https://impeccable.style/live-mode" data-nav="live" target="_blank" rel="noopener">Live</a>
|
|
</nav>
|
|
<button
|
|
type="button"
|
|
class="theme-toggle"
|
|
data-theme-toggle
|
|
aria-label="Theme: auto, matching your system. Click to switch to light mode."
|
|
title="Theme: auto (matches system)"
|
|
>
|
|
<svg class="theme-toggle-icon--auto" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true">
|
|
<circle cx="12" cy="12" r="9"/>
|
|
<path d="M12 3a9 9 0 0 1 0 18z" fill="currentColor" stroke="none"/>
|
|
</svg>
|
|
<svg class="theme-toggle-icon--light" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true">
|
|
<circle cx="12" cy="12" r="4.5"/>
|
|
<path d="M12 2v2M12 20v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M2 12h2M20 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/>
|
|
</svg>
|
|
<svg class="theme-toggle-icon--dark" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" aria-hidden="true">
|
|
<path d="M21 14.5A8.5 8.5 0 0 1 9.5 3a7 7 0 1 0 11.5 11.5z"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</header>
|
|
|
|
<main>
|
|
<h1 class="visually-hidden">Design context — Hanazono Atelier</h1>
|
|
|
|
<section class="dcx-shell" aria-label="Design context categories">
|
|
<div class="dcx-grid">
|
|
|
|
<div class="dcx-badge" data-reveal style="--reveal-delay: 0.45s" aria-hidden="true">
|
|
<svg viewBox="0 0 24 24" focusable="false">
|
|
<path d="M5 2.5 L13.5 2.5 L5.5 21.5 L5 21.5 Q2.5 21.5 2.5 19 L2.5 5 Q2.5 2.5 5 2.5 Z"/>
|
|
<path d="M16.5 2.5 L19 2.5 Q21.5 2.5 21.5 5 L21.5 19 Q21.5 21.5 19 21.5 L8.5 21.5 Z"/>
|
|
</svg>
|
|
</div>
|
|
|
|
<button class="dcx-tile dcx-tile--audience" type="button" data-category="audience" data-name="Audience" data-reveal style="--reveal-delay: 0s" aria-label="Open Audience">
|
|
<span class="dcx-tile-viz" aria-hidden="true">
|
|
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg" class="dcx-viz-svg">
|
|
<circle cx="15" cy="14" r="4" stroke="var(--viz-ink)" class="vz-stroke" vector-effect="non-scaling-stroke"/>
|
|
<path d="M7 30c0-5 3.6-8 8-8s8 3 8 8" stroke="var(--viz-ink)" stroke-linecap="round" class="vz-stroke" vector-effect="non-scaling-stroke"/>
|
|
<g class="anim-join">
|
|
<circle cx="28" cy="16" r="3.4" stroke="var(--viz-accent)" class="vz-stroke" vector-effect="non-scaling-stroke"/>
|
|
<path d="M21.5 30c0-4.4 3-7 6.5-7s6.5 2.6 6.5 7" stroke="var(--viz-accent)" stroke-linecap="round" class="anim-draw vz-stroke" vector-effect="non-scaling-stroke"/>
|
|
</g>
|
|
</svg>
|
|
</span>
|
|
<h2 class="dcx-tile-title">Audience</h2>
|
|
</button>
|
|
|
|
<button class="dcx-tile dcx-tile--product" type="button" data-category="product" data-name="Product" data-reveal style="--reveal-delay: 0.05s" aria-label="Open Product">
|
|
<span class="dcx-tile-viz" aria-hidden="true">
|
|
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg" class="dcx-viz-svg">
|
|
<line x1="8" y1="13" x2="26" y2="13" stroke="var(--viz-ink)" stroke-linecap="round" class="vz-stroke" vector-effect="non-scaling-stroke"/>
|
|
<line x1="8" y1="19" x2="30" y2="19" stroke="var(--viz-mist)" stroke-linecap="round" class="vz-stroke" vector-effect="non-scaling-stroke"/>
|
|
<line x1="8" y1="25" x2="22" y2="25" stroke="var(--viz-accent)" stroke-linecap="round" class="vz-stroke" vector-effect="non-scaling-stroke"/>
|
|
<line x1="26" y1="23" x2="26" y2="27" stroke="var(--viz-accent)" class="anim-blink vz-stroke" vector-effect="non-scaling-stroke"/>
|
|
</svg>
|
|
</span>
|
|
<h2 class="dcx-tile-title">Product</h2>
|
|
</button>
|
|
|
|
<button class="dcx-tile dcx-tile--brand" type="button" data-category="brand" data-name="Brand" data-reveal style="--reveal-delay: 0.1s" aria-label="Open Brand">
|
|
<span class="dcx-tile-viz" aria-hidden="true">
|
|
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg" class="dcx-viz-svg">
|
|
<path d="M6 17 L34 17" stroke="var(--viz-mist)" class="vz-ghost" vector-effect="non-scaling-stroke"/>
|
|
<path d="M12 10 L16 17 L20 33 M28 10 L24 17 L20 33" stroke="var(--viz-mist)" stroke-linejoin="round" class="vz-ghost" vector-effect="non-scaling-stroke"/>
|
|
<path d="M12 10 L28 10 L34 17 L20 33 L6 17 Z" stroke="var(--viz-accent)" stroke-linejoin="round" class="anim-draw vz-stroke" vector-effect="non-scaling-stroke"/>
|
|
<circle cx="20" cy="13.5" r="1.4" fill="var(--viz-accent)" class="anim-fade-in"/>
|
|
</svg>
|
|
</span>
|
|
<h2 class="dcx-tile-title">Brand</h2>
|
|
</button>
|
|
|
|
<button class="dcx-tile dcx-tile--color" type="button" data-category="color" data-name="Color" data-reveal style="--reveal-delay: 0.15s" aria-label="Open Color">
|
|
<span class="dcx-tile-viz" aria-hidden="true">
|
|
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg" class="dcx-viz-svg">
|
|
<circle cx="16" cy="20" r="8" stroke="var(--viz-ink)" class="anim-move-x vz-stroke" vector-effect="non-scaling-stroke"/>
|
|
<circle cx="24" cy="20" r="8" stroke="var(--viz-accent)" class="anim-move-x-opp anim-join vz-stroke" vector-effect="non-scaling-stroke"/>
|
|
<path d="M20 14.5C21.5 16 22.5 18 22.5 20C22.5 22 21.5 24 20 25.5C18.5 24 17.5 22 17.5 20C17.5 18 18.5 16 20 14.5Z" fill="var(--viz-accent)" class="anim-fade-in"/>
|
|
</svg>
|
|
</span>
|
|
<h2 class="dcx-tile-title">Color</h2>
|
|
</button>
|
|
|
|
<button class="dcx-tile dcx-tile--typography" type="button" data-category="typography" data-name="Typography" data-reveal style="--reveal-delay: 0.2s" aria-label="Open Typography">
|
|
<span class="dcx-tile-viz" aria-hidden="true">
|
|
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg" class="dcx-viz-svg">
|
|
<path d="M10 30L20 10L30 30" stroke="var(--viz-mist)" stroke-linecap="round" stroke-linejoin="round" class="vz-ghost" vector-effect="non-scaling-stroke"/>
|
|
<path d="M10 30L20 10L30 30" stroke="var(--viz-accent)" stroke-linecap="round" stroke-linejoin="round" class="anim-draw vz-stroke" vector-effect="non-scaling-stroke"/>
|
|
<path d="M15 22H25" stroke="var(--viz-accent)" stroke-linecap="round" class="anim-draw-delay vz-stroke" vector-effect="non-scaling-stroke"/>
|
|
</svg>
|
|
</span>
|
|
<h2 class="dcx-tile-title">Typography</h2>
|
|
</button>
|
|
|
|
<button class="dcx-tile dcx-tile--iconography" type="button" data-category="iconography" data-name="Iconography" data-reveal style="--reveal-delay: 0.25s" aria-label="Open Iconography">
|
|
<span class="dcx-tile-viz" aria-hidden="true">
|
|
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg" class="dcx-viz-svg">
|
|
<circle cx="14" cy="14" r="4.5" stroke="var(--viz-ink)" class="anim-pop anim-pop-1 vz-stroke" vector-effect="non-scaling-stroke"/>
|
|
<rect x="22" y="9.5" width="9" height="9" rx="1.5" stroke="var(--viz-ink)" class="anim-pop anim-pop-2 vz-stroke" vector-effect="non-scaling-stroke"/>
|
|
<path d="M14 22.5L18.5 30.5H9.5L14 22.5Z" stroke="var(--viz-ink)" stroke-linejoin="round" class="anim-pop anim-pop-3 vz-stroke" vector-effect="non-scaling-stroke"/>
|
|
<path d="M26.5 22.5V30.5M22.5 26.5H30.5" stroke="var(--viz-accent)" stroke-linecap="round" class="anim-pop anim-pop-4 vz-stroke" vector-effect="non-scaling-stroke"/>
|
|
</svg>
|
|
</span>
|
|
<h2 class="dcx-tile-title">Iconography</h2>
|
|
</button>
|
|
|
|
<button class="dcx-tile dcx-tile--material" type="button" data-category="material" data-name="Material" data-reveal style="--reveal-delay: 0.3s" aria-label="Open Material">
|
|
<span class="dcx-tile-viz" aria-hidden="true">
|
|
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg" class="dcx-viz-svg">
|
|
<rect x="5" y="10" width="30" height="18.5" stroke="var(--viz-mist)" class="vz-ghost" vector-effect="non-scaling-stroke"/>
|
|
<line x1="23.5" y1="10" x2="23.5" y2="28.5" stroke="var(--viz-mist)" class="vz-ghost" vector-effect="non-scaling-stroke"/>
|
|
<line x1="23.5" y1="21.5" x2="35" y2="21.5" stroke="var(--viz-mist)" class="vz-ghost" vector-effect="non-scaling-stroke"/>
|
|
<path d="M5 28.5C5 18.28 13.28 10 23.5 10C29.85 10 35 15.15 35 21.5C35 25.42 31.82 28.5 27.9 28.5" fill="none" stroke="var(--viz-accent)" stroke-linecap="round" class="anim-draw vz-stroke" vector-effect="non-scaling-stroke"/>
|
|
</svg>
|
|
</span>
|
|
<h2 class="dcx-tile-title">Material</h2>
|
|
</button>
|
|
|
|
<button class="dcx-tile dcx-tile--interface" type="button" data-category="interface" data-name="Interface" data-reveal style="--reveal-delay: 0.35s" aria-label="Open Interface">
|
|
<span class="dcx-tile-viz" aria-hidden="true">
|
|
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg" class="dcx-viz-svg">
|
|
<rect x="10" y="14" width="20" height="12" rx="6" stroke="var(--viz-ink)" class="vz-stroke" vector-effect="non-scaling-stroke"/>
|
|
<circle cx="16" cy="20" r="4" fill="var(--viz-mist)" class="anim-toggle-move"/>
|
|
</svg>
|
|
</span>
|
|
<h2 class="dcx-tile-title">Interface</h2>
|
|
</button>
|
|
|
|
</div>
|
|
</section>
|
|
</main>
|
|
|
|
<!-- ==========================================================
|
|
Category articles — real data from the Hanazono Atelier
|
|
example project (PRODUCT.md + DESIGN.md), structured by the
|
|
per-category section lists in CONTENTS.md.
|
|
========================================================== -->
|
|
|
|
<template id="dcx-detail-audience">
|
|
<article class="dcx-article">
|
|
<header>
|
|
<span class="dcx-eyebrow">Design context · 01 / 08 · Hanazono Atelier</span>
|
|
<h2 class="dcx-title">Audience</h2>
|
|
<p class="dcx-lede">Who it is for, emotional state, needs, trust triggers.</p>
|
|
</header>
|
|
|
|
<div class="dcx-block" data-label="Who they are">
|
|
<span class="dcx-block-label">Who they are</span>
|
|
<dl class="dcx-defs">
|
|
<div class="dcx-def">
|
|
<dt>Primary (next 6 months)</dt>
|
|
<dd>Wedding planners sourcing ceremony florals for discerning clients. They arrive with a brief already formed and need proof of craft, a clear seasonal point of view, and a fast path to book a consultation before their client meeting.</dd>
|
|
</div>
|
|
<div class="dcx-def">
|
|
<dt>Secondary</dt>
|
|
<dd>Couples commissioning ceremony florals directly; corporate hospitality teams and office managers evaluating gifting or subscription programs.</dd>
|
|
</div>
|
|
</dl>
|
|
</div>
|
|
|
|
<div class="dcx-block" data-label="Emotional state">
|
|
<span class="dcx-block-label">Emotional state</span>
|
|
<div class="dcx-callout dcx-callout--accent">
|
|
<p class="dcx-callout-name">Quiet authority</p>
|
|
<p>All commissioners arrive with intent, not casual browsing. The landing experience should read as calm proof of craft rather than persuasion: one hero arrangement per viewport, copy that doesn't oversell.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="dcx-block" data-label="Needs">
|
|
<span class="dcx-block-label">Needs</span>
|
|
<ul class="dcx-list">
|
|
<li><strong>Proof of craft</strong> before they will put the atelier in front of a client.</li>
|
|
<li><strong>A clear seasonal point of view</strong> that distinguishes the atelier from directory florists.</li>
|
|
<li><strong>A fast path to book a consultation</strong>, often before their own client meeting.</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="dcx-block" data-label="Trust triggers">
|
|
<span class="dcx-block-label">Trust triggers</span>
|
|
<ul class="dcx-list">
|
|
<li><strong>Partner quotes</strong> from planners and past commissions.</li>
|
|
<li><strong>Seasonal POV</strong> expressed as a disciplined editorial stance, not a portfolio dump.</li>
|
|
<li><strong>Studio story</strong> that establishes the Tokyo atelier credential in person-referral conversations.</li>
|
|
</ul>
|
|
</div>
|
|
</article>
|
|
</template>
|
|
|
|
<template id="dcx-detail-product">
|
|
<article class="dcx-article">
|
|
<header>
|
|
<span class="dcx-eyebrow">Design context · 02 / 08 · Hanazono Atelier</span>
|
|
<h2 class="dcx-title">Product</h2>
|
|
<p class="dcx-lede">Purpose, differentiator, proof points, use cases, what must be clear first.</p>
|
|
</header>
|
|
|
|
<div class="dcx-block" data-label="Purpose">
|
|
<span class="dcx-block-label">Purpose</span>
|
|
<div class="dcx-callout">
|
|
<p class="dcx-callout-name">Hanazono Atelier <span class="dcx-chip dcx-chip--muted" style="margin-left:10px; font-size:0.72rem; padding:3px 10px;">register: brand</span></p>
|
|
<p>Marketing site for a boutique Tokyo flower atelier. Communicate craft, seasonal restraint, and three commission lanes. The visual experience sells the atelier.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="dcx-block" data-label="Conversion">
|
|
<span class="dcx-block-label">Conversion</span>
|
|
<dl class="dcx-defs">
|
|
<div class="dcx-def">
|
|
<dt>Primary conversion</dt>
|
|
<dd>Book a consultation. The booking path stays obvious without urgency gimmicks, price grids, or package upsell scaffolding.</dd>
|
|
</div>
|
|
<div class="dcx-def">
|
|
<dt>Success definition</dt>
|
|
<dd>8–12 qualified consultation requests per month from planners and corporate buyers combined; at least half convert to a paid commission within 90 days. Brand credibility for in-person referrals matters as much as direct bookings.</dd>
|
|
</div>
|
|
<div class="dcx-def">
|
|
<dt>What must be clear first</dt>
|
|
<dd>Proof before pitch: planners need credible signals (partner quotes, seasonal POV, studio story) before they will book.</dd>
|
|
</div>
|
|
</dl>
|
|
</div>
|
|
|
|
<div class="dcx-block" data-label="Use cases">
|
|
<span class="dcx-block-label">Use cases</span>
|
|
<dl class="dcx-defs">
|
|
<div class="dcx-def">
|
|
<dt>Weddings</dt>
|
|
<dd>Ceremony florals commissioned through planners or directly by couples; the flagship lane.</dd>
|
|
</div>
|
|
<div class="dcx-def">
|
|
<dt>Corporate gifting</dt>
|
|
<dd>Hospitality teams and office managers commissioning seasonal gifting programs.</dd>
|
|
</div>
|
|
<div class="dcx-def">
|
|
<dt>Subscriptions</dt>
|
|
<dd>Recurring arrangements for offices and hospitality spaces, evaluated by the same corporate buyers.</dd>
|
|
</div>
|
|
</dl>
|
|
</div>
|
|
</article>
|
|
</template>
|
|
|
|
<template id="dcx-detail-brand">
|
|
<article class="dcx-article">
|
|
<header>
|
|
<span class="dcx-eyebrow">Design context · 03 / 08 · Hanazono Atelier</span>
|
|
<h2 class="dcx-title">Brand</h2>
|
|
<p class="dcx-lede">Identity, voice, tone, cues, taste boundaries, anti-references.</p>
|
|
</header>
|
|
|
|
<div class="dcx-block" data-label="Creative North Star">
|
|
<span class="dcx-block-label">Creative North Star</span>
|
|
<div class="dcx-callout dcx-callout--accent">
|
|
<p class="dcx-callout-name">“The Specimen Gallery”</p>
|
|
<p>A commission-first visual system that treats each page like a museum craft gallery: one object per viewport, bilingual hierarchy, specimen metadata in mono, and captions that never compete with the work. Warm mechanical opinion shows up as disciplined spacing and label codes, not decoration.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="dcx-block" data-label="Personality & voice">
|
|
<span class="dcx-block-label">Personality & voice</span>
|
|
<div class="dcx-chips" style="margin-bottom:14px;">
|
|
<span class="dcx-chip">Warm</span>
|
|
<span class="dcx-chip">Mechanical</span>
|
|
<span class="dcx-chip">Opinionated</span>
|
|
</div>
|
|
<p style="margin:0; font-size:0.94rem; line-height:1.6; color:var(--ks-text-muted);">Seasonal craft with editorial restraint. Tokyo atelier, not Pinterest floristry.</p>
|
|
</div>
|
|
|
|
<div class="dcx-block" data-label="References">
|
|
<span class="dcx-block-label">References (specific steals)</span>
|
|
<dl class="dcx-defs">
|
|
<div class="dcx-def">
|
|
<dt>Studio Mondine</dt>
|
|
<dd>Commission-gallery pacing: one hero arrangement per viewport, no thumbnail clutter.</dd>
|
|
</div>
|
|
<div class="dcx-def">
|
|
<dt>Aesop</dt>
|
|
<dd>Product-page restraint: generous whitespace, one focal image, copy that doesn't oversell.</dd>
|
|
</div>
|
|
<div class="dcx-def">
|
|
<dt>Ikenobo Tokyo exhibitions</dt>
|
|
<dd>Seasonal asymmetry and disciplined negative space; not Instagram-perfect symmetry.</dd>
|
|
</div>
|
|
</dl>
|
|
</div>
|
|
|
|
<div class="dcx-block" data-label="Anti-references">
|
|
<span class="dcx-block-label">Anti-references</span>
|
|
<ul class="dcx-list">
|
|
<li><strong>The Knot / WeddingWire directory style</strong>: stock rose photos, price tables, “packages from $X” grids.</li>
|
|
<li><strong>Canva wedding mood-board aesthetic</strong>: blush-to-gold gradients, script fonts on every heading, floating petal PNG overlays.</li>
|
|
<li><strong>Squarespace “Flora” theme family</strong>: identical three-column service cards with leaf icons.</li>
|
|
<li><strong>Pinterest-pastel monoculture</strong>: identical card grids with icon + heading + blurb; italic display serif + mono labels on every section.</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="dcx-block" data-label="Design principles">
|
|
<span class="dcx-block-label">Design principles</span>
|
|
<ol class="dcx-principles">
|
|
<li><strong>One focal point per viewport</strong> — gallery pacing over thumbnail clutter; let a single arrangement or proof moment carry the screen.</li>
|
|
<li><strong>Restraint is the sell</strong> — generous whitespace, copy that doesn't oversell; Aesop-level discipline on every lane page.</li>
|
|
<li><strong>Seasonal asymmetry, not Instagram symmetry</strong> — compose like an exhibition, not a mood board; negative space is part of the craft.</li>
|
|
<li><strong>Proof before pitch</strong> — planners need credible signals before they will book.</li>
|
|
<li><strong>Earn the consultation</strong> — make the booking path obvious without urgency gimmicks.</li>
|
|
</ol>
|
|
</div>
|
|
</article>
|
|
</template>
|
|
|
|
<template id="dcx-detail-color">
|
|
<article class="dcx-article">
|
|
<header>
|
|
<span class="dcx-eyebrow">Design context · 04 / 08 · Hanazono Atelier</span>
|
|
<h2 class="dcx-title">Color</h2>
|
|
<p class="dcx-lede">Palette, roles, tints, strategy, copyable values.</p>
|
|
</header>
|
|
|
|
<div class="dcx-block" data-label="Palette">
|
|
<span class="dcx-block-label">Palette</span>
|
|
<div class="dcx-fan" role="group" aria-label="Hanazono palette, click a panel to copy its value">
|
|
<button class="dcx-fan-panel" type="button" data-copy-color="#faf6f0" data-color-name="Warm Cream"
|
|
style="--panel-left:0%; --panel-z:1; --panel-swatch:#faf6f0; --panel-ink:#2a2118;" aria-label="Copy Warm Cream #faf6f0">
|
|
<span class="dcx-fan-info"><span class="dcx-fan-name">Warm Cream</span><span class="dcx-fan-value">#faf6f0</span></span>
|
|
</button>
|
|
<button class="dcx-fan-panel" type="button" data-copy-color="#a66b88" data-color-name="Plum Muted"
|
|
style="--panel-left:20%; --panel-z:2; --panel-swatch:#a66b88; --panel-ink:#faf6f0;" aria-label="Copy Plum Muted #a66b88">
|
|
<span class="dcx-fan-info"><span class="dcx-fan-name">Plum Muted</span><span class="dcx-fan-value">#a66b88</span></span>
|
|
</button>
|
|
<button class="dcx-fan-panel" type="button" data-copy-color="#8b4a6b" data-color-name="Muted Plum"
|
|
style="--panel-left:40%; --panel-z:3; --panel-swatch:#8b4a6b; --panel-ink:#faf6f0;" aria-label="Copy Muted Plum #8b4a6b">
|
|
<span class="dcx-fan-info"><span class="dcx-fan-name">Muted Plum</span><span class="dcx-fan-value">#8b4a6b</span></span>
|
|
</button>
|
|
<button class="dcx-fan-panel" type="button" data-copy-color="#2a2118" data-color-name="Umber Ink"
|
|
style="--panel-left:60%; --panel-z:4; --panel-swatch:#2a2118; --panel-ink:#faf6f0;" aria-label="Copy Umber Ink #2a2118">
|
|
<span class="dcx-fan-info"><span class="dcx-fan-name">Umber Ink</span><span class="dcx-fan-value">#2a2118</span></span>
|
|
</button>
|
|
</div>
|
|
<p class="dcx-fan-note">From the <code>colors:</code> frontmatter of DESIGN.md. Hover to fan; click to copy the hex.</p>
|
|
</div>
|
|
|
|
<div class="dcx-block" data-label="Roles">
|
|
<span class="dcx-block-label">Roles</span>
|
|
<dl class="dcx-defs">
|
|
<div class="dcx-def">
|
|
<dt>Muted Plum <code>#8b4a6b</code></dt>
|
|
<dd>CTA fills, primary links, consultation path emphasis. Must pass 4.5:1 on cream for labels; darker hover state required.</dd>
|
|
</div>
|
|
<div class="dcx-def">
|
|
<dt>Deep Umber Ink <code>#2a2118</code></dt>
|
|
<dd>Headlines, primary body text, filled primary button ground. The authoritative voice, not pure black.</dd>
|
|
</div>
|
|
<div class="dcx-def">
|
|
<dt>Seasonal Moss</dt>
|
|
<dd>Lane labels, season codes, commission metadata accents. Marks the seasonal point of view without becoming a second CTA color. Exact token to be resolved during implementation.</dd>
|
|
</div>
|
|
<div class="dcx-def">
|
|
<dt>Warm Cream Ground <code>#faf6f0</code></dt>
|
|
<dd>Page background, exhibition hall surface. Not Pinterest-pastel; a true warm ground with controlled chroma.</dd>
|
|
</div>
|
|
<div class="dcx-def">
|
|
<dt>Caption Charcoal</dt>
|
|
<dd>Figcaptions, secondary metadata, mono label text. Subordinate to umber ink; never competes with display headlines.</dd>
|
|
</div>
|
|
</dl>
|
|
</div>
|
|
|
|
<div class="dcx-block" data-label="Named rules">
|
|
<span class="dcx-block-label">Named rules</span>
|
|
<div class="dcx-callout">
|
|
<p class="dcx-callout-name">The No-Wash Rule</p>
|
|
<p>Backgrounds stay flat cream or tonal ink fills. Gradient washes, blush-to-gold transitions, and floating PNG petal overlays are prohibited.</p>
|
|
</div>
|
|
<div class="dcx-callout">
|
|
<p class="dcx-callout-name">The Accent Rarity Rule</p>
|
|
<p>Plum is for conversion and proof-path emphasis, not eyebrow decoration on every section. Moss is for seasonal lane identity, not a second hero accent.</p>
|
|
</div>
|
|
</div>
|
|
</article>
|
|
</template>
|
|
|
|
<template id="dcx-detail-typography">
|
|
<article class="dcx-article">
|
|
<header>
|
|
<span class="dcx-eyebrow">Design context · 05 / 08 · Hanazono Atelier</span>
|
|
<h2 class="dcx-title">Typography</h2>
|
|
<p class="dcx-lede">Font families, hierarchy, font scale, readability rules.</p>
|
|
</header>
|
|
|
|
<div class="dcx-block" data-label="Type sample">
|
|
<span class="dcx-block-label">Type sample</span>
|
|
<div class="dcx-specimen">
|
|
<p class="dcx-specimen-display">花園アトリエ — seasonal craft, quiet authority.</p>
|
|
<p class="dcx-specimen-body">Shippori Mincho carries the display voice: editorial warmth and Japanese headline authority, never faux-bolded. Source Sans 3 carries English body clarity at sixteen pixels and above, with captions that defer to the object.</p>
|
|
<p class="dcx-specimen-label">SPECIMEN 04 · SEASON CODE SPRING</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="dcx-block" data-label="Families">
|
|
<span class="dcx-block-label">Families</span>
|
|
<dl class="dcx-defs">
|
|
<div class="dcx-def">
|
|
<dt>Display</dt>
|
|
<dd>Shippori Mincho (fallbacks: Hiragino Mincho ProN, Yu Mincho, Georgia). Tokyo craft gallery meets specimen ledger.</dd>
|
|
</div>
|
|
<div class="dcx-def">
|
|
<dt>Body</dt>
|
|
<dd>Source Sans 3 (system-ui fallback). EN body clarity at ≥16px.</dd>
|
|
</div>
|
|
<div class="dcx-def">
|
|
<dt>Label / mono</dt>
|
|
<dd>Narrow mono for specimen labels, season codes, commission metadata only; never UI chrome.</dd>
|
|
</div>
|
|
</dl>
|
|
</div>
|
|
|
|
<div class="dcx-block" data-label="Hierarchy">
|
|
<span class="dcx-block-label">Hierarchy</span>
|
|
<dl class="dcx-defs">
|
|
<div class="dcx-def">
|
|
<dt>Display</dt>
|
|
<dd><code>clamp(2.75rem, 3.5vw + 1.25rem, 4.5rem)</code> · weight 400 · line-height 1.15. Hero headlines, one per viewport; max width ~16ch with <code>text-wrap: balance</code>.</dd>
|
|
</div>
|
|
<div class="dcx-def">
|
|
<dt>Body</dt>
|
|
<dd><code>16px</code> · weight 400 · line-height 1.6. Max line length 65–75ch; <code>text-wrap: pretty</code> on long prose.</dd>
|
|
</div>
|
|
<div class="dcx-def">
|
|
<dt>Label</dt>
|
|
<dd><code>14px</code> · weight 600 · line-height 1.35. Season codes, kickers; uppercase only where the plum accent applies.</dd>
|
|
</div>
|
|
<div class="dcx-def">
|
|
<dt>Caption</dt>
|
|
<dd><code>12px</code> · weight 400 · line-height 1.6. Charcoal tone with generous leading, museum-signage style.</dd>
|
|
</div>
|
|
</dl>
|
|
</div>
|
|
|
|
<div class="dcx-block" data-label="Named rules">
|
|
<span class="dcx-block-label">Named rules</span>
|
|
<div class="dcx-callout">
|
|
<p class="dcx-callout-name">The Mono-Is-Metadata Rule</p>
|
|
<p>Mono appears on specimen labels, season codes, and commission metadata. Never on navigation chrome, never on every section eyebrow.</p>
|
|
</div>
|
|
<div class="dcx-callout">
|
|
<p class="dcx-callout-name">The Bilingual Hierarchy Rule</p>
|
|
<p>Japanese display lines use mincho; English body uses Source Sans 3. Both scripts read at ≥16px body minimum. No faux-bold on mincho.</p>
|
|
</div>
|
|
</div>
|
|
</article>
|
|
</template>
|
|
|
|
<template id="dcx-detail-iconography">
|
|
<article class="dcx-article">
|
|
<header>
|
|
<span class="dcx-eyebrow">Design context · 06 / 08 · Hanazono Atelier</span>
|
|
<h2 class="dcx-title">Iconography</h2>
|
|
<p class="dcx-lede">Icon style, stroke weight, metaphor rules, allowed and forbidden icon types.</p>
|
|
</header>
|
|
|
|
<div class="dcx-block" data-label="Status">
|
|
<span class="dcx-block-label">Status</span>
|
|
<div class="dcx-empty">
|
|
<p class="dcx-empty-title">Nothing captured yet</p>
|
|
<p>Zero iconography mentions in PRODUCT.md, DESIGN.md, or the design.json sidecar for this project. Closest adjacent decision is a taste boundary, not an icon rule: no decorative floral motifs in UI chrome.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="dcx-block" data-label="Planned">
|
|
<span class="dcx-block-label">Planned (visual document seed)</span>
|
|
<p style="margin:0 0 14px; font-size:0.94rem; line-height:1.6; color:var(--ks-text-muted);">The upcoming seed flow asks the icon-library question directly, with named options:</p>
|
|
<div class="dcx-chips">
|
|
<span class="dcx-chip dcx-chip--muted">Lucide</span>
|
|
<span class="dcx-chip dcx-chip--muted">Tabler Icons</span>
|
|
<span class="dcx-chip dcx-chip--muted">Hugeicons</span>
|
|
</div>
|
|
<p class="dcx-fan-note">Library choice covers icon style only; stroke weight, metaphor rules, and icon-button behavior remain open fields.</p>
|
|
</div>
|
|
</article>
|
|
</template>
|
|
|
|
<template id="dcx-detail-material">
|
|
<article class="dcx-article">
|
|
<header>
|
|
<span class="dcx-eyebrow">Design context · 07 / 08 · Hanazono Atelier</span>
|
|
<h2 class="dcx-title">Material</h2>
|
|
<p class="dcx-lede">Radius, borders, shadows, elevation, surfaces, texture, motion feel.</p>
|
|
</header>
|
|
|
|
<div class="dcx-block" data-label="Radius">
|
|
<span class="dcx-block-label">Radius</span>
|
|
<div class="dcx-radius-row">
|
|
<div class="dcx-radius-sample">
|
|
<div class="dcx-radius-box" style="border-radius: 4px;"></div>
|
|
<span class="dcx-radius-caption">sm · 4px</span>
|
|
</div>
|
|
<div class="dcx-radius-sample">
|
|
<div class="dcx-radius-box" style="border-radius: 8px;"></div>
|
|
<span class="dcx-radius-caption">md · 8px</span>
|
|
</div>
|
|
<div class="dcx-radius-sample">
|
|
<div class="dcx-radius-box" style="border-radius: 16px;"></div>
|
|
<span class="dcx-radius-caption">lg · 16px</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="dcx-block" data-label="Spacing scale">
|
|
<span class="dcx-block-label">Spacing scale</span>
|
|
<div class="dcx-space-rows">
|
|
<div class="dcx-space-row"><span class="dcx-space-name">2xs</span><span class="dcx-space-bar" style="--bar: 4%;"></span><span class="dcx-space-value">0.25rem</span></div>
|
|
<div class="dcx-space-row"><span class="dcx-space-name">xs</span><span class="dcx-space-bar" style="--bar: 8%;"></span><span class="dcx-space-value">0.5rem</span></div>
|
|
<div class="dcx-space-row"><span class="dcx-space-name">sm</span><span class="dcx-space-bar" style="--bar: 12%;"></span><span class="dcx-space-value">0.75rem</span></div>
|
|
<div class="dcx-space-row"><span class="dcx-space-name">md</span><span class="dcx-space-bar" style="--bar: 16%;"></span><span class="dcx-space-value">1rem</span></div>
|
|
<div class="dcx-space-row"><span class="dcx-space-name">lg</span><span class="dcx-space-bar" style="--bar: 24%;"></span><span class="dcx-space-value">1.5rem</span></div>
|
|
<div class="dcx-space-row"><span class="dcx-space-name">xl</span><span class="dcx-space-bar" style="--bar: 32%;"></span><span class="dcx-space-value">2rem</span></div>
|
|
<div class="dcx-space-row"><span class="dcx-space-name">2xl</span><span class="dcx-space-bar" style="--bar: 48%;"></span><span class="dcx-space-value">3rem</span></div>
|
|
<div class="dcx-space-row"><span class="dcx-space-name">3xl</span><span class="dcx-space-bar" style="--bar: 64%;"></span><span class="dcx-space-value">4rem</span></div>
|
|
<div class="dcx-space-row"><span class="dcx-space-name">gutter</span><span class="dcx-space-bar" style="--bar: 56%;"></span><span class="dcx-space-value">clamp(1.25–3.5rem)</span></div>
|
|
<div class="dcx-space-row"><span class="dcx-space-name">section</span><span class="dcx-space-bar" style="--bar: 88%;"></span><span class="dcx-space-value">clamp(3–5.5rem)</span></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="dcx-block" data-label="Elevation & surfaces">
|
|
<span class="dcx-block-label">Elevation & surfaces</span>
|
|
<div class="dcx-callout">
|
|
<p class="dcx-callout-name">The Flat Exhibition Rule</p>
|
|
<p>Flat by default. Depth comes from tonal layering: cream ground, umber ink hierarchy, hairline rules at 8–12% opacity. Shadows are not part of the vocabulary; section separation uses spacing rhythm and 1px rules, not cards with drop shadows.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="dcx-block" data-label="Motion feel">
|
|
<span class="dcx-block-label">Motion feel</span>
|
|
<ul class="dcx-list">
|
|
<li><strong>Responsive motion only</strong>: hover and focus feedback; no scroll choreography, no auto-playing reveals.</li>
|
|
<li><strong>Subtle opacity shifts</strong> on hover/focus; no lift-and-glow card elevation.</li>
|
|
<li><strong><code>prefers-reduced-motion</code></strong> collapses transitions to instant state changes.</li>
|
|
</ul>
|
|
</div>
|
|
</article>
|
|
</template>
|
|
|
|
<template id="dcx-detail-interface">
|
|
<article class="dcx-article">
|
|
<header>
|
|
<span class="dcx-eyebrow">Design context · 08 / 08 · Hanazono Atelier</span>
|
|
<h2 class="dcx-title">Interface</h2>
|
|
<p class="dcx-lede">Buttons, inputs, cards, component states, interaction patterns, accessibility.</p>
|
|
</header>
|
|
|
|
<div class="dcx-block" data-label="Components">
|
|
<span class="dcx-block-label">Components</span>
|
|
<div class="dcx-empty" style="margin-bottom:18px;">
|
|
<p class="dcx-empty-title">No component library seeded yet</p>
|
|
<p>Components are documented on the first scan pass, once there's code to capture actual tokens and states from.</p>
|
|
</div>
|
|
<dl class="dcx-defs">
|
|
<div class="dcx-def">
|
|
<dt>Commission Lane</dt>
|
|
<dd>Signature component: a lane page hero pairing one arrangement with the seasonal POV and the booking path.</dd>
|
|
</div>
|
|
<div class="dcx-def">
|
|
<dt>Consultation Form</dt>
|
|
<dd>The conversion surface. Labels, error states, and focus order are screen-reader priorities.</dd>
|
|
</div>
|
|
<div class="dcx-def">
|
|
<dt>Gallery Grid</dt>
|
|
<dd>Exhibition pacing: one object per viewport, no thumbnail clutter grids.</dd>
|
|
</div>
|
|
<div class="dcx-def">
|
|
<dt>Proof Quote</dt>
|
|
<dd>Partner quotes with clear attribution order; the proof-before-pitch signal.</dd>
|
|
</div>
|
|
</dl>
|
|
</div>
|
|
|
|
<div class="dcx-block" data-label="Interaction states">
|
|
<span class="dcx-block-label">Interaction states</span>
|
|
<ul class="dcx-list">
|
|
<li><strong>Hover/focus feedback</strong> on every interactive element; instant state changes under reduced motion.</li>
|
|
<li><strong>Plum CTA hover</strong> requires a darker state that still passes 4.5:1 on cream.</li>
|
|
<li><strong>No decorative borders</strong>, glassmorphism, or side-stripe accents on cards or callouts.</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="dcx-block" data-label="Accessibility & inclusion">
|
|
<span class="dcx-block-label">Accessibility & inclusion</span>
|
|
<dl class="dcx-defs">
|
|
<div class="dcx-def">
|
|
<dt>Target</dt>
|
|
<dd>WCAG 2.2 AA on all public pages.</dd>
|
|
</div>
|
|
<div class="dcx-def">
|
|
<dt>Bilingual type</dt>
|
|
<dd>Japanese and English both at ≥16px body; display mincho for JP headings, Source Sans 3 for EN body; no faux-bold on mincho.</dd>
|
|
</div>
|
|
<div class="dcx-def">
|
|
<dt>Contrast</dt>
|
|
<dd>Plum accent <code>#8b4a6b</code> must pass 4.5:1 on cream for links and CTA labels, with a darker hover state.</dd>
|
|
</div>
|
|
<div class="dcx-def">
|
|
<dt>Reduced motion</dt>
|
|
<dd>Respect <code>prefers-reduced-motion</code>; no auto-playing scroll reveals.</dd>
|
|
</div>
|
|
<div class="dcx-def">
|
|
<dt>Screen reader priority</dt>
|
|
<dd>Consultation form labels, error states, and proof-quote attribution order.</dd>
|
|
</div>
|
|
</dl>
|
|
</div>
|
|
</article>
|
|
</template>
|
|
|
|
<!-- Expander shell: sidebar + topbar + main, cloned per open. -->
|
|
<template id="dcx-shell-template">
|
|
<div class="dcx-expander-inner">
|
|
<aside class="dcx-sidebar">
|
|
<a class="dcx-sidebar-brand" href="https://impeccable.style/" target="_blank" rel="noopener" aria-label="Impeccable home (opens the live site)">
|
|
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
<path d="M5 2.5 L13.5 2.5 L5.5 21.5 L5 21.5 Q2.5 21.5 2.5 19 L2.5 5 Q2.5 2.5 5 2.5 Z"/>
|
|
<path d="M16.5 2.5 L19 2.5 Q21.5 2.5 21.5 5 L21.5 19 Q21.5 21.5 19 21.5 L8.5 21.5 Z"/>
|
|
</svg>
|
|
<span class="dcx-sidebar-brand-name">Impeccable</span>
|
|
</a>
|
|
<nav class="dcx-nav" aria-label="Design context categories">
|
|
<span class="dcx-nav-label">Design context</span>
|
|
<ul class="dcx-nav-list">
|
|
<li data-category="audience"><a class="dcx-nav-link" href="#audience" data-dcx-nav="audience">Audience</a><div class="dcx-subnav"></div></li>
|
|
<li data-category="product"><a class="dcx-nav-link" href="#product" data-dcx-nav="product">Product</a><div class="dcx-subnav"></div></li>
|
|
<li data-category="brand"><a class="dcx-nav-link" href="#brand" data-dcx-nav="brand">Brand</a><div class="dcx-subnav"></div></li>
|
|
<li data-category="color"><a class="dcx-nav-link" href="#color" data-dcx-nav="color">Color</a><div class="dcx-subnav"></div></li>
|
|
<li data-category="typography"><a class="dcx-nav-link" href="#typography" data-dcx-nav="typography">Typography</a><div class="dcx-subnav"></div></li>
|
|
<li data-category="iconography"><a class="dcx-nav-link" href="#iconography" data-dcx-nav="iconography">Iconography</a><div class="dcx-subnav"></div></li>
|
|
<li data-category="material"><a class="dcx-nav-link" href="#material" data-dcx-nav="material">Material</a><div class="dcx-subnav"></div></li>
|
|
<li data-category="interface"><a class="dcx-nav-link" href="#interface" data-dcx-nav="interface">Interface</a><div class="dcx-subnav"></div></li>
|
|
</ul>
|
|
</nav>
|
|
</aside>
|
|
<div class="dcx-panel">
|
|
<header class="dcx-topbar">
|
|
<div class="dcx-current"></div>
|
|
<button class="dcx-close" type="button" aria-label="Close"></button>
|
|
</header>
|
|
<div class="dcx-main" tabindex="-1"></div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
(function () {
|
|
'use strict';
|
|
|
|
/* ---------- Theme toggle (mirrors the site's theme.js) ---------- */
|
|
var STORAGE_KEY = 'impeccable-theme';
|
|
var LABELS = {
|
|
auto: 'Theme: auto, matching your system. Click to switch to light mode.',
|
|
light: 'Theme: light. Click to switch to dark mode.',
|
|
dark: 'Theme: dark. Click to switch back to auto.'
|
|
};
|
|
var TITLES = { auto: 'Theme: auto (matches system)', light: 'Theme: light', dark: 'Theme: dark' };
|
|
|
|
function getStoredPref() {
|
|
var v = localStorage.getItem(STORAGE_KEY);
|
|
return v === 'light' || v === 'dark' ? v : 'auto';
|
|
}
|
|
function setStoredPref(pref) {
|
|
if (pref === 'auto') localStorage.removeItem(STORAGE_KEY);
|
|
else localStorage.setItem(STORAGE_KEY, pref);
|
|
}
|
|
function systemTheme() {
|
|
return window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
|
|
}
|
|
function applyTheme(pref) {
|
|
var html = document.documentElement;
|
|
var resolved = pref === 'auto' ? systemTheme() : pref;
|
|
html.classList.remove('light', 'dark');
|
|
html.classList.add(resolved);
|
|
html.setAttribute('data-theme-pref', pref);
|
|
document.querySelectorAll('[data-theme-toggle]').forEach(function (btn) {
|
|
btn.setAttribute('aria-label', LABELS[pref]);
|
|
btn.setAttribute('title', TITLES[pref]);
|
|
});
|
|
}
|
|
function nextPref(pref) {
|
|
return pref === 'auto' ? 'light' : pref === 'light' ? 'dark' : 'auto';
|
|
}
|
|
|
|
applyTheme(getStoredPref());
|
|
if (window.matchMedia) {
|
|
var mq = window.matchMedia('(prefers-color-scheme: light)');
|
|
var onChange = function () { if (getStoredPref() === 'auto') applyTheme('auto'); };
|
|
if (mq.addEventListener) mq.addEventListener('change', onChange);
|
|
else if (mq.addListener) mq.addListener(onChange);
|
|
}
|
|
document.querySelectorAll('[data-theme-toggle]').forEach(function (btn) {
|
|
btn.addEventListener('click', function () {
|
|
var next = nextPref(getStoredPref());
|
|
setStoredPref(next);
|
|
applyTheme(next);
|
|
});
|
|
});
|
|
|
|
/* ---------- Mobile nav drawer (mirrors Header.astro's inline script) ---------- */
|
|
(function () {
|
|
var header = document.querySelector('[data-site-header]');
|
|
var btn = document.querySelector('[data-site-header-menu]');
|
|
if (!header || !btn) return;
|
|
btn.addEventListener('click', function () {
|
|
var open = header.getAttribute('data-nav-open') === 'true';
|
|
header.setAttribute('data-nav-open', String(!open));
|
|
btn.setAttribute('aria-expanded', String(!open));
|
|
});
|
|
})();
|
|
|
|
/* ---------- Staggered reveal on load ---------- */
|
|
requestAnimationFrame(function () {
|
|
requestAnimationFrame(function () {
|
|
document.querySelectorAll('[data-reveal]').forEach(function (el) {
|
|
el.classList.add('revealed');
|
|
});
|
|
});
|
|
});
|
|
|
|
/* ---------- Expander ---------- */
|
|
var reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
var MORPH_MS = reduceMotion ? 0 : 540;
|
|
var READY_MS = reduceMotion ? 0 : 260;
|
|
|
|
var tiles = Array.prototype.slice.call(document.querySelectorAll('.dcx-tile'));
|
|
var names = {};
|
|
tiles.forEach(function (t) { names[t.dataset.category] = t.dataset.name; });
|
|
var shellTemplate = document.getElementById('dcx-shell-template');
|
|
|
|
var current = null;
|
|
|
|
function copyText(value) {
|
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
return navigator.clipboard.writeText(value).catch(function () { fallbackCopy(value); });
|
|
}
|
|
fallbackCopy(value);
|
|
return Promise.resolve();
|
|
}
|
|
|
|
function fallbackCopy(value) {
|
|
var field = document.createElement('textarea');
|
|
field.value = value;
|
|
field.setAttribute('readonly', '');
|
|
field.style.position = 'fixed';
|
|
field.style.opacity = '0';
|
|
document.body.appendChild(field);
|
|
field.select();
|
|
document.execCommand('copy');
|
|
field.remove();
|
|
}
|
|
|
|
function initFan(root) {
|
|
root.querySelectorAll('.dcx-fan').forEach(function (fan) {
|
|
var panels = Array.prototype.slice.call(fan.querySelectorAll('.dcx-fan-panel'));
|
|
if (!panels.length) return;
|
|
|
|
function setActive(index) {
|
|
fan.classList.add('is-engaged');
|
|
panels.forEach(function (panel, i) {
|
|
panel.classList.toggle('is-active', i === index);
|
|
panel.classList.toggle('is-neighbor', Math.abs(i - index) === 1);
|
|
});
|
|
}
|
|
|
|
function clearActive() {
|
|
fan.classList.remove('is-engaged');
|
|
panels.forEach(function (panel) { panel.classList.remove('is-active', 'is-neighbor'); });
|
|
}
|
|
|
|
fan.addEventListener('mousemove', function (event) {
|
|
var rect = fan.getBoundingClientRect();
|
|
var progress = Math.min(0.999, Math.max(0, (event.clientX - rect.left) / rect.width));
|
|
var active = 0;
|
|
panels.forEach(function (panel, i) {
|
|
var left = parseFloat(panel.style.getPropertyValue('--panel-left')) / 100;
|
|
if (progress >= left) active = i;
|
|
});
|
|
setActive(active);
|
|
});
|
|
fan.addEventListener('mouseleave', clearActive);
|
|
|
|
panels.forEach(function (panel, i) {
|
|
panel.addEventListener('focus', function () { setActive(i); });
|
|
panel.addEventListener('blur', function () {
|
|
if (!fan.matches(':focus-within')) clearActive();
|
|
});
|
|
panel.addEventListener('click', function () {
|
|
var value = panel.dataset.copyColor;
|
|
if (!value) return;
|
|
copyText(value);
|
|
var label = panel.querySelector('.dcx-fan-name');
|
|
var original = panel.dataset.colorName || 'Color';
|
|
panel.classList.add('is-copied');
|
|
if (label) label.textContent = 'Copied!';
|
|
window.clearTimeout(panel._copyTimer);
|
|
panel._copyTimer = window.setTimeout(function () {
|
|
panel.classList.remove('is-copied');
|
|
if (label) label.textContent = original;
|
|
}, 900);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
function buildSubnav(expander, activeId) {
|
|
expander.querySelectorAll('.dcx-nav-list li').forEach(function (li) {
|
|
var isActive = li.dataset.category === activeId;
|
|
li.classList.toggle('is-active', isActive);
|
|
var subnav = li.querySelector('.dcx-subnav');
|
|
if (!subnav) return;
|
|
subnav.innerHTML = '';
|
|
if (!isActive) return;
|
|
expander.querySelectorAll('.dcx-main .dcx-block[data-label]').forEach(function (block, i) {
|
|
var btn = document.createElement('button');
|
|
btn.className = 'dcx-sub-link';
|
|
btn.type = 'button';
|
|
btn.textContent = block.dataset.label.replace(/&/g, '&');
|
|
btn.setAttribute('data-dcx-subsection', String(i));
|
|
subnav.appendChild(btn);
|
|
});
|
|
});
|
|
expander.querySelectorAll('.dcx-nav-link').forEach(function (link) {
|
|
if (link.dataset.dcxNav === activeId) link.setAttribute('aria-current', 'page');
|
|
else link.removeAttribute('aria-current');
|
|
});
|
|
}
|
|
|
|
function scrollToBlock(expander, index) {
|
|
var blocks = expander.querySelectorAll('.dcx-main .dcx-block[data-label]');
|
|
var target = blocks[Number(index)];
|
|
if (!target) return;
|
|
var main = expander.querySelector('.dcx-main');
|
|
var top = target.getBoundingClientRect().top - main.getBoundingClientRect().top + main.scrollTop - 18;
|
|
main.scrollTo({ top: Math.max(0, top), behavior: reduceMotion ? 'auto' : 'smooth' });
|
|
}
|
|
|
|
function renderCategory(id, expander, updateHash) {
|
|
var template = document.getElementById('dcx-detail-' + id);
|
|
if (!template) return;
|
|
|
|
expander.querySelector('.dcx-current').textContent = names[id] || id;
|
|
|
|
var main = expander.querySelector('.dcx-main');
|
|
main.innerHTML = '';
|
|
main.appendChild(template.content.cloneNode(true));
|
|
main.scrollTop = 0;
|
|
initFan(main);
|
|
|
|
if (current) {
|
|
var tile = document.querySelector('.dcx-tile--' + id);
|
|
current.id = id;
|
|
current.tile = tile;
|
|
current.rect = tile.getBoundingClientRect();
|
|
}
|
|
|
|
buildSubnav(expander, id);
|
|
if (updateHash) history.pushState({ category: id }, '', '#' + id);
|
|
}
|
|
|
|
function openCategory(id, updateHash) {
|
|
if (!(id in names)) return;
|
|
if (current && current.id === id) return;
|
|
closeCategory(false);
|
|
|
|
var tile = document.querySelector('.dcx-tile--' + id);
|
|
if (!tile) return;
|
|
|
|
var rect = tile.getBoundingClientRect();
|
|
var expander = document.createElement('section');
|
|
expander.className = 'dcx-expander';
|
|
expander.setAttribute('role', 'dialog');
|
|
expander.setAttribute('aria-modal', 'true');
|
|
expander.setAttribute('aria-label', names[id] + ' details');
|
|
expander.style.top = rect.top + 'px';
|
|
expander.style.left = rect.left + 'px';
|
|
expander.style.width = rect.width + 'px';
|
|
expander.style.height = rect.height + 'px';
|
|
|
|
expander.appendChild(shellTemplate.content.cloneNode(true));
|
|
document.body.appendChild(expander);
|
|
document.body.classList.add('is-locked');
|
|
current = { id: null, expander: expander, tile: tile, rect: rect, opener: tile };
|
|
renderCategory(id, expander, false);
|
|
current.id = id;
|
|
|
|
requestAnimationFrame(function () {
|
|
expander.classList.add('is-full');
|
|
window.setTimeout(function () {
|
|
expander.classList.add('is-ready');
|
|
var close = expander.querySelector('.dcx-close');
|
|
if (close) close.focus({ preventScroll: true });
|
|
}, READY_MS);
|
|
});
|
|
|
|
expander.querySelector('.dcx-close').addEventListener('click', function () { closeCategory(true); });
|
|
|
|
expander.querySelector('.dcx-nav').addEventListener('click', function (event) {
|
|
var subLink = event.target.closest('[data-dcx-subsection]');
|
|
if (subLink) {
|
|
scrollToBlock(expander, subLink.getAttribute('data-dcx-subsection'));
|
|
return;
|
|
}
|
|
var navLink = event.target.closest('[data-dcx-nav]');
|
|
if (!navLink) return;
|
|
event.preventDefault();
|
|
if (current && navLink.dataset.dcxNav === current.id) return;
|
|
renderCategory(navLink.dataset.dcxNav, expander, true);
|
|
});
|
|
|
|
if (updateHash) history.pushState({ category: id }, '', '#' + id);
|
|
}
|
|
|
|
function closeCategory(updateHash) {
|
|
if (!current) return;
|
|
var expander = current.expander;
|
|
var rect = current.rect;
|
|
var opener = current.opener;
|
|
expander.classList.remove('is-ready', 'is-full');
|
|
expander.style.top = rect.top + 'px';
|
|
expander.style.left = rect.left + 'px';
|
|
expander.style.width = rect.width + 'px';
|
|
expander.style.height = rect.height + 'px';
|
|
window.setTimeout(function () {
|
|
expander.remove();
|
|
document.body.classList.remove('is-locked');
|
|
}, MORPH_MS);
|
|
current = null;
|
|
if (opener) opener.focus({ preventScroll: true });
|
|
if (updateHash && location.hash) history.pushState(null, '', location.pathname + location.search);
|
|
}
|
|
|
|
tiles.forEach(function (tile) {
|
|
tile.addEventListener('click', function () { openCategory(tile.dataset.category, true); });
|
|
});
|
|
|
|
/* Vignette draw animations use the homepage's stroke-dasharray: 100
|
|
(user units), but non-scaling-stroke makes Chromium measure dashes in
|
|
screen pixels. Translate: --pl = 100 viewBox units at the rendered
|
|
scale, refreshed on resize. */
|
|
function sizeDrawStrokes() {
|
|
document.querySelectorAll('.dcx-viz-svg').forEach(function (svg) {
|
|
var paths = svg.querySelectorAll('.anim-draw, .anim-draw-delay');
|
|
if (!paths.length) return;
|
|
var rect = svg.getBoundingClientRect();
|
|
if (!rect.width || !rect.height) return;
|
|
var scale = Math.min(rect.width, rect.height) / 40;
|
|
paths.forEach(function (path) {
|
|
/* The homepage icons fit inside dasharray 100; custom paths longer
|
|
than 60 units (breathe shows the first 60% of the dash) get a
|
|
proportionally larger dash so they still finish drawing. */
|
|
var units = Math.max(100, path.getTotalLength() / 0.6);
|
|
path.style.setProperty('--pl', (units * scale).toFixed(1) + 'px');
|
|
});
|
|
});
|
|
}
|
|
sizeDrawStrokes();
|
|
var drawResizeTimer;
|
|
window.addEventListener('resize', function () {
|
|
window.clearTimeout(drawResizeTimer);
|
|
drawResizeTimer = window.setTimeout(sizeDrawStrokes, 150);
|
|
});
|
|
|
|
document.addEventListener('keydown', function (event) {
|
|
if (event.key === 'Escape') closeCategory(true);
|
|
});
|
|
|
|
window.addEventListener('popstate', function () {
|
|
var id = location.hash.replace('#', '');
|
|
if (id && id in names) {
|
|
if (current) renderCategory(id, current.expander, false);
|
|
else openCategory(id, false);
|
|
} else {
|
|
closeCategory(false);
|
|
}
|
|
});
|
|
|
|
var initial = location.hash.replace('#', '');
|
|
if (initial && initial in names) {
|
|
window.setTimeout(function () { openCategory(initial, false); }, 120);
|
|
}
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|