Warm, atmospheric, and grounded. Valley Real Life’s interface evokes a quiet evening — deep, faintly green-tinted darks that feel organic rather than clinical, broken open by a single luminous green that glows wherever it lands. The aesthetic is photographic and welcoming, not flat or sterile; surfaces have layered depth from subtle gradients and soft brand-tinted glows.
The system is dark-first but ships a light theme via a single semantic-token override. Both modes share the same primitive scales — light mode flips which stops the semantic tokens point at. No pure black, no pure white. Even the darkest backgrounds carry a hint of green undertone (hue 135); even the brightest text sits a step below pure white.
The voice should feel like a trusted invitation. Headlines are large and slightly tracked-in; body copy is comfortable, never crowded. Eyebrow labels — uppercase, heavily tracked, brand-colored — are the system’s signature, reinforcing a hierarchical, sectioned reading experience inspired by church bulletins and editorial design.
Architecture is three-tier and cascading:
v-green-50…950, v-neutral-50…950), theme-stable.primary, surface, on-surface, etc.) reference primitives.To re-skin: change a primitive, every semantic token cascades, every component updates. To override locally: change a semantic token at any scope.
The palette is built from two OKLCH-derived scales. A single saturated green carries the entire brand identity; everything else is a warm gray-greige with a faint green undertone (OKLCH hue 135) that keeps the neutrals from feeling cold.
v-green is the brand scale, anchored at v-green-500 (#9BC93C). The 300 stop is the brightest highlight; the 600 stop is the natural hover step-down; the 800–950 stops are reserved for pressed states and deep backgrounds.
v-neutral is a 22-stop neutral ramp. It is not pure gray — every stop carries chroma ≈ 0.008–0.016 at hue 135, giving the system a subtle warm-green undertone that ties the neutrals to the brand without competing with it.
Both themes pull from the same primitive scales. The light theme override only changes which stops the semantic tokens reference (e.g. surface swaps from v-neutral-800 → v-neutral-100). Brand stays identical in both modes for instant recognition.
For brand-colored text on light backgrounds (e.g. eyebrow labels in light theme), use v-green-700 instead of v-green-500 to maintain WCAG AA contrast. The system token brand-text handles this swap automatically.
success, info, warning, and error are tuned to read clearly on dark surfaces without competing with brand green for attention. They are currently one-offs; when a second use case appears, promote them into full v-amber, v-blue, v-red primitive scales using the same 50-950 pattern.
Montserrat carries the entire system — headings, body, labels. Weight and tracking do the work that a second typeface would normally do. Six weights ship: 300 (sparingly, very large display only), 400 (body), 500–600 (UI), 700–800 (headings).
The type scale is fully rem-based and modulated by a single --font-scale multiplier (default 1.0625, ≈ 17px root). Changing that one value lifts or shrinks every size proportionally while remaining compatible with the user’s browser font-size preference.
-0.02em) at extra-bold weight for impact without shouting.0.01em) and weighted 600.Headings get -0.02em to -0.01em (tracking-tight). Body stays neutral. UI labels get +0.01em to +0.04em. Eyebrow labels get +0.25em (tracking-widest) and text-transform: uppercase.
The page is centered in a 1180px max-width container with 24px side gutters. Inside, a rem-based spacing scale anchors a consistent vertical rhythm.
The spacing scale runs 3xs (2px) through 4xl (96px) on a roughly Fibonacci-ish curve. Most UI uses md (16px) for internal padding and lg (24px) for the gap between related blocks. Use 2xl (48px) or 3xl (64px) between distinct page sections. Always prefer logical properties (padding-inline, margin-block) for future-proofing.
Two ratios cover nearly all imagery:
Apply via aspect-ratio: var(--thumb-ratio) or aspect-ratio: var(--video-ratio). Never crop content inside these — always object-fit: cover the image and let the ratio drive the box.
No strict grid system. Cards and tiles use display: grid with grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)) to flex naturally without breakpoint juggling. Tile grids commonly use 4 columns wide, 3 columns, or 2 columns depending on density.
Elevation comes from two layers: tonal layering (surfaces stack from bg → bg-elevated → surface → surface-hover → surface-active, each ~3-5% lighter in dark mode, darker in light mode), and shadow + glow on top.
shadow-xs — subtle lift on tags and small chips.shadow-sm — resting state on cards.shadow-md — floating elements like popovers.shadow-lg — hover states, dropdown menus, dialogs.shadow-xl — modals, sheets.shadow-inset — pressed buttons (rarely used).All shadows use oklch(0 0 0 / α) for true neutrality and reduce ~3× in opacity in light mode.
The signature elevation effect. A soft radial glow in brand green, used on:
box-shadow ring at oklch(from primary l c h / 0.2)).Two glow tokens: shadow-glow (default) and shadow-glow-lg (hover). Both auto-derive from the brand color via the relative color syntax — change the brand once and every glow updates.
Generously rounded, with pill shapes as the signature for any interactive element.
rounded-xs (4px) — code blocks, very small inset chips.rounded-sm (8px) — inputs nested inside larger containers, tabs.rounded-md (12px) — text inputs, dropdown items, smaller cards.rounded-lg (16px) — default for cards. Most cards and thumbnails.rounded-xl (24px) — heroes, large containers, modals.rounded-2xl (32px) — extra-large display containers.rounded-full (9999px) — default for buttons, inputs, tags, badges, breadcrumb pills, the theme toggle. The system’s signature shape.A separate shape primitive — an iOS-style superellipse, “square but not square.” Implemented as a CSS mask-image (inline SVG) rather than a numeric border-radius, because superellipses can’t be expressed with border-radius alone. Use the .squircle utility class or mask-image: var(--squircle-mask). Note: mask-image clips outer box-shadow, so wrap the element if you need a drop shadow, or use filter: drop-shadow() on the parent.
Component styling consumes semantic tokens only. The component layer never references primitives or hex values directly — change primary and every primary-based component updates.
Pill-shaped (rounded-full), generously padded, with label-button typography. Five variants:
on-primary text, brand glow (shadow-glow default, shadow-glow-lg on hover). Hover steps up to tertiary (brighter); active steps down to v-green-600 (the deepest stop still passing WCAG AA against on-primary). Used for the single most important action per visual unit.border-strong outline, on-surface text. Hover swaps border and text to primary. Used for paired secondary actions next to a primary.surface and brightens text. Used for tertiary or destructive-but-safe actions, icon-only buttons.brand-text color. Subtle but unmistakably branded. Used for “soft” CTAs inside detail cards.@property --sparkle-angle) over a pulsing radial brand glow. Used sparingly — “I’m New”, “Get Started”, major onboarding moments. Respects prefers-reduced-motion.Sizes: sm, default, lg. Icon-only variant collapses padding to a square shape (aspect-ratio: 1).
All cards use rounded-lg (16px) by default. Three primary patterns:
surface to bg-elevated. Used for “Next Steps” entry points.transform: translateY(50%) puts them half on the image, half on the body), title, meta line with brand icon, body, and a split-action footer (two buttons sharing a horizontal bar at the bottom).rounded-md (12px) for most fields. Border defaults to border-strong; hover lifts to border-strong-hover (a step up); focus removes the outline and replaces it with a 1px brand border plus a 4px tinted ring (oklch(from primary l c h / 0.2)).
Search Pill — A signature input shape. rounded-full container with a 4px padding gap and an embedded circular brand-filled button on the right. Used for filter/browse at the top of list views.
Selection Controls — Custom-styled checkboxes (square + animated checkmark), radio buttons (circle + filled dot), and toggle switches (pill with brand-filled knob when on). All built with the native input hidden and a styled adjacent element.
Tiny pill labels (caption typography). Default variant inverts per theme — dark pill on light surfaces, light pill on dark surfaces — for guaranteed contrast. Brand variant uses the full primary fill. Ghost variant has a transparent background and a thin border.
Compatible with Rock RMS markup (<ol class="breadcrumb"> with <li> items, .active or aria-current="page" on the current page). Three variants:
rounded-full pill container with a surface background. Used inside hero headers.Two patterns for internal pages (the homepage gets its own treatment):
.page-title) — Text-only. Optional eyebrow kicker, h1, lede, and right-aligned actions. Border-bottom rule. Most internal pages..page-hero) — Image background with a brand-tinted radial overlay in the bottom-left plus a vertical dark gradient. Breadcrumb, kicker, large headline, lede, action buttons. Always treated as a dark surface regardless of page theme — the image and overlay provide their own context. Three modifiers: --short (220px min), --tall (420px min), --centered (text-align center).A pill-shaped track containing a brand-filled circular knob that translates horizontally between sun and moon icons. The knob carries the brand glow. State persists in localStorage; initial state respects prefers-color-scheme.
primary) exactly once per visual unit. Never stack two primary actions.label-eyebrow) to introduce sections, not as standalone headings.rounded-full) for all interactive elements — buttons, inputs, tags, breadcrumb pills.v-green-700 for brand text on light backgrounds.Do consume semantic tokens in components. If you need a primitive directly, that’s a signal to add a new semantic token.
on-surface and v-neutral-950 instead.rounded-xs and rounded-full elements next to each other.v-green-500) directly in components. Always go through a semantic token (primary).box-shadow clipping — wrap the element or use filter: drop-shadow().object-fit: cover do the work so different image sizes degrade gracefully.