Technology, humans, and nature. One system. A CSS design system for a climate-tech agency.
We spend pixels, color, motion, and bytes the way a good engineer spends compute. Deliberately, and never just because we can. Restraint is the default. Generosity is a decision. Tokens, type, spacing, motion, and components. CSS custom properties. One Google Font (~8 kB). Zero JS dependencies. Fully static output.
Cool ground, living accent Color tokens
The base carries the blue. It is the color of the dark, free and ambient. Green is the one rationed thing that moves your eye. Two earthy hues, one accent, discipline intact. 8 tokens. Dark: #0B1016 ground, #5AA17C accent. Light: #F3F0E8 ground, #2F7E58 accent. One accent — it does all lifting. Reserve blue for land-use category only.
Ground
slate-ocean ink · ambient, never decorativeInk
warm off-white, not pure white · less glare, less lightAccent
living green · links, one CTA, one eye-pull per viewReserve
ocean blue · sparingly, second category only (land vs energy)Conserve on body, spend on display Typography
Three voices, one for each part of the brand. An organic display for the human and natural note. A neutral system face for the work. A mono for the numbers, because the numbers are the proof. Fraunces (Google Fonts) for headings — one HTTP request, ~8 kB. System sans-serif for body — zero bytes. System monospace for figures. Type scale: 6 steps on a 1.2 ratio.
Six steps on a 1.2 ratio. Exactly one webfont loads on this page. The text in this paragraph is rendering in your own system font right now.
An 8px grid, biased generous Spacing scale
A small, fixed set of steps. White space does the work of signaling confidence, so the scale leans toward the large end. Base unit: 8px. Seven steps: 4, 8, 16, 24, 40, 64, 96. Use only these values — no ad-hoc margins or padding.
A budget, not a vibe Motion
Cheap transitions everywhere. One signature moment per page. Nothing that runs when no one is looking. All transitions: 160ms cubic-bezier(0.2, 0.8, 0.2, 1). Opacity + transform only — no layout properties. One CSS animation on page load: staggered translateY(10px) → 0, 70ms delay per item. prefers-reduced-motion: skips all of it.
Default: cheap
Opacity and transform only. Under 200ms, ease-out. GPU-friendly, barely measurable. Hover the button.
Signature: the page load
One orchestrated, staggered reveal. The deliberate spend. It honors prefers-reduced-motion and simply does not run for anyone who asked it not to.
No autoplay video. No looping background. No parallax-everything. That is the lesson from sites that look light while shipping heavy.
One real thing, in the voice Case study card
The case-study card, the format the brand actually uses. Problem, action, result, with numbers. Watch it survive the theme flip. Structure: eyebrow label, display-font title, body paragraph, stats row (mono figures + caps labels), CTA row. Uses --shadow token. Border via --border.
Two weeks of interconnection paperwork, gone in six
A 200 MW solar developer was losing two weeks per project on interconnection paperwork: copying data between PDFs, utility portals, and a homegrown tracker nobody trusted. We replaced the workflow in six weeks. The first project through the new system closed in three days.
Three weights, clear states Button variants
Primary carries the one action that matters. Ghost holds the secondary. Everything quieter is a link. Sizes and states stay predictable, because surprise is not a feature in a button. Variants: btn-primary (filled), btn-ghost (outlined), a.link (inline). Sizes: default 10/18px padding, btn-sm 7/13px, btn-lg 13/24px. Disabled: 42% opacity, hover suppressed. Spinner: CSS border animation, respects prefers-reduced-motion.
Inputs that ask real questions Form inputs
Labels say what we want to know in plain words. Placeholders show, they don't nag. When something is wrong, the error names the problem and moves on. .input, .textarea, .select. Focus ring: 3px at 22% accent opacity. Error state (.field.error): --err border + adjusted ring. Custom select arrow via inline SVG background-image. All render in system font at 1rem.
Status in our voice Status messages
When something works, say so plainly. When it breaks, name what happened, name the fix, and apologize last. The brand has a position on this. The component carries it. 4 variants: .ok, .err, .warn, .info. Border and background via color-mix() against --surface. Icon: 18px inline SVG. Status tokens: --ok, --err, --warn, --info — theme-aware.
The conversion surface, composed Contact form
Foundations and components assembled into the one block that has to earn a reply. Flip the theme, flip the power mode. It holds in all three. Composed from: .field × 3, .checkbox × 1, .btn-primary, a.link. Two-column grid (.row2) above 560px, single column below. Padding: --s6 desktop, --s5/--s4 mobile.
Tell us what you're sitting in Contact form
Not a pitch form. Three sentences about the work and what's stuck. If we're the right fit we'll say so. If we're not, we'll say that too, and point you somewhere better. Name, email, message, consent checkbox. Submit POSTs to /api/contact (not yet wired). No third-party form service.