Design System — Niamoto Demo Video¶
Project : media/demo-video/ (Remotion 4.0.448, React 19, 1920×1080, 30fps)
Total duration : ~92.5s (2775 frames) — 8 sequences with 7 transitions (15f each)
1. Visual Theme & Atmosphere¶
Light, airy, data-forward aesthetic inspired by the Niamoto frond theme — a scientific workspace that feels trustworthy without being sterile. The overall mood is calm and precise : neutral whites and soft greys provide breathing room, while the forest-green brand accent anchors the eye on what matters (primary actions, active states, validated outcomes). No decorative flourishes — every visual element serves to orient or reassure the viewer.
The motion language is confident but gentle : spring-driven entrances land softly (damping 14-15), cursor trajectories follow cubic Bézier curves, and transitions between acts use 15-frame fades to avoid visual whiplash.
2. Color Palette & Roles¶
Brand¶
Name |
Hex |
Role |
|---|---|---|
Deep Forest Green |
|
Primary CTA fill, active focus rings, success gradient start |
Vibrant Leaf Green |
|
Success badges, progress-bar fill, active tree-item highlight |
Muted Steel Blue |
|
Sidebar active chip background, preview header gradient end, accent |
Charcoal |
|
Reserved for dark accents (traffic-light titlebar aside, logo fallback) |
Surfaces (light frond)¶
Name |
Hex |
Role |
|---|---|---|
Cloud Canvas |
|
Page/video background — the base canvas |
Pearl Light |
|
Window interior fallback |
Window Interior |
|
AppWindow body + TopBar background |
Titlebar Mist |
|
macOS titlebar stripe |
Sidebar Fog |
|
Sidebar column |
Sidebar Hover |
|
Inactive nav-item hover (unused in video but available) |
Card White |
|
Elevated cards, form panels |
Subtle Surface |
|
Recessed mock widgets in Site Builder editor |
Muted Surface |
|
Canvas gradient end, secondary fill |
Text¶
Name |
Hex |
Role |
|---|---|---|
Ink Slate |
|
Body text, labels, input values |
Ink Graphite |
|
Legacy “textWhite” token — used where a slightly softer ink reads better |
Stone Grey |
|
Secondary text, muted labels, inactive nav labels |
Pebble Grey |
|
Placeholder text, tertiary copy |
Snow |
|
Text on primary CTA ( |
Borders & Semantic¶
Name |
Value |
Role |
|---|---|---|
Border Default |
|
Standard 1px dividers |
Border Strong |
|
Input rings, card outlines |
Window Shadow |
|
AppWindow elevation |
Success |
|
Published badge, step-complete dots |
Warning |
|
“Stale” status badges |
Danger |
|
Error states |
FileTypeChip palette (Act 3)¶
Type |
Hex |
Extensions |
|---|---|---|
Tabular data |
|
|
Spatial data |
|
|
Raster data |
|
|
macOS Traffic Lights¶
#FF5F57 (red) · #FEBC2E (yellow) · #28C840 (green) — 12px circles, 8px gap.
3. Typography Rules¶
Two families — one for UI, one for code.
Plus Jakarta Sans (display) — weights 400, 500, 600, 700. Friendly geometric sans with generous counters. Used for every label, headline, button, and nav item. Loaded via
@remotion/fontsfrompublic/fonts/plus-jakarta-sans/*.woff2.JetBrains Mono (code) — weights 400, 500. Used exclusively for paths (
~/projects/my-ecology-project), YAML content, and domain names displayed as URLs.
Scale (observed from the codebase)¶
Size |
Usage |
|---|---|
56 / 700 |
Act 1 hero logotype (“Niamoto”) |
42 / 700 |
Legacy gradient hero (unused in final composition) |
32 / 700 |
Act 2 wizard title “Créer un nouveau projet” |
28 / 700 |
MockCard count numbers |
24 / 500 |
TransitionLabel pill text |
22 / 700 |
Act headers (“Import Data”, “Collections”, “Publish Site”) |
20 / 600 |
ShimmerText “Auto-configuration…” |
18 / 700 |
Site Builder editor section title |
17 / 500-600 |
Wizard field labels, MockCard name |
16 / 400-500 |
Buttons, tagline, editor body |
15 / 500-600 |
Input values, mock widget labels |
14 / 400-500 |
Sidebar labels, card subtitle, descriptions |
13 / 400-500 |
TopBar search, YAML code, editor fields |
12 / 500-600 |
Section headers (“PAGES”, “PREVIEW”), status badges |
11 / 500 |
Stat labels, keyboard hints (⌘K) |
9 / 400 |
Browser chrome URL, “Powered by Niamoto” footer |
Letter-spacing stays at 0 everywhere except for two cases : hero logotype (-1.5), wizard title (-0.8) — subtle negative tracking to tighten display type.
4. Component Stylings¶
AppWindow¶
macOS-style container with gently rounded corners (borderRadius: 12), a 1px borderStrong outline and the Window Shadow elevation. Body is Window Interior (#FCFDFE). Titlebar is a 32px Titlebar Mist stripe with traffic lights left-aligned (56,46) and an optional centered title in Stone Grey. Props: showSidebar, showTopBar, activeSidebarItem, title, sidebarSlideInDuration (default 20 frames). The sidebar width interpolates from 0 to LAYOUT.sidebar.width (200px) over the slide-in window.
TopBar¶
48px bar on Window Interior. Left: a subtle sidebar-toggle icon (stylized column). Right-aligned: search field (rgba(255,255,255,0.94), borderRadius: 10, width 330, Border Default) with magnifier glyph, “Search…” placeholder in Stone Grey, and a ⌘K keycap chip. Far right: a bell and a help icon (16px, Stone Grey / Ink Slate).
MockInput¶
Vertical stack (6px gap) with a Stone Grey 13/500 label. Field: rgba(255,255,255,0.96) on 1px Border Strong, borderRadius: 8, 10/14 padding. Value in Ink Slate (mono when mono prop is set). Typing reveal via text.substring(0, revealedCount) — blinking 2px cursor in accent colour (now steelBlue) every 15 frames once typing is done.
MockCard¶
Elevated rectangle on Card White with 1px subtle border and borderRadius: 7. 20px padding, 14px gap between rows. Rows: (1) name + status chip, (2) large 28/700 count + “records” suffix, (3) divider + two stats. Status chips are pill-shaped (padding 3/10, 11/500), tinted at 9% of the status colour (Leaf Green / Amber / Danger). Entrance is wrapped in SpringPopIn staggered 0.3s apart.
FileTypeChip¶
Pill-shaped (borderRadius: 20), padding 6px 14px, 8px gap. Background uses the type colour at ~10% opacity (${color}18), border at ~25% (${color}40). Left: 8px circular dot in the type colour. Label is 13/500 in the type colour; extensions list is 11/400 in the same colour at ~60% opacity. Slides in from the left (-30px → 0) with a 15-frame opacity fade.
FileUploadZone¶
Dashed outline (2px dashed rgba(255,255,255,0.15)) with borderRadius: 12, centered Upload glyph, empty-state text in Stone Grey. When the filesEnterFrame milestone is crossed, padding collapses and the zone accepts child content (the FileTypeChips list). Background shifts from rgba(255,255,255,0.03) to 0.02 to signal “filled”. Note : the zone was designed against the original dark theme — on the current light theme its dashed ring reads subtly; could be retuned.
YamlPreview¶
Dark scientific-notebook plate (#16161A) with borderRadius: 10 and a soft top divider. Header bar shows import.yml in 11px Stone Grey. Code uses JetBrains Mono 13 at line-height 1.7. Simple syntax highlighting: keys → Vibrant Leaf Green, values → Muted Steel Blue (legacy #3FA9F5), colons → Stone Grey. Enters with a 30px upward slide + opacity fade over 20 frames.
ProgressBar¶
Two parts: (1) a 6px-tall track (Card White-on-dark cardDark — visually white now) with rounded ends; fill is linear-gradient(90deg, forestGreen → lightGreen) driven by Easing.out(Easing.cubic). (2) Optional step list (8px row gap) with 16px circular indicators: complete → green filled check, active → green outline ring, pending → grey dot.
MockTree¶
Compact hierarchical list, 1px row gap. Per-item padding 5px 8px, indented 18px per depth level. Each row: chevron (collections only) + type icon + 13/400 label. Active item gets a subtle rgba(75, 175, 80, 0.1) background and label switches to Leaf Green / 600. Items fade in staggered (4 frames per item) once expandAtFrame is reached.
MockPreviewPanel¶
A “mini-browser” simulation of a published Niamoto site. Top: 28px chrome bar in #E8E8EC with three grey dots and a white URL pill showing my-ecology-project.niamoto.io. Body: header band with linear-gradient(135deg, forestGreen → #26A69A) + 14/700 title in white, a 2×2 grid of white placeholder cards (boxShadow 0 1px 3px rgba(0,0,0,0.08)), footer divider + “Powered by Niamoto” 9px caption. Supports an array of transitions: [{atFrame, title}] — each transition cross-fades between the outgoing and incoming layout over 15 frames.
TransitionLabel¶
Pill-shaped contextual label appearing in the first 30 frames of Acts 3, 5 and 6. Centered near the top (72px from the top), padded 10px 18px, borderRadius: 999. Background rgba(30, 30, 34, 0.84) with a thin light-alpha border and a soft drop shadow. Text is 24/500 in Stone Grey with letterSpacing: 0.5. Fade-in 10f → hold → fade-out 10f.
NiamotoLogo¶
Vector-built logotype (see ui/NiamotoLogo.tsx). Used at 112px in Act 1 hero and 96px in the Act 2 wizard card. Renders crisply at any scale — no raster asset needed.
5. Layout Principles¶
Canvas : fixed at 1920×1080. All coordinates assume that frame.
AppWindow frame : inset 40px left/right, 30px top, 30px bottom (1840×1020).
borderRadius: 12, shadow as specified.Titlebar : 32px. Traffic lights at
(x=56, y=46)— relative to canvas.Sidebar : 200px wide when visible. Animated slide-in (width 0 → 200) over 20 frames when
showSidebarbecomes true.TopBar : 48px tall when visible.
Content region : the remaining area inside the AppWindow body, minus sidebar and topbar. Padding is 28px horizontal / 28px top by default.
Vertical rhythm : 8px base unit. Common gaps: 6/8/10/12/16/20/24/28 px.
Site Builder panels : 20% / 50% / 30% split, each separated by a 1px Border Default divider.
Cursor coordinates : defined in canvas space (not window-relative). See
cursor/cursorPaths.ts.
6. Depth & Elevation¶
Layering is flat but deliberate. Shadows are whisper-soft — never dominant — and used only to lift elevated surfaces from the canvas.
z-index |
Layer |
|---|---|
|
Canvas (page background gradient) |
|
AppWindow body content |
|
Sidebar (stays at natural stacking inside the window) |
|
TopBar (natural stacking) |
|
Modal cards (e.g. Act 2 wizard — uses scale + opacity instead of a z-index shift) |
|
TransitionLabel pill |
|
CursorOverlay (always on top) |
Shadows in use:
AppWindow :
0 16px 44px rgba(15, 23, 42, 0.12)— primary elevationWizard card (Act 2) :
0 12px 28px rgba(15, 23, 42, 0.06)— secondary elevationWelcome action card (primary) :
0 14px 34px rgba(46, 125, 50, 0.18)— green-tinted haloWelcome action card (secondary) :
0 8px 24px rgba(15, 23, 42, 0.06)Preview panel inner cards :
0 1px 3px rgba(0,0,0,0.08)— hairline elevationGradient buttons :
0 10px 24px rgba(46, 125, 50, 0.18)— matches the primary CTA haloInput fields :
0 1px 2px rgba(15, 23, 42, 0.03)— barely perceptible liftTransitionLabel :
0 10px 30px rgba(0,0,0,0.22)— the only darker shadow, because the label floats on top of variable content
7. Do’s and Don’ts¶
Do¶
✅ Use
spring()for discrete entrances (cards, buttons, logos appearing).✅ Use
interpolate()for progressions (typing, progress bars, opacity fades, cross-fades).✅ Always pass
extrapolateLeft: "clamp"andextrapolateRight: "clamp"tointerpolate().✅ Reveal text via
text.substring(0, revealedCount)— never per-character opacity.✅ Drive click feedback from frame, not from event handlers (
clickAtFrameprop pattern).✅ Use generic ecological mock data (
MOCK_COLLECTIONS,MOCK_YAML) — no NC-specific place or species names.✅ Wrap costly scenes in
<Sequence>at the composition level for isolation.✅ Block render on font load via
delayRender/continueRender+ensureFontsLoaded().
Don’t¶
❌ Never use CSS
@keyframes,transition, oranimation.❌ Never adjacent-pair
TransitionSeries.Overlaywith aTransitionSeries.Transition— use a<Sequence>inside the act instead.❌ Never use
pnpm buildas a typecheck — it triggers a render. Runpnpm exec tsc --noEmitinstead.❌ Never hardcode cursor coordinates inline in act components — add them to
cursor/cursorPaths.ts.❌ Never import from
src/niamoto/gui/ui— the video must stay fully self-contained.❌ Never introduce raster assets for UI chrome — everything is SVG or DOM primitives.
8. Scene Inventory¶
# |
Scene |
Duration |
AppWindow |
Sidebar active |
Key components |
Primitives |
|---|---|---|---|---|---|---|
0 |
IntroLogo |
4s |
— |
— |
NiamotoLogo (raster img), tagline |
|
1 |
Act1Welcome |
8s |
no chrome |
— |
|
|
2 |
Act2ProjectWizard |
12s |
no chrome |
— |
|
|
3 |
Act3Import |
20s |
with chrome |
Data |
|
|
4 |
Act4Collections |
14s |
with chrome |
Collections |
|
|
5 |
Act5SiteBuilder |
20s |
with chrome |
Site |
|
|
6 |
Act6Publish |
12s |
with chrome |
Publish |
|
|
7 |
OutroCTA |
6s |
— |
— |
Logo, arsis.dev, GitHub URL |
|
Transitions : 7 total, 15 frames each.
Intro → Act1 → Act2 :
fadeAct2 → Act3 :
slide({ direction: "from-right" })— unique moment, signals workspace revealAct3 → Act4 → Act5 → Act6 → Outro :
fade
Cursor paths live in cursor/cursorPaths.ts, keyed per act. Coordinates are in canvas space.
9. Agent Prompt Guide¶
When asking an AI agent to modify or regenerate a scene, follow this template :
Modify <Act N> in media/demo-video/src/acts/ActNName.tsx.
Constraints:
- Duration stays at <N>s — do not change DURATIONS in config.ts unless explicitly asked.
- Use only interpolate() and spring() — NEVER CSS @keyframes / transition / animation.
- Always pass extrapolateLeft + extrapolateRight: "clamp" to interpolate().
- Respect the design tokens in src/shared/theme.ts — do not introduce new hex values.
- Respect typography: Plus Jakarta Sans (display) + JetBrains Mono (code only).
- Respect the layout constants in src/shared/layout.ts — do not redefine window / sidebar sizes.
- If you need a new mock value, add it to src/shared/mockData.ts (generic ecological data only).
- Keep cursor waypoints in cursor/cursorPaths.ts — do not inline them.
- Validate with: cd media/demo-video && pnpm exec tsc --noEmit
- Preview in Studio: pnpm dev → open http://localhost:3000
Recipes¶
Add a new UI primitive → create
ui/<Name>.tsxfollowing the pattern ofMockCardorMockButton: namedReact.FC, takes props, pure interpolate/spring, no side effects, no events.Add a new animation primitive → add to
animations/with a 30-80 line component, export a clean interface, zero CSS animations, always readuseCurrentFrame()anduseVideoConfig().Adjust timing → modify
DURATIONSinshared/config.ts. The total frame count is recomputed automatically ;TRANSITION_COUNTin the same file assumes 7 transitions.Adjust cursor path → edit
cursor/cursorPaths.ts. Coordinates are canvas-space.holdis in frames.click: truetriggers the ripple + scale spring insideCursorFlow.Render final MP4 →
npx remotion render MarketingLandscape --output=out/niamoto-demo.mp4frommedia/demo-video/.
Generated 2026-04-14 — reflects the state of feat/demo-video after Phase 4.