Alpine Lodge
The Wooden Hotel Kazbegi design system. Outdoorsy and earthy — bark wood, deep forest,
alpine stone and harvest gold — with every color pairing meeting WCAG 2.1 AA contrast.
All tokens live in public/css/styles.css under :root.
Color
Four families: bark (wood browns) for dark surfaces and headings, forest greens for actions, stone neutrals for backgrounds, and gold for warm accents. Feedback colors handle success, warning and danger states.
Bark — dark surfaces & headings
#221b14#32291f#4f4232#71624eForest — actions & brand green
#3d6450#2c4639Gold — warm accent (two rules, one exception)
#c79a53#d8b46e#7d6029Stone — neutrals (no yellow)
#ffffff#f7f7f3#edeee7#dfe1d6#24251f#5a5b51#d9dbce#e7ebe4Feedback
#2c6b45#e2f0e8#96591b#f5e9d7#a83a2e#b3453b#f7e4deTypography
Fraunces (serif) for headings and prices — warm and characterful. Inter (sans) for everything else. Body line-height is 1.6; headings 1.12.
--font-serif 600clamp(2.5–4.4rem) · hero only
--font-serif 600clamp(1.9–2.8rem) · section titles
--font-serif 6001.2–1.4rem · card & panel titles
--font-sans 400 · 1remcolor
--ink--font-sans 400 · 0.9–0.95remcolor
--ink-soft--font-sans 600 · 0.8remletter-spacing 0.16em ·
--gold-700--font-sans 600 · 0.78remletter-spacing 0.06em ·
--ink-softSpacing
A 4px base scale. Odd values in older code are being migrated to the nearest step.
4pxIcon-to-text gaps8pxChip gaps, tight stacks12pxRelated items in a group16pxDefault gap between elements20pxCard grid gaps, page gutter24pxCard padding32pxBetween component groups40pxSection head to content56pxLarge section breaks72pxSection vertical padding (52px mobile)Rule: default to generous, not tight. The 16px "default gap" is for elements within one group — labels and their inputs, a run of chips. The moment content splits into distinct groups (a room type's checkboxes, a cluster of stat cards, one settings block vs. the next), separate them by at least 32px. Reaching for 8–16px between groups is what makes a screen read as cluttered even when nothing is technically overlapping — leave room to breathe.
Shape & elevation
Flat design — no drop shadows, no decorative borders. Depth comes from surface color steps (white → stone-100 → stone-200). The softly-squared controls (buttons & form fields) carry a 12px radius, plain rounded (no squircle) — they're focusable and a squircle can only be applied by clipping the box, which would cut off the keyboard focus ring. Larger panels/cards (search card, room cards, modals, etc.) aren't focusable, so those get a real squircle curve instead of a plain circular corner, computed and applied as a clip-path by /js/squircle.js (a CSS-only corner-shape property exists for this, but current Chrome parses it without actually painting the curve — it silently renders a hard, un-rounded corner instead of falling back gracefully, so we don't rely on it).
--radius-btn · 12pxButtons, form field inputs (softly squared)--radius-sm · 10pxAlerts, small tags--radius · 16pxPanels--radius-lg · 24pxCards, modals999px · pillChips, badgesButtons and form fields are deliberately square-cornered, not pill-shaped — the agency portal (/platform) is a separate product with its own style guide (platform.css) and keeps the pill look; don't cross-pollinate the two.
Rule: state reads from fill color alone — never add a border on top of it. A closed calendar day, a restricted-room tag, an error banner, an unpaid notice: color says everything, so a matching-hued outline is always redundant clutter. The only exception is outline buttons, where the border is the button's whole identity (View Rooms on the hero, Update search on the rooms page, Change dates) — never add a border anywhere else.
Buttons
Softly-squared (12px radius, plain rounded), Inter 600, 13px×24px padding (16×30 for large). One primary action per view.
.btn-primary / .btn-forest / .btn-dark forest-500 → forest-700 on hover · .btn-outline-forest green text + outline, no fill · .btn-outline stone fill · .btn-danger danger-500 → 600
Rule: primary buttons are green (forest-500) on every light surface — white/cream cards, panels, modals. That covers almost every button on the site (room cards, booking modal, manage-booking panels). They only switch to gold when they sit directly on a dark surface with no light card behind them — the hero photo before the header scrolls solid, and the reservation page's dark header bar. Never use gold on a light card; never use green directly on a photo or dark bar.
.btn-ghost translucent white fill, white text — only on dark imagery or wood surfaces · .btn-primary flips to gold-400 here (see .site-header:not(.solid) .btn-primary and .res-header .btn-primary) for contrast against dark backgrounds
Form fields
Borderless, softly-squared (12px radius, textareas included) with three interaction tiers. All rings are zero-blur — flat, no glow. Labels are uppercase 0.78rem ink-soft, always outside the field; placeholders are hints, never labels. Try the fields below.
Interaction tiers
- Rest:
--fieldsage-mist fill + a faint 1px inset hairline in--field-edge(forest @ 14%) that gives the field an elegant silhouette even at rest. Selects carry an ink-soft chevron (--icon-chevron); date fields an ink-soft calendar glyph. - Hover: text entry stays inert; only click-to-open controls (selects, date fields) step one surface darker to
--stone-200. - Focus (any modality): the surface wakes to white and the same hairline deepens to a 1.5px inset edge in
--focus-edge(forest @ 70%) + a calm 4px halo in--focus-halo(forest @ 12%). Rest and focus share one silhouette, so clicking in refines the field rather than replacing it. Transition 160ms ease on background-color and box-shadow only. - Keyboard (:focus-visible): adds the loud 3px solid
--forest-500outline at 2px offset, snapping in instantly. A tiny modality gate in each page's head keeps this ring keyboard-only. - Details: caret is
--forest-500; font-size ≥16px (prevents iOS focus zoom); selects reserve 44px right padding (Safari hard-clips select text); autofill is repainted onto the sage palette; disabled =--stone-100fill + ink-soft.
Badges, chips & status
Gold badge = the one place gold-500 sits on light layouts (as a fill with wood-900 text, 6:1). Status chips pair a 700-level text tone with its 100-level tint.
Alerts
Message thread
Sender's own messages sit right in success-100; the other party sits left in stone-100. Meta line is uppercase 0.72rem ink-soft.
Accessibility
Every text/background pairing in this system meets WCAG 2.1 AA (4.5:1 body text, 3:1 large text & UI). Key pairings:
| Pairing | Used for | Ratio | AA |
|---|---|---|---|
| Aaink / stone-50 | Body text | 15.9:1 | Pass |
| Aaink-soft / stone-100 | Secondary text on panels | 5.9:1 | Pass |
| Aastone-50 / forest-500 | Primary buttons | 6.2:1 | Pass |
| Aaforest-700 / white | Secondary button text | 9.8:1 | Pass |
| Aagold-700 / stone-50 | Eyebrows, weekend tags | 5.5:1 | Pass |
| Aawood-900 / gold-500 | Gold badges | 6.0:1 | Pass |
| Aastone-100 / wood-800 | Header & footer text | 12.4:1 | Pass |
| Aaamber-700 / amber-100 | Pending / warning chips | 4.7:1 | Pass |
| Aadanger-700 / danger-100 | Errors, cancelled chips | 5.1:1 | Pass |
| Aasuccess-700 / success-100 | Success alerts | 5.4:1 | Pass |
Rules
- Focus: buttons & links show a 3px
--forest-500ring on keyboard focus (--gold-400on dark surfaces). Form fields always show the calm ring (1px forest inset edge + 3px halo, a ≥3:1 state change per WCAG 2.4.13) and add the loud outline for keyboard users. Never remove either. - Gold: gold-500/400 never as text on light — use
--gold-700. - Color alone: status is always color + a label (chips carry text, availability carries counts).
- Touch targets: buttons and nav toggles are ≥44px tall.
- Motion: all animation is disabled under
prefers-reduced-motion. - Type floor: nothing below 0.72rem, and 0.72–0.8rem only for bold labels/meta.
Voice & tone
- Warm, family-run, first person plural — “our family”, “we'll take care of it”.
- Plain words over hotel-speak: “Change of plans?” not “Modification policy”.
- Always give the next step in errors: “…Please pick different dates or message us.”
- Material (MUI) icons as wayfinding in action lists & buttons (inline SVG via
js/icons.js), never in headings or body copy.