Static HTML and CSS with self-hosted Geist fonts and light/dark themes, deployed by pages.yml to https://boxpositron.github.io/envsitter/ (guard page at /guard/).
8.6 KiB
EnvSitter site design system
Source of truth for site/. Every color, size, radius, shadow, and motion value in assets/styles.css maps to a token here. Add the token here first, then use it. This file is excluded from the published Pages artifact.
0. Research Log
- Embedded references: shortlisted
vercel,linear.app,supabase(developer tooling, monochrome, precise). Pickedvercelas Layer B for its achromatic palette, shadow-as-border elevation stack, compressed display tracking, and mono technical labels, which suit a CLI security library. Layer A:minimalist-skillfor macro whitespace, plain copy, and no gradient or emoji slop. - Lazyweb real-product screens: skipped. The site is a two-page static docs landing whose content is fixed by the READMEs; no product-screen grammar was needed.
- StyleGallery spatial patterns: skipped. Both pages are scroll-the-document pages with no app shell; the document owns the scroll.
- Imagen concept drafts: skipped. The focal object is a typographic artifact (redacted env file plus terminal), rendered in HTML/CSS so it stays exact, accessible, and theme-aware.
1. Direction
A redacted dossier for developer infrastructure. The canvas is engineering white (deep graphite in dark mode), and the only "imagery" is the act of redaction: secret values appear as solid redaction bars while keys stay legible in mono. The memorable moment is the hero, where values are redacted in front of the visitor and a terminal beside them answers with keys and booleans only. Color is functional: green for a match or an allowed action, red for a blocked one, blue for fingerprints and focus.
2. Color tokens
All theme tokens use light-dark() with color-scheme: light dark, so one declaration serves both themes and the OS preference decides.
| Token | Light | Dark | Role |
|---|---|---|---|
--bg |
#ffffff |
#0a0a0a |
Page canvas |
--bg-subtle |
#fafafa |
#0f0f0f |
Alternate section band |
--surface |
#ffffff |
#141414 |
Cards, panels |
--surface-2 |
#f5f5f5 |
#1c1c1c |
Inline code, chips, table header |
--text |
#171717 |
#ededed |
Headings, primary text |
--text-2 |
#4d4d4d |
#a3a3a3 |
Body copy |
--text-3 |
#666666 |
#8f8f8f |
Captions, metadata (AA on both canvases) |
--ring |
rgb(0 0 0 / 0.08) |
rgb(255 255 255 / 0.10) |
Shadow-as-border |
--ring-strong |
rgb(0 0 0 / 0.14) |
rgb(255 255 255 / 0.18) |
Hover border, dividers |
--inner |
#fafafa |
rgb(255 255 255 / 0.03) |
Inner highlight ring in the card stack |
--redact |
#171717 |
#e5e5e5 |
Redaction bar ink |
--ok |
#0f7b3d |
#3fcf7f |
Match true, allowed |
--block |
#c4291c |
#ff7a6e |
Blocked, refused |
--hash |
#0068d6 |
#5aa9ff |
Fingerprints, links |
--focus |
hsl(212 100% 48%) |
same | Focus ring |
Terminal palette (dark in both themes, so the terminal reads as a real console): --term-bg #0b0b0c, --term-bar #151517, --term-text #e6e6e6, --term-muted #8f8f94, --term-ok #3fcf7f, --term-block #ff7a6e, --term-hash #5aa9ff, --term-key #f2c46d (keys in JSON output).
Atmosphere: hero dot grid --dot (rgb(0 0 0 / 0.07) / rgb(255 255 255 / 0.06)) masked to fade out, plus one radial glow --glow (rgb(0 104 214 / 0.07) / rgb(90 169 255 / 0.08)) behind the hero visual.
3. Typography
--font-sans:"Geist", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif(self-hosted variable woff2, OFL)--font-mono:"Geist Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace(self-hosted variable woff2, OFL)- Ligatures on (
font-feature-settings: "liga"); weights 400 read, 500 interact, 600 announce. No 700.
| Role | Size | Weight | Line height | Tracking |
|---|---|---|---|---|
| Display (h1) | clamp(2.5rem, 4.2vw + 1rem, 4rem) |
600 | 1.02 | -0.045em |
| Section (h2) | clamp(1.75rem, 2vw + 1rem, 2.5rem) |
600 | 1.1 | -0.04em |
| Card title (h3) | 1.25rem |
600 | 1.3 | -0.02em |
| Lede | clamp(1.0625rem, 0.4vw + 1rem, 1.25rem) |
400 | 1.7 | normal |
| Body | 1rem |
400 | 1.65 | normal |
| Small | 0.875rem |
400/500 | 1.55 | normal |
| Eyebrow / label | 0.75rem mono, uppercase |
500 | 1.3 | 0.08em |
| Code | 0.8125rem to 0.875rem mono |
400 | 1.7 | normal |
4. Space, radius, elevation
- Spacing (4px base):
--s-1 4px,--s-2 8px,--s-3 12px,--s-4 16px,--s-5 24px,--s-6 32px,--s-7 48px,--s-8 64px,--s-9 96px,--s-10 128px. Section padding--section-y: clamp(64px, 9vw, 128px). - Container:
--container: 1120px, guttersclamp(20px, 4vw, 32px). - Radius:
--r-xs 4px(inline code),--r-sm 6px(buttons),--r-md 8px(cards),--r-lg 12px(panels),--r-pill 9999px(badges only). - Elevation (vercel shadow-as-border stack, never a CSS border on cards):
--e-1:0 0 0 1px var(--ring)--e-2:0 0 0 1px var(--ring), 0 2px 2px rgb(0 0 0 / 0.04)--e-3:0 0 0 1px var(--ring), 0 2px 2px rgb(0 0 0 / 0.04), 0 12px 24px -12px rgb(0 0 0 / 0.12), inset 0 0 0 1px var(--inner)--e-term:0 0 0 1px rgb(255 255 255 / 0.06), 0 24px 48px -24px rgb(0 0 0 / 0.45), 0 2px 6px rgb(0 0 0 / 0.10)
5. Primitives and states
| Primitive | Anatomy | States |
|---|---|---|
.btn-primary |
--text fill, --bg text, --r-sm, 40px min height |
hover: 85% fill; active: scale(0.98); focus-visible: 2px --focus outline, 2px offset |
.btn-secondary |
--surface fill, --e-1 ring |
hover: --ring-strong ring; active, focus as above |
.copy-cmd |
mono command in a --surface box with --e-2, copy button on the right |
button hidden until JS confirms clipboard support; copied: check glyph plus "Copied" announced through an aria-live region |
.card |
--surface, --e-2, --r-md, 24px to 32px padding |
static (no hover lift: cards are not interactive) |
.card-link |
.card that is a link |
hover: --e-3; focus-visible ring |
.badge |
pill, --surface-2, mono 12px uppercase |
static |
.op chip |
mono op name plus one-line description | static |
.terminal |
window bar with three dots and a title, mono body lines .ln-cmd, .ln-out, .ln-ok, .ln-block, .ln-muted |
static; horizontal scroll on narrow screens with a visible focusable region |
.envfile |
panel with key column and .redact bars |
bars animate in once on load (see motion) |
.data-table |
hairline rows, --surface-2 header |
static |
.site-header |
sticky, translucent --bg with blur, hairline bottom |
nav links: hover --text, current page aria-current |
.section-head |
eyebrow label plus h2 plus lede | static |
No coloured edge stripes anywhere: state is shown with tone (--surface-2), ink color, or glyphs.
6. Motion
- Redaction reveal (signature):
.redactbarstransform: scaleX(0 -> 1)from the left, 520mscubic-bezier(0.16, 1, 0.3, 1), staggered 90ms per row, after a 250ms delay. It demonstrates the product idea (values become bars) and runs once. - Buttons:
transformandbackground-color150ms ease;scale(0.98)on active. - Copy confirmation: glyph swap with
opacity150ms. prefers-reduced-motion: reduce: no transitions or animations; bars render in their final state.- Only
transformandopacityanimate. No scroll-triggered reveals.
7. Responsive
- Breakpoints:
640px(nav collapses to essentials, single column),960px(hero switches from stacked to two columns, grids to 2 or 3 columns). - Hero visual stacks below the copy under 960px. Terminal and code blocks scroll horizontally inside their own focusable region instead of overflowing the page.
- Tap targets at least 40px; body text never below 16px.
8. Accessibility constraints and accepted debt
- WCAG AA contrast for all text tokens in both themes (verified pairs listed in section 2); terminal colors checked against
--term-bg. - Semantic landmarks (
header,nav,main,footer), oneh1per page, skip link,aria-current="page"on the active nav item. - Redaction bars are decorative: each value cell has
aria-label="value hidden"text for assistive tech. - Every page works without JavaScript; JS only adds copy buttons.
- Accepted debt: no manual theme toggle (the OS preference decides); no search; content is duplicated from the READMEs and must be updated by hand when the tool surface changes.
- Accepted debt: Lighthouse mobile performance is 99 (simulated slow 4G with gzip: FCP 1.2s, LCP 1.8s, TBT 0, CLS 0); desktop is 100, and accessibility, best practices, and SEO are 100 on both. The last point needs the stylesheet inlined at deploy time, deferred to keep the site build-free. Dropping the Geist preload was measured and made FCP worse (1.7s), so the preload stays.