The system this site is built from
The verno/dev tokens, rules and components, in the open and in full. Not a showcase: the same values and the same imports the production bundle is built from.
Values are read from src/styles/_variables.sass. The buttons, cards and eyebrows below are neither screenshots nor copied markup — they are the same components used on every other page.
Principles
Five decisions everything else follows from. Each one cost either a measurement or a rebuild.
- 01
One hue, committed
Everything accented is evergreen, hue ≈162°. Even ::selection and the scrollbar are re-coloured. Exactly two exceptions: the signal colours and the deliberate macOS traffic lights in the terminal.
- 02
Physically motivated glass
Light strikes from the top left: a catchlight in the corner, brighter top and left edges, a shadowed bottom-right lip. The effect reads as light behaviour, not decoration.
- 03
Dark-first
Dark values live on :root; the light theme overrides them via [data-theme="light"]. Theme-invariant tokens are declared once.
- 04
Contrast is measured, not assumed
Accent text is tuned against the worst realistic backdrop — glow over glass — not against the bare background. The measured ratio is recorded in a comment beside the token.
- 05
Effects are priced in frame time
The frame-time cost was not backdrop-filter but blurred shadows repeated across 15–20 cards: 35 ms versus 17 ms. Repeating cards use the no-blur recipe; full glass is reserved for singleton surfaces.
Colour
One hue across the interface and two themes. The light theme is not a lightened copy of the dark one but a set of values in its own right.
Evergreen
Brand, hue ≈162°
- Text on accent fills (--accent-ink)
- Heading gradient start, dark theme
- Small accent text, dark theme (--accent-text)
- The bright accent: icons, borders, glows (--accent-bright)
- Heading gradient start, light theme
- Light-theme meta text — the greenest shade that still passes AA
- Primary button fill, ::selection, skip link (--accent-btn)
- Small accent text, light theme (--accent-text)
- Deep base accent (--accent)
- Heading gradient end, light theme
Neutral
Green-tinted — never pure grey
- Dark-theme ink (--ink)
- Light-theme bg — deliberately deeper than white so glass can show
- Light-theme deep bg (--bg-deep)
- Dark-theme descriptions (--ink-soft)
- Dark-theme muted (--muted)
- Light-theme muted (--muted)
- Light-theme descriptions (--ink-soft)
- Dark-theme bg — green charcoal, not a pitch-black void
- Dark-theme deep bg (--bg-deep)
- Light-theme ink (--ink)
Signal
The only non-evergreen colours in the interface
- Success: submitted form, availability status
- Field error — soft coral, legible on dark glass
Semantic tokens
What components actually reference. The name states a role, not a shade: the colour behind it changes with the theme and, for the accent tokens, at runtime to match the platform of whichever project is open.
Accent
Theme-invariant: declared once on :root. useAccentHover rewrites exactly these when the page re-tints to a project platform — which is why components may only reference var(--accent-*), never a resolved hex.
| Token | Dark | Light |
|---|---|---|
--accent | #1A4D3E | #1A4D3E |
--accent-bright | #2E8B6F | #2E8B6F |
--accent-btn | #256B56 | #256B56 |
--accent-ink | #F4FBF7 | #F4FBF7 |
--accent-glow | rgba(46, 139, 111, 0.28) | rgba(46, 139, 111, 0.28) |
--ok | #17C46B | #17C46B |
--ok-glow | rgba(23, 196, 107, 0.18) | rgba(23, 196, 107, 0.18) |
Text and background
| Token | Dark | Light |
|---|---|---|
--bg | #191D1B | #E7ECE6 |
--bg-deep | #111413 | #DCE3DA |
--ink | #ECF2EF | #0B140F |
--ink-soft | #B9C4BE | #35423B |
--muted | #8B9891 | #4E5B54 |
--accent-text | #42AE8E | #1A5F4C |
--heading-grad-a | #4CBE9C | #2C7E66 |
--heading-grad-b | #2E8B6F | #14402D |
Surfaces and borders
Alpha values differ per theme by design: dark glass is white-tinted at 7–12 %, light glass at 45–68 %, because a pane barely darker than white on a near-white page is invisible.
| Token | Dark | Light |
|---|---|---|
--surface-glass | rgba(255, 255, 255, 0.07) | rgba(255, 255, 255, 0.45) |
--surface-glass-strong | rgba(255, 255, 255, 0.12) | rgba(255, 255, 255, 0.68) |
--surface-dock | rgba(16, 24, 20, 0.94) | rgba(252, 253, 252, 0.96) |
--border-glass | rgba(255, 255, 255, 0.14) | rgba(20, 32, 25, 0.16) |
--border-glass-strong | rgba(255, 255, 255, 0.26) | rgba(20, 32, 25, 0.28) |
--border-glass-lit | rgba(255, 255, 255, 0.3) | rgba(255, 255, 255, 0.92) |
Glass optics and atmosphere
| Token | Dark | Light |
|---|---|---|
--glass-specular | rgba(255, 255, 255, 0.09) | rgba(255, 255, 255, 0.6) |
--glass-grad-a | rgba(255, 255, 255, 0.06) | rgba(255, 255, 255, 0.5) |
--glass-grad-b | rgba(46, 139, 111, 0.05) | rgba(46, 139, 111, 0.07) |
--glass-highlight | rgba(255, 255, 255, 0.35) | rgba(255, 255, 255, 0.9) |
--glass-inner-shadow | rgba(0, 0, 0, 0.22) | rgba(20, 35, 28, 0.12) |
--shadow-color | rgba(1, 4, 3, 0.65) | rgba(20, 35, 28, 0.18) |
--atmo-glow | rgba(46, 139, 111, 0.09) | rgba(46, 139, 111, 0.13) |
--atmo-light | rgba(255, 255, 255, 0.04) | rgba(255, 255, 255, 0.5) |
Contrast
The page computes these ratios from the values in the table above — they are not transcribed from a report and cannot drift away from the tokens.
| Pair | Where | Dark | Light |
|---|---|---|---|
--ink on --bg | Body text | 15.02:1AAA | 15.64:1AAA |
--ink-soft on --bg | Descriptions | 9.49:1AAA | 8.80:1AAA |
--muted on --bg | Captions, section ledes | 5.68:1AA | 5.95:1AA |
--accent-text on --bg | Small accent text | 6.23:1AA | 6.30:1AA |
--accent-ink on --accent-btn | Primary button, text selection | 6.03:1AA | 6.03:1AA |
--accent-bright on --bg | Large text and icons only | 4.09:1AA | 3.48:1AA |
Only solid pairs appear here, where the ratio is exact. Glass surfaces are translucent, and their stacks — glow over glass, the worst case on the site — were measured by hand, with the result recorded in a comment beside the token definition.
Typography
Three faces, and both text faces carry full Cyrillic and Latin in one file — no glyph mismatch between the Russian and English versions.
- Headings carry the page
- Body text, buttons and form fields
- EYEBROW / 01 — MONO 12PX
Static scale
Every step is a multiple of 4. After consolidation some roles landed on the same value — the names stay so the code speaks in roles, not pixels.
- The quick brown fox jumps over the lazy dog
- The quick brown fox jumps over the lazy dog
- The quick brown fox jumps over the lazy dog
- The quick brown fox jumps over the lazy dog
- The quick brown fox jumps over the lazy dog
- The quick brown fox jumps over the lazy dog
- The quick brown fox jumps over the lazy dog
- The quick brown fox jumps over the lazy dog
- The quick brown fox jumps over the lazy dog
Fluid scale
Headings are set with clamp: a floor, a growth rate and a ceiling.
- Home hero (h1)
clamp(40px, 5.2vw, 72px) - Page title
clamp(36px, 5vw, 64px) - Case-study title
clamp(28px, 4.4vw, 52px) - Section heading (h2)
clamp(28px, 3.4vw, 44px) - Block title
clamp(24px, 2.8vw, 36px) - Card title
clamp(20px, 2.2vw, 28px)
Line height moves with the role — 1.1 for large headings, 1.2 for the smaller ones. A single tight value breaks at card size once a title wraps: on a two-line name the descenders nearly touch the following line.
Spacing and layout
One scale for the whole site. New margins and padding come from it and nowhere else.
$space-18px$space-212px$space-320px$space-432px$space-540px$space-660px$space-780px$space-8120px
Layout
- Container 1200px
- Padding 28px, 16px on mobile. The --narrow modifier drops to 760px for long-form text
- Section rhythm 80 / 60 px
- Default and .section--tight. Tablet 68px, mobile 48px
- Section head 640px
- Bottom margin 40px, hairline rule, mono /0N index at the bottom right
- Scroll clearance 104px
- scroll-padding-top on html is the single mechanism. scroll-margin-top on sections stacks additively and is therefore banned
Breakpoints
All max-width. The nav breakpoint is deliberately separate from the tablet one: “does the primary nav fit” is a question about 887 pixels of layout, not about a class of device.
$bp-mobile620px- Phone
$bp-nav930px- “Does the primary nav fit” — a different question from “is this a tablet”
$bp-tablet980px- Tablet
$bp-laptop1080px- Intermediate step so 621–980 is not one undifferentiated bucket
Shape and glass
Three radii and four surface recipes. Choosing a recipe is choosing a frame-time cost, not a taste.
$radius-sm12pxInputs, chips, skip link$radius20pxGlass cards and panels$radius-pill999pxButtons, scrollbar thumb
Surface recipes
The cards below demonstrate themselves: each one is built with the recipe it describes.
glass-effectSingleton surfaces: header, terminal, contact form, side menu
Catchlight + white-to-evergreen wash + translucent fill; lit top and left edges; backdrop-filter: blur(20px) saturate(160%); ambient shadow, contact shadow and two inset lips.
glass-card--no-blurRepeating cards in grids and carousels
The same face without backdrop-filter and without the blurred shadow — inset lips only. That blurred shadow repeated across 15–20 cards is what cost 35 ms per frame versus 17 ms.
glass-puckIcon containers repeated in grids
A gradient, a 1px border and one small shadow. No backdrop-filter — the icon sits in the material, not on a flat tinted box.
cursor-sheenInteractive cards
A soft radial accent pool follows the pointer inside the card: 7 % in dark, 10 % in light. useCursorSheen writes the coordinates straight into style, with no React state. The card itself never moves.
Where backdrop-filter is unsupported, every recipe falls back to the opaque --surface-glass-strong — the panel stays readable, it simply stops being glass.
Motion
One law of movement across Sass and Framer Motion — the same four numbers in both places.
Durations
$dur-fast0.2s- Micro-interactions, Framer button hover
$dur-base0.25s- Button colours, field borders, cursor sheen
$dur-slow0.6s- Large reveals
Rules
- Press scale(0.97)
- The only touch feedback there is: the native tap highlight is disabled globally because it painted a rectangle over pill buttons
- Button hover y: −2
- Framer Motion; tap is scale 0.96
- Cards no movement
- The lift and skew were removed on 13.09.2026. A card hover is its glow and the cursor sheen, nothing more
- Theme switch 0.3s
- Only the background travels; text colour snaps. Cross-fading both sends them through a grey-on-grey midpoint of ~1.1:1 where the text briefly vanishes
- Eyebrow pulse 2.6s
- A ring expands and fades on an infinite loop
- Page transition 0.35s
- Sections settle into the plane over 0.7s
Components
Live, not captured: everything below is imported from the same files the rest of the site uses. Hover and press them — the states are real.
Button
Three variants, four states. Pill, Manrope 600, 16px, padding 15×28.
| Variant | Default | Hover | Active | Disabled |
|---|---|---|---|---|
primary | --accent-btn / --accent-ink | 6px glow ring + soft halo | scale(0.97) | opacity 0.6, cursor: progress |
ghost | White 4% + blur(20px), --border-glass-strong border | Border and text go --accent-bright | scale(0.97) | opacity 0.6, cursor: progress |
on-accent | --bg / --ink | --bg-deep | scale(0.97) | opacity 0.6, cursor: progress |
The pressed state is scale(0.97) on all three variants: the native tap highlight is disabled globally because it painted a rectangle over a pill button and its shape cannot be styled. That makes touch feedback the component’s own job.
GlassCard
One surface, three modes. They differ in frame-time cost, not in appearance.
- <GlassCard />
Full glass: backdrop-filter and soft shadows. For singleton surfaces.
- noBlur
No blur and no blurred shadow, inset lips only. For grids and carousels.
- hoverLift
Accent border and glow on hover. The card itself never moves.
Eyebrow and section head
The two patterns that run through every page. The head index is a CSS counter: sections number themselves by document order.
Section heading
A 640px subtitle with a hairline rule and a mono index on the right.
Form field
Radius 12, glass fill, accent border on focus.
The 16px text size is not decorative: below it, Safari on iOS zooms the page when the field takes focus. The focus ring stays separate from the accent border — suppressing the outline outright is not an option.
Accessibility
Rules that cannot be broken without breaking the system. The first four are about colour, the last two about focus and motion.
- Small accent text uses --accent-text only. --accent-bright never carries text below large-text size: 3.97:1 under --accent-ink and 3.48:1 on the light background.
- A new accent-on-surface combination is measured against the worst stack it will sit on — glow over glass — not against bare --bg.
- Headings may take the brighter gradient stops only while the clamp floor and weight 600 keep them in WCAG large-text territory (≥18.66px bold → a 3:1 threshold).
- The measured ratio goes into a comment beside the token definition — that is how all of _variables.sass is written.
- The focus ring is 2px var(--accent-text) at a 3px offset with no border-radius override: a forced radius restyled the element itself while it held focus, squaring off circular carousel dots.
- Every motion effect must fall under the global prefers-reduced-motion kill switch — it zeroes animations, transitions and smooth scrolling, while MotionConfig reducedMotion="user" stops what Framer drives through inline styles.
Want a system like this?
If a project already has a mockup or a live site, the system can be extracted from what exists and the work can run on it from there.