)
H2 mt-4 font-heading text-[2rem] font-bold leading-[1.1] tracking-[-1.5px] text-[#1a1a1a] sm:text-[2.5rem]
SUPPORT mt-4 text-[15px] leading-relaxed text-[#737373]
SEAM border-t border-[#e5e5e5]
```
`components/ui/section.tsx` holds the same pattern as components no page imports, plus three strings that appear on no live page: the lede clause (`text-[15px] leading-relaxed text-[#737373]` with a `strong` in `font-semibold text-[#1a1a1a]`), the dashed divider (`border-0 border-t border-dashed border-[#e5e5e5]`) and the media tile (`aspect-[4/3] overflow-hidden rounded-2xl bg-[#f5f5f5]`).
Chips and badges
- Hero badge (`components/landing/Hero.tsx`): `inline-flex items-center gap-2.5 rounded-full bg-[#f0f0f0] px-4 py-2`, label `text-[13px] font-medium text-[#525252]`.
- Chip `.vb-chip`: white, `#E5E5E5` border, radius 999, padding 7/14, 13px `#525252`. The /brand specimen row is the product's Production lanes: Photos · Video · Floor plans · Virtual tours · Brochures.
- Add-on tag (`app/pricing/page.tsx`): `inline-block whitespace-nowrap rounded-full border border-[#e5e5e5] px-2 py-0.5 text-[11px] font-medium text-[#525252]`.
- Dark pill — the home hero's scroll cue (`components/landing/hero-story/HeroStage.tsx`): `inline-flex items-center gap-2 rounded-full bg-[#1a1a1a]/85 px-4 py-2 text-[11px] font-medium uppercase tracking-[0.12em] text-white` + a 12px down-arrow. One line that must pop.
- Status chip: section 9.
- `components/ui/badge.tsx` and `components/ui/mockup.tsx` are never imported. Dead code; do not build from them.
Cards
- Card: `rounded-2xl border border-[#e5e5e5] bg-white p-6` on the `#FCFCFC` ground; a filled variant drops the border for `bg-[#f5f5f5]`; a card holding a product shot drops the padding: `overflow-hidden rounded-2xl border border-[#e5e5e5]`.
- Window `.vb-win`: section 9.1. Section 06 shows one vertical JobCard shared with section 07 (`dash/Projects.tsx`): payment badge, technician avatar, order/client, flat status/media row, address, area and schedule. It is a decorative excerpt with illustrative account data.
- Dark card `.vb-pc` (in `.vb-comp.is-dark`, which drops the panel padding so the card fills the cell): `#0C0C0E`, radius 16, padding 26/26/28; mono number `01` 11.5px `#6EE7B7`; title IBM Plex Sans 700 20px `#F5F5F7`; sub 14.5px / 1.55 `#A1A1AA`; label 11px uppercase .15em `#8B8B93`. Specimen: "Agent books" / "A slot is only offered if someone can actually serve that package." (its limits as copy are in section 13).
- Emerald CTA card — how /pricing and every page built on `FeaturePageLayout` close (`components/layout/FeaturePageLayout.tsx`): `mt-16 rounded-2xl max-w-2xl relative overflow-hidden` (on /pricing: `relative overflow-hidden rounded-2xl`, `app/pricing/page.tsx`) + the emerald card gradient (section 5) + `relative z-10 p-6 sm:p-8 md:p-10` holding `text-xl font-bold text-white font-heading` title, `mt-2 text-white/60` line, and a white pill `mt-6 inline-flex items-center rounded-full bg-white px-6 py-3 text-sm font-semibold text-[#1a1a1a] transition-colors hover:bg-white/90` with an `ArrowRight h-4 w-4 ml-2`.
- White card on the wash `.vb-quote` (in `.vb-comp.is-wash` over the hero wash): white, hairline, radius 22, padding 34/44/32, `--vb-shadow-quote` (at ≤1024px: radius 18, padding 26/22/24, the quote 18px, and the wash cell's padding 22/16); a 40px IBM Plex Serif `“` in `#059669`; IBM Plex Sans 500 21px / 1.35 ink; a 30px box logo (radius 7) + "Vremly" 13.5/600. It carries the company line "Vremly is built and run by a working real-estate media company." — owner-asserted, NOT a testimonial. Never put a customer quote, a name or a headshot in it.
Footer — `CinematicFooter` (`components/ui/motion-footer.tsx`): pinned under the page on screens at least 640px wide and 700px tall; a full-bleed grainient panel; centred white CTA (H2 "Run your entire operation in one place." with the white serif italic accent, `text-white/70` line "Book, dispatch, deliver. One platform.", white "Get started free" + outline "Book a Demo"); then a white `rounded-2xl bg-white` card with the name "Vremly" (15px bold ink), the tagline "Real estate media, handled." in 13px `#A3A3A3`, and link columns (Brand → /brand among them), over a bottom bar with the line "Built by people who've lived it." (`text-[11px] sm:text-[13px] text-[#525252] text-center sm:text-left`, `.accent-word` on its last four words — a live exception, section 3). Every page ends with it.
## 9. Dashboards, consoles & UI graphics — the product-window kit (page 07)
Every NEW product graphic is one kit: a light or dark app window with a three-dot bar and an `app.vremly.com` address, grey chrome, emerald for data and status hues for state — and under it, an ink console that shows Claude reading a Vremly account through the Vremly MCP connector — read only unless you allow writes. It is a visual kit: it depicts the connector, and states nothing about which plans include it. The live reference is https://vremly.com/brand#dash (`components/brand/sections/Dash.tsx`, `dash/Console.tsx`, `dash/scripts.ts`, `dash.css`). The CSS for every class below is in Appendix C; phone rules in Appendix D.
### 9.1 The window
- Shell `.vb-win` — `#FFFFFF`, 1px `#E5E5E5`, radius 12 (14 for the dashboard), `--vb-shadow-window` `0 26px 60px -30px rgba(16, 32, 24, 0.35)`, `overflow: hidden`, text left. Max 480px as a specimen; the dashboard fills a 760px column (`.vb-dash`).
- Bar `.vb-winbar` — padding 13/18, hairline bottom, three 9px `#F0F0F0` dots (`.vb-windot`), the address centred in IBM Plex Mono 11.5px `#737373` (`.vb-winurl`): `app.vremly.com/projects`, `app.vremly.com · dashboard`, `app.vremly.com/booking`. Never real browser chrome, never coloured traffic lights.
- The former three-column table specimen has been replaced by the app-based JobCard. Do not rebuild a generic Property / Status / Technician table as a platform screenshot.
- Caption `.vb-cap` — 12.5px `#525252` on a hairline, padding 12/18/14.
- A replica is a picture, never a control: `.vb-inert` (no pointer events, no selection) + `aria-hidden`, like `lib/product-ui.ts` `INERT` / `inert(className)`. Spreading `{...INERT}` and then a `className` silently drops the pointer-events — use `inert()`.
### 9.2 Dashboard previews
`components/brand/sections/dash/Preview.tsx` renders two static, explicitly labelled excerpts controlled by one Light / Dark button group. The buttons expose `aria-pressed` and remain outside the decorative replica. The default is Light; switching does not change page chrome or the console.
- Overview excerpt: app header, left sidebar, timeframe, four metric cards (Total Jobs 51; Technician Utilization 78%, 2/9 available; Avg Delivery Time 19h; Revenue CA$22.5k) and two rate cards (94% on-time delivery; 97% client approval). Figures are illustrative, regular weight, with no deltas or sparklines. There is no chart or table on this dashboard.
- Render the overview on a 1080×700 logical canvas using `Scaled`; below 761px use 480×1140, one column, no sidebar. The app sidebar is off-canvas on phones. The overview intentionally stops before Schedule.
- Active Projects is a separate excerpt from further down the app page, after Schedule and Live Project Map. Three flat JobCards mirror the vertical app component: 5:2 assigned-status auroras, Unpaid badges, centered technician pills with avatar initials and checkmarks, prominent order/client names, flat status dots beside media icons, two-line addresses, area badges, shoot dates/times and optional notes. Desktop renders a 1080×520 canvas; phones stack the cards at native type sizes. Avatar initials use the app fallback and account data is illustrative. Do not imply they directly follow metrics in the app.
- Product palette variables live on `.vb-preview-theme`, including the window bar. The local `.dark` class enables the aurora's dark colourways. No global brand tokens are swapped.
- Both excerpts have a descriptive image role; their decorative contents are hidden from assistive technology and inert to pointers. Only the genuine theme controls enter keyboard navigation.
Badge implementation (shared by both themes):
```ts
export function tint(hex: string, alpha: number): string {
const n = parseInt(hex.slice(1), 16);
return `rgba(${(n >> 16) & 255}, ${(n >> 8) & 255}, ${n & 255}, ${alpha})`;
}
export function statusChipStyle(hex: string) {
return {
backgroundColor: tint(hex, 0.15),
color: hex,
boxShadow: `inset 0 0 0 1px ${tint(hex, 0.3)}`,
} as const;
}
```
### 9.3 The console (`.vb-term` — "vremly-mcp")
- Shell — `#0C0C0E`, radius 10, `--vb-shadow-console` `0 26px 60px -30px rgba(16, 32, 24, 0.5)`, `overflow: hidden`, in a 760px column 18px under the window (`.vb-console`).
- Bar `.vb-termbar` — padding 11/14, `rgba(255, 255, 255, 0.06)` bottom line, three 9px `rgba(255, 255, 255, 0.14)` dots, the title centred in mono 11.5px `#8B8B93`: `vremly-mcp · invoices` / `· schedule` / `· project` / `· delivered`.
- Screen `.vb-term-screen` — a fixed 340px at EVERY width, `overflow: hidden`, padding 16/20 (14 on phones), IBM Plex Mono 12px / 1.7 (11px phones), `#D4D4D8`. When a new line makes the content taller, the screen scrolls to the bottom (smooth; instant on a fresh run or under reduced motion). The console never grows.
- Lines: the prompt — `›` in `#34D399` + `#F5F5F7` text + a 7×13 `#34D399` caret blinking at 1s (`steps(1)`); a tool call — a 9px spinner ring (1.5px `rgba(255, 255, 255, 0.22)`, top `#34D399`, 0.8s linear) that becomes a 6px `#34D399` dot, then the tool name in `#F5F5F7` + ` · ` + its argument in `#A1A1AA`; the answer panel `.vb-term-out` — 1px `rgba(255, 255, 255, 0.08)` border, radius 8, padding 10/12, 11.5px (a table with a mono uppercase `#8B8B93` header and a total row, or label/value fields, or grouped lines); a note line in `#F5F5F7`; the done line `✓ Read only · nothing changed` in `#34D399`. New lines rise in (`vbDashIn`: opacity 0 + 4px → none, .35s; the answer panel .5s) — only on a live run.
- Step strip `.vb-tabs` — under the console: a white pill track (1px `#E5E5E5`, radius 999, padding 3, gap 2), buttons 12px `#525252` padding 6/12 with a mono 10.5px `#A3A3A3` number `01`–`04`; the active tab `#ECFDF5` with `#047857` text and a `#047857` number; `aria-pressed`. Phones scroll it sideways and keep the active tab centred.
- A screen reader gets a transcript of the finished run of the current tab (`sr-only`), not the moving text.
- Console art only — never a headline, CTA, caption or marketing line: the prompts (section 9.4; tab 01's is "Which of my invoices are still unpaid?") and the done line "✓ Read only · nothing changed".
- Where the connector facts come from: read only preselected, and writes refused on a read-only connection, are read from the product's own connect screen (Settings → Developers, "Connect Vremly to Claude"); the three tools and the saved prompts from its hosted MCP server. That is code in the product, not the public claim set (the product files for the prompts and tools are listed in the header of `components/brand/sections/dash/scripts.ts`). Draw the console from it; never write copy from it.
### 9.4 Timing (read from `components/brand/sections/dash/scripts.ts`)
```ts
/** Per character. The site's typewriter speed: lib/use-loop.ts useTypewriter's default, which every caller uses. */
export const TYPE_MS = 45;
/** The caret keeps blinking this long after the last character, then goes. */
export const CARET_MS = 300;
/** Caret gone → first tool call. */
export const SETTLE_MS = 400;
/** Pitch between tool calls. */
export const STEP_MS = 850;
/** A call's spinner becomes an emerald dot this long after it appears. */
export const DOT_MS = 650;
/** Last dot → answer panel. */
export const OUT_MS = 350;
/** Answer panel → "✓ Read only · nothing changed". */
export const DONE_MS = 600;
/** Done line → next tab (auto-cycle only). */
export const HOLD_MS = 3000;
```
- Per tab: type the prompt at 45ms a character (the site's typewriter speed — `useTypewriter`'s default in `lib/use-loop.ts`); the caret blinks 300ms more and goes; the first tool call lands 400ms later; the next every 850ms; each spinner becomes a dot 650ms after it appears; the answer panel lands 350ms after the last dot; the done line 600ms after that; a 3s hold; the next tab.
- `typeEnd = chars × 45 + 300` · `stepAt[i] = typeEnd + 400 + i × 850` · `dotAt[i] = stepAt[i] + 650` · `outAt = last dot + 350` · `doneAt = outAt + 600` · `total = doneAt + 3000`. One `setTimeout` at a time, aimed at the next moment the frame changes — no per-frame loop, nothing during the hold.
- Tab lengths: 01 8.71s (38 characters) · 02 9.025s (45) · 03 9.925s (65) · 04 9.07s (46).
- Runs only while the console is on screen (`IntersectionObserver`, `rootMargin: "80px 0px"`) and the browser tab is visible; off screen it pauses and resumes where it stopped. The server renders tab 01 finished (no caret, no spinner), so there is no hydration mismatch and a reader without JavaScript sees the whole run. The first arrival from below restarts tab 01 from an empty prompt; if the page opens with the console already in view, that run starts at its done line instead of wiping what the reader is looking at.
- A click on a tab pins it: auto-cycle stops for good and that tab replays from the prompt.
- Reduced motion: the finished frame of the current tab, no cycle, no caret, no spinner, no rise.
The four scripts (`SCRIPTS`, verbatim) — every call is a read, so every run ends "✓ Read only · nothing changed":
| # | Tab | Prompt | Tool calls | Answer |
|---|---|---|---|---|
| 01 | Unpaid invoices | Which of my invoices are still unpaid? | `vremly_search_endpoints · "invoices"` → `vremly_describe_endpoint · GET /invoices` → `vremly_request · GET /invoices?status=SENT&status=OVERDUE` | Customer / Amount / Overdue: Marc Lefebvre CA$612.15 21 days · Priya Sandhu CA$389.00 9 days · Daniel Okafor CA$489.00 not yet due · Total CA$1,490.15 |
| 02 | This week's shoots | Show the Vremly shoot schedule for this week. | `vremly_search_endpoints · "list projects"` → `vremly_describe_endpoint · GET /projects` → `vremly_request · GET /projects?scheduledAfter=2026-09-14&scheduledBefore=2026-09-20` | When / Address / Crew / Status: Tue 10:00 902 Crescent Rd NW Reyes Duke Editing · Thu 09:30 88 Bowness Rd NW Emily Tran In Progress · Fri 10:00 740 Aspen Stone Blvd SW Marcus Kaur Assigned · Sat 13:00 76 Silverado Blvd SW Unassigned Pending; note "Unassigned: 76 Silverado Blvd SW, Saturday 13:00." |
| 03 | Project status | Give the current status of the Vremly project: 902 Crescent Rd NW | `vremly_request · GET /projects?search=902+Crescent+Rd+NW` → `vremly_request · GET /invoices/by-project/{id}` → `vremly_request · GET /projects/{id}/delivery` | Status Editing · Shoot Tue Sep 15, 10:00 · Reyes Duke · Media Uploaded · not delivered yet · Invoice CA$489.00 · sent, unpaid |
| 04 | Delivered this week | Summarise Vremly projects delivered this week. | `vremly_search_endpoints · "list projects"` → `vremly_describe_endpoint · GET /projects` → `vremly_request · GET /projects?firstDeliveredAfter=2026-09-14` | Marc Lefebvre — 820 Copperfield Blvd SE · Sep 15 · Essential Listing · Priya Sandhu — 76 Sagewood Dr SW, Airdrie · Sep 16 · Premium Listing |
(`{id}` is a uuid in the real script.) The prompts are the product's own: 01 is the "First prompt" on the in-app "Connect Vremly to Claude" screen; 02–04 are the hosted connector's saved prompts.
### 9.5 Rules for any new dashboard, console or UI graphic
1. Match the actual product screen and its theme. The dashboard has a sidebar, metrics, Schedule, Live Project Map and Active Projects; charts belong to Reports and tables to Projects. Use labelled excerpts when omitting intermediate sections.
2. In windows: grey chrome, emerald for data (bars, a legend square), status hues for state, and nothing else. Emerald never means "good" and never marks a delta.
3. Product excerpts use the app's Inter body and regular-weight metric figures. The outer window address uses IBM Plex Mono. Section 06 shares the section 07 JobCard implementation instead of drawing a generic table. Money uses the organisation's currency; compact dashboard totals may use the app's compact formatting.
4. Data is illustrative STATE, never an outcome: counts, amounts, statuses, dates. No deltas, no "% more", no "saved N hours", no forecast.
5. Windows never resize while animating; content scrolls inside a fixed screen.
6. Every animation has a finished static frame for reduced motion and for the server render.
7. The console is Claude using the Vremly MCP connector, drawn as the product's own connector screen describes it. Never "Vremly AI", never "ask your data", never a chatbot inside the product. It reads; it writes only if the connection allows it — read only is preselected. Never state which plan includes the connector. Only its three tools exist: `vremly_search_endpoints`, `vremly_describe_endpoint`, `vremly_request`. Never print an endpoint or operation count; never show it building a feature, pushing into the app or syncing to a BI tool; never name any AI client as supported, Claude included — the console only depicts it.
8. Use names and Calgary-area addresses the site's demos already use (Marcus Kaur, Reyes Duke, Emily Tran, Priya Sandhu, Daniel Okafor, Marc Lefebvre; 902 Crescent Rd NW, 88 Bowness Rd NW, 740 Aspen Stone Blvd SW, 76 Silverado Blvd SW …). Never a real customer.
9. Never draw: Marketplace, App Store or Google Play badges, SOC 2 or GDPR badges, "AI Studio", MLS, named testimonials, "+N more" chips.
10. Older windows on the home page differ — the hero's Safari SVG frame (`components/ui/safari.tsx`, given `url="yourcompany.vremly.com"` by `components/landing/hero-story/HeroScreen.tsx`) and the product story's `Window` with macOS traffic lights (`components/landing/product-story/shell.tsx`). They stay where they are; do not copy them for new graphics.
## 10. Icons
Vremly has no drawn icon family. Icons are stock `lucide-react` strokes used as-is; brand glyphs of third parties come from `react-icons` where needed. Never present a Lucide icon as Vremly's own, never draw a custom family to fill a brief, never fill an icon.
- Sizes in use: `h-3 w-3` (small icons on the home AI card, `AIVideo.tsx`), `h-3.5 w-3.5` (the breadcrumb chevron, `components/layout/Breadcrumbs.tsx`), `h-4 w-4` (in buttons, beside a label), `h-5 w-5` (list checks).
- Colour: ink or a grey by default. The inclusion marks on /pricing: `Check` `text-[#059669]` = included, `Minus` `text-[#d4d4d4]` = not included.
- The one decorative arrow is `ArrowRight h-4 w-4` after a CTA label on dark cards; a `↓` text glyph in an `` marks a download or a scroll target on /brand.
- Live one-offs, not system — raw Tailwind emerald utilities, which Tailwind v4 defines in oklch, not as the hexes in section 2: `emerald-500` (`oklch(69.6% 0.17 162.48)`) in the feature pages' "Key highlights" checks (`components/layout/FeaturePageLayout.tsx`), the home lifecycle's progress bars and step dots (`WorkflowStepper.tsx`) and the Capture pulse dot (`VremlyCapture.tsx`); `text-emerald-600` checks on /features/ai-video; `text-emerald-400` icons and `text-emerald-300` labels on the home AI card (`AIVideo.tsx`). Also off-system reds: `red-400` on the home Problem section's "Your current stack" label and pain bullets (`ProblemSection.tsx`), and a `text-[#dc2626]` `X` on the /pricing Enterprise fee row. Do not reuse any of these colours.
## 11. Motion (page 08)
Rules
- Sections rise once, as they arrive (`whileInView`, `viewport={{ once: true }}`). Nothing re-animates on scroll back.
- Product replicas loop through their steps only while they are on screen (`useLoop` in `lib/use-loop.ts`).
- Product loops (replicas, the console) live inside windows and consoles. A window never grows while it animates. The loops outside a window are decorative: the accent-word (and `.accent-gold`) drift, the two role marquees, the grainient, the emerald card's breathing, the plan cards' 5200ms flip, and two one-offs — the gold `ShineBorder` lines on the Built by operators band (`motion-safe:animate-shine`) and the Capture pulse dot (`animate-pulse`, `VremlyCapture.tsx`). Add no new ones.
- Tailwind's default transition is the site's: every `transition-colors` without a `duration-*` runs 150ms `cubic-bezier(0.4, 0, 0.2, 1)` (no override in `app/globals.css`).
- Reduced motion never removes content and never changes colour; it removes movement (table below).
The timings (the rows on https://vremly.com/brand with their values spelled out; `components/brand/sections/Motion.tsx`)
| Scene | What happens | Timing |
|---|---|---|
| Section rise | Section heads rise 16px into place as they arrive, once; tiles follow in 0.4s (`BentoGrid.tsx` and the other section heads) | 0.6s |
| Hero entrance | Copy is visible from the server; it rises 8px after the splash over 0.45s. The product frame rises 14px over 0.6s after a 0.54s delay. Reduced motion skips movement. | `Hero.tsx`, `HeroStage.tsx` |
| Header | The nav pill fades in (`Navbar.tsx`) | 0.5s |
| Dock | The docked pill springs down once the header scrolls away — `{ type: "spring", stiffness: 320, damping: 30, mass: 0.8 }` from y −72 | spring |
| Features menu | Rises 8px and scales up from .96, `easeOut` | 0.2s |
| Accent word | The gradient drifts across the word and back (`gradient-shift`, ease-in-out, infinite) | 6s |
| Marquee | Role rows drift in opposite directions (`animate-marquee` / `-reverse`, linear, `translateX(-25%)`) | 30s |
| Self-serve booking | Package grid, package picked, the day's times, a time picked, the total — holds 1.4 · 1.9 · 1.4 · 1.9 · 3s from its `useLoop` (`bento-demos/BookingDemo.tsx`) | 9.6s |
| Easing | Section entrances ease on `cubic-bezier(0.25, 0.4, 0.25, 1)`; the hero's badge, buttons and frame, and the header, on `(0.25, 0.46, 0.45, 0.94)`. Under reduced motion, replicas pin their last step, the accent word and marquees freeze, and the grainient draws one frame. | — |
Also exact
- The home hero uses one restrained copy entrance, not a per-line text cascade. Its serif accent is “payment.” On desktop, the 560svh stage starts at zero progress before the order arrives; expansion ends at 18% of the scroll runway, then the existing assignment-to-delivery sequence plays. Product position follows the measured copy height with a 112px desktop gap, and the copy becomes inert when faded. On phones (below 640px), the hero stays in normal flow: a native-size JobCard below the copy has tappable Pending, Assigned, and Delivered stages. No pinned runway, simulated cursor, or scroll progress border on phones.
- Splash (home only, and only at `(min-width: 640px)` — `NOT_PHONE`, `lib/use-media-query.ts`): a `#FCFCFC` panel holds 2200ms, then exits `y: "-100%"` over `{ duration: 0.85, ease: [0.76, 0, 0.24, 1] }` (`components/landing/SplashScreen.tsx`). The hero cascade starts with the exit.
- Footer: its magnetic buttons follow the pointer on `{ stiffness: 260, damping: 18, mass: 0.6 }` through `useSpring` (`MagneticButton`, `components/ui/motion-footer.tsx`).
- The plan cards (/pricing and the home page) auto-flip every 5200ms (`components/ui/plan-cards.tsx`); never under reduced motion.
- `.clause-mark` sweeps in 0.5s `cubic-bezier(0.4, 0, 0.2, 1)` (/switch-from-aryeo only).
- The typewriter: 45ms per character (`useTypewriter` default).
- /brand's own: the console clock (section 9.4); a swatch reads "Copied" for 1400ms; the copy-file button settles after 1800ms; step-strip tabs cross-fade their colours over .25s; pills and buttons .15s.
- The live replica on /brand: the home page's `BookingDemo` inside a `.vb-win` at `app.vremly.com/booking`, 482px wide (its 480px canvas at scale 1), its screen an `aspect-ratio: 480 / 256` box so the height depends on width only; in a `.vb-panel.is-wash` 720px wide, min-height 600 (440 on phones), over the hero wash.
Reduced-motion contract (`prefers-reduced-motion: reduce`)
| Behaviour | File |
|---|---|
| every looping replica pins its LAST step and never advances — so the last step must be the one worth reading | `lib/use-loop.ts` |
| the typewriter shows its full text | `lib/use-loop.ts` |
| the accent word and both marquees stop moving; the gradients stay, frozen | `app/globals.css` |
| the grainient paints one frame and never starts its loop | `components/ui/grainient.tsx` |
| the emerald card paints once; only the breathing loop is skipped | `components/ui/animated-gradient-background.tsx` |
| `BlurFade` leaves the element visible, no animation | `components/ui/blur-fade.tsx` |
| the hero and the product story lose their scroll pin and render as a static column (the hero also uses a static layout on viewports below 600px high) | `components/landing/hero-story/HeroStage.tsx`, `components/landing/product-story/index.tsx` |
| anchor links jump instead of gliding (`scroll-behavior: smooth` only under `no-preference`) | `app/globals.css` |
| the highlighter lands fully drawn | `app/globals.css` |
| the /brand console shows the finished frame; caret, spinner and rise-ins are off | `components/brand/sections/dash.css`, `dash/Console.tsx` |
The home hero explicitly respects reduced motion and displays the finished project frame without pinning. Short viewports below 600px high use the same static layout.
## 12. Page recipes (band order, top to bottom)
Home — https://vremly.com (`app/page.tsx`; numbered as its own order comment numbers them — each section answers the question the one above raises)
- Nav (floating pill).
1. Hero (`Hero.tsx`, in an `sm:p-4` inset): audience badge “For real estate media teams”; H1 “From first booking / to final payment.” (Medium 500; payment in emerald serif italic); sub “Run scheduling, dispatch, delivery, invoicing, and payroll in one place.”; “Get started free” + “Book a demo”. The existing no-card / 14-day trial line remains live copy only. Caption “One booking, through assignment and delivery” introduces the product preview. Marketing chrome remains light.
2. Roles — "For everyone in real estate media", with role marquees.
3. Problem — "Running a media company shouldn't take five tools and a group chat."
4. Replaces stack — "The tools you're stitching together, …" and what Vremly plugs into.
5. Before / after — the one `#F5F5F5` band: "One platform, …"
6. Lifecycle (`WorkflowStepper`) — "One job. One record. Start to finish."
7. Product story — `#0C0C0E` dark band: one real job through five real screens, scroll-pinned.
8. How it works — "Three steps. Zero chaos." — the same job from each role's side.
9. Platform bento — "Everything your operation needs": replica tiles (booking, dispatch, analytics, messaging, packages, team).
10. Capture (`VremlyCapture`) — "Own your floor plans and virtual tours."
11. AI (`AIVideo`) — on the emerald card gradient.
12. Security (`Enterprise`) — `#0C0C0E` dark band: "Switch without breaking your operation."
13. Onboarding (`Training`) — "You're not switching alone."
14. Pricing — "Pay nothing until work comes through." + the plan cards.
15. Built by operators — `#0C0C0E` dark band (its live H2 still names the operating company, which `lib/canonical/claims.ts` withdraws from public copy — never copy that line).
16. FAQs — "Frequently asked questions".
- CinematicFooter.
Pricing — https://vremly.com/pricing (`app/pricing/page.tsx`)
header (`pt-24 pb-8 md:pt-32`, `max-w-5xl px-6`: breadcrumbs, eyebrow "Pricing", the ink → grey H1 (section 3), an 18px `#737373` lede) → the plan cards (`PlanCards`, the same component as the home page) → "What each plan includes" comparison table (emerald checks, grey dashes) → FAQ on a seam, "Questions people actually ask" (`dt` 15px semibold ink, `dd` 15px `#737373`) → the emerald CTA card "Still deciding?" with "Start free" + "Talk to us" → a 13px `#A3A3A3` support-email line → footer.
Feature pages — https://vremly.com/features/* (`components/layout/FeaturePageLayout.tsx`)
hero (`pt-24 pb-16 md:pt-32 md:pb-20`, `max-w-7xl px-6`: breadcrumbs, eyebrow = category, H1 40 → 48px Bold with the LAST word as the accent, an 18–20px `#737373` subheadline, and — when a feature is built but not open — an availability note in a `#F5F5F5` box above the fold) → body (`py-16 md:py-24`: the feature nav; an 18px `#525252` body paragraph; "Key highlights" list; the live product replica, inert; the emerald CTA card "Ready to see … in action?" + "Book a Demo") → footer.
/features: hero → feature categories grid → the AI card on the emerald card gradient → more features. /features/ai-video: the light emerald card hero → how it works → a `#F5F5F5` roadmap band (not for sale) → the emerald CTA card.
Switch from Aryeo — https://vremly.com/switch-from-aryeo (`app/switch-from-aryeo/page.tsx`, the document page)
header (`px-5 pt-32 pb-16 sm:px-8 sm:pt-40 sm:pb-24`, `max-w-3xl`): H1 IBM Plex Sans 600 38 → 56px, lh 1.05, −0.03em, "Your photos. Your clients. / Your platform."; lede 17 → 19px / 1.65 `#525252`; the `h-12` ink pill + a source link; the exhibit — Aryeo's Terms §7.3 quoted with `.clause-mark` highlights → sections on seams (`border-t border-[#e5e5e5] py-16 sm:py-24`, eyebrow + H2 600 28 → 36px): The clause · The clause nobody quotes · Side by side · The conflict · Pricing · Migration · Who built it · The offer · Questions → footer. No accent word on this page; the highlighter is its one device, and only for words quoted from a linked source.
Brand — https://vremly.com/brand (`app/brand/page.tsx`)
Nav → hero (centred: kicker "Brand guidelines · v2 · September 2026", H1 "Everything you need to build like Vremly." with "Vremly" as the accent, lede, "See the brand ↓" + "Get the brand LLM file", "Prefer to paste? Copy the LLM file to clipboard") → 01 Logo (base) → 02 Colour (white) → 03 Type (base) → 04 The gradient (white) → 05 The mark (base) → 06 Components (white) → 07 Dashboards & consoles (base) → 08 Motion (base, no line between) → 09 For AI (white: the wash panel with "Download vremly-brand.md", "Open in the browser", "Copy to clipboard" and the file URL) → footer. Title "Brand - Vremly", canonical `/brand`.
Other surfaces
- Open Graph: `app/opengraph-image.png`, exactly 1200 × 630 (inherited by every route; a page that sets `openGraph` must name it again — metadata merges shallowly).
- Social canvases (`RULE SET HERE` — the site has no social templates): safe margin 8% of the short edge. Square 1080 × 1080 (86px) · Portrait 1080 × 1350 (86px) · Story / Reel 1080 × 1920 (86px sides; keep type out of the top 250px and bottom 320px). Ground `#FCFCFC` or a gradient form; one idea, one accent word; place logo FILES on a matching ground, never cut out onto a photograph.
- Email (`RULE SET HERE`; only the `lib/email.ts` stack quoted below is read from code): assume the fallback font is what ships. Sans `Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif`; accent `'IBM Plex Serif', Georgia, 'Times New Roman', serif` italic (`lib/email.ts` currently sends `-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif`). The accent word in email is a SOLID `color: #059669; font-style: italic` — never the gradient (Outlook on Windows does not support `background-clip: text`, so the word renders invisible). Weights 400 and 700 only. Carry contrast with `#1A1A1A` on `#FFFFFF`, not a near-white ground (clients invert near-white in dark mode).
## 13. Copy — voice and the approved lines
Voice: plain, specific, unhurried. Short declarative sentences, concrete nouns, second person, present tense ("You book it once", not "users can book"). Name the reader's real problem before naming the product. Confidence comes from precision, not adjectives. Headlines are claims; the accent word is where they resolve.
- British / Canadian spelling — "colour", "centre", "licence" (noun) — with one standing exception: "organization", with a z, because it is the name of an object in the product and the marketing, the privacy policy and the terms match the interface. Product and site labels quoted verbatim keep their own spelling ("Technician Utilization", "Help Center").
- No exclamation marks. Em dashes and commas instead.
- Never stack three adjectives. Never write a headline that could belong to any other company.
- Banned words: revolutionary, seamless, game-changing (or "game changing"), empower (`lib/canonical/claims.ts` `BANNED_WORDS`); also unlock, supercharge, effortless, 10x, and "AI-powered" as a virtue.
- Forbidden shapes (enforced in `lib/canonical/claims.ts`): efficacy outcomes ("cut turnaround by 30%", "saved 5 hours"); zero-occurrence claims ("not one missed deadline"); customer results or counts ("200 companies"). Vremly is pre-launch: there are no customers to count, no results to report, no testimonials to quote.
- Never bury the price: where pricing appears, the 3% must be as legible as the $0, and it is "3% per transaction" — never "per booking". $0 never appears alone.
- Naming: "Vremly" in prose. The legal entity, "2817230 Alberta Ltd.", only where a legal entity is required. Companies that run on the platform are named as themselves, never as Vremly, and never in a way that implies Vremly did their work.
Approved lines (verbatim, with where they live)
- "Real estate media, handled." — the tagline (the footer, `components/ui/motion-footer.tsx`; the site title in `app/layout.tsx` sets it as "Vremly | Real estate media, handled", no full stop).
- "One platform where agents book media, teams run operations, and providers find work. From order to delivery, everything stays in one place." — the site description (owner-asserted, llms-full.txt).
- "Vremly is the operating system for real-estate media production." (owner-asserted, llms-full.txt)
- "Vremly is built and run by a working real-estate media company." — owner-asserted, unnamed (llms-full.txt).
- "From first booking to final payment." / "Run scheduling, dispatch, delivery, invoicing, and payroll in one place." — home hero.
- "Three steps. Zero chaos." · "One job. One record. Start to finish." · "Everything your operation needs" · "Own your floor plans and virtual tours." · "Switch without breaking your operation." · "You're not switching alone." · "Pay nothing until work comes through." — home section heads.
- "Run your entire operation in one place." / "Book, dispatch, deliver. One platform." — the footer CTA.
- "Start free. Pay only when you get paid." — /pricing. "Free plan: $0/month, 3% per transaction." (owner-asserted, llms-full.txt)
- "Your photos. Your clients. Your platform." — /switch-from-aryeo.
- "Self-serve booking" / "Agents select services, pick a date, and go." — the home booking tile.
- "A slot is only offered if someone can actually serve that package." — one of the Claims Fact Sheet's mechanisms cleared for organic social. It is not in llms-full.txt, so use it only as the section 8 dark-card specimen line and never widen it (never "prevents double-booking").
- Buttons: "Get started free", "Book a demo" (hero; "Book a Demo" on feature pages and the footer), "Log in", "Start free", "Talk to us".
Never claim (for product capabilities the authority is https://vremly.com/llms-full.txt — if a capability is not there, treat it as not existing in copy. Two things in this file are not in it: the Vremly MCP connector the section 9 console depicts, as the product's own connector screen describes it; the dark-card specimen line above. Use each only where this file places it, and make no capability, plan or availability claim about the connector.)
- AI video generated from listing photos; an AI assistant, chatbot or "ask your data" inside the product; anything predictive.
- Social publishing or scheduling; a visual automation builder; MLS syndication; BI connectors or a bulk data-export API.
- SSO (SAML, Okta, Azure AD); SOC 2 or ISO certification; App Store or Google Play listings for the Vremly app.
- Which plan includes API keys, webhooks or the Vremly MCP connector — the site's own sources disagree (llms-full.txt puts API keys and webhooks on every plan; `lib/features.ts` calls API key management an Enterprise feature). Name no plan for any of them.
- Anything about a marketplace: vetted or insured providers, escrow, held funds, payouts, disputes, guaranteed payments, marketplace numbers.
- Double-booking prevention; automatic editor assignment or auto-start of editing; per-item media proofing.
- Endpoint, file, test or model counts; the operating company's name, headcount, years trading or revenue; the names of the third-party engines behind Vremly's six AI capabilities (the section 9 console's Claude is the customer's own assistant and is named only inside that kit); any Enterprise price figure.
- Customer names, logos, counts, quotes or results; invented incidents, dates or amounts presented as real.
## 14. Do / Don't
Do: `#FCFCFC` as the page and `#FFFFFF` as a surface on it; one idea per section; IBM Plex Sans headlines with at most one accent word; Inter for everything you read; ink or outline pills; light or dark windows in the matching app palette for the product; status hues only in product art (window chips, the aurora under a home replica); the gradient clipped by the box it fills, with white type on the dark forms and ink on the pale ones; the logo files, placed; IBM Plex Mono for IDs, values and URLs; illustrative state in windows; a finished frame for reduced motion; the class strings in this file, pasted.
Don't: add a second accent colour or a colour for one section; accent two words or a function word; accent a name outside the approved /brand hero; use `font-black` or any weight above 700; retype, redraw, recolour, outline, rotate, shadow or stretch the V or the logo; use the Scan mark for Vremly; use `/logos/vrem-icon.svg`, the VR monogram files or the mascot concepts; put the white logo on a light ground or the emerald logo on a gradient; sample colour from a raster; apply the product dark theme to marketing-page chrome; draw traffic lights, a browser chrome, a sparkline, a line chart or a delta in a product window; let a window grow while it animates; make the console "Vremly AI" or let it write on a read-only connection; raise the grain above 2%; invent a mascot, a pattern, an illustration style or an icon family; invent a customer, a quote, a metric or a count; use exclamation marks.
## Appendix A — the gradient files, complete (paste as-is)
Verbatim from `public/brand/texture/`. The three radials are `objectBoundingBox` gradients carrying the CSS ellipse radii, so with `preserveAspectRatio="none"` they stretch exactly to any box. Use them as a background image or inline, clipped by the box they fill (section 5). The grainient has no vector form: use its stills or the live `` the viewBox as its `width`/`height` attributes and fix the rendered height in CSS (`height: 28px; width: auto` for the horizontal logo and wordmark, `76px` for marks and the stacked logo, `72px` + `border-radius: 14px` for the box logo) — the box reserves its aspect ratio before the file loads (`components/brand/primitives.tsx` `LogoTile`).
## Appendix C — reference CSS, verbatim from the site (desktop, tablet and reduced motion; the phone block is Appendix D)
Every rule of the /brand kit, in cascade order: `app/brand/brand.css` first, then each section sheet as the page imports it. Comments are removed and whitespace is collapsed. Most rules are on one line; the `.vb` token block and `.vb-dl` keep one declaration per line, and the at-rule blocks (`@media`, `@container`, `@keyframes`) keep their nested rules on their own lines. Selectors, properties and values are verbatim. The section sheets use `.vb .vb-*` (two classes) so they refine the shared one-class primitives. Tokens are the `.vb` block in section 2.
```css
/* app/brand/brand.css */
.vb {
--vb-ground: #fcfcfc;
--vb-surface: #ffffff;
--vb-raised: #fafafa;
--vb-recessed: #f5f5f5;
--vb-fill: #f0f0f0;
--vb-wash: #ecfdf5;
--vb-wash-tint: #d1fae5;
--vb-ink: #1a1a1a;
--vb-ink-hover: #333333;
--vb-lede: #525252;
--vb-body: #737373;
--vb-label: #a3a3a3;
--vb-quiet: #d4d4d4;
--vb-hair: #e5e5e5;
--vb-divider: #f0f0f0;
--vb-accent: #059669;
--vb-accent-deep: #047857;
--vb-accent-mid: #34d399;
--vb-accent-light: #6ee7b7;
--vb-accent-edge: rgba(5, 150, 105, 0.14);
--vb-accent-hover: rgba(5, 150, 105, 0.35);
--vb-accent-hover-strong: rgba(5, 150, 105, 0.45);
--vb-dark: #0c0c0e;
--vb-dark-text: #f5f5f7;
--vb-dark-body: #a1a1aa;
--vb-dark-label: #8b8b93;
--vb-shadow-window: 0 26px 60px -30px rgba(16, 32, 24, 0.35);
--vb-shadow-console: 0 26px 60px -30px rgba(16, 32, 24, 0.5);
--vb-shadow-card: 0 12px 34px -26px rgba(16, 32, 24, 0.35);
--vb-shadow-quote: 0 6px 18px rgba(16, 32, 24, 0.06);
--vb-font-body: var(--font-inter), ui-sans-serif, system-ui, sans-serif;
--vb-font-head: var(--font-ibm-plex-sans), ui-sans-serif, system-ui, sans-serif;
--vb-font-serif: var(--font-ibm-plex-serif), ui-serif, Georgia, serif;
--vb-font-mono: var(--font-ibm-plex-mono), ui-monospace, SFMono-Regular, Menlo, monospace;
font-family: var(--vb-font-body);
color: var(--vb-ink);
background-color: var(--vb-ground);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
}
.vb :where(.vb-hero, .vb-sec) :where(a, button, [tabindex]):focus-visible { outline: 2px solid var(--vb-accent); outline-offset: 2px }
.vb-inert { pointer-events: none; user-select: none }
.vb-in { max-width: 1180px; margin: 0 auto }
.vb-hero { padding: 144px 32px 88px; text-align: center }
.vb-sec { padding: 96px 32px 104px; scroll-margin-top: 80px }
.vb-sec.is-white { background: var(--vb-surface); border-top: 1px solid var(--vb-hair); border-bottom: 1px solid var(--vb-hair) }
.vb-sec.is-white + .vb-sec.is-white { border-top: 0 }
.vb-k { font-family: var(--vb-font-body); font-size: 11px; font-weight: 600; line-height: 1.5; letter-spacing: 0.15em; text-transform: uppercase; color: var(--vb-label) }
.vb-h1 { font-family: var(--vb-font-head); font-weight: 500; font-size: 64px; line-height: 1.08; letter-spacing: -1.5px; color: var(--vb-ink); margin: 18px 0 0; text-wrap: balance }
.vb-lede0 { font-family: var(--vb-font-body); font-weight: 400; font-size: 21px; line-height: 1.5; color: var(--vb-body); max-width: 620px; margin: 16px auto 0; text-wrap: pretty }
.vb-hero .vb-btns { margin-top: 32px }
.vb-head { max-width: 720px }
.vb-h2 { font-family: var(--vb-font-head); font-weight: 700; font-size: 44px; line-height: 1.1; letter-spacing: -1.5px; color: var(--vb-ink); margin: 14px 0 0; max-width: 720px; text-wrap: balance }
.vb-lede { font-family: var(--vb-font-body); font-size: 17px; line-height: 1.6; color: var(--vb-lede); margin: 16px 0 0; max-width: 480px; text-wrap: pretty }
.vb-sub { display: flex; flex-wrap: wrap; align-items: baseline; gap: 12px; margin: 44px 0 16px }
.vb-sub-t { font-family: var(--vb-font-head); font-size: 18px; font-weight: 600; line-height: 1.3; letter-spacing: -0.3px; color: var(--vb-ink); margin: 0 }
.vb-sub-c { font-size: 13.5px; line-height: 1.4; color: var(--vb-body) }
.vb-btns { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 12px }
.vb-btns.is-left { justify-content: flex-start }
.vb-btn-dark, .vb-btn-soft { display: inline-flex; align-items: center; justify-content: center; gap: 8px; border-radius: 999px; font-family: var(--vb-font-body); font-size: 14px; font-weight: 600; line-height: 20px; white-space: nowrap; text-decoration: none; cursor: pointer; transition: background-color 0.15s ease, border-color 0.15s ease }
.vb-btn-dark { background: var(--vb-ink); color: #ffffff; border: 1px solid var(--vb-ink); padding: 13px 28px }
.vb-btn-dark:hover { background: var(--vb-ink-hover); border-color: var(--vb-ink-hover) }
.vb-btn-soft { background: var(--vb-surface); color: var(--vb-ink); border: 1px solid var(--vb-hair); padding: 13px 24px }
.vb-btn-soft:hover { background: var(--vb-recessed) }
.vb-btn-dark i, .vb-btn-soft i { font-style: normal; font-size: 12px; opacity: 0.7 }
.vb-btn-dark svg, .vb-btn-soft svg { flex: none }
.vb-copy { display: inline-block; background: none; border: 0; padding: 0; font-family: var(--vb-font-body); font-size: 13px; line-height: 1.5; color: var(--vb-body); text-decoration: underline; text-decoration-color: var(--vb-quiet); text-underline-offset: 4px; cursor: pointer; transition: color 0.15s ease, text-decoration-color 0.15s ease }
.vb-copy:hover { color: var(--vb-ink); text-decoration-color: var(--vb-ink) }
.vb-copy svg { display: inline-block; vertical-align: -3px; margin-right: 6px }
.vb-hero .vb-copy { margin-top: 18px }
.vb-dls { display: flex; flex-wrap: wrap; gap: 6px; flex: none }
.vb-dl {
display: inline-flex;
align-items: center;
gap: 5px;
font-family: var(--vb-font-body);
font-size: 12px;
font-weight: 500;
line-height: 1;
color: var(--vb-ink);
background: var(--vb-surface);
border: 1px solid var(--vb-hair);
border-radius: 999px;
padding: 6px 11px;
white-space: nowrap;
text-decoration: none;
transition: background-color 0.15s ease, border-color 0.15s ease;
}
.vb-dl i { font-style: normal; font-size: 11px; color: var(--vb-label) }
.vb-dl:hover { background: var(--vb-wash); border-color: var(--vb-accent-hover) }
.vb-grid3, .vb-grid4 { display: grid; gap: 16px; margin-top: 36px }
.vb-grid3 { grid-template-columns: repeat(3, minmax(0, 1fr)) }
.vb-grid4 { grid-template-columns: repeat(4, minmax(0, 1fr)) }
.vb-sub + .vb-grid3, .vb-sub + .vb-grid4, .vb-sub + .vb-swatches { margin-top: 0 }
.vb-rules { list-style: none; margin: 32px 0 0; padding: 0; display: grid; grid-template-columns: 1fr 1fr; gap: 10px 40px }
.vb-rules li { position: relative; padding-left: 18px; font-size: 14px; line-height: 1.55; color: var(--vb-lede) }
.vb-rules li::before { content: ""; position: absolute; left: 0; top: 8px; width: 7px; height: 7px; border-radius: 2px; background: var(--vb-accent) }
.vb-rules code { font-family: var(--vb-font-mono); font-size: 12px; color: var(--vb-ink) }
.vb-tile { container: vb-tile / inline-size; background: var(--vb-surface); border: 1px solid var(--vb-hair); border-radius: 16px; overflow: hidden; min-width: 0 }
.vb-tile-art { height: 168px; display: flex; align-items: center; justify-content: center; background: var(--vb-raised); border-bottom: 1px solid var(--vb-divider); font-size: 12px; line-height: 1.4; color: var(--vb-body) }
.vb-tile-art.is-ink { background: var(--vb-ink); border-bottom-color: var(--vb-ink); color: var(--vb-dark-body) }
.vb-tile-art.is-brand { background: var(--vb-accent); border-bottom-color: var(--vb-accent) }
.vb-tile-art img { display: block; width: auto; height: 28px; max-width: calc(100% - 32px); object-fit: contain }
.vb-tile-art img.is-box { border-radius: 14px }
.vb-tile-cap { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 14px 16px 14px 18px }
.vb-tile-cap > div:first-child { min-width: 0 }
.vb-tile-cap b { display: block; font-size: 13.5px; font-weight: 600; line-height: 1.3; color: var(--vb-ink) }
.vb-tile-cap span { display: block; font-size: 12px; line-height: 1.4; color: var(--vb-body); margin-top: 2px }
.vb-tile-cap code { display: block; margin-top: 6px; font-family: var(--vb-font-mono); font-size: 10.5px; line-height: 1.4; color: var(--vb-body) }
.vb-tile-cap.is-col { flex-direction: column; align-items: flex-start; padding: 16px 18px 18px }
@container vb-tile (max-width: 276px) {
.vb-tile-cap { flex-direction: column; align-items: flex-start; gap: 10px }
}
.vb-cgroup { margin-top: 8px }
.vb-cgroup .vb-sub { margin: 36px 0 14px }
.vb-head + .vb-cgroup { margin-top: 36px }
.vb-head + .vb-cgroup .vb-sub { margin-top: 0 }
.vb-swatches { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px }
.vb-sw { display: flex; flex-direction: column; width: 100%; min-width: 0; text-align: left; background: var(--vb-surface); border: 1px solid var(--vb-hair); border-radius: 14px; padding: 0; overflow: hidden; cursor: pointer; font: inherit; color: inherit; transition: border-color 0.15s ease }
.vb-sw:hover, .vb-sw.is-copied { border-color: var(--vb-accent-hover-strong) }
.vb-sw-c { display: block; height: 76px; border-bottom: 1px solid var(--vb-divider) }
.vb-sw-t { display: block; padding: 11px 14px 12px }
.vb-sw-t b { display: block; font-size: 13.5px; font-weight: 600; line-height: 1.3; color: var(--vb-ink) }
.vb-sw-t code { display: block; font-family: var(--vb-font-mono); font-size: 11.5px; line-height: 1.35; color: var(--vb-accent-deep); margin-top: 3px }
.vb-sw-t small { display: block; font-size: 11.5px; line-height: 1.4; color: var(--vb-body); margin-top: 3px; overflow-wrap: anywhere }
.vb-table { margin-top: 28px; background: var(--vb-surface); border: 1px solid var(--vb-hair); border-radius: 14px; overflow: hidden }
.vb-trow { display: grid; grid-template-columns: 1.1fr 1.3fr 2fr; gap: 16px; align-items: baseline; padding: 12px 18px; font-size: 13.5px; line-height: 1.5; color: var(--vb-lede) }
.vb-trow + .vb-trow { border-top: 1px solid var(--vb-divider) }
.vb-trow > :first-child { color: var(--vb-ink); font-weight: 500 }
.vb-trow > :last-child { font-family: var(--vb-font-mono); font-size: 12px; color: var(--vb-lede) }
.vb-table.vb-anat .vb-trow { grid-template-columns: 0.8fr 1.1fr 2.7fr }
.vb-table.vb-anat .vb-trow > :nth-child(2) { font-family: var(--vb-font-mono); font-size: 12px; color: var(--vb-accent-deep) }
.vb-table.vb-anat .vb-trow > :last-child { font-family: inherit; font-size: 13.5px; color: var(--vb-lede) }
.vb-table.is-motion .vb-trow { grid-template-columns: 0.7fr 3fr 0.6fr }
.vb-table.is-motion .vb-trow > :last-child { text-align: right; color: var(--vb-accent-deep) }
.vb-trow.is-sum { background: var(--vb-raised) }
.vb-trow.is-sum > :first-child { font-family: var(--vb-font-mono); font-size: 10.5px; font-weight: 400; letter-spacing: 0.1em; text-transform: uppercase; color: var(--vb-body); align-self: center }
.vb-comps { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; margin-top: 36px }
.vb-comp { position: relative; overflow: hidden; min-width: 0; background: var(--vb-raised); border: 1px solid var(--vb-hair); border-radius: 18px; padding: 26px 28px 30px }
.vb-comp .vb-k { margin-bottom: 12px }
.vb-mt { margin-top: 26px }
.vb-comp.is-dark { background: var(--vb-dark); border-color: var(--vb-dark); padding: 0 }
.vb-comp.is-wash { background: var(--vb-wash); border-color: var(--vb-accent-edge); display: flex; align-items: center; justify-content: center; padding: 32px }
.vb-win { width: 100%; max-width: 480px; background: var(--vb-surface); border: 1px solid var(--vb-hair); border-radius: 12px; box-shadow: var(--vb-shadow-window); overflow: hidden; text-align: left }
.vb-winbar { position: relative; display: flex; align-items: center; gap: 7px; padding: 13px 18px; border-bottom: 1px solid var(--vb-hair) }
.vb-windot { flex: none; width: 9px; height: 9px; border-radius: 50%; background: var(--vb-fill) }
.vb-winurl { position: absolute; left: 50%; transform: translateX(-50%); font-family: var(--vb-font-mono); font-size: 11.5px; line-height: 1.3; color: var(--vb-body); white-space: nowrap }
.vb-wrow { display: grid; grid-template-columns: minmax(0, 1fr) 120px 110px; align-items: center; padding: 11px 18px; font-size: 13px; line-height: 1.4; color: var(--vb-ink); text-align: left }
.vb-wrow + .vb-wrow { border-top: 1px solid var(--vb-divider) }
.vb-wrow > * { min-width: 0 }
.vb-wrow.is-h { font-size: 10.5px; font-weight: 500; letter-spacing: 0.1em; text-transform: uppercase; color: var(--vb-body) }
.vb-cap { padding: 12px 18px 14px; border-top: 1px solid var(--vb-hair); font-size: 12.5px; line-height: 1.5; color: var(--vb-lede) }
.vb-chip-status { display: inline-flex; align-items: center; width: fit-content; border-radius: 999px; padding: 2px 8px; font-family: var(--vb-font-body); font-size: 11px; font-weight: 500; line-height: 1.4; white-space: nowrap }
.vb-chips { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 8px; margin-top: 14px }
.vb-chip { background: var(--vb-surface); border: 1px solid var(--vb-hair); border-radius: 999px; padding: 7px 14px; font-size: 13px; line-height: 1.3; color: var(--vb-lede); white-space: nowrap }
.vb-tex { position: absolute; inset: 0; z-index: 0; pointer-events: none }
.vb-on-tex { position: relative; z-index: 10 }
.vb .is-checker { background-color: var(--vb-surface); background-image: linear-gradient(45deg, var(--vb-recessed) 25%, transparent 25%, transparent 75%, var(--vb-recessed) 75%), linear-gradient(45deg, var(--vb-recessed) 25%, transparent 25%, transparent 75%, var(--vb-recessed) 75%); background-size: 18px 18px; background-position: 0 0, 9px 9px }
.vb-panel { position: relative; overflow: hidden; border-radius: 14px }
.vb-panel.is-wash { background: var(--vb-wash); border: 1px solid var(--vb-accent-edge); padding: 56px 48px }
.vb-ai { position: relative; overflow: hidden; background: var(--vb-wash); border: 1px solid var(--vb-accent-edge); border-radius: 24px; padding: 56px 48px 52px }
.vb-ai-in { position: relative; z-index: 10; max-width: 720px }
.vb-ai-in .vb-lede { max-width: 640px }
.vb-ai .vb-btns { margin-top: 28px }
.vb-url { display: block; margin-top: 18px; font-family: var(--vb-font-mono); font-size: 12px; line-height: 1.5; color: var(--vb-lede); overflow-wrap: anywhere }
@media (min-width: 761px) and (max-width: 1024px) {
.vb-grid4 { grid-template-columns: repeat(2, minmax(0, 1fr)) }
}
@media (prefers-reduced-motion: reduce) {
.vb-btn-dark, .vb-btn-soft, .vb-copy, .vb-dl, .vb-sw { transition: none }
}
/* components/brand/sections/hero.css */
.vb .vb-hero .vb-copy { max-width: 100%; text-wrap: balance }
/* components/brand/sections/colour.css */
.vb .vb-colour-note { max-width: 780px; margin: 14px 0 22px; color: var(--vb-lede); font-size: 13px; line-height: 1.7 }
.vb .vb-colour-note a { color: var(--vb-accent-deep); text-decoration: underline; text-underline-offset: 3px }
.vb .vb-status-pairs { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px }
.vb .vb-status-pair { border: 1px solid var(--vb-hair); border-radius: 14px; overflow: hidden; padding: 16px }
.vb .vb-status-pair h4 { font-size: 14px; font-weight: 500 }
.vb .vb-status-pair p { color: var(--vb-lede); font-size: 12px; margin: 5px 0 14px; min-height: 36px }
.vb .vb-status-pair > div { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px }
.vb .vb-status-pair .vb-sw { min-width: 0 }
.vb .vb-status-pair .vb-sw-t { padding: 10px }
.vb .vb-status-pair .vb-sw-t b { font-size: 11px }
/* components/brand/sections/type.css */
.vb .vb-tgrid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; margin-top: 36px }
.vb .vb-tcard { min-width: 0; background: var(--vb-surface); border: 1px solid var(--vb-hair); border-radius: 18px; padding: 28px 30px 30px }
.vb .vb-t-h1 { margin: 12px 0 0; font-family: var(--vb-font-head); font-weight: 500; font-size: 46px; line-height: 1.08; letter-spacing: -1.5px; color: var(--vb-ink); text-wrap: balance }
.vb .vb-t-h2 { margin: 10px 0 0; font-family: var(--vb-font-head); font-weight: 700; font-size: 32px; line-height: 1.12; letter-spacing: -1px; color: var(--vb-ink); text-wrap: balance }
.vb .vb-t-note { margin: 12px 0 0; font-size: 17px; line-height: 1.5; color: var(--vb-body); text-wrap: pretty }
.vb .vb-t-lede { margin: 12px 0 0; font-size: 18px; line-height: 1.55; color: var(--vb-lede); text-wrap: balance }
.vb .vb-t-body { margin: 14px 0 0; max-width: 470px; font-size: 15px; line-height: 1.625; color: var(--vb-body) }
.vb .vb-t-body strong { font-weight: 600; color: var(--vb-ink) }
.vb .vb-t-mk { margin-top: 22px }
.vb .vb-tcard .vb-k { color: var(--vb-body) }
.vb .vb-t-mono { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 18px; margin-top: 12px; font-family: var(--vb-font-mono); font-size: 11.5px; line-height: 1.4 }
.vb .vb-t-label { font-size: 10.5px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--vb-label) }
.vb .vb-t-id { color: var(--vb-accent-deep) }
.vb .vb-t-url { color: var(--vb-lede); overflow-wrap: anywhere }
.vb .vb-fonts { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; margin-top: 16px }
.vb .vb-font { min-width: 0; background: var(--vb-surface); border: 1px solid var(--vb-hair); border-radius: 14px; padding: 16px 18px }
.vb .vb-font b { display: block; font-size: 18px; font-weight: 500; line-height: 1.3; color: var(--vb-ink) }
.vb .vb-font.is-sans b { font-family: var(--vb-font-head) }
.vb .vb-font.is-inter b { font-family: var(--vb-font-body) }
.vb .vb-font.is-serif b { font-family: var(--vb-font-serif); font-style: italic; font-weight: 400 }
.vb .vb-font.is-mono b { font-family: var(--vb-font-mono) }
.vb .vb-font > span { display: block; margin-top: 4px; font-size: 12.5px; line-height: 1.4; color: var(--vb-body) }
.vb .vb-font code { display: block; margin-top: 10px; font-family: var(--vb-font-mono); font-size: 11px; line-height: 1.5; color: var(--vb-lede) }
.vb .vb-font code > span { display: block; white-space: nowrap }
@media (min-width: 761px) and (max-width: 1024px) {
.vb .vb-fonts { grid-template-columns: repeat(2, minmax(0, 1fr)) }
}
/* components/brand/sections/gradient.css */
.vb .vb-zgrid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; margin-top: 36px }
.vb .vb-ztile { min-width: 0; background: var(--vb-raised); border: 1px solid var(--vb-hair); border-radius: 16px; overflow: hidden }
.vb .vb-zt { position: relative; overflow: hidden; height: 200px; border-bottom: 1px solid var(--vb-divider); border-radius: 15px 15px 0 0 }
.vb .vb-zt-gl, .vb .vb-zt-gl > div, .vb .vb-zt-gl canvas { border-radius: inherit }
.vb .vb-zt.is-checker { isolation: isolate }
.vb .vb-zt-grain { background-repeat: repeat; background-size: 256px 256px; mix-blend-mode: multiply }
.vb .vb-ztile .vb-tile-cap.is-col { gap: 12px }
/* components/brand/sections/components.css */
.vb .vb-comp .vb-k { color: var(--vb-body) }
.vb .vb-crow { display: flex; flex-wrap: wrap; align-items: center; gap: 10px }
.vb .vb-bcard { display: flex; flex-direction: column; gap: 8px; max-width: 336px; margin: 30px auto 0; padding: 16px; background: var(--vb-surface); border: 1px solid var(--vb-hair); border-radius: 16px; box-shadow: var(--vb-shadow-card) }
.vb .vb-component-project { max-width: 360px; margin-inline: auto }
.vb .vb-component-project .vb-win { background: var(--preview-bg) }
.vb .vb-pillrow { display: flex; padding-top: 22px }
.vb .vb-pc { height: 100%; background: var(--vb-dark); border-radius: 16px; padding: 26px 26px 28px }
.vb .vb-pc-n { margin-top: 18px; font-family: var(--vb-font-mono); font-size: 11.5px; line-height: 1.3; letter-spacing: 0.1em; color: var(--vb-accent-light) }
.vb .vb-pc-t { margin-top: 12px; font-family: var(--vb-font-head); font-size: 20px; font-weight: 700; line-height: 1.3; letter-spacing: -0.3px; color: var(--vb-dark-text); text-wrap: balance }
.vb .vb-pc-s { margin-top: 8px; font-size: 14.5px; line-height: 1.55; color: var(--vb-dark-body) }
.vb .vb-pc-l { margin-top: 18px; font-size: 11px; font-weight: 600; line-height: 1.5; letter-spacing: 0.15em; text-transform: uppercase; color: var(--vb-dark-label); text-wrap: balance }
.vb .vb-quote { width: 100%; margin: 0; padding: 34px 44px 32px; background: var(--vb-surface); border: 1px solid var(--vb-hair); border-radius: 22px; box-shadow: var(--vb-shadow-quote) }
.vb .vb-quote-mark { display: block; margin-bottom: 8px; font-family: var(--vb-font-serif); font-size: 40px; font-weight: 400; line-height: 0.55; color: var(--vb-accent) }
.vb .vb-quote-q { max-width: 620px; margin: 0; font-family: var(--vb-font-head); font-size: 21px; font-weight: 500; line-height: 1.35; color: var(--vb-ink); text-wrap: balance }
.vb .vb-quote-who { display: flex; align-items: center; justify-content: center; gap: 10px; margin-top: 16px }
.vb .vb-quote-who img { flex: none; width: 30px; height: 30px; border-radius: 7px }
.vb .vb-quote-who b { font-size: 13.5px; font-weight: 600; line-height: 1.3; color: var(--vb-ink) }
@media (max-width: 1024px) {
.vb .vb-comps .vb-wrow { grid-template-columns: minmax(0, 1fr) 92px 90px; padding: 10px 14px; font-size: 12px }
.vb .vb-comp.is-wash { padding: 22px 16px }
.vb .vb-quote { padding: 26px 22px 24px; border-radius: 18px }
.vb .vb-quote-q { font-size: 18px }
}
/* components/brand/sections/dash.css */
.vb .vb-console { max-width: 760px; margin: 18px auto 0 }
.vb .vb-term { background: var(--vb-dark); border-radius: 10px; box-shadow: var(--vb-shadow-console); overflow: hidden; text-align: left }
.vb .vb-termbar { position: relative; display: flex; align-items: center; gap: 7px; padding: 11px 14px; border-bottom: 1px solid rgba(255, 255, 255, 0.06) }
.vb .vb-termbar i { flex: none; width: 9px; height: 9px; border-radius: 50%; background: rgba(255, 255, 255, 0.14) }
.vb .vb-termtitle { position: absolute; left: 0; right: 0; top: 50%; transform: translateY(-50%); text-align: center; font-family: var(--vb-font-mono); font-size: 11.5px; line-height: 1.3; color: var(--vb-dark-label); white-space: nowrap; pointer-events: none }
.vb .vb-term-screen { height: 340px; overflow: hidden; padding: 16px 20px; font-family: var(--vb-font-mono); font-size: 12px; line-height: 1.7; color: #d4d4d8 }
.vb .vb-term-prompt { min-height: 1.7em; margin: 0 0 12px; color: var(--vb-dark-text); overflow-wrap: anywhere }
.vb .vb-term-glyph { color: var(--vb-accent-mid); margin-right: 8px }
.vb .vb-term-caret { display: inline-block; width: 7px; height: 13px; margin-left: 2px; vertical-align: -2px; background: var(--vb-accent-mid); animation: vbDashCaret 1s steps(1) infinite }
.vb .vb-term-step { position: relative; margin: 0; padding-left: 18px; color: var(--vb-dark-body); overflow-wrap: anywhere }
.vb .vb-term-step b { font-weight: 400; color: var(--vb-dark-text) }
.vb .vb-term-mark { position: absolute; left: 0; top: 0; width: 10px; height: 1.7em; display: flex; align-items: center; justify-content: center }
.vb .vb-term-spin { display: block; width: 9px; height: 9px; box-sizing: border-box; border-radius: 50%; border: 1.5px solid rgba(255, 255, 255, 0.22); border-top-color: var(--vb-accent-mid); animation: vbDashSpin 0.8s linear infinite }
.vb .vb-term-dot { display: block; width: 6px; height: 6px; border-radius: 50%; background: var(--vb-accent-mid) }
.vb .vb-term-out { margin-top: 10px; border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 8px; padding: 10px 12px; font-size: 11.5px; line-height: 1.6 }
.vb .vb-term-tr { display: grid; grid-template-columns: var(--vb-term-cols); gap: 10px; align-items: baseline; font-variant-numeric: tabular-nums }
.vb .vb-term-tr > span { min-width: 0 }
.vb .vb-term-tr.is-h { font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--vb-dark-label); padding-bottom: 2px }
.vb .vb-term-tr.is-total { margin-top: 4px; padding-top: 4px; border-top: 1px solid rgba(255, 255, 255, 0.08); color: var(--vb-dark-text) }
.vb .vb-term-tr .is-r { text-align: right }
.vb .vb-term-tr .is-dim { color: var(--vb-dark-label) }
.vb .vb-term-field { display: grid; grid-template-columns: 76px minmax(0, 1fr); gap: 10px }
.vb .vb-term-field > span:first-child { color: var(--vb-dark-label) }
.vb .vb-term-group + .vb-term-group { margin-top: 6px }
.vb .vb-term-group b { display: block; font-weight: 400; color: var(--vb-dark-text) }
.vb .vb-term-group span { display: block; padding-left: 12px; color: var(--vb-dark-body) }
.vb .vb-term-note { margin: 8px 0 0; color: var(--vb-dark-text) }
.vb .vb-term-done { margin: 10px 0 0; color: var(--vb-accent-mid) }
.vb .vb-term-screen.is-live .vb-term-step, .vb .vb-term-screen.is-live .vb-term-note, .vb .vb-term-screen.is-live .vb-term-done { animation: vbDashIn 0.35s ease both }
.vb .vb-term-screen.is-live .vb-term-out { animation: vbDashIn 0.5s ease both }
.vb .vb-tabs { position: relative; display: flex; gap: 2px; width: fit-content; max-width: 100%; margin: 12px auto 0; padding: 3px; background: var(--vb-surface); border: 1px solid var(--vb-hair); border-radius: 999px }
.vb .vb-tabs button { flex: none; display: inline-flex; align-items: baseline; padding: 6px 12px; border: 0; border-radius: 999px; background: transparent; font-family: var(--vb-font-body); font-size: 12px; line-height: 1.3; color: var(--vb-lede); white-space: nowrap; cursor: pointer; transition: background-color 0.25s ease, color 0.25s ease }
.vb .vb-tabs button:hover { color: var(--vb-ink) }
.vb .vb-tab-n { margin-right: 6px; font-family: var(--vb-font-mono); font-size: 10.5px; color: var(--vb-label) }
.vb .vb-tabs button.is-on { background: var(--vb-wash); color: var(--vb-accent-deep) }
.vb .vb-tabs button.is-on .vb-tab-n { color: var(--vb-accent-deep) }
.vb .vb-table.vb-dash-timing { margin-top: 16px }
@keyframes vbDashIn {
from { opacity: 0; transform: translateY(4px) }
to { opacity: 1; transform: none }
}
@keyframes vbDashSpin {
to { transform: rotate(360deg) }
}
@keyframes vbDashCaret {
50% { opacity: 0 }
}
@media (prefers-reduced-motion: reduce) {
.vb .vb-term-caret, .vb .vb-term-spin, .vb .vb-term-screen.is-live .vb-term-step, .vb .vb-term-screen.is-live .vb-term-out, .vb .vb-term-screen.is-live .vb-term-note, .vb .vb-term-screen.is-live .vb-term-done { animation: none }
.vb .vb-tabs button { transition: none }
}
.vb .vb-dashboard-preview { max-width: 760px; margin: 36px auto 0 }
.vb .vb-preview-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 12px; font-size: 13px; color: var(--vb-lede) }
.vb .vb-theme-toggle { display: flex; padding: 3px; border: 1px solid var(--vb-hair); border-radius: 999px; background: white }
.vb .vb-theme-toggle button { min-height: 40px; padding: 8px 20px; border-radius: 999px; font-size: 13px; cursor: pointer; color: var(--vb-lede) }
.vb .vb-theme-toggle button[aria-pressed="true"] { background: var(--vb-ink); color: white }
.vb .vb-preview-caption { margin: 18px 0 10px; color: var(--vb-lede); font-size: 12px; line-height: 1.6 }
.vb .vb-preview-theme .vb-win { max-width: none; border-radius: 14px; background: var(--preview-bg); border-color: var(--preview-border); color: var(--preview-text) }
.vb .vb-preview-theme .vb-winbar { background: var(--preview-surface); border-color: var(--preview-border) }
.vb .vb-preview-theme .vb-windot { background: var(--preview-active) }
.vb .vb-preview-theme .vb-winurl { color: var(--preview-muted) }
.vb .vb-app { height: 100%; background: var(--preview-bg); color: var(--preview-text); font-family: var(--font-inter), sans-serif; font-size: 14px; line-height: 1.4 }
.vb .vb-app-header { height: 56px; padding: 0 20px; background: var(--preview-surface); display: flex; align-items: center; justify-content: space-between }
.vb .vb-app-header > div { display: flex; align-items: center; gap: 18px }
.vb .vb-app-org { font-size: 16px; font-weight: 500 }
.vb .vb-app-avatar { display: grid; place-items: center; width: 32px; height: 32px; background: var(--preview-active); border-radius: 50%; font-size: 11px }
.vb .vb-app-layout { display: flex; height: calc(100% - 56px) }
.vb .vb-app-sidebar { width: 192px; flex: none; padding: 16px 8px; background: var(--preview-sidebar); font-size: 13px }
.vb .vb-app-sidebar p { margin: 16px 8px 4px; color: var(--preview-muted); font-size: 11px }
.vb .vb-app-sidebar > div > span, .vb .vb-app-search { display: flex; align-items: center; gap: 9px; padding: 7px 8px; border-radius: 6px }
.vb .vb-app-sidebar .is-active { background: var(--preview-active) }
.vb .vb-app-search { color: var(--preview-muted); margin-bottom: 10px }
.vb .vb-app-search small { margin-left: auto }
.vb .vb-app-main { flex: 1; min-width: 0; padding: 32px }
.vb .vb-app-timeframe { display: flex; align-items: center; gap: 10px; margin-bottom: 24px }
.vb .vb-app-timeframe > span { color: var(--preview-muted); font-size: 12px }
.vb .vb-app-metrics, .vb .vb-app-rates { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px }
.vb .vb-app-rates { margin-top: 16px }
.vb .vb-app-metric { display: flex; flex-direction: column; gap: 8px; padding: 24px; border-radius: 6px; background: var(--preview-active) }
.vb .vb-app-metric > div { display: flex; justify-content: space-between; align-items: start; gap: 10px; font-family: var(--vb-font-head); font-size: 14px }
.vb .vb-app-metric svg { flex: none; color: var(--preview-muted) }
.vb .vb-app-metric > b { font-size: 24px; font-weight: 400; font-variant-numeric: tabular-nums }
.vb .vb-app-metric > span { font-size: 12px; color: var(--preview-muted) }
.vb .vb-app-rates .vb-app-metric > b { font-size: 30px }
.vb .vb-app-progress { display: block; height: 16px; border-radius: 999px; background: color-mix(in srgb, var(--preview-text) 20%, transparent); overflow: hidden }
.vb .vb-app-progress i { display: block; height: 100%; background: var(--preview-text) }
.vb .vb-app.is-phone .vb-app-metrics, .vb .vb-app.is-phone .vb-app-rates { grid-template-columns: 1fr }
.vb .vb-app.is-phone .vb-app-main { padding: 24px }
/* components/brand/sections/dash/projects.css */
.vb .vb-app-projects { padding: 16px; background: var(--preview-bg); color: var(--preview-text); font-family: var(--font-inter), sans-serif }
.vb .vb-app-project-heading { display: flex; justify-content: space-between; align-items: center; margin-bottom: 40px; font-size: 18px; font-weight: 600; line-height: 28px }
.vb .vb-app-project-heading small { font-size: 14px; font-weight: 500 }
.vb .vb-app-job-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px }
.vb .vb-app-job { min-width: 0; overflow: hidden; border-radius: 6px }
.vb .vb-app-job-art { position: relative; display: flex; flex-direction: column; justify-content: space-between; aspect-ratio: 5 / 2; padding: 8px; border-radius: 6px; overflow: hidden; isolation: isolate }
.vb .vb-app-job-payment, .vb .vb-app-job-tech { position: relative; z-index: 1; display: flex; align-items: center; width: fit-content; background: color-mix(in srgb, var(--preview-surface) 60%, transparent); backdrop-filter: blur(12px); border-radius: 999px }
.vb .vb-app-job-payment { gap: 4px; padding: 2px 8px; color: #b91c1c; font-size: 11px; font-weight: 500; line-height: 16px }
.vb .dark .vb-app-job-payment { color: #f87171 }
.vb .vb-app-job-tech { align-self: center; max-width: 80%; gap: 12px; padding: 4px 12px 4px 4px; color: var(--preview-text) }
.vb .vb-app-job-tech > span:nth-child(2) { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; font-weight: 600; line-height: 1.4 }
.vb .vb-app-job-tech > svg { flex: none; margin-left: -6px }
.vb .vb-app-job-avatar { flex: none; display: grid; place-items: center; width: 32px; height: 32px; background: color-mix(in srgb, var(--preview-muted) 20%, transparent); border-radius: 50%; font-size: 12px }
.vb .vb-app-job-content { display: flex; flex-direction: column; gap: 8px; padding: 16px }
.vb .vb-app-job-identity { display: flex; flex-direction: column; min-width: 0; font-weight: 600 }
.vb .vb-app-job-identity > span { font-size: 14px; line-height: 20px; font-variant-numeric: tabular-nums }
.vb .vb-app-job-identity > b { font-size: 16px; line-height: 1.3; font-weight: 600; overflow: hidden; white-space: nowrap; text-overflow: ellipsis }
.vb .vb-app-job-status-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-height: 24px }
.vb .vb-app-job-status { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 500; line-height: 1.4 }
.vb .vb-app-job-status i { width: 6px; height: 6px; border-radius: 50%; background: currentColor }
.vb .vb-app-job-media { display: flex; flex: none }
.vb .vb-app-job-media > span { display: grid; place-items: center; width: 20px; height: 20px; border: 1px solid var(--preview-bg); border-radius: 50%; background: var(--preview-active); color: var(--preview-muted) }
.vb .vb-app-job-media > span + span { margin-left: -6px }
.vb .vb-app-job-address { color: var(--preview-muted) }
.vb .vb-app-job-address p { margin: 0; font-size: 13px; line-height: 1.4; overflow: hidden; white-space: nowrap; text-overflow: ellipsis }
.vb .vb-app-job-address p + p { margin-top: 2px; font-size: 12px; opacity: 0.8 }
.vb .vb-app-job-area { display: inline-flex; align-items: center; gap: 4px; margin-top: 4px; padding: 2px 8px; border: 1px dashed var(--preview-border); border-radius: 999px; font-size: 10px; line-height: 14px; opacity: 0.7 }
.vb .vb-app-job-schedule { display: flex; flex-wrap: wrap; gap: 4px 12px; margin-bottom: 8px; color: var(--preview-muted); font-size: 12px; line-height: 16px }
.vb .vb-app-job-schedule > span { display: inline-flex; align-items: center; gap: 4px }
.vb .vb-app-job-notes { margin: 8px 0 0; padding-top: 8px; border-top: 1px solid var(--preview-border); color: var(--preview-muted); font-size: 13px; line-height: 1.4; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden }
.vb .vb-app-projects.is-phone .vb-app-job-grid { grid-template-columns: 1fr; gap: 24px }
.vb .vb-app-projects.is-phone .vb-app-project-heading { margin-bottom: 24px }
/* components/brand/sections/motion.css */
.vb .vb-motion { max-width: 720px; margin: 36px auto 0 }
.vb .vb-mo-panel { min-height: 600px; display: flex; align-items: center; justify-content: center }
.vb .vb-mo-win { max-width: 482px }
.vb .vb-mo-screen { position: relative; aspect-ratio: 480 / 256; overflow: hidden; background: var(--vb-surface) }
.vb .vb-mo-fill { position: absolute; inset: 0 }
.vb .vb-mo-win .vb-cap b { font-weight: 600; color: var(--vb-ink) }
.vb .vb-mo-table code { font-family: var(--vb-font-mono); font-size: 12px; color: var(--vb-ink) }
```
## Appendix D — phone rules (@media (max-width: 760px)) for the kit above
Verbatim from the same files, in the same order: every `@media (max-width: 760px)` block, wrapped in its own at-rule. Paste as-is. /brand collapses at 760px; its tablet steps — `@media (min-width: 761px) and (max-width: 1024px)`, and `@media (max-width: 1024px)` in `components.css` — are in Appendix C.
```css
/* app/brand/brand.css */
@media (max-width: 760px) {
.vb-hero { padding: 112px 20px 64px }
.vb-h1 { font-size: 40px }
.vb-h1 br { display: none }
.vb-lede0 { font-size: 17px }
.vb-sec { padding: 64px 20px 72px }
.vb-h2 { font-size: 32px }
.vb-lede { font-size: 15px }
.vb-btns, .vb-btns.is-left { flex-direction: column; align-items: stretch }
.vb-btns .vb-btn-dark, .vb-btns .vb-btn-soft { justify-content: center; text-align: center }
.vb-grid3, .vb-grid4, .vb-comps { grid-template-columns: 1fr }
.vb-rules { grid-template-columns: 1fr }
.vb-swatches { grid-template-columns: repeat(2, minmax(0, 1fr)) }
.vb-tile-art { height: 140px }
.vb-tile-cap { flex-direction: column; align-items: flex-start; gap: 10px }
.vb-trow, .vb-table.vb-anat .vb-trow, .vb-table.is-motion .vb-trow { grid-template-columns: 1fr; gap: 2px; padding: 12px 16px }
.vb-table.is-motion .vb-trow > :last-child { text-align: left }
.vb-comp { padding: 22px 20px 24px }
.vb-winurl { font-size: 10px; max-width: 70%; overflow: hidden; text-overflow: ellipsis }
.vb-chips { gap: 6px }
.vb-chip { padding: 6px 12px; font-size: 12.5px }
.vb-panel.is-wash { padding: 36px 16px }
.vb-ai { padding: 32px 22px 34px; border-radius: 20px }
}
/* components/brand/sections/colour.css */
@media (max-width: 760px) {
.vb .vb-status-pairs { grid-template-columns: 1fr }
.vb .vb-status-pair p { min-height: 0 }
}
/* components/brand/sections/type.css */
@media (max-width: 760px) {
.vb .vb-tgrid, .vb .vb-fonts { grid-template-columns: 1fr }
.vb .vb-tcard { padding: 22px 20px 24px }
.vb .vb-t-h1 { font-size: 36px }
.vb .vb-t-h2 { font-size: 27px }
}
/* components/brand/sections/gradient.css */
@media (max-width: 760px) {
.vb .vb-zgrid { grid-template-columns: 1fr }
}
/* components/brand/sections/components.css */
@media (max-width: 760px) {
.vb .vb-bcard { max-width: none; margin-top: 22px }
.vb .vb-pc { padding: 22px 20px 22px }
}
/* components/brand/sections/dash.css */
@media (max-width: 760px) {
.vb .vb-term-screen { padding: 14px; font-size: 11px }
.vb .vb-term-out { padding: 9px 10px; font-size: 10.5px }
.vb .vb-term-tr { grid-template-columns: var(--vb-term-cols-phone); gap: 8px }
.vb .vb-term-tr .is-wide { display: none }
.vb .vb-term-field { grid-template-columns: 60px minmax(0, 1fr); gap: 8px }
.vb .vb-termtitle { font-size: 10.5px }
.vb .vb-tabs { overflow-x: auto; scrollbar-width: none; overscroll-behavior-x: contain }
.vb .vb-tabs::-webkit-scrollbar { display: none }
.vb .vb-tabs button { padding: 6px 10px }
.vb .vb-tabs button:focus-visible { outline-offset: -2px }
}
@media (max-width: 760px) {
.vb .vb-dashboard-preview { max-width: 420px; margin-top: 28px }
}
/* components/brand/sections/motion.css */
@media (max-width: 760px) {
.vb .vb-mo-panel { min-height: 440px }
}
```
The marketing site is mobile first on Tailwind's stock breakpoints (`sm` 640 · `md` 768 · `lg` 1024): the bare utility is the phone value. The pairs that carry its layout:
```
px-4 sm:px-6 lg:px-8 /* section gutter 16 → 24 → 32px (components/landing/Training.tsx and the other home sections) */
py-16 sm:py-24 /* section rhythm 64 → 96px */
text-[2rem] sm:text-[2.5rem] /* section headline 32 → 40px */
grid-cols-1 sm:grid-cols-3 /* the bento card grid is one column on a phone (components/landing/BentoGrid.tsx) */
w-full sm:w-auto /* the hero CTAs fill the phone width (components/landing/HeroCTA.tsx) */
```
Behaviours that change rather than resize: the splash never runs below 640px (`NOT_PHONE = "(min-width: 640px)"`, `lib/use-media-query.ts`); the footer only pins when the screen is at least 640px wide AND 700px tall (`[@media(min-width:640px)_and_(min-height:700px)]:fixed`, `components/ui/motion-footer.tsx`), because a landscape phone would push its CTA out of reach. The scroll pin does not change with width — "ONLY reduced motion loses the pin. Screen width does not — …" (`components/landing/hero-story/HeroStage.tsx`).
The bar every page is held to: it works at 390px — no horizontal page scroll, and any wide thing (a table, a code block) scrolls inside its own container. Small repeated items may stay in two or more columns on a phone (the tool tiles in `ProblemSection.tsx`; the footer link grid, `components/ui/motion-footer.tsx`).