Files
fieldwork/dist/style.css
T
jzzliu bd89201e25 Rework storefront into modules and tokenised styles
Split the 100-line script into catalog, cart, format, dom and main modules.
Money is integer cents, the bag is capped and sanitised and syncs across
tabs, and the view (category, sort) lives in the URL. Templates escape every
interpolation.

Rewrite the minified stylesheet as cascade layers on design tokens. Fixes
heading tracking that collapsed word gaps, a 2.6:1 focus ring on the hero,
1.3:1 control borders, misaligned cart quantities, uneven product cards and
undersized touch targets. Adds a favicon and a resolvable dialog label.
2026-11-07 21:18:38 -07:00

498 lines
14 KiB
CSS

/*
* Fieldwork Supply storefront.
*
* Organisation: cascade layers, lowest priority first (reset < tokens < base < layout <
* components < motion < utilities), so rules never fight over specificity.
*
* Design language
* - Pills (999px) for navigation and toggles: bag, close, filter chips.
* - Square corners for actions and inputs: primary buttons, add, quantity, select.
* - Mono caps for labels and prices; tight, bold sans for display headings.
* - Every colour, size and space comes from a token below; no one-off values in components.
*
* Contrast (WCAG): --ink 13.8:1, --muted 6.7:1 on paper (5.8:1 on --sage), --lime on --forest
* 10.5:1, --focus 5.7:1 on paper, --control 3.4:1 on paper (UI components need 3:1).
*/
@layer reset, tokens, base, layout, components, motion, utilities;
/* ------------------------------------------------------------------ reset */
@layer reset {
*, *::before, *::after { box-sizing: border-box; }
body, h1, h2, h3, p { margin: 0; }
img { display: block; max-width: 100%; height: auto; }
button, select { font: inherit; color: inherit; }
button { cursor: pointer; }
button, a, select { touch-action: manipulation; }
a { color: inherit; text-decoration: none; }
}
/* ------------------------------------------------------------------ tokens */
@layer tokens {
:root {
color-scheme: light;
/* colour */
--paper: #f7f5ed;
--sage: #e4e7d9; /* story section */
--ink: #1d2924;
--muted: #50594e;
--forest: #20392d;
--forest-deep: #192c24; /* image caption chip */
--lime: #e2f596;
--lime-strong: #d3ea78; /* lime hover */
--leaf: #53794b; /* wordmark full stop */
--line: #d8dbce; /* decorative dividers only */
--control: #808876; /* borders of interactive controls */
--on-dark: #fbfaf0;
--on-dark-soft: #e1e6d9;
--focus: #3d6b35;
--scrim: rgb(16 27 22 / .67);
/* type */
--font-sans: system-ui, sans-serif;
--font-mono: ui-monospace, "SF Mono", Menlo, monospace;
--text-label: .72rem;
--text-small: .875rem;
--text-base: 1rem;
--text-title: clamp(1.05rem, .6rem + 1.6vw, 1.3rem);
--text-h2: clamp(2.5rem, 5vw, 4.75rem);
--text-h1: clamp(4rem, 8.4vw, 8.5rem);
--track-display: -.05em;
--track-label: .12em;
/* space */
--space-1: .25rem;
--space-2: .5rem;
--space-3: .75rem;
--space-4: 1rem;
--space-5: 1.5rem;
--space-6: 2rem;
--space-7: 3rem;
--space-8: 4.5rem;
--page: min(1320px, 100% - 8vw);
--target: 2.5rem; /* minimum control size; 44px on touch screens, see below */
/* shape, depth, motion */
--radius-pill: 999px;
--shadow-float: 0 10px 30px rgb(16 27 22 / .22);
--ease: cubic-bezier(.2, .7, .2, 1);
--duration: .2s;
}
@media (pointer: coarse) {
:root { --target: 2.75rem; }
}
}
/* ------------------------------------------------------------------ base */
@layer base {
html {
scroll-behavior: smooth;
scrollbar-gutter: stable;
-webkit-text-size-adjust: 100%;
}
html:has(dialog[open]) { overflow: hidden; } /* no background scroll behind a modal */
body {
background: var(--paper);
color: var(--ink);
font-family: var(--font-sans);
line-height: 1.5;
}
h1, h2, h3 { text-wrap: balance; }
:focus-visible {
outline: 3px solid var(--focus);
outline-offset: 4px;
}
.hero :focus-visible { outline-color: var(--lime); } /* the green ring is only 2.6:1 on forest */
.eyebrow {
font-family: var(--font-mono);
font-size: var(--text-label);
font-weight: 700;
letter-spacing: var(--track-label);
line-height: 1.4;
}
.price {
font-family: var(--font-mono);
font-size: var(--text-small);
font-weight: 700;
font-variant-numeric: tabular-nums;
margin-top: var(--space-1);
}
.wordmark {
font-size: 2rem;
font-weight: 850;
letter-spacing: -.06em;
line-height: 1;
}
.wordmark span { color: var(--leaf); }
}
/* ------------------------------------------------------------------ layout */
@layer layout {
.notice {
padding: .62rem var(--space-4);
background: var(--lime);
text-align: center;
font-family: var(--font-mono);
font-size: clamp(.6rem, 2.7vw, var(--text-label)); /* scales down so it stays on one line */
font-weight: 700;
letter-spacing: var(--track-label);
}
.top, main, footer { width: var(--page); margin-inline: auto; }
.top {
height: 78px;
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-4);
}
.top nav { display: flex; gap: var(--space-6); font-size: var(--text-small); }
.top nav a:hover, footer a:hover { text-decoration: underline; text-underline-offset: 4px; }
/* Standalone links get a full-size hit area (inline links inside paragraphs are exempt). */
.top nav a, .story > a, footer > a:last-child {
display: inline-flex;
align-items: center;
min-height: var(--target);
}
/* hero */
.hero {
display: grid;
grid-template-columns: 43% 57%;
min-height: 520px;
background: var(--forest);
color: var(--on-dark);
}
.hero-copy {
padding: clamp(2rem, 5.4vw, 6rem);
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
gap: 1.3rem;
}
.hero .eyebrow { color: var(--lime); }
.eyebrow i {
display: inline-block;
width: .55rem;
height: .55rem;
margin-right: var(--space-2);
border-radius: 50%;
background: currentColor;
}
.hero h1 {
font-size: var(--text-h1);
line-height: .84;
letter-spacing: var(--track-display);
word-spacing: .06em;
}
.hero h1 span { color: var(--lime); }
.hero-copy > p:not(.eyebrow) { max-width: 29ch; color: var(--on-dark-soft); }
.hero-art { position: relative; min-height: 100%; overflow: hidden; }
.hero-art img { position: absolute; width: 100%; height: 100%; object-fit: cover; }
.image-label {
position: absolute;
right: var(--space-4);
bottom: var(--space-4);
padding: .55rem var(--space-3);
background: var(--forest-deep);
color: #fff;
font-family: var(--font-mono);
font-size: var(--text-label);
letter-spacing: .07em;
}
/* shop */
.shop { padding: var(--space-8) 0; }
.shop-heading {
display: flex;
align-items: end;
justify-content: space-between;
gap: var(--space-4);
margin-bottom: var(--space-5);
}
.shop h2, .story h2 {
margin-top: var(--space-2);
font-size: var(--text-h2);
line-height: .98;
letter-spacing: var(--track-display);
word-spacing: .06em;
}
.shop-heading > p { color: var(--muted); }
.controls {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: var(--space-3);
padding: var(--space-3) 0;
margin-bottom: var(--space-5);
border-block: 1px solid var(--line);
}
#filters { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.controls label {
display: flex;
align-items: center;
gap: var(--space-2);
font-size: var(--text-small);
}
.grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: var(--space-6) var(--space-4);
}
/* story */
.story { padding: clamp(2rem, 6vw, 6rem); background: var(--sage); }
.story h2 { max-width: 15ch; }
.story > p:not(.eyebrow) { max-width: 53ch; margin: var(--space-5) 0 var(--space-4); line-height: 1.65; }
.story a { text-decoration: underline; text-underline-offset: 4px; }
/* footer */
footer {
display: grid;
grid-template-columns: 1fr auto 1fr;
align-items: center;
gap: var(--space-4);
padding: 2.5rem 0;
font-size: var(--text-small);
}
footer p { text-align: center; }
footer > a:last-child {
justify-self: end;
text-decoration: underline;
text-underline-offset: 4px;
}
@media (max-width: 760px) {
.top { height: 65px; }
.top nav { gap: var(--space-3); }
.hero { grid-template-columns: 1fr; min-height: 0; }
.hero-art { min-height: 310px; }
.hero h1 { font-size: clamp(4.2rem, 14vw, 6.5rem); }
.shop { padding: var(--space-7) 0; }
.grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
footer { grid-template-columns: 1fr; justify-items: start; gap: var(--space-3); }
footer p { text-align: left; }
footer > a:last-child { justify-self: start; }
}
@media (max-width: 420px) {
.top nav a:last-child { display: none; }
.shop-heading > p { display: none; }
}
@media (max-width: 380px) {
.notice { padding-inline: var(--space-2); letter-spacing: .04em; }
}
@media (max-width: 340px) {
.grid { grid-template-columns: 1fr; }
}
}
/* ------------------------------------------------------------------ components */
@layer components {
/* pills: navigation and toggles */
.bag, .close, #filters button {
min-height: var(--target);
padding: 0 var(--space-4);
border: 1px solid var(--control);
border-radius: var(--radius-pill);
background: transparent;
}
.bag { display: inline-flex; align-items: center; gap: var(--space-2); }
.bag span {
display: inline-grid;
place-items: center;
width: 1.4rem;
height: 1.4rem;
border-radius: 50%;
background: var(--forest);
color: #fff;
font-size: .75rem;
font-variant-numeric: tabular-nums;
}
.close { min-width: var(--target); padding: 0; font-size: 1.35rem; line-height: 1; }
#filters button { font-size: var(--text-small); }
#filters button[aria-pressed="true"] {
background: var(--forest);
border-color: var(--forest);
color: #fff;
}
/* square: actions and inputs */
.primary {
display: inline-flex;
align-items: center;
justify-content: space-between;
gap: var(--space-4);
min-height: var(--target);
padding: .9rem 1.15rem;
border: 0;
background: var(--lime);
color: var(--ink);
font-weight: 750;
}
.add, .qty button {
display: grid;
place-items: center;
min-width: var(--target);
min-height: var(--target);
padding: 0;
border: 1px solid var(--control);
background: transparent;
font-size: 1.3rem;
line-height: 1;
}
.qty button:disabled { opacity: .4; cursor: not-allowed; }
.controls select {
min-height: var(--target);
padding: 0 var(--space-3);
border: 1px solid var(--control);
border-radius: 0;
background: transparent;
}
@media (hover: hover) {
.primary:hover { background: var(--lime-strong); }
.add:hover, .qty button:not(:disabled):hover { background: var(--lime); }
#filters button[aria-pressed="false"]:hover,
.bag:hover, .close:hover { background: var(--sage); }
}
/* product card */
.product { min-width: 0; }
.product-image { display: block; width: 100%; padding: 0; border: 0; background: transparent; }
.product-image img { transition: filter var(--duration); }
@media (hover: hover) {
.product-image:hover img { filter: brightness(.94); }
}
.product-info {
display: grid;
grid-template-columns: 1fr auto;
align-items: start;
gap: var(--space-2);
padding: var(--space-3) 0;
}
.product h3 { font-size: var(--text-title); letter-spacing: -.03em; line-height: 1.2; }
.product .eyebrow { margin-bottom: var(--space-1); color: var(--muted); }
/* dialogs */
dialog {
width: 100%;
max-width: min(520px, calc(100% - 2rem));
max-height: 90dvh;
padding: 1.3rem;
overflow: auto;
border: 1px solid var(--line);
background: var(--paper);
color: var(--ink);
}
dialog::backdrop { background: var(--scrim); }
.dialog-head, .subtotal {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-4);
margin-bottom: var(--space-4);
}
#product-detail h2 { margin-top: var(--space-4); font-size: 1.6rem; letter-spacing: -.03em; line-height: 1.15; }
#product-detail > p { margin: var(--space-3) 0 var(--space-4); line-height: 1.5; }
/* bag drawer */
#cart {
height: 100dvh;
max-height: 100dvh;
max-width: min(470px, 100%);
margin: 0 0 0 auto;
border: 0;
box-shadow: var(--shadow-float);
}
.cart-row {
display: grid;
grid-template-columns: 75px 1fr auto;
align-items: center;
gap: var(--space-3);
padding: var(--space-3) 0;
border-bottom: 1px solid var(--line);
}
.qty {
display: grid;
grid-template-columns: var(--target) 2ch var(--target); /* fixed columns: rows line up */
align-items: center;
gap: var(--space-1);
text-align: center;
font-variant-numeric: tabular-nums;
}
.subtotal { margin-top: var(--space-4); }
.fine { margin-top: var(--space-3); font-size: var(--text-small); color: var(--muted); }
.empty { margin-bottom: var(--space-4); color: var(--muted); }
#order { padding: var(--space-4); background: var(--sage); line-height: 1.5; }
#order:focus-visible { outline-offset: 2px; }
#toast {
position: fixed;
right: var(--space-4);
bottom: var(--space-4);
z-index: 3;
max-width: calc(100% - 2rem);
padding: var(--space-3) var(--space-4);
background: var(--forest);
color: var(--on-dark);
box-shadow: var(--shadow-float);
}
#toast:empty { display: none; }
.notice-js { padding: var(--space-4); background: var(--sage); }
@media (max-width: 760px) {
#toast { left: var(--space-4); }
}
}
/* ------------------------------------------------------------------ motion */
@layer motion {
@media (prefers-reduced-motion: no-preference) {
dialog[open] { animation: rise var(--duration) var(--ease); }
#cart[open] { animation: slide-in .28s var(--ease); }
dialog[open]::backdrop { animation: fade var(--duration) ease-out; }
#toast:not(:empty) { animation: rise .24s var(--ease); }
}
@media (prefers-reduced-motion: reduce) {
html { scroll-behavior: auto; }
* { transition: none !important; }
}
@keyframes rise { from { opacity: 0; translate: 0 .5rem; } }
@keyframes slide-in { from { translate: 100% 0; opacity: 1; } }
@keyframes fade { from { opacity: 0; } }
}
/* ------------------------------------------------------------------ utilities */
@layer utilities {
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
.skip-link {
position: absolute;
left: var(--space-4);
top: var(--space-2);
z-index: 10;
padding: var(--space-2) var(--space-4);
background: var(--forest);
color: var(--on-dark);
translate: 0 -200%;
}
.skip-link:focus { translate: 0 0; }
[hidden] { display: none !important; }
}