---
source: https://design.tea-rex.com.ua
version: 0.11.3
last-updated: 2026-04-15
format: brand-spec/1.0
token-file: https://design.tea-rex.com.ua/design-tokens.json
schema: https://www.designtokens.org/tr/2025.10/format/
---

# Tea-Rex Brand Design System

Tea-Rex is a premium Ukrainian specialty tea brand. The visual identity is warm, focused, and editorial — restrained luxury without pomposity. Design decisions consistently serve four emotional anchors:

**Спокій · Зібраність · Концентрація · Затишок**
_(Calm · Composure · Focus · Cosiness)_

All design tokens referenced below follow the W3C DTCG 2025.10 format and are defined in `https://design.tea-rex.com.ua/design-tokens.json`.

---

## §1 Brand Identity

### §1.1 Brand Name

Written as **Tea-Rex** (hyphen, sentence case) in editorial and UI contexts. Display/banner form: **TEA-REX.** (all-caps with trailing period) rendered in Rubik Bold.

### §1.2 Emotional Anchors

| Anchor | Ukrainian | Design expression |
|--------|-----------|-------------------|
| Calm | Спокій | Achieved through whitespace, restrained palette, and no competing visual noise |
| Composure | Зібраність | Tight type hierarchy, consistent structure, systematic spacing |
| Focus | Концентрація | High contrast between premium surfaces, single accent colour |
| Cosiness | Затишок | Warm cream background, obsidian–coral combination, paper grain texture |

### §1.3 Premium Combination

The canonical brand combination: **Obsidian `{color.brand.obsidian}` background + Coral `{color.brand.coral}` accent + Cream `{color.brand.cream}` text.**

Apply to: banners, packaging, print covers, footer, masthead. This is the highest-impact treatment in the system. Maximum contrast within the palette; warm intimacy from cream on obsidian.

### §1.4 Logo

Two variants are served from `https://design.tea-rex.com.ua/logo/`:

| File | Usage |
|------|-------|
| `logo-round.png` | Round mark — favicons, avatars, compact contexts |
| `logo-text.png` | Wordmark with text — full branding contexts |

**Rules:**
- Display in original brand colours only. No recolouring.
- Minimum clear space: equal to the diameter of the round mark on every side.
- Do NOT: stretch, rotate, add drop shadow, or place on a background without adequate contrast.

### §1.5 Voice and Tone

Concise, precise, warm but never cold. Content follows the AFABA structure: Attention → Features → Advantages → Benefits → Action.

**Restrictions:** No religious references, no political content, no medical or health claims, no other typefaces beyond Rubik Bold and Montserrat.

---

## §2 Color Palette

### §2.1 Primary Brand Colors

| Token | Name | HEX | RGB | CMYK | PMS |
|-------|------|-----|-----|------|-----|
| `{color.brand.coral}` | Main Coral | #FC4E4D | 252, 78, 77 | 0, 69, 69, 1 | PMS 1787 C |
| `{color.brand.coral-bright}` | Coral Bright | #FF6B6A | — | — | — |
| `{color.brand.cream}` | Off-White | #FAF8F4 | 250, 248, 244 | 0, 1, 2, 2 | PMS 663 C |
| `{color.brand.obsidian}` | Obsidian Black | #1A1C1B | 26, 28, 27 | 7, 0, 4, 89 | Obsidian Black |

`{color.brand.coral-bright}` is a hover-only state colour. Never use it as a default fill.

### §2.2 Extended Palette — Light Mode

| Token | Value | Role |
|-------|-------|------|
| `{color.background.light.default}` | `{color.brand.cream}` | Page background |
| `{color.background.light.paper}` | #ffffff | Card and surface background |
| `{color.text.light.primary}` | `{color.brand.obsidian}` | Primary text |
| `{color.text.light.secondary}` | #525252 | Secondary / muted text |
| `{color.divider.light.subtle}` | rgba(26, 28, 27, 0.08) | Card and paper borders |
| `{color.divider.light.strong}` | rgba(26, 28, 27, 0.16) | Divider lines |
| `{color.common.white}` | #ffffff | Pure white |

### §2.3 Extended Palette — Dark Mode

| Token | Value | Role |
|-------|-------|------|
| `{color.background.dark.default}` | #121212 | Page background |
| `{color.background.dark.paper}` | #1e1e1e | Card and surface background |
| `{color.text.dark.primary}` | #ffffff | Primary text |
| `{color.text.dark.secondary}` | #c8c4bc | Secondary / muted text (warm off-white) |
| `{color.divider.dark.subtle}` | rgba(250, 248, 244, 0.10) | Card and paper borders |
| `{color.divider.dark.strong}` | rgba(250, 248, 244, 0.20) | Divider lines |

### §2.4 Color Usage Rules

**Do:**
- Use `{color.brand.coral}` for the logo, accents, CTAs, hover states, and decorative underlines.
- Use `{color.brand.cream}` as the default page background — never substitute `#ffffff`.
- Use `{color.brand.obsidian}` for primary text and as the dark premium surface (footer, banner, masthead).
- Apply the premium combination (obsidian + coral + cream) to all hero, banner, and packaging contexts.
- Always display the logo in original brand colours.

**Don't:**
- Use coral as a large background fill on its own — reserve it for accents and highlights.
- Substitute cream with pure white for the page background.
- Use `#000000` as the brand black — obsidian `{color.brand.obsidian}` is the system black.
- Mix other palette colours not defined in this spec.

---

## §3 Typography

### §3.1 Font Families

| Token | Family | Weights | Source | Usage |
|-------|---------|---------|--------|-------|
| `{typography.font-family.primary}` | Montserrat | 100–900 (variable) | Google Fonts | All web body text, UI labels, and headings |
| `{typography.font-family.display}` | Rubik | 700 only | Google Fonts | Banners, hero display text, video text overlays **only** |

CSS variables loaded by Next.js `next/font/google`: `--font-montserrat`, `--font-rubik`.

### §3.2 Type Scale

Base: `{typography.font-size.base}` = 14px. All `rem` values are relative to this root.

| Variant | Token | Size | Weight | Line-height | Letter-spacing | Transform |
|---------|-------|------|--------|-------------|----------------|-----------|
| h1 | `{typography.style.h1}` | 3rem (42px) | 700 | 1.2 | 0.04em | uppercase |
| h2 | `{typography.style.h2}` | 2.5rem (35px) | 700 | 1.3 | 0.04em | uppercase |
| h3 | `{typography.style.h3}` | 2rem (28px) | 600 | 1.3 | 0.05em | uppercase |
| h4 | `{typography.style.h4}` | 1.75rem (24.5px) | 600 | 1.4 | 0.05em | uppercase |
| h5 | `{typography.style.h5}` | 1.5rem (21px) | 600 | 1.4 | 0.05em | uppercase |
| h6 | `{typography.style.h6}` | 1.25rem (17.5px) | 600 | 1.5 | 0.05em | uppercase |
| body1 | `{typography.style.body1}` | 1rem (14px) | 400 | 1.6 | 0.01em | — |
| body2 | `{typography.style.body2}` | 0.875rem (12.25px) | 400 | 1.6 | 0.01em | — |
| button | `{typography.style.button}` | 0.875rem (12.25px) | 500 | 1.6 | 0.01em | none |
| caption | `{typography.style.caption}` | 0.75rem (10.5px) | 400 | 1.6 | 0.01em | — |
| overline | `{typography.style.overline}` | 0.75rem (10.5px) | 500 | 1.4 | 0.16em | uppercase |
| eyebrow | `{typography.style.eyebrow}` | 0.6875rem (9.625px) | 500 | 1 | 0.24em | uppercase |

### §3.3 Rubik Bold Display Usage

`{typography.style.banner}` — Rubik Bold 700, range **42–52px** (banner), **24–36px** (video captions).

Letter-spacing: `-0.015em` (tight, editorial). Line-height: 1.05–1.2.

Use for: print covers, full-bleed hero images, video text overlays, packaging.
Do NOT use Rubik for: body paragraphs, UI labels, web component text.

### §3.4 H2 Coral Underline Rule

Every `<h2>` in the web layout automatically renders a coral decorative underline via `::after` pseudo-element:

```css
h2 {
  position: relative;
}

h2::after {
  content: '';
  position: absolute;
  bottom: -10px;   /* {effect.h2-underline.bottom-offset} */
  left: 0;
  height: 3px;     /* {effect.h2-underline.height} */
  width: 128px;    /* {effect.h2-underline.width-mobile} */
  background-color: #FC4E4D; /* {effect.h2-underline.color} */
}

@media (min-width: 900px) {
  h2::after {
    width: 160px;  /* {effect.h2-underline.width-desktop} */
  }
}
```

**Stack clearance:** When `<h2>` appears inside a flex/stack container, apply `margin-bottom: 32px` (`{spacing.h2-margin-bottom-in-stack}`) to create space below the underline.

### §3.5 Utility Typography Classes

These CSS classes are defined in `src/app/globals.css`:

| Class | Description | Key properties |
|-------|-------------|----------------|
| `.eyebrow` | Small-caps kicker labels | Montserrat 500, 0.6875rem, letter-spacing 0.24em, uppercase |
| `.section-numeral` | "01 / 02 / 03" section marginalia | Rubik Bold 700, tabular-nums, letter-spacing −0.02em |
| `.tabular` | Monospace numeric values (HEX codes, etc.) | font-variant-numeric: tabular-nums; font-feature-settings: 'tnum' |

### §3.6 Typography Rules

**Do:**
- Use Montserrat (`{typography.font-family.primary}`) for all web body and heading text.
- Use Rubik Bold (`{typography.font-family.display}`) exclusively for banner and video text.
- Apply `text-transform: uppercase` to all h1–h6 headings consistently.
- Apply the H2 coral underline rule whenever using `<h2>` in a web layout.
- Apply `.eyebrow` for kicker labels above headings.

**Don't:**
- Mix any other typefaces into the system.
- Use Rubik for paragraph text, UI labels, or web component text.
- Override letter-spacing on headings in local/per-component styles.
- Use Montserrat lighter than 400 weight in body contexts.

---

## §4 Layout & Spacing

### §4.1 Container

- Max width: `{spacing.container-max-width}` = 1200px
- Horizontal padding: `{spacing.container-padding-mobile}` (16px) mobile / `{spacing.container-padding-desktop}` (24px) desktop

### §4.2 Spacing Scale

Base unit: `{spacing.base}` = 8px. All layout values are multiples.

| Context | Mobile | Desktop |
|---------|--------|---------|
| Section bottom margin | `{spacing.section-margin-mobile}` 80px | `{spacing.section-margin-desktop}` 128px |
| H2 bottom margin inside Stack | `{spacing.h2-margin-bottom-in-stack}` 32px | 32px |

### §4.3 Breakpoints

Tailwind v4 defaults — no `--breakpoint-*` overrides are declared:

| Name | Min width |
|------|-----------|
| sm | 640px |
| md | 768px |
| lg | 1024px |
| xl | 1280px |
| 2xl | 1536px |

`1rem` resolves to the browser default 16px; no base font-size override is set.

Key responsive behaviour: the H2 coral underline widens 128px → 160px at an explicit `@media (min-width: 900px)`, which deliberately does not line up with a Tailwind breakpoint.

### §4.4 Border Radius

`{border.radius.default}` = **4px** applied globally to all components: cards, papers, chips, buttons, input fields.

### §4.5 Elevation

Zero elevation everywhere. No `box-shadow` on cards, papers, or surfaces.

Cards and papers use a 1px hairline border (`{border.width.hairline}`) with `{color.divider.light.subtle}` colour instead.

### §4.6 Card Hover

Cards transition `border-color` to `{color.brand.coral}` on hover. Transition: `{motion.transition.color}` (0.3s ease).

---

## §5 Components

### §5.1 Cards and Papers

```
elevation: 0  (no box-shadow, no background gradient)
border: 1px solid {color.divider.light.subtle}   /* light mode */
border: 1px solid {color.divider.dark.subtle}    /* dark mode */
border-radius: {border.radius.default}           /* 4px */
:hover border-color → {color.brand.coral}
transition: border-color {motion.duration.base} ease
```

### §5.2 Buttons

Three variants, all coral-first. Common behaviour:

```
transition: background-color {motion.duration.base} ease,
            color {motion.duration.base} ease,
            border-color {motion.duration.base} ease,
            transform {motion.duration.fast} ease;

:active  → transform: scale(0.96)
:disabled:active → transform: none
:link    → text-decoration: none
```

**Contained (primary CTA):**

| State | Background | Text |
|-------|-----------|------|
| Default | `{color.brand.coral}` | #ffffff |
| :hover | `{color.brand.coral-bright}` | #ffffff |
| :disabled (light) | rgba(252, 78, 77, 0.26) | rgba(255, 255, 255, 0.26) |
| :disabled (dark) | rgba(252, 78, 77, 0.38) | rgba(255, 255, 255, 0.38) |

**Outlined:**

| State | Border | Text | Background |
|-------|--------|------|------------|
| Default | `{color.brand.coral}` | `{color.brand.coral}` | transparent |
| :hover | `{color.brand.coral-bright}` | `{color.brand.coral-bright}` | transparent |
| :disabled (light) | rgba(252, 78, 77, 0.26) | rgba(252, 78, 77, 0.26) | — |
| :disabled (dark) | rgba(252, 78, 77, 0.38) | rgba(252, 78, 77, 0.38) | — |

**Text:**

| State | Text | Background |
|-------|------|------------|
| Default | `{color.brand.coral}` | transparent |
| :hover | `{color.brand.coral-bright}` | transparent |
| :disabled (light) | rgba(252, 78, 77, 0.26) | — |
| :disabled (dark) | rgba(252, 78, 77, 0.38) | — |

### §5.3 Links

```
color: {color.brand.coral}
text-decoration: underline
transition: color {motion.duration.base} ease

:hover  color: {color.brand.coral-bright}
        text-decoration: underline
```

Exceptions — these do not inherit link styling:

- Links rendered as buttons (an `<a>` carrying button styles, e.g. `<Button asChild><a … /></Button>`).
- Structural links whose whole area is clickable — nav entries, cards — marked `no-underline`. The global rule must exclude them explicitly, because `text-decoration` propagates to every descendant and cannot be removed further down the tree.

### §5.4 Dividers

```
border-color: {color.divider.light.strong}   /* light mode */
border-color: {color.divider.dark.strong}    /* dark mode */
```

### §5.5 Chips

```
font-weight: {typography.font-weight.medium}  /* 500 */
letter-spacing: {typography.letter-spacing.chip}  /* 0.02em */
border-radius: {border.radius.default}        /* 4px */
```

### §5.6 AppBar

```
elevation: 0
background-image: none
```

### §5.7 Section Intro Pattern

A composite layout pattern used for all major section headings:

1. **Row:** coral section numeral (`.section-numeral` Rubik Bold) + `.eyebrow` kicker text + `.hairline-accent` extending to fill remaining width
2. **Below:** optional icon (28×28px, `{color.brand.coral}`) + `<h2>` variant title

---

## §6 Visual Effects

### §6.1 Premium Grain (`.premium-grain`)

Fine paper-grain texture for obsidian dark surfaces. Applied to: masthead, footer, `PremiumCombinationShowcase`.

```css
.premium-grain {
  position: relative;
  isolation: isolate;
}

.premium-grain::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  opacity: 0.06;            /* {effect.premium-grain.opacity} */
  mix-blend-mode: overlay;  /* {effect.premium-grain.blend-mode} */
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='180' height='180'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 1  0 0 0 0 0.95  0 0 0 0 0.85  0 0 0 0.5 0'/></filter><rect width='100%' height='100%' filter='url(#n)'/></svg>");
}

/* Children must sit above the grain */
.premium-grain > * {
  position: relative;
  z-index: 1;
}
```

SVG filter: `fractalNoise`, `baseFrequency='0.9'`, `numOctaves='2'`, `stitchTiles='stitch'`, warm colour matrix.

### §6.2 Coral Glow (`.coral-glow`)

Ambient coral radial gradient glow behind the masthead wordmark. Applied to the masthead only — one instance per page.

```css
.coral-glow::before {
  content: '';
  position: absolute;
  inset: -20% -10%;
  pointer-events: none;
  z-index: 0;
  background:
    radial-gradient(60% 50% at 20% 30%, rgba(252, 78, 77, 0.22), transparent 70%),
    radial-gradient(40% 45% at 85% 75%, rgba(252, 78, 77, 0.10), transparent 70%);
  filter: blur(2px);  /* {effect.coral-glow.blur} */
}

.coral-glow > * {
  position: relative;
  z-index: 1;
}
```

Primary glow opacity: `{effect.coral-glow.primary-opacity}` (0.22) at position 20% 30%.
Secondary glow opacity: `{effect.coral-glow.secondary-opacity}` (0.10) at position 85% 75%.

### §6.3 Text Stroke Utilities

For placing text over complex or photographic backgrounds. Compose classes:

| Class | Property |
|-------|----------|
| `.text-stroke-outer` | `paint-order: stroke; -webkit-text-stroke: 0.06em` |
| `.text-stroke-white` | `-webkit-text-stroke-color: #ffffff` |
| `.text-stroke-black` | `-webkit-text-stroke-color: #1A1C1B` |
| `.text-stroke-brand` | `-webkit-text-stroke-color: #FC4E4D` |
| `.stroke-6` | `-webkit-text-stroke-width: 6px` (explicit width override) |

Example: `class="text-stroke-outer stroke-6 text-stroke-black"`

Rules: stroke thickness ≈ 20% of font size. Minimum text size for stroke usage: 24px. Always verify contrast between fill colour, stroke colour, and background.

### §6.4 Hairline Accent Separator (`.hairline-accent`)

A 1px coral gradient line used as a visual divider, especially in the Section Intro pattern to fill horizontal space after kicker text.

```css
.hairline-accent {
  height: 1px;
  background: linear-gradient(
    to right,
    rgba(252, 78, 77, 0.7),
    rgba(252, 78, 77, 0.18) 70%,
    transparent
  );
}
```

### §6.5 Complex Background Patterns (`.complex-bg`, `.complex-bg-light`)

Animated SVG geometric tile backgrounds. **Documentation/demo use only** — used in the style guide to demonstrate text stroke legibility against complex surfaces. Not a general-purpose brand pattern. `animation: complex-bg-move 20s linear infinite`.

### §6.6 Visual Effects Rules

**Do:**
- Apply `.premium-grain` to obsidian surfaces (dark hero sections, footers, premium cards).
- Apply `.coral-glow` to the masthead/hero only — one instance per page maximum.
- Ensure all children of `.premium-grain` and `.coral-glow` parents have `position: relative; z-index: 1`.
- Use text strokes when placing type on complex, photographic, or high-contrast backgrounds.

**Don't:**
- Apply `.coral-glow` to cards, content sections, or repeated elements — hero-level effect only.
- Stack multiple grain or glow effects on the same element.
- Omit the `z-index: 1` on children — this will hide text behind the grain overlay.

---

## §7 Dark Mode

### §7.1 Strategy

- **Toggle target:** `dark` class on the `<html>` element, declared in CSS as `@custom-variant dark (&:where(.dark, .dark *))`.
- **Owner:** `next-themes`. It is the only thing that may write the class or the stored value.
- **Persistence:** `localStorage` key `theme-mode`. Valid values: `"light"` | `"dark"` | `"system"`.
- **Fallback:** `prefers-color-scheme`, applied whenever the stored value is `"system"` or absent.
- **Default (SSR):** nothing is server-rendered as dark. `next-themes` writes the class before React hydrates.

### §7.2 Implementation Contract

```typescript
// Provider — wraps the whole tree
<ThemeProvider
  attribute="class"
  defaultTheme="system"
  enableSystem
  storageKey="theme-mode"
  disableTransitionOnChange
>

// Consumer — any client component
const { resolvedTheme, setTheme } = useTheme() // from 'next-themes'
```

Rules:

- **Read `resolvedTheme`, never `theme`.** `theme` can be `"system"`, which is not a renderable value.
- **Guard the un-hydrated render.** `resolvedTheme` is `undefined` on the server and on the first client render. Render a neutral placeholder until mounted, or theme-conditional markup will hydrate mismatched.
- **Never touch `localStorage` directly**, and do not add a `matchMedia` listener — `next-themes` already owns both.
- **`<html>` must carry `suppressHydrationWarning`**, because the class is mutated before React hydrates.
- **Prefer CSS over JavaScript.** Anything expressible as a `dark:` utility or a `.dark` custom-property override should never reach a `resolvedTheme` branch.

### §7.3 Surface Tokens by Mode

| Surface | Light | Dark |
|---------|-------|------|
| Page background | `{color.background.light.default}` #FAF8F4 | `{color.background.dark.default}` #121212 |
| Card background | `{color.background.light.paper}` #ffffff | `{color.background.dark.paper}` #1e1e1e |
| Primary text | `{color.text.light.primary}` #1A1C1B | `{color.text.dark.primary}` #ffffff |
| Secondary text | `{color.text.light.secondary}` #525252 | `{color.text.dark.secondary}` #c8c4bc |
| Card border | `{color.divider.light.subtle}` | `{color.divider.dark.subtle}` |
| Divider | `{color.divider.light.strong}` | `{color.divider.dark.strong}` |

### §7.4 Dark Mode Rules

**Do:**
- Store user choice in `localStorage` under the key `"theme-mode"` with exact values `"light"` or `"dark"`.
- Apply the `dark` class to `<html>` — not `<body>` or a content wrapper.
- Use the `mounted` pattern to prevent hydration flicker on initial load.
- Honour `localStorage` override over `prefers-color-scheme` — manual choice takes priority.

**Don't:**
- Hard-code dark mode colours into individual components — always use semantic palette tokens.
- Use `prefers-color-scheme` as the sole mechanism.
- Server-render dark mode — always hydrate as light then switch.

---

## §8 Accessibility

### §8.1 Colour Contrast

| Pairing | Ratio (approx.) | Compliance |
|---------|----------------|------------|
| Cream `#FAF8F4` on Obsidian `#1A1C1B` | ~15:1 | AA + AAA ✓ |
| Obsidian `#1A1C1B` on Cream `#FAF8F4` | ~15:1 | AA + AAA ✓ |
| White on Coral `#FC4E4D` | ~3:1 | AA for large text / buttons ✓ |
| Coral `#FC4E4D` on Cream `#FAF8F4` | ~3.2:1 | AA for large text only — do not use for body copy |

Recommended primary text pairing: Obsidian on Cream (light mode) and White on near-black (dark mode).

### §8.2 Keyboard Navigation

- All interactive elements (buttons, links, card onClick) must have visible focus states.
- The coral colour is acceptable as a focus ring colour given its contrast against both light and dark backgrounds.
- Button `transform: scale(0.96)` active state must only trigger on pointer (mouse/touch) interaction — not on keyboard Enter/Space activation.

### §8.3 Motion

The `.complex-bg` animation runs at a 20-second loop — acceptable for non-vestibular users, but guard with:

```css
@media (prefers-reduced-motion: reduce) {
  .complex-bg::before,
  .complex-bg-light::before {
    animation: none;
  }
}
```

Apply the same guard to any transition added during the Tailwind v4 migration.

### §8.4 Semantic Markup

- Content elements: `<h2>`, `<h3>`, `<h4>`, `<p>`, `<a>`, `<ul>`, `<ol>`, `<li>`, `<strong>`.
- No layout tables.
- Logo `<img>` elements must have meaningful `alt` text.
- Section anchor IDs to preserve: `#colors`, `#logo`, `#fonts`, `#text-requirements`, `#ai`.
- Required meta-tags: `<title>`, `<meta name="description">`, `<meta name="keywords">`.

---

## §9 Content Rules

### §9.1 Product Naming Format

```
{category} {name} ({original-name}) {count} шт {weight} грам, {origin}
```

Fields: exact category name, optional original Chinese name in Latin transliteration, count (omit for loose-weight products), province of origin.

### §9.2 SEO Requirements

| Metric | Requirement |
|--------|-------------|
| Content uniqueness | ≥ 85% |
| Stop-word ("water content") | 50–65% |
| Unique word ratio | > 0.7 |
| Single-word repetition | ≤ 3% |
| Academic repetition score | ≤ 9% |
| Average sentence length | ≥ 8 words |

### §9.3 Product Description Structure

1. Brief description (≤ 10 sentences)
2. Flavour profile
3. Characteristics
4. Brewing instructions
5. Storage

### §9.4 Approved Emotional Vocabulary

For slogans, banner copy, and marketing text: **Спокій, Зібраність, Концентрація, Задоволення, Умиротворення, Усамітнення, Затишок.**

### §9.5 Content Restrictions

- NO religious references
- NO political content
- NO medical or healing claims
- NO other typefaces or font families beyond Rubik Bold and Montserrat

---

## §10 Tailwind v4 Implementation

Tailwind v4 is CSS-first: there is no `tailwind.config.ts`. Everything in this section lives in the site's single stylesheet, which is the executable copy of what follows.

### §10.1 Color Tokens → CSS Custom Properties

Colours are declared in two layers. Theme-aware semantic tokens are mapped through CSS variables with `@theme inline`; fixed brand colours are declared directly with `@theme`. All values are OKLCH.

```css
/* Semantic tokens — resolve through :root / .dark */
@theme inline {
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --color-card: var(--card);
  --color-muted-foreground: var(--muted-foreground);
  --color-border: var(--border);
  /* …plus primary, secondary, accent, popover, destructive, input, ring */
  --radius-sm: calc(var(--radius) - 2px);
  --radius-md: var(--radius);
  --radius-lg: calc(var(--radius) + 4px);
}

/* Brand colours — identical in both modes */
@theme {
  --color-coral: oklch(0.63 0.21 25); /* #FC4E4D */
  --color-coral-bright: oklch(0.69 0.20 25); /* #FF6B6A */
  --color-obsidian: oklch(0.17 0.005 160); /* #1A1C1B */
  --color-cream: oklch(0.98 0.007 80); /* #FAF8F4 */
}

:root {
  --background: oklch(0.98 0.007 80); /* cream */
  --foreground: oklch(0.17 0.005 160); /* obsidian */
  --card: oklch(1 0 0); /* white */
  --muted-foreground: oklch(0.44 0 0); /* #525252 */
  --border: oklch(0.5 0 0 / 18%);
  --radius: 0.25rem;
}

.dark {
  --background: oklch(0.145 0 0); /* #121212 */
  --foreground: oklch(1 0 0);
  --card: oklch(0.18 0 0); /* #1e1e1e */
  --muted-foreground: oklch(0.81 0.006 80); /* #c8c4bc */
}
```

Utility names follow the token names: `--color-background` → `bg-background`, `--color-muted-foreground` → `text-muted-foreground`, `--color-coral` → `text-coral` / `bg-coral` / `border-coral`.

### §10.2 Typography → @theme

`next/font/google` injects the font variables in the root layout; `@theme` aliases them onto Tailwind's font utilities:

```css
@theme {
  --font-sans: var(--font-montserrat), sans-serif; /* → font-sans */
  --font-display: var(--font-rubik), sans-serif; /* → font-display */
}
```

No base font-size override is set — `1rem` is the browser default 16px.

### §10.3 H2 Underline

The coral underline is a named utility applied per heading, not a blanket `h2` rule, so headings that carry no brand mark stay unstyled:

```css
@utility section-heading {
  position: relative;
  letter-spacing: 0.04em;
  margin-bottom: 2rem;

  &::after {
    content: '';
    position: absolute;
    bottom: -10px;
    left: 0;
    width: 128px;
    height: 3px;
    background-color: var(--color-coral);

    @media (min-width: 900px) {
      width: 160px;
    }
  }
}
```

Usage: `<h2 class="section-heading …">`.

### §10.4 Border Radius

`--radius: 0.25rem` (4px) is set on `:root` and exposed as `--radius-sm` / `--radius-md` / `--radius-lg`, giving `rounded-sm`, `rounded-md` and `rounded-lg`.

### §10.5 Alpha Hairline Tokens

Alpha hairlines change between modes, so they stay CSS custom properties rather than fixed `@theme` colours — `--border: oklch(0.5 0 0 / 18%)` in light, redefined under `.dark`. A base-layer rule sets `border-color: var(--border)` on `*`, `::before` and `::after` so bare `border` utilities pick up the token instead of `currentColor`.

### §10.6 Dark Mode Toggle

Tailwind v4 has no `darkMode` config key; the class strategy is declared in CSS:

```css
@custom-variant dark (&:where(.dark, .dark *));
```

`next-themes` puts the `dark` class on `<html>` — see §7.2 for the full contract.
