Files
hallmark/site/css/components.css
T
Youssef 3fab5ba75b Move theme picker out of navbar to a fixed bottom-left chip
The 12-theme popover was congested when opened inline in the
navbar. Now it lives as a floating chip in the bottom-left corner:
always reachable on desktop and mobile, popover opens upward, and
the navbar is a clean two-column wordmark + meta layout.

- index.html: themer markup moved to end of <body>, navbar simplified
- components.css: .themer is position: fixed with bottom/left anchors,
  popover opens upward (bottom: 100% + 0.5rem), responsive grid
  collapses to 2 columns under 60rem
- sections.css: removed .nav__themer rules; .nav restored to flex
  space-between
- README.md: rewritten concise + complete, mirrors the v0.2.0 surface
  (10 reference files, 12 themes, 4 verbs, 20-question slop test)
2026-04-27 22:38:45 +01:00

809 lines
21 KiB
CSS

/* Hallmark — components */
/* — Wordmark ——————————————————————————————————————— */
.wordmark {
font-family: var(--font-label);
font-weight: 500;
font-size: var(--text-sm);
letter-spacing: 0.22em;
text-transform: uppercase;
color: var(--color-ink);
}
[data-theme="manifesto"] .wordmark {
font-family: var(--font-display);
font-weight: 800;
letter-spacing: -0.02em;
text-transform: uppercase;
font-size: var(--text-md);
}
[data-theme="terminal"] .wordmark::before {
content: "$ ";
color: var(--color-accent);
}
/* — Theme picker (fixed bottom-left chip + upward popover) ————— */
.themer {
position: fixed;
left: max(1rem, env(safe-area-inset-left));
bottom: max(1rem, env(safe-area-inset-bottom));
z-index: var(--z-sticky);
display: inline-flex;
align-items: center;
gap: 0.5rem;
font-family: var(--font-label);
font-size: 0.6875rem;
letter-spacing: var(--tracking-label);
text-transform: uppercase;
color: var(--color-ink);
}
.themer__trigger {
display: inline-flex;
align-items: center;
gap: 0.55rem;
padding: 0.55rem 0.85rem 0.55rem 0.6rem;
background: var(--color-paper);
border: var(--rule-hair) solid var(--color-rule-2);
color: var(--color-ink);
font: inherit;
letter-spacing: 0.14em;
cursor: pointer;
min-height: 36px;
/* explicit transitions, not transition-all */
transition: background-color var(--dur-micro) var(--ease-out),
border-color var(--dur-micro) var(--ease-out),
color var(--dur-micro) var(--ease-out);
box-shadow: 0 1px 2px oklch(20% 0.02 60 / 0.05),
0 8px 24px -8px oklch(20% 0.02 60 / 0.10);
}
@media (hover: hover) and (pointer: fine) {
.themer__trigger:hover { border-color: var(--color-ink); }
}
.themer__trigger:focus-visible {
outline: 2px solid var(--color-focus);
outline-offset: 2px;
}
/* Tiny accent swatch on the trigger — confirms current theme at a glance */
.themer__trigger-swatch {
width: 0.55rem;
height: 0.55rem;
background: var(--color-accent);
display: inline-block;
border-radius: 50%;
flex-shrink: 0;
}
.themer__trigger-label {
color: var(--color-muted);
}
.themer__trigger-current {
color: var(--color-ink);
font-weight: 500;
}
.themer__trigger-caret {
width: 0.4em;
height: 0.4em;
border-right: 1.5px solid currentColor;
border-bottom: 1.5px solid currentColor;
transform: rotate(-135deg) translate(-1px, 1px); /* points up — popover opens up */
transition: transform var(--dur-short) var(--ease-out);
}
.themer[aria-expanded="true"] .themer__trigger-caret {
transform: rotate(45deg) translate(-2px, -1px); /* points down when open */
}
/* Keyboard-shortcut hint — delay-reveals on hover, instant on focus */
.themer__kbd {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.15rem 0.45rem;
font-family: var(--font-label);
font-size: 0.625rem;
letter-spacing: 0.08em;
color: var(--color-muted);
background: var(--color-paper-2);
border: var(--rule-hair) solid var(--color-rule);
opacity: 0;
transform: translateX(-4px);
pointer-events: none;
min-height: 28px;
/* hover delay: 1.2s before reveal */
transition: opacity 180ms var(--ease-out) 1.2s,
transform 180ms var(--ease-out) 1.2s;
}
.themer__kbd::before {
content: "press ";
margin-right: 0.35em;
text-transform: uppercase;
}
.themer__trigger:hover ~ .themer__kbd {
opacity: 1;
transform: none;
}
.themer__trigger:focus-visible ~ .themer__kbd,
.themer[aria-expanded="true"] .themer__kbd {
opacity: 1;
transform: none;
/* no delay on focus or open */
transition: opacity 180ms var(--ease-out),
transform 180ms var(--ease-out);
}
.themer__kbd-key {
font-weight: 500;
color: var(--color-ink);
}
@media (max-width: 40rem) {
.themer__kbd { display: none; }
.themer__trigger { padding-block: 0.6rem; }
}
@media (prefers-reduced-motion: reduce) {
.themer__kbd { transition-duration: 0.01ms; transition-delay: 0.01ms; }
}
/* Popover panel — opens upward from the chip */
.themer__panel {
position: absolute;
bottom: calc(100% + 0.5rem);
left: 0;
transform: translateY(4px);
width: min(40rem, calc(100vw - 2rem));
max-height: calc(100dvh - 6rem);
overflow-y: auto;
background: var(--color-paper);
border: var(--rule-hair) solid var(--color-rule-2);
padding: var(--space-lg);
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: var(--space-lg);
opacity: 0;
pointer-events: none;
box-shadow: 0 24px 60px -20px oklch(20% 0.02 60 / 0.18),
0 0 0 1px oklch(20% 0.02 60 / 0.04);
transition: opacity var(--dur-short) var(--ease-out),
transform var(--dur-short) var(--ease-out);
}
.themer[aria-expanded="true"] .themer__panel {
opacity: 1;
pointer-events: auto;
transform: translateY(0);
}
@media (max-width: 60rem) {
.themer__panel {
grid-template-columns: repeat(2, 1fr);
width: min(26rem, calc(100vw - 2rem));
}
}
@media (max-width: 36rem) {
.themer__panel {
grid-template-columns: 1fr 1fr;
/* anchor to viewport edges so it doesn't clip on small screens */
left: 0;
right: auto;
width: calc(100vw - 2rem);
max-width: 26rem;
padding: var(--space-md);
gap: var(--space-md);
}
}
.themer__group {
display: grid;
gap: var(--space-xs);
}
.themer__group-label {
font-family: var(--font-label);
font-size: 0.625rem;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--color-muted);
padding-bottom: 0.4rem;
border-bottom: var(--rule-hair) solid var(--color-rule);
}
.themer__btn {
font: inherit;
font-family: var(--font-label);
display: grid;
grid-template-columns: 1fr auto;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0.55rem;
background: transparent;
border: 0;
cursor: pointer;
text-align: left;
color: var(--color-neutral);
letter-spacing: 0.10em;
text-transform: uppercase;
font-size: 0.6875rem;
min-height: 32px;
}
.themer__btn:hover { color: var(--color-ink); background: var(--color-paper-2); }
.themer__btn:focus-visible {
outline: 2px solid var(--color-focus);
outline-offset: -2px;
}
.themer__btn[aria-pressed="true"] {
color: var(--color-ink);
background: var(--color-paper-2);
position: relative;
}
.themer__btn[aria-pressed="true"]::before {
content: "";
position: absolute;
left: 0;
top: 0.5rem;
bottom: 0.5rem;
width: 2px;
background: var(--color-accent);
}
.themer__swatches {
display: inline-flex;
gap: 2px;
}
.themer__swatch {
width: 0.55rem;
height: 0.55rem;
border-radius: 50%;
}
/* — Section label (01 / FOUNDATIONS) —————————————————— */
.section-label {
font-family: var(--font-label);
font-size: var(--text-xs);
font-weight: 400;
letter-spacing: var(--tracking-label);
text-transform: uppercase;
color: var(--color-muted);
display: flex;
align-items: baseline;
gap: var(--space-sm);
}
.section-label .num { color: var(--color-ink); font-weight: 500; }
.section-label .divider { color: var(--color-rule-2); font-weight: 300; }
[data-theme="terminal"] .section-label::before {
content: "> ";
color: var(--color-accent);
}
[data-theme="manifesto"] .section-label .num {
color: var(--color-accent);
font-family: var(--font-display);
font-weight: 800;
font-size: var(--text-md);
}
[data-theme="sport"] .section-label .num {
color: var(--color-accent);
font-weight: 700;
font-family: var(--font-display);
font-size: var(--text-md);
font-style: italic;
}
[data-theme="almanac"] .section-label {
font-family: var(--font-mono);
}
/* — Button (outlined default) ————————————————————————— */
.btn {
display: inline-flex;
align-items: center;
gap: var(--space-sm);
padding: var(--space-sm) var(--space-lg);
font-family: var(--font-label);
font-size: var(--text-sm);
font-weight: 500;
letter-spacing: 0.04em;
color: var(--color-ink);
background: transparent;
border: var(--rule-fine) solid var(--color-ink);
border-radius: 0;
min-height: 44px;
/* explicit properties — never `transition: all` */
transition: transform 100ms var(--ease-out),
background-color var(--dur-micro) var(--ease-out),
color var(--dur-micro) var(--ease-out),
border-color var(--dur-micro) var(--ease-out);
cursor: pointer;
}
@media (hover: hover) and (pointer: fine) {
.btn:hover {
background: var(--color-ink);
color: var(--color-paper);
transform: translateY(-1px);
}
}
.btn:active { transform: translateY(1px); transition-duration: 60ms; }
/* — Inline link ————————————————————————————————————— */
.link {
color: var(--color-ink);
text-decoration: none;
background-image: linear-gradient(var(--color-accent), var(--color-accent));
background-repeat: no-repeat;
background-position: 0 100%;
background-size: 100% 1px;
padding-bottom: 0.08em;
transition: background-size var(--dur-short) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
.link:hover { background-size: 100% 2px; }
}
[data-theme="terminal"] .link {
background: none;
text-decoration: underline;
text-underline-offset: 2px;
}
/* — Code block ——————————————————————————————————————— */
.code {
font-family: var(--font-mono);
font-size: var(--text-sm);
font-weight: 400;
color: var(--color-ink);
background: var(--color-paper-2);
border: var(--rule-hair) solid var(--color-rule);
padding: var(--space-md) var(--space-lg);
overflow-x: auto;
white-space: nowrap;
line-height: 1.7;
display: flex;
align-items: center;
gap: var(--space-md);
position: relative;
}
.code .prompt { color: var(--color-muted); user-select: none; }
.code--block { white-space: pre; display: block; }
/* — Copy-to-clipboard button (silent success pattern) ————— */
.code__copy {
position: absolute;
top: 50%;
right: var(--space-sm);
transform: translateY(-50%);
font-family: var(--font-label);
font-size: 0.6875rem;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--color-muted);
background: var(--color-paper);
border: var(--rule-hair) solid var(--color-rule);
padding: 0.35rem 0.65rem;
cursor: pointer;
opacity: 0;
transition: opacity var(--dur-short) var(--ease-out),
color var(--dur-short) var(--ease-out),
border-color var(--dur-short) var(--ease-out);
min-height: 28px;
display: inline-flex;
align-items: center;
gap: 0.4rem;
}
.code:hover .code__copy,
.code:focus-within .code__copy,
.code__copy:focus-visible {
opacity: 1;
}
@media (pointer: coarse) {
.code__copy { opacity: 1; }
}
.code__copy:hover { color: var(--color-ink); border-color: var(--color-rule-2); }
.code__copy[data-state="copied"] {
color: var(--color-accent);
border-color: var(--color-accent);
opacity: 1;
}
.code__copy-default { display: inline; }
.code__copy-done { display: none; }
.code__copy[data-state="copied"] .code__copy-default { display: none; }
.code__copy[data-state="copied"] .code__copy-done { display: inline; }
[data-theme="terminal"] .code__copy {
background: var(--color-paper-2);
border-color: var(--color-rule);
}
[data-theme="manifesto"] .code__copy {
background: var(--color-paper);
color: var(--color-paper);
border-color: var(--color-paper);
font-weight: 500;
}
[data-theme="manifesto"] .code__copy:hover {
color: var(--color-paper);
background: var(--color-accent);
border-color: var(--color-accent);
}
[data-theme="terminal"] .code {
background: var(--color-paper-3);
border-color: var(--color-rule-2);
}
[data-theme="manifesto"] .code {
background: var(--color-ink);
color: var(--color-paper);
border-color: var(--color-ink);
}
[data-theme="manifesto"] .code .prompt { color: var(--color-accent); }
/* — Specimen card ——————————————————————————————————— */
.spec {
display: flex;
flex-direction: column;
gap: var(--space-lg);
padding: var(--space-xl) 0;
border-top: var(--rule-hair) solid var(--color-rule);
}
.spec__head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: var(--space-md);
}
.spec__name {
font-family: var(--font-display);
font-weight: var(--display-weight, 400);
font-style: var(--display-style, normal);
font-size: var(--text-xl);
letter-spacing: var(--tracking-tight);
color: var(--color-ink);
line-height: 1;
font-optical-sizing: auto;
}
.spec__num {
font-family: var(--font-label);
font-size: var(--text-xs);
color: var(--color-muted);
letter-spacing: var(--tracking-label);
}
.spec__rule {
font-family: var(--font-serif);
font-weight: 400;
font-size: var(--text-md);
line-height: var(--lh-snug);
color: var(--color-ink-2);
font-style: italic;
max-width: 40ch;
}
[data-theme="brutal"] .spec__rule,
[data-theme="manifesto"] .spec__rule,
[data-theme="terminal"] .spec__rule,
[data-theme="sport"] .spec__rule,
[data-theme="almanac"] .spec__rule {
font-style: normal;
font-family: var(--font-body);
font-weight: 500;
}
[data-theme="manifesto"] .spec__name { text-transform: uppercase; }
.spec__body { margin-top: var(--space-xs); }
/* — Swatches ——————————————————————————————————————— */
.swatch-row {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: var(--space-2xs);
height: 3rem;
}
.swatch {
display: flex;
align-items: flex-end;
padding: var(--space-2xs) var(--space-xs);
font-family: var(--font-label);
font-size: 0.625rem;
letter-spacing: 0.08em;
color: var(--color-paper);
text-transform: uppercase;
}
.swatch--light { color: var(--color-ink); }
/* — Type specimen ——————————————————————————————————— */
.type-spec { display: grid; gap: var(--space-xs); }
.type-spec__display {
font-family: var(--font-display);
font-size: var(--text-2xl);
font-weight: var(--display-weight, 400);
font-style: var(--display-style, normal);
line-height: var(--lh-tight);
letter-spacing: var(--tracking-display);
font-optical-sizing: auto;
}
.type-spec__body {
font-family: var(--font-serif);
font-style: italic;
font-size: var(--text-md);
line-height: var(--lh-normal);
color: var(--color-ink-2);
max-width: 32ch;
}
[data-theme="brutal"] .type-spec__body,
[data-theme="manifesto"] .type-spec__body,
[data-theme="terminal"] .type-spec__body,
[data-theme="sport"] .type-spec__body,
[data-theme="almanac"] .type-spec__body {
font-style: normal;
font-family: var(--font-body);
}
/* — Easing curve SVG ——————————————————————————————— */
.curve {
width: 100%;
height: 4.5rem;
color: var(--color-ink);
}
/* — Tell card (gallery) ————————————————————————————— */
.tell {
flex: 0 0 18rem;
display: grid;
grid-template-rows: 13rem auto;
border: var(--rule-hair) solid var(--color-rule);
scroll-snap-align: start;
background: var(--color-paper);
}
.tell__viz {
position: relative;
overflow: hidden;
background: var(--color-paper-2);
}
.tell__meta {
padding: var(--space-md) var(--space-md) var(--space-lg);
display: grid;
gap: var(--space-xs);
border-top: var(--rule-hair) solid var(--color-rule);
}
.tell__name {
font-family: var(--font-display);
font-weight: var(--display-weight, 400);
font-style: var(--display-style, normal);
font-size: var(--text-lg);
color: var(--color-ink);
letter-spacing: var(--tracking-tight);
line-height: 1;
font-optical-sizing: auto;
}
[data-theme="manifesto"] .tell__name { text-transform: uppercase; }
.tell__why {
font-family: var(--font-serif);
font-style: italic;
font-weight: 400;
font-size: var(--text-sm);
color: var(--color-neutral);
max-width: 34ch;
line-height: var(--lh-snug);
}
[data-theme="brutal"] .tell__why,
[data-theme="manifesto"] .tell__why,
[data-theme="terminal"] .tell__why,
[data-theme="sport"] .tell__why,
[data-theme="almanac"] .tell__why {
font-style: normal;
font-family: var(--font-body);
}
/* — Tell visualisations (intentional anti-pattern demos) ————————— */
.viz-purple-hero {
position: absolute; inset: 0;
background: linear-gradient(135deg, #6b4de0 0%, #e04db4 60%, #e0904d 100%);
display: grid; place-items: center;
color: #fff;
font-family: "Inter", sans-serif;
font-size: 1rem; font-weight: 700;
letter-spacing: -0.02em;
}
.viz-purple-hero::after { content: "Unleash the future"; }
.viz-gradient-text {
position: absolute; inset: 0;
display: grid; place-items: center;
background: #fff;
}
.viz-gradient-text::after {
content: "Supercharged";
font-family: "Inter", sans-serif;
font-weight: 800; font-size: 2rem;
letter-spacing: -0.03em;
background: linear-gradient(90deg, #8a3fe0, #e03f8a);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.viz-icon-tile {
position: absolute; inset: 0;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 0.5rem; padding: 0.75rem;
background: #fff;
}
.viz-icon-tile > div {
background: #f7f7f9;
border-radius: 8px; padding: 0.5rem;
display: grid; gap: 0.35rem;
align-content: start;
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
}
.viz-icon-tile .icon {
width: 1.4rem; height: 1.4rem;
background: #e5e4ff; border-radius: 6px;
}
.viz-icon-tile .h { height: 0.45rem; background: #222; border-radius: 2px; width: 80%; }
.viz-icon-tile .t { height: 0.3rem; background: #d7d8df; border-radius: 2px; }
.viz-centered-hero {
position: absolute; inset: 0;
background: #fff;
display: grid; place-items: center; align-content: center;
gap: 0.5rem; text-align: center;
font-family: "Inter", sans-serif; padding: 1rem;
}
.viz-centered-hero .h { font-size: 1.3rem; font-weight: 800; color: #111; letter-spacing: -0.03em; }
.viz-centered-hero .s { font-size: 0.8rem; color: #666; max-width: 20ch; margin: 0 auto; }
.viz-centered-hero .c {
display: inline-block;
padding: 0.5rem 1rem;
background: linear-gradient(90deg, #6366f1, #a855f7);
color: #fff;
border-radius: 9999px;
font-size: 0.72rem; font-weight: 600;
margin-top: 0.25rem;
}
/* — Install block ————————————————————————————————————— */
.install { display: grid; gap: var(--space-sm); }
.install__head {
font-family: var(--font-label);
font-size: var(--text-xs);
letter-spacing: var(--tracking-label);
text-transform: uppercase;
color: var(--color-muted);
}
.install__body {
font-family: var(--font-mono);
font-size: var(--text-sm);
color: var(--color-ink);
padding: var(--space-md) 0;
border-top: var(--rule-hair) solid var(--color-rule);
border-bottom: var(--rule-hair) solid var(--color-rule);
line-height: 1.7;
white-space: pre-wrap;
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-md);
}
.install__body .code__copy--block {
position: static;
transform: none;
flex-shrink: 0;
}
/* — Reveals ————————————————————————————————————————— */
.reveal {
opacity: 0;
transform: translateY(10px);
}
.reveal.is-in {
animation: reveal var(--dur-long) var(--ease-out) forwards;
animation-delay: calc(var(--i, 0) * 60ms);
}
@keyframes reveal { to { opacity: 1; transform: none; } }
/* Theme-specific reveal patterns */
[data-theme="brutal"] .reveal,
[data-theme="manifesto"] .reveal,
[data-theme="sport"] .reveal {
transform: translateX(-16px);
}
@keyframes reveal-sweep { to { opacity: 1; transform: none; } }
[data-theme="brutal"] .reveal.is-in,
[data-theme="manifesto"] .reveal.is-in,
[data-theme="sport"] .reveal.is-in {
animation: reveal-sweep var(--dur-long) var(--ease-out) forwards;
animation-delay: calc(var(--i, 0) * 60ms);
}
[data-theme="atelier"] .reveal,
[data-theme="salon"] .reveal,
[data-theme="newsprint"] .reveal {
transform: none; /* no spatial reveal — fade only */
}
[data-theme="atelier"] .reveal.is-in,
[data-theme="salon"] .reveal.is-in,
[data-theme="newsprint"] .reveal.is-in {
animation: reveal-fade calc(var(--dur-long) * 1.4) var(--ease-out) forwards;
animation-delay: calc(var(--i, 0) * 80ms);
}
@keyframes reveal-fade { to { opacity: 1; } }
[data-theme="terminal"] .reveal,
[data-theme="terminal"] .reveal.is-in {
transform: none;
opacity: 1;
animation: none;
}
@media (prefers-reduced-motion: reduce) {
.reveal { transform: none; animation-duration: 150ms !important; }
@keyframes reveal { to { opacity: 1; } }
*, *::before, *::after {
animation-duration: 150ms !important;
transition-duration: 150ms !important;
}
}
/* — View transitions ————————————————————————————————— */
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 280ms;
animation-timing-function: var(--ease-out);
}