# Business Event Stack — DESIGN.md

The brand and design system for **Business Event Stack** (businesseventstack.com) — the
free community calendar, map and directory of business networking, run **region by region**.
Each region (Hampshire, Dorset, …) is a **platform** under one brand: same system, its own
name, colours and logo.

This file is the single source of truth. Drop it into any agent alongside `ui.nuxt.com`
and it will build UI that signs the brand from the first render. It is kept identical across
the three repos — the **design site** (this showcase), the **app / Hostdesk**, and the
**marketing site**.

**Stack:** Nuxt 4 · Nuxt UI v4 · Tailwind v4 (`@theme`, CSS-first). Light-first. No dark-mode
toggle — the brand is white paper with one ink surface per page when it earns it.

**One brand, every region.** The **base** Business Event Stack identity is deliberately
**monochrome** — near-black + grey, professional. A region layers its own **primary** and
**spark** over that same system, and because every colour is a **CSS variable**, picking a
region re-themes the whole product at runtime (ramps, components, logo, dark surfaces and all).
The logo is generated from the **first letter of the region's name** (Hampshire → H, Dorset → D).

---

## 01 · Visual theme

Five things make it feel like Business Event Stack:

- **Monochrome base, colour per region.** Premium near-black by default; each region brings a
  primary and a spark. Never corporate grey-on-grey.
- **Drawn as a network.** The mark is the region wired together — the region's initial, drawn
  as nodes and links with one lit heart. The connection is the point.
- **Place is first-class.** Pins, radius, towns. The brand lives where the events do.
- **Motion with manners.** Things drop, pulse and lift — gently, and never against
  `prefers-reduced-motion`.
- **One spark.** The secondary colour is the "happening now" spark. Used once per view — as a
  live dot, or a highlighter on key words — it always means something.

The feeling: a helpful neighbour who knows every room in the region and is glad to walk you in.

---

## 02 · Colour

A **primary**, a **spark**, **ink** to read by, **white** to breathe. Amber/rose/sky stay fixed
for warning/danger/info. The base is monochrome; a region recolours the primary + spark.

### The model

| Role | Base (Business Event Stack) | Example region (Hampshire) | Use |
|---|---|---|---|
| **Primary** | `#1a1a1a` (near-black) | `#0f9d6e` (emerald) | Actions, links, the mark |
| **Spark / Today** | `#737373` (grey) | `#84e635` (lime) | Live now, highlights, the logo heart |
| **Ink** | neutral near-black | tinted near-black | Text + dark surfaces (adjusts to the scheme) |
| Warning | `#f59e0b` | `#f59e0b` | Clash with a big venue event |
| Danger | `#f43f5e` | `#f43f5e` | Cancelled, destructive |
| Info | `#0ea5e9` | `#0ea5e9` | Tips, neutral notices |

### Ramps

Each chosen colour expands to a full **50–950 ramp**, generated in **OKLCH** so any hue looks
balanced: the pick lands exactly on its anchor stop (**primary → 500**, **spark → 400**), the
light end runs toward near-white (good for washes) and the dark end toward near-black. **Ink**
is derived as a low-chroma dark of the primary hue, so dark surfaces tint toward the region
instead of a fixed colour.

### The contrast rule

The spark is a **fill and a highlight, not a body colour**. Text on the spark is always **ink**.
For spark *text* on white, drop to `~600` or darker. Primary-500 is a fill; primary *text* on
white reads best at `~600`.

### The highlight

The spark, used as a **marker on key words** — a signature device. Write `==key words==` and
they render highlighted (`<mark>` / `.hl`: ink text on the spark). `.hl-line` is the lower-half
swipe for body text. It themes with the region and follows the "one spark per view" rule:
highlight what ==actually matters==, not everything.

### Signature gradient

`linear-gradient(135deg, primary-500, primary-400, spark-400)` — hero washes, the gradient
mark, the date badge, the "best date" highlight. One gradient, used sparingly.

---

## 03 · Typography

A unique voice. **Plus Jakarta Sans** carries the brand; Inter does the reading; JetBrains
Mono labels the data.

| Role | Family / weight | Spec |
|---|---|---|
| Display | Plus Jakarta Sans 800 | `clamp(2.6rem, 6.2vw, 4.6rem)`, tracking −0.035em |
| Heading | Plus Jakarta Sans 700 | `clamp(1.5rem, 2.6vw, 2.1rem)`, tracking −0.02em |
| Body | Inter 400 | 1rem / 1.65, tracking 0 |
| Lead | Inter 400 | `clamp(1.05rem, 1.6vw, 1.3rem)` |
| Data / label | JetBrains Mono 600 | 0.78rem, tracking 0.1em, **UPPERCASE** |

**Principles.** Plus Jakarta Sans carries display — headlines, event titles, the wordmark —
never long paragraphs. Mono is always uppercase and only for data: dates, times, towns, tokens.
Set headings tight; let body breathe.

---

## 04 · The logo — the connected mark

Nodes (circles) wired by links (lines) into a **letter**, with one lit **heart**. The letter is
the **first character of the region's name** — Hampshire → H, Dorset → D, the base → **B** — so
the mark reads as the region *and* as a network at once. The heart glows in the **spark**
colour: the moment a connection is made.

Every A–Z is generated from one shared geometry (a node/edge graph on a 100×100 grid); node
size falls out of how many links meet it (endpoints large, junctions small). The original
Hampshire "Connected-H" is preserved exactly as the H.

### Treatments

| Variant | Structure | Heart | On |
|---|---|---|---|
| Duotone *(default)* | primary | spark | white |
| Gradient | primary → spark | spark | white |
| On-dark | white | spark | ink surfaces |
| Mono-ink | ink | ink | print / emboss |
| Mono-white | white | white | photo / colour knockout |

### Rules

- **Clear space:** one node's width of air on every side. Never crowd it.
- **Minimum size:** mark 20px; lockup 120px wide. Below that, mark alone.
- **The heart stays the spark** in every colour treatment except the two monos. Don't recolour
  it, don't drop it.
- **Don't** rotate, skew, add a drop-shadow to, or re-space the nodes.
- Colours read from the theme variables, so the mark **recolours with the region** for free; an
  uploaded logo may override the generated letter.

---

## 05 · Runtime theming & multi-region

The system is built so a region re-themes everything with no rebuild:

- **Every colour is a CSS variable.** A region's primary/spark generate ramps (§02) which are
  injected as a `:root:root { … }` override — higher specificity than the compiled defaults, so
  the override wins on the **first paint** (injected server-side) and on switch, with no flash.
- **Tailwind utilities** (`bg-primary-500`, `text-*`) resolve those vars at paint time, so they
  recolour automatically.
- **Nuxt UI** components read `--ui-primary` / `--ui-secondary` — the override sets those too, so
  buttons, badges, inputs follow the region.
- **Ink adjusts.** Dark surfaces (footer, ink sections, scrims) derive from `--cw-ink*`, a
  low-chroma dark of the primary, so they tint toward the region instead of a fixed colour.
- **The mark + wordmark** rebrand to the active region (letter + name).

On the design site a **scheme picker** in the top bar flips the whole page between the base and
example regions. In the app, the platform is resolved by host and themed the same way.

---

## 06 · Components

Built on **Nuxt UI v4**. Map the **semantic roles** once and the brand carries everywhere
(`app.config.ts`). Token *names* differ per repo — map the roles to yours:

```ts
// design site: emerald / lime / ink   ·   app: brand / lime / accent / ink
ui: {
  colors: { primary: '<primary>', secondary: '<spark>', neutral: 'ink' },
  button: { slots: { base: 'rounded-full font-medium' } }
}
```

Defaults to apply without asking:

- **Buttons** are primary **pills** with white text; hover one step deeper. The spark
  `secondary` is reserved for "Today / live" actions, never the main fill.
- **Cards** are white, `rounded-2xl`, `1px` ink-200 ring, soft tinted shadow on hover.
- **Badges & chips** are pills. Categories use primary soft; "Today" uses the spark solid.
- **Inputs** are `rounded-xl`, ink-200 border, primary focus ring.
- **Alerts** use the soft variant of the matching semantic colour.

---

## 07 · Layout & depth

- **Container:** max `1180px`, fluid side padding `clamp(1.1rem, 3.5vw, 2.5rem)`.
- **Section rhythm:** `clamp(4rem, 9vw, 7.5rem)` vertical, hairline between sections.
- **Radii:** sm `0.625rem` · md `1rem` · lg `1.5rem` (cards) · pill `999px` (buttons, chips).
- **Shadows** are soft and tinted, never hard black: sm `0 4px 14px -6px`, md `0 14px 34px -16px`,
  glow `0 16px 40px -16px` in the primary — for the date badge, primary CTA.
- **Spacing** is a 4px base. Depth comes from lightness and soft shadow, not heavy borders.

---

## 08 · Motion

Motion is part of the brand — alive, then out of the way. Every animation is gated behind
`@media (prefers-reduced-motion: reduce)`.

- **Cursor orb** (design site): a lerp-chased primary ring; fills on hover, shrinks on press,
  flips to the spark over dark surfaces.
- **Logo draw-in:** links sketch themselves, nodes pop, the heart pulses on a slow loop.
- **Pin drop:** map markers fall and settle with a soft bounce.
- **Live pulse:** the spark "Today" dot pings.
- **Lift:** cards rise `−4px` with a tinted edge on hover.
- **Reveal:** hero content fades up in sequence on load.

Timing: `cubic-bezier(0.2, 0.8, 0.2, 1)`, 180–240ms for interactions, slower loops for pulses.

---

## 09 · Voice

Warm, local, useful. Talk like a helpful neighbour who knows the room.

| Anchor | Sounds like |
|---|---|
| Warm | "Bring cards." — not "attendees should prepare collateral." |
| Local | Towns by name — the region's own, not "the local area". |
| Useful | Time, place, price, free-or-not — up front, every time. |
| Plain | "Free to list." — never "zero-cost onboarding funnel." |

Use **guest / organiser / room**. Avoid **user, leverage, synergy, solution**. No emoji in
chrome. Sentence case for everything except mono data labels.

---

## 10 · Imagery

Real local photography — the region's waterfronts, high streets and the people in the room.
Never generic stock.

**Treatments:**

- **Natural** — full colour, lightly warmed. Faces and places as they are.
- **Brand duotone** — grayscale tinted into the brand gradient (`mix-blend-mode: color`). The
  signature treatment for heroes and headers; it turns any photo on-brand for the active region.
- **Ink scrim** — a dark gradient (from `--cw-ink`) up from the base so white text always reads
  over a photo. It tints toward the region, not a fixed colour.

**In product:** card headers crop 16:9, mono by default, easing to colour on hover; full-bleed
heroes use the ink scrim with a Plus Jakarta headline and a spark CTA. **Don't** letterbox,
drop-shadow or stretch photos, or reach for stocky handshake clichés. One duotone hero per view.

---

## 11 · Surfaces

- **Paper (default):** white ground, ink text, primary actions. 90% of the product.
- **Wash:** primary-50 → spark-50 soft gradient for feature sections and the planner.
- **Brand gradient:** the full primary → spark surface — invitations and hero moments. White
  text, white mark.
- **Ink:** one dark surface per page (footer, focus mode). White headlines, muted body, the
  spark as the pointer — and the dark **adjusts to the region's colour**, not a fixed green.

---

## 12 · Do & don't

| Do | Don't |
|---|---|
| Primary pills for the primary action | Spark fills for big buttons — it's a spark, not a base |
| One spark accent per view (today / live / a highlight) | Spark everywhere until nothing stands out |
| Plus Jakarta for display, Inter for reading | A display face in long paragraphs |
| White paper as the ground | Heavy grey panels and hard black drop-shadows |
| Round everything to soft pills & cards | Sharp 2px corners and boxy chrome |
| Keep the mark's spark heart | Recolour or drop the heart node |
| Let dark surfaces + logo follow the region | Hardcode a colour that can't re-theme |

---

## 13 · Tokens at a glance

Colours are CSS variables so a region can override them at runtime. Names differ per repo
(`emerald`/`brand`), roles don't.

```css
:root {
  /* base — monochrome; a region overrides primary + spark, ink is derived */
  --primary-500: #1a1a1a;   /* Hampshire example: #0f9d6e */
  --spark-400:   #737373;   /* Hampshire example: #84e635 */
  --ink:         #0a0a0a;   /* dark surfaces + text, tinted per region */
  --paper:       #ffffff;

  --font-display: 'Plus Jakarta Sans';
  --font-body:    'Inter';
  --font-mono:    'JetBrains Mono';

  --radius-card: 1.5rem;
  --radius-pill: 999px;
  --gradient: linear-gradient(135deg, var(--primary-500), var(--primary-400), var(--spark-400));
}
```

---

## 14 · Agent brief (one page)

You are building UI for **Business Event Stack** — a free, multi-region business-networking
calendar / map / directory. Each region is a platform under one brand. Nuxt 4 + Nuxt UI v4,
light-first.

- **Colour:** a **monochrome base** (primary near-black `#1a1a1a`, spark grey `#737373`); each
  region picks its own **primary** + **spark** and the whole system recolours from CSS-variable
  ramps. Ink (text + dark surfaces) is derived from the primary, so darks adjust too. White
  ground; amber/rose/sky fixed for warning/danger/info.
- **Type:** Plus Jakarta Sans (display/titles), Inter (body), JetBrains Mono (uppercase data
  labels — dates, times, towns).
- **Components:** Nuxt UI, semantic roles `primary` / `secondary (spark)` / `neutral (ink)`
  (map to your repo's token names). Buttons are primary pills; cards `rounded-2xl`; chips pills.
- **Logo:** the connected mark — the **first letter of the region's name** as nodes + links with
  a spark heart. Recolours with the region.
- **The spark** means "now": a live dot, or a **highlighter** on key words (`==like this==` /
  `.hl`). One per view.
- **Motion:** gentle drop / pulse / lift, always reduced-motion safe.
- **Voice:** warm, local, plain. Time-place-price up front. No jargon, no emoji in chrome.

When in doubt, look at the live design site and match it.
