Espresso Cloud — Design System v1.0
Netlify for business apps. A SaaS open-source application hosting platform that makes deploying Frappe/ERPNext and modern frontends as simple as
git push.
Implementation: Tailwind CSS via tailwind.config.js + VitePress custom theme. All tokens below map to Tailwind utility classes.
1. Brand Identity & Philosophy
Brand Essence
An espresso is precision-engineered: exact temperature, exact pressure, exact timing. The result is something concentrated, reliable, and unmistakably premium. That is the experience this platform delivers for infrastructure deployment.
Design Principles
- Precision over decoration. Every visual element serves a purpose. Ornament is earned through clarity.
- Warmth signals trust. Enterprise platforms default to cold blues and stark whites. Espresso Cloud uses warm tones to communicate approachability without sacrificing authority.
- Craft implies quality. The serif-meets-grotesk typography pairing signals intentional design — the same intention applied to the platform's engineering.
- Motion is functional. Animations orient the user, confirm actions, and create rhythm. They never delay or distract.
- Density with breathing room. Information-dense when needed (pricing, features), spacious when persuading (hero, CTA).
Voice & Tone
- Technical but not jargon-heavy
- Confident but not aggressive
- Warm but not casual
- Precise language, short sentences, active voice
Competitor Differentiation
| Aspect | Sevalla | PikaPods | Espresso Cloud |
|---|---|---|---|
| Primary hue | Orange #FA7216 | Green | Copper #C4501A |
| Typography | Sans-serif only | System fonts | Serif + Grotesk pairing |
| Motion | Cascading slide-ups | Minimal | Purposeful reveals + warm glow micro-interactions |
| Personality | Clean minimalist | Utilitarian | Warm premium craft |
| Background | Pure white | White | Warm cream #FBF8F4 |
2. Color System
2.1 Core Palette
| Swatch | Name | Hex | Usage |
|---|---|---|---|
![]() | Deep Espresso | #1C1210 | Near-black, dark backgrounds, primary text |
![]() | Roast | #2E1F1A | Dark elevated surfaces |
![]() | Copper | #C4501A | Primary CTA, links, brand accents |
![]() | Copper Hover | #A84315 | Hover state for copper elements |
![]() | Copper Active | #8E3812 | Active/pressed state |
![]() | Crema Gold | #D4956A | Warm decorative accent, overlines, highlights |
![]() | Foam White | #FBF8F4 | Primary light background |
![]() | Parchment | #F3EDE6 | Alternate light background |
![]() | Latte | #E8DFD5 | Soft background, dividers |
Why Copper instead of Orange?
#C4501Ais distinctly burnt/brown-orange — firmly separated from Sevalla's pure orange#FA7216. It reads as warm and earthy, not energetic and citrus.
Tailwind usage: bg-ec-copper, text-ec-crema, bg-ec-foam, border-ec-latte, text-ec-espresso, etc.
2.2 Warm Neutral Grays
All grays have a subtle warm undertone (shifted toward brown/amber) instead of the typical blue-gray used in most SaaS products.
Tailwind usage: bg-warmgray-100, text-warmgray-600, border-warmgray-300, etc.
warmgray-50: #FAF8F6
warmgray-100: #F3EDE6
warmgray-200: #E8DFD5
warmgray-300: #D4C8BA
warmgray-400: #B5A899
warmgray-500: #8F8277
warmgray-600: #6B6059
warmgray-700: #4D4440
warmgray-800: #352E2B
warmgray-900: #1C12102.3 Semantic Colors
Tailwind usage: text-steam, bg-mint, text-amber-dark, bg-rosso-solid, etc.
| Category | Text (.dark) | Default | Solid BG | Soft BG |
|---|---|---|---|---|
Info (steam) | #4A7FA8 (.light: #8CBCE0) | #5B8FB9 | #6BA0CA | rgba(91,143,185,0.12) |
Success (mint) | #2D8F6F (.light: #6DDAB4) | #3DB88C | #4AC99A | rgba(61,184,140,0.12) |
Warning (amber) | #9A6B2C (.light: #E0B060) | #C4872E | #D49A3A | rgba(196,135,46,0.12) |
Danger (rosso) | #B83340 (.light: #F07080) | #D94052 | #E55565 | rgba(217,64,82,0.12) |
2.4 Light Mode Theme
These CSS custom properties are set in docs/.vitepress/theme/style.css to override VitePress defaults. Use the Tailwind utility classes (e.g., bg-ec-foam, text-ec-espresso) rather than referencing these variables directly.
:root {
/* Backgrounds */
--ec-bg: #FBF8F4; /* Foam White — main canvas */
--ec-bg-alt: #F3EDE6; /* Parchment — alternate sections */
--ec-bg-elevated: #FFFFFF; /* Cards, dialogs floating above bg */
--ec-bg-soft: #F3EDE6; /* Subtle distinction areas */
--ec-bg-inverse: #1C1210; /* Dark sections for contrast (footer) */
/* Text */
--ec-text-1: #1C1210; /* Primary text — Deep Espresso */
--ec-text-2: #6B6059; /* Secondary/muted text */
--ec-text-3: #8F8277; /* Tertiary/placeholder text */
--ec-text-inverse: #FBF8F4; /* Text on dark backgrounds */
/* Borders */
--ec-border: #D4C8BA; /* Interactive component borders */
--ec-divider: #E8DFD5; /* Section dividers */
--ec-gutter: #E8DFD5; /* Layout gutters */
/* Brand / CTA */
--ec-brand-1: #C4501A; /* Copper — links, text accents */
--ec-brand-2: #A84315; /* Copper Hover */
--ec-brand-3: #C4501A; /* Copper — solid button bg */
--ec-brand-soft: rgba(196, 80, 26, 0.10);
/* Accent */
--ec-accent-1: #D4956A; /* Crema Gold — decorative, highlights */
--ec-accent-soft: rgba(212, 149, 106, 0.15);
/* Shadows — warm-tinted */
--ec-shadow-sm: 0 1px 2px rgba(28,18,16,0.05), 0 1px 2px rgba(28,18,16,0.04);
--ec-shadow-md: 0 4px 12px rgba(28,18,16,0.06), 0 1px 4px rgba(28,18,16,0.04);
--ec-shadow-lg: 0 12px 32px rgba(28,18,16,0.08), 0 2px 6px rgba(28,18,16,0.04);
--ec-shadow-xl: 0 18px 56px rgba(28,18,16,0.12), 0 4px 12px rgba(28,18,16,0.06);
/* Glow — for hover/focus states */
--ec-glow-brand: 0 0 0 3px rgba(196, 80, 26, 0.15);
--ec-glow-accent: 0 0 20px rgba(212, 149, 106, 0.20);
}2.5 Dark Mode Theme
Dark mode activates via the .dark class (VitePress toggle). Tailwind dark variants use dark: prefix (e.g., dark:bg-[#0F0B09]). VitePress components auto-adapt via these overrides:
.dark {
/* Backgrounds — rich warm darks, never pure black */
--ec-bg: #0F0B09;
--ec-bg-alt: #1A1311;
--ec-bg-elevated: #241B17;
--ec-bg-soft: #1A1311;
--ec-bg-inverse: #FBF8F4;
/* Text — warm off-whites */
--ec-text-1: #EDE6DC;
--ec-text-2: #A89A8E;
--ec-text-3: #6B6059;
--ec-text-inverse: #1C1210;
/* Borders */
--ec-border: #3D322C;
--ec-divider: #2A221E;
--ec-gutter: #0A0706;
/* Brand — lighter copper for dark bg readability */
--ec-brand-1: #E0875A;
--ec-brand-2: #C4501A;
--ec-brand-3: #B8481A;
--ec-brand-soft: rgba(224, 135, 90, 0.14);
/* Accent */
--ec-accent-1: #D4956A;
--ec-accent-soft: rgba(212, 149, 106, 0.12);
/* Semantic — adjusted for dark mode contrast */
--ec-info-1: #8CBCE0;
--ec-success-1: #6DDAB4;
--ec-warning-1: #E0B060;
--ec-danger-1: #F07080;
/* Shadows — deeper on dark */
--ec-shadow-sm: 0 1px 2px rgba(0,0,0,0.3);
--ec-shadow-md: 0 4px 12px rgba(0,0,0,0.4);
--ec-shadow-lg: 0 12px 32px rgba(0,0,0,0.5);
--ec-shadow-xl: 0 18px 56px rgba(0,0,0,0.6);
--ec-glow-brand: 0 0 0 3px rgba(224, 135, 90, 0.20);
--ec-glow-accent: 0 0 24px rgba(212, 149, 106, 0.15);
}2.6 Accessibility — Contrast Ratios
| Combination | Ratio | WCAG |
|---|---|---|
Copper #C4501A on Foam #FBF8F4 | 4.7:1 | AA (normal), AAA (large) |
Deep Espresso #1C1210 on Foam #FBF8F4 | 15.8:1 | AAA |
#E0875A on Dark #0F0B09 | 5.2:1 | AA |
#EDE6DC on Dark #0F0B09 | 14.1:1 | AAA |
2.7 VitePress Variable Mapping
These are already applied in style.css. Shown here for reference — you don't need to write these again.
:root {
--vp-c-brand-1: var(--ec-brand-1);
--vp-c-brand-2: var(--ec-brand-2);
--vp-c-brand-3: var(--ec-brand-3);
--vp-c-brand-soft: var(--ec-brand-soft);
--vp-c-bg: var(--ec-bg);
--vp-c-bg-alt: var(--ec-bg-alt);
--vp-c-bg-elv: var(--ec-bg-elevated);
--vp-c-bg-soft: var(--ec-bg-soft);
--vp-c-text-1: var(--ec-text-1);
--vp-c-text-2: var(--ec-text-2);
--vp-c-text-3: var(--ec-text-3);
--vp-c-border: var(--ec-border);
--vp-c-divider: var(--ec-divider);
--vp-c-gutter: var(--ec-gutter);
--vp-c-gray-1: #D4C8BA;
--vp-c-gray-2: #E8DFD5;
--vp-c-gray-3: #F3EDE6;
--vp-c-gray-soft: rgba(143, 130, 119, 0.14);
--vp-c-green-1: var(--ec-success-1);
--vp-c-green-2: var(--ec-success-2);
--vp-c-green-3: var(--ec-success-3);
--vp-c-green-soft: var(--ec-success-soft);
--vp-c-yellow-1: var(--ec-warning-1);
--vp-c-yellow-2: var(--ec-warning-2);
--vp-c-yellow-3: var(--ec-warning-3);
--vp-c-yellow-soft: var(--ec-warning-soft);
--vp-c-red-1: var(--ec-danger-1);
--vp-c-red-2: var(--ec-danger-2);
--vp-c-red-3: var(--ec-danger-3);
--vp-c-red-soft: var(--ec-danger-soft);
--vp-shadow-1: var(--ec-shadow-sm);
--vp-shadow-2: var(--ec-shadow-md);
--vp-shadow-3: var(--ec-shadow-lg);
--vp-shadow-4: var(--ec-shadow-xl);
--vp-shadow-5: var(--ec-shadow-xl);
/* Buttons */
--vp-button-brand-border: transparent;
--vp-button-brand-text: #FBF8F4;
--vp-button-brand-bg: var(--ec-brand-3);
--vp-button-brand-hover-border: transparent;
--vp-button-brand-hover-text: #FBF8F4;
--vp-button-brand-hover-bg: var(--ec-brand-2);
--vp-button-brand-active-border: transparent;
--vp-button-brand-active-text: #FBF8F4;
--vp-button-brand-active-bg: var(--ec-brand-1);
--vp-button-alt-border: var(--ec-border);
--vp-button-alt-text: var(--ec-text-1);
--vp-button-alt-bg: var(--ec-bg-alt);
--vp-button-alt-hover-border: var(--ec-brand-1);
--vp-button-alt-hover-text: var(--ec-brand-1);
--vp-button-alt-hover-bg: var(--ec-bg-elevated);
/* Nav */
--vp-nav-bg-color: rgba(251, 248, 244, 0.85);
--vp-nav-height: 72px;
/* Hero gradient */
--vp-home-hero-name-color: var(--ec-brand-1);
--vp-home-hero-name-background: linear-gradient(135deg, #C4501A 0%, #D4956A 100%);
}
.dark {
--vp-c-gray-1: #4D4440;
--vp-c-gray-2: #3D322C;
--vp-c-gray-3: #2A221E;
--vp-c-gray-soft: rgba(107, 96, 89, 0.16);
--vp-nav-bg-color: rgba(15, 11, 9, 0.85);
--vp-home-hero-name-background: linear-gradient(135deg, #E0875A 0%, #D4956A 100%);
}3. Typography
3.1 Font Families
| Role | Family | Source | Character | Weights |
|---|---|---|---|---|
| Display / Hero | Instrument Serif | Google Fonts | Elegant, editorial, high-contrast serif. Unexpected in tech — immediately differentiates. The "espresso shot" of the type system. | 400, 400 Italic |
| Headings | Space Grotesk | Google Fonts | Geometric, monolinear, modern. The technical counterpart to Instrument Serif. | 300, 400, 500, 600, 700 |
| Body | DM Sans | Google Fonts | Geometric sans-serif with generous x-height and open apertures. Reads cleanly at all sizes. | 400, 500, 600, 700 + Italics |
| Monospace | JetBrains Mono | Google Fonts | Developer-friendly monospace with coding ligatures. Signals technical credibility. | 400, 500, 700 |
3.2 Google Fonts Import
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700;1,9..40,400;1,9..40,500&family=Instrument+Serif:ital@0;1&family=JetBrains+Mono:wght@400;500;700&family=Space+Grotesk:wght@300;400;500;600;700&display=swap');3.3 Font Tokens
Tailwind usage:
<h1 class="font-display">Hero Headline</h1>
<h2 class="font-heading">Section Title</h2>
<p class="font-body">Body paragraph</p>
<code class="font-mono">code snippet</code>Defined in tailwind.config.js → theme.extend.fontFamily:
font-display→ Instrument Serif, Georgia, seriffont-heading→ Space Grotesk, system-ui, sans-seriffont-body→ DM Sans, system-ui, sans-serif (also set as VitePress base)font-mono→ JetBrains Mono, Menlo, Monaco, Consolas, monospace
3.4 Size Scale
:root {
--ec-text-xs: 0.75rem; /* 12px */
--ec-text-sm: 0.875rem; /* 14px */
--ec-text-base: 1rem; /* 16px */
--ec-text-lg: 1.125rem; /* 18px */
--ec-text-xl: 1.25rem; /* 20px */
--ec-text-2xl: 1.5rem; /* 24px */
--ec-text-3xl: 1.875rem; /* 30px */
--ec-text-4xl: 2.25rem; /* 36px */
--ec-text-5xl: 3rem; /* 48px */
--ec-text-6xl: 3.75rem; /* 60px */
--ec-text-7xl: 4.5rem; /* 72px */
}3.5 Line Height & Spacing
:root {
/* Line heights */
--ec-leading-none: 1;
--ec-leading-tight: 1.15;
--ec-leading-snug: 1.3;
--ec-leading-normal: 1.5;
--ec-leading-relaxed: 1.65;
--ec-leading-loose: 1.8;
/* Letter spacing */
--ec-tracking-tighter: -0.03em;
--ec-tracking-tight: -0.015em;
--ec-tracking-normal: 0;
--ec-tracking-wide: 0.025em;
--ec-tracking-wider: 0.05em;
--ec-tracking-widest: 0.1em;
/* Font weights */
--ec-weight-light: 300;
--ec-weight-regular: 400;
--ec-weight-medium: 500;
--ec-weight-semibold: 600;
--ec-weight-bold: 700;
}3.6 Type Scale Application
| Element | Font | Size | Weight | Line Height | Letter Spacing |
|---|---|---|---|---|---|
| Hero headline | --ec-font-display | 72px (48px mobile) | 400 | 1 | -0.03em |
| Hero subtitle | --ec-font-body | 20px | 400 | 1.65 | 0 |
| Section title (H2) | --ec-font-display | 48px (36px mobile) | 400 | 1.15 | -0.015em |
| Feature heading (H3) | --ec-font-heading | 24px | 600 | 1.3 | -0.015em |
| Card title (H4) | --ec-font-heading | 20px | 600 | 1.3 | 0 |
| Subheading (H5) | --ec-font-heading | 18px | 500 | 1.3 | 0 |
| Overline / Label | --ec-font-heading | 12px | 500 | 1.5 | 0.1em (uppercase) |
| Body | --ec-font-body | 16px | 400 | 1.65 | 0 |
| Body large | --ec-font-body | 18px | 400 | 1.65 | 0 |
| Body small | --ec-font-body | 14px | 400 | 1.5 | 0 |
| Nav links | --ec-font-heading | 14px | 500 | 1 | 0.025em |
| Button text | --ec-font-heading | 14px | 600 | 1 | 0.025em |
| Inline code | --ec-font-mono | 14px | 400 | 1.5 | 0 |
| Code block | --ec-font-mono | 14px | 400 | 1.7 | 0 |
| Price number | --ec-font-mono | 48px | 700 | 1 | -0.03em |
4. Spacing & Layout
4.1 Spacing Scale (8px base)
:root {
--ec-space-0: 0;
--ec-space-px: 1px;
--ec-space-0-5: 0.125rem; /* 2px */
--ec-space-1: 0.25rem; /* 4px */
--ec-space-1-5: 0.375rem; /* 6px */
--ec-space-2: 0.5rem; /* 8px — base unit */
--ec-space-3: 0.75rem; /* 12px */
--ec-space-4: 1rem; /* 16px */
--ec-space-5: 1.25rem; /* 20px */
--ec-space-6: 1.5rem; /* 24px */
--ec-space-8: 2rem; /* 32px */
--ec-space-10: 2.5rem; /* 40px */
--ec-space-12: 3rem; /* 48px */
--ec-space-16: 4rem; /* 64px */
--ec-space-20: 5rem; /* 80px */
--ec-space-24: 6rem; /* 96px */
--ec-space-32: 8rem; /* 128px */
--ec-space-40: 10rem; /* 160px */
}4.2 Semantic Spacing
:root {
--ec-section-gap: var(--ec-space-32); /* 128px — between major SPA sections */
--ec-section-gap-sm: var(--ec-space-24); /* 96px — smaller section gaps */
--ec-content-gap: var(--ec-space-16); /* 64px — between content blocks */
--ec-card-padding: var(--ec-space-8); /* 32px — card internal padding */
--ec-card-gap: var(--ec-space-6); /* 24px — gap between cards */
--ec-inline-gap: var(--ec-space-4); /* 16px — between inline elements */
}4.3 Container Widths
:root {
--ec-container-xs: 480px;
--ec-container-sm: 640px;
--ec-container-md: 768px;
--ec-container-lg: 1024px;
--ec-container-xl: 1200px; /* Primary content width */
--ec-container-2xl: 1400px; /* Max layout width */
--ec-gutter-page: var(--ec-space-6); /* 24px page gutters */
}4.4 Layout Pattern
Each SPA section spans full viewport width with contained content.
Tailwind usage:
<!-- Section wrapper -->
<section class="w-full py-32 px-6 bg-ec-foam">
<!-- Contained content -->
<div class="w-full max-w-container-xl mx-auto">
<!-- Feature grid with auto-fit -->
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6">
<!-- cards -->
</div>
</div>
</section>Custom container widths available: max-w-container-xs through max-w-container-2xl.
4.5 Border Radius
:root {
--ec-radius-sm: 4px;
--ec-radius-md: 8px;
--ec-radius-lg: 12px;
--ec-radius-xl: 16px;
--ec-radius-2xl: 24px;
--ec-radius-full: 9999px; /* Pills, avatars */
}5. Component Patterns
All components can be built with Tailwind utility classes. The examples below show the Tailwind approach alongside the semantic intent.
5.1 Buttons
Three tiers: Primary (copper, high emphasis), Secondary (outlined, medium emphasis), Ghost (text-only, low emphasis).
Primary Button:
<button class="font-heading text-sm font-semibold tracking-wide
px-6 py-3 bg-ec-copper text-ec-foam border-2 border-transparent rounded-md
cursor-pointer transition-all duration-normal ease-ec-out
hover:bg-ec-copper-hover hover:shadow-glow-brand hover:-translate-y-px
active:translate-y-0 active:shadow-none">
Get Started
</button>Secondary Button:
<button class="font-heading text-sm font-semibold tracking-wide
px-6 py-3 bg-transparent text-ec-espresso border-2 border-warmgray-300 rounded-md
cursor-pointer transition-all duration-normal ease-ec-out
hover:border-ec-copper hover:text-ec-copper hover:bg-ec-copper/10">
View on GitHub
</button>Ghost Button:
<button class="font-heading text-sm font-medium tracking-wide
px-4 py-2 bg-transparent text-ec-copper border-none
cursor-pointer transition-colors duration-fast
hover:text-ec-copper-hover hover:underline hover:underline-offset-4">
Learn more
</button>Large variant (hero CTAs): Add px-8 py-4 text-base rounded-lg
5.2 Cards
Base card:
<div class="bg-white border border-warmgray-200 rounded-xl p-8
transition-all duration-300 ease-ec-out
hover:border-ec-crema hover:shadow-ec-md hover:-translate-y-0.5">
<!-- card content -->
</div>Feature card with icon:
<div class="bg-white border border-warmgray-200 rounded-xl p-8
transition-all duration-300 ease-ec-out
hover:border-ec-crema hover:shadow-ec-md hover:-translate-y-0.5">
<div class="w-12 h-12 rounded-lg bg-ec-copper/10 flex items-center justify-center mb-4 text-ec-copper">
<!-- Lucide icon -->
</div>
<h3 class="font-heading text-xl font-semibold text-ec-espresso mb-2">Feature Title</h3>
<p class="font-body text-base text-warmgray-600 leading-relaxed">Feature description.</p>
</div>Pricing card — featured variant uses border-ec-copper border-2 relative with a positioned badge.
5.3 Navigation (Fixed Top Nav)
<nav class="fixed top-0 inset-x-0 z-50 h-18 px-6
bg-ec-foam/85 backdrop-blur-nav backdrop-saturate-nav
border-b border-warmgray-200
transition-all duration-300">
<div class="max-w-container-xl mx-auto h-full flex items-center justify-between">
<!-- Logo: Instrument Serif wordmark -->
<a class="font-display text-2xl text-ec-espresso tracking-tight no-underline">
<span class="text-ec-copper">Espresso</span> Cloud
</a>
<!-- Nav links -->
<ul class="flex items-center gap-8">
<li><a href="#features" class="font-heading text-sm font-medium tracking-wide text-warmgray-600
hover:text-ec-espresso transition-colors duration-fast">Features</a></li>
<!-- ... more links ... -->
</ul>
<!-- CTA -->
<button class="font-heading text-sm font-semibold px-5 py-2
bg-ec-copper text-ec-foam rounded-md
hover:bg-ec-copper-hover hover:shadow-glow-brand
transition-all duration-normal">
Get Started
</button>
</div>
</nav>Nav link underline animation uses a CSS ::after pseudo-element (defined in style.css) since Tailwind can't target pseudo-element width transitions.
5.4 Badges
<!-- Brand badge -->
<span class="inline-flex items-center font-heading text-xs font-semibold
tracking-wider uppercase px-3 py-1 rounded-full
bg-ec-copper/10 text-ec-copper">
New
</span>
<!-- Success badge -->
<span class="... bg-mint/10 text-mint-dark">Active</span>
<!-- Tech stack badge (Supported Tech section) -->
<span class="inline-flex items-center gap-2 font-heading text-sm font-semibold
px-4 py-2 rounded-full bg-white border border-warmgray-200 text-ec-espresso">
<img src="..." class="w-5 h-5" /> React
</span>5.5 Overline / Section Label
A distinctive element — the small uppercase label above section titles, with a decorative line.
<div class="flex items-center gap-3 mb-4">
<span class="w-6 h-0.5 bg-ec-crema rounded-full"></span>
<span class="font-heading text-xs font-medium tracking-widest uppercase text-ec-crema">
CAPABILITIES
</span>
</div>5.6 Stat / Metric Display
For trust indicators (99.9% uptime, etc.).
<div class="text-center">
<span class="font-mono text-4xl font-bold text-ec-espresso leading-none tabular-nums">
99.9%
</span>
<p class="font-body text-sm text-warmgray-600 mt-2">Uptime SLA</p>
</div>Use tabular-nums (Tailwind utility) for aligned number columns in pricing.
5.7 Dividers
<!-- Standard -->
<hr class="w-full h-px bg-warmgray-200 border-none" />
<!-- Decorative gradient fade -->
<hr class="h-px border-none bg-divider-fade opacity-30" />5.8 Focus States
Global :focus-visible with warm copper glow is defined in style.css — applies to all interactive elements automatically.
6. Motion & Animation
6.1 Principles
- Purposeful — every animation communicates state, draws attention, or orients the user.
- Performant — only animate
transformandopacity(compositor-friendly). Never animate layout properties. - Respectful — all animations disabled for
prefers-reduced-motion: reduce.
6.2 Easing & Duration (Tailwind utilities)
Easing — ease-{name} classes:
| Class | Curve | Usage |
|---|---|---|
ease-ec-out | cubic-bezier(0.25, 0.46, 0.45, 0.94) | Standard interactions |
ease-ec-enter | cubic-bezier(0.0, 0.0, 0.2, 1.0) | Elements appearing |
ease-ec-exit | cubic-bezier(0.4, 0.0, 1.0, 1.0) | Elements disappearing |
ease-ec-spring | cubic-bezier(0.34, 1.56, 0.64, 1.0) | Subtle bounce for emphasis |
ease-ec-smooth | cubic-bezier(0.4, 0.0, 0.2, 1.0) | Gentle, relaxed motion |
Duration — duration-{name} classes:
| Class | Value |
|---|---|
duration-instant | 75ms |
duration-fast | 150ms |
duration-normal | 250ms |
duration-slow | 400ms |
duration-slower | 600ms |
duration-slowest | 1000ms |
6.3 Keyframe Animations (Tailwind utilities)
All keyframes are defined in tailwind.config.js and available as animate-{name} classes:
| Class | Effect |
|---|---|
animate-reveal-up | Fade in + rise 24px (600ms) |
animate-reveal-left | Fade in + slide from left 24px |
animate-reveal-right | Fade in + slide from right 24px |
animate-reveal-scale | Fade in + scale from 95% |
animate-steam-rise | Ambient vapor rising (infinite, hero only) |
animate-glow-pulse | Warm copper glow pulse (infinite) |
animate-shimmer | Gradient shimmer sweep (infinite) |
Stagger delays (defined in style.css):
<div class="animate-reveal-up stagger-1">First</div>
<div class="animate-reveal-up stagger-2">Second (80ms delay)</div>
<div class="animate-reveal-up stagger-3">Third (160ms delay)</div>6.4 Scroll Reveal System
Elements use data-reveal attributes and animate in when entering the viewport. The CSS is in style.css; a lightweight Intersection Observer composable adds the .revealed trigger class.
Usage in Vue templates:
<div data-reveal="up" class="stagger-1">Fades up on scroll</div>
<div data-reveal="left" class="stagger-2">Slides from left</div>
<div data-reveal="scale" class="stagger-3">Scales in</div>Companion Vue composable (composables/useScrollReveal.ts):
export function useScrollReveal() {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('revealed')
observer.unobserve(entry.target)
}
})
},
{ threshold: 0.15, rootMargin: '0px 0px -40px 0px' }
)
document.querySelectorAll('[data-reveal]').forEach((el) => observer.observe(el))
return observer
}Respects prefers-reduced-motion: reduce — elements show instantly with no animation.
6.5 Ambient Steam Effect (Hero Only)
Soft, blurred shapes that drift upward in the hero background. Uses pseudo-elements with animate-steam-rise:
<!-- Hero section uses ec-hero class defined in style.css -->
<section class="ec-hero relative overflow-hidden">
<!-- ::before and ::after create the steam blobs via CSS -->
<!-- Content here -->
</section>The ec-hero class and its pseudo-elements are defined in style.css since Tailwind can't generate ::before/::after content with radial gradients and blur.
6.6 Micro-interactions
Card hover lift: hover:-translate-y-0.5 hover:shadow-ec-md transition-all duration-300 ease-ec-out
Button glow: hover:shadow-glow-brand hover:-translate-y-px transition-all duration-normal ease-ec-out
Smooth scroll: Set globally in style.css with scroll-padding-top: 88px (nav + buffer).
7. Iconography
7.1 Library
Lucide Icons — lucide.dev
- Open source, MIT licensed
- Consistent 24x24 grid with 2px stroke
- Vue components via
lucide-vue-next - Clean geometric style pairs well with Space Grotesk
7.2 Icon Sizing
:root {
--ec-icon-xs: 16px; /* Inline with small text, badges */
--ec-icon-sm: 20px; /* Inline with body text */
--ec-icon-md: 24px; /* Default / nav */
--ec-icon-lg: 32px; /* Feature card icons */
--ec-icon-xl: 48px; /* Section illustrations */
}7.3 Icon Color Rules
| Context | Color |
|---|---|
| Default | var(--ec-text-2) (muted) |
| Hover / Active | var(--ec-text-1) or var(--ec-brand-1) |
| Feature cards | var(--ec-brand-1) inside var(--ec-brand-soft) container |
| Semantic | Corresponding semantic color (success-1, danger-1, etc.) |
7.4 Recommended Icons by Section
| Section | Icons |
|---|---|
| CI/CD & Deployment | GitBranch, Workflow, Rocket |
| Environments | Layers, Server, Globe |
| Zero-downtime | RefreshCcw, ShieldCheck, Timer |
| Storage | Database, HardDrive, Cloud |
| Secrets / RBAC | Lock, KeyRound, UserCheck |
| Observability | BarChart3, Activity, Eye |
| How It Works steps | Link, Zap, CheckCircle |
| Pricing checkmarks | Check (success color) |
| Footer social | Github, Twitter, MessageCircle |
| Theme toggle | Sun, Moon |
8. SPA Section Structure
8.1 Page Skeleton
┌─────────────────────────────────────────────────────┐
│ [Fixed Nav] │
│ Logo │ Features How It Works Stack Pricing │ Get Started │
├─────────────────────────────────────────────────────┤
│ │
│ [Hero] id="hero" bg: --ec-bg │
│ Overline: "OPEN SOURCE APP HOSTING" │
│ H1: "Deploy with the precision │
│ of espresso." │
│ Subtitle: One-paragraph value prop │
│ CTAs: "Get Started" + "View on GitHub" │
│ Stats: 99.9% Uptime │ Your Cloud │ Open Source │
│ ✦ Ambient steam effect in background │
│ │
├─────────────────────────────────────────────────────┤
│ │
│ [Features] id="features" bg: --ec-bg-alt │
│ Overline: "CAPABILITIES" │
│ H2: "Everything your team needs to ship." │
│ 3×2 grid of feature cards │
│ │
├─────────────────────────────────────────────────────┤
│ │
│ [How It Works] id="how-it-works" bg: --ec-bg │
│ Overline: "HOW IT WORKS" │
│ H2: "Three steps. Zero friction." │
│ Horizontal 3-step flow: │
│ Connect Repo → Configure → Deploy │
│ │
├─────────────────────────────────────────────────────┤
│ │
│ [Supported Tech] id="stack" bg: --ec-bg-alt │
│ Overline: "ECOSYSTEM" │
│ H2: "Built for the frameworks you love." │
│ Cloud providers: AWS, GCP, Azure │
│ Frameworks: Frappe, ERPNext, React, Vue, etc. │
│ │
├─────────────────────────────────────────────────────┤
│ │
│ [Pricing] id="pricing" bg: --ec-bg │
│ Overline: "PRICING" │
│ H2: "Transparent, predictable pricing." │
│ 2–3 pricing cards side by side │
│ Featured card with copper border │
│ Price numbers in JetBrains Mono │
│ │
├─────────────────────────────────────────────────────┤
│ │
│ [CTA / Footer] id="get-started" │
│ bg: --ec-bg-inverse (Deep Espresso) │
│ H2: "Ready to brew your first deployment?" │
│ CTA button (inverted) │
│ Footer links, copyright, social icons │
│ │
└─────────────────────────────────────────────────────┘8.2 Section Background Rhythm
Alternating backgrounds create visual separation without hard dividers:
| Section | Background | Text |
|---|---|---|
| Hero | --ec-bg (Foam White) | --ec-text-1 |
| Features | --ec-bg-alt (Parchment) | --ec-text-1 |
| How It Works | --ec-bg (Foam White) | --ec-text-1 |
| Supported Tech | --ec-bg-alt (Parchment) | --ec-text-1 |
| Pricing | --ec-bg (Foam White) | --ec-text-1 |
| CTA / Footer | --ec-bg-inverse (Deep Espresso) | --ec-text-inverse |
8.3 Smooth Scrolling
Configured in docs/.vitepress/theme/index.ts — routes with hash anchors smooth-scroll automatically. Global scroll-behavior: smooth and scroll-padding-top: 88px set in style.css.
9. Responsive Breakpoints
9.1 Breakpoint Scale (Mobile-First)
Defined in tailwind.config.js → theme.screens:
| Tailwind prefix | Width | Description |
|---|---|---|
sm: | 640px | Large phone / small tablet |
md: | 768px | Tablet portrait |
lg: | 960px | VitePress sidebar breakpoint |
xl: | 1200px | Desktop |
2xl: | 1400px | Wide desktop |
Example — responsive hero title:
<h1 class="font-display text-5xl md:text-6xl xl:text-7xl tracking-tighter leading-none">
Deploy with the precision of espresso.
</h1>9.2 Responsive Behavior
| Element | Mobile (<640px) | Tablet (640–960px) | Desktop (>960px) |
|---|---|---|---|
| Nav | Hamburger menu | Hamburger menu | Horizontal links |
| Hero title | 48px | 60px | 72px |
| Section title | 36px | 42px | 48px |
| Feature grid | 1 column | 2 columns | 3 columns |
| How It Works | Vertical stack | Vertical stack | Horizontal 3-step |
| Pricing cards | 1 column | 2 columns | 3 columns |
| Section padding | 64px block | 96px block | 128px block |
| Container gutter | 16px | 24px | 24px |
| Nav height | 64px | 64px | 72px |
9.3 Mobile Navigation
On mobile/tablet (< lg:), the nav links collapse into a slide-out panel:
<!-- Mobile nav overlay (hidden by default, shown via Vue ref) -->
<div class="fixed inset-y-0 right-0 w-70 bg-white flex flex-col
pt-20 px-8 gap-6 shadow-ec-xl z-[200]
translate-x-full transition-transform duration-slow ease-ec-out
lg:hidden"
:class="{ 'translate-x-0': menuOpen }">
<a class="font-heading text-lg">Features</a>
<!-- ... -->
</div>10. Implementation Reference
10.1 File Structure
espresso_webfe/
tailwind.config.js ← All design tokens (colors, fonts, spacing, animations)
postcss.config.js ← Tailwind + Autoprefixer
docs/.vitepress/
config.mts ← VitePress config: font preloads, nav, meta
theme/
index.ts ← Custom theme extending DefaultTheme
style.css ← Tailwind import + VitePress overrides + scroll reveal CSS
components/
EcNav.vue ← Fixed top navigation
EcHero.vue ← Hero section with steam effect
EcFeatures.vue ← Feature cards grid
EcHowItWorks.vue ← 3-step flow
EcTechStack.vue ← Supported frameworks/cloud providers
EcPricing.vue ← Pricing cards
EcFooter.vue ← CTA + footer
composables/
useScrollReveal.ts ← Intersection Observer for scroll animations
useActiveSection.ts ← Track active nav section on scroll10.2 Key Config Files (already created)
tailwind.config.js— All design tokens as Tailwind theme extensionspostcss.config.js—@tailwindcss/postcss+autoprefixerdocs/.vitepress/theme/style.css— Tailwind import, Google Fonts, VitePress variable overrides, scroll reveal system, steam effect, stagger utilitiesdocs/.vitepress/theme/index.ts— Custom theme entry, smooth scroll routingdocs/.vitepress/config.mts— Font preconnect/preload, site meta
10.3 Development
npm run docs:dev # Start dev server with Tailwind + VitePress
npm run docs:build # Production build (Tailwind auto-purges unused classes)
npm run docs:preview # Preview production build10.4 Accessibility Checklist
- All text color combinations pass WCAG AA minimum (see Section 2.6)
- Interactive elements have visible
:focus-visiblewith warm copper glow - All animations respect
@media (prefers-reduced-motion: reduce) - Semantic
<nav>witharia-labelfor navigation - Proper heading hierarchy: single H1 in hero, H2 per section
scroll-padding-topaccounts for fixed nav heighttabular-numson pricing for aligned number columns








