24 KiB
Pulse Design System
Version: 1.0
Last Updated: 2026-02-06
Maintained by: Ciphera Design Team
Overview
This document defines the visual language and design patterns for Pulse Analytics. All components and pages should follow these standards to ensure consistency, accessibility, and brand alignment.
🎨 Colors
Brand Colors
/* Primary Brand Color */
--brand-orange: #FD5E0F;
--brand-orange-hover: #E54E00; /* Darker for hover states */
/* Injected by @ciphera-net/ui preset — use in SVG, Recharts, rgba() */
--color-brand-orange: #FD5E0F;
--color-brand-orange-rgb: 253, 94, 15; /* Used by .glow-orange utility; also for custom rgba(var(--color-brand-orange-rgb), 0.5) */
/* Usage */
- Primary CTAs, links, focus rings
- Accent elements, badges
- Never use for large backgrounds (too vibrant)
- var(--color-brand-orange) for SVG/Recharts where Tailwind classes don't apply
Neutral Scale
Using Tailwind's neutral palette (50-950):
| Token | Light Mode | Dark Mode | Usage |
|---|---|---|---|
neutral-50 |
#FAFAFA | - | Subtle backgrounds |
neutral-100 |
#F5F5F5 | - | Card backgrounds, disabled states |
neutral-200 |
#E5E5E5 | - | Borders, dividers |
neutral-300 |
#D4D4D4 | - | Borders (hover) |
neutral-400 |
#A3A3A3 | #A3A3A3 | Secondary text (dark mode) |
neutral-500 |
#737373 | - | Tertiary text, placeholders |
neutral-600 |
#525252 | - | Body text (light mode) |
neutral-700 |
#404040 | - | - |
neutral-800 |
#262626 | #262626 | Borders, backgrounds (dark mode) |
neutral-900 |
#171717 | #171717 | Headings, primary text |
neutral-950 |
#0A0A0A | - | - |
Semantic Colors
--color-success: #10B981; /* Green for success states */
--color-warning: #F59E0B; /* Amber for warnings */
--color-error: #EF4444; /* Red for errors, destructive actions */
Color Usage Rules
Text Colors:
- Headings:
text-neutral-900 dark:text-white - Body text:
text-neutral-600 dark:text-neutral-400 - Secondary text:
text-neutral-500 dark:text-neutral-400 - Tertiary/disabled:
text-neutral-400 dark:text-neutral-500 - Links:
text-brand-orange hover:text-brand-orange-hover
Backgrounds:
- Page:
bg-white dark:bg-neutral-900 - Cards:
bg-white dark:bg-neutral-900 - Subtle sections:
bg-neutral-50 dark:bg-neutral-800/50 - Overlays:
bg-white/80 dark:bg-neutral-900/80(with backdrop-blur)
Borders:
- Standard:
border-neutral-200 dark:border-neutral-800 - Subtle:
border-neutral-200/50 dark:border-neutral-800/50 - Focus rings:
ring-brand-orange - Danger actions:
border-red-200 dark:border-red-900
📝 Typography
Font Family
font-family: 'Plus Jakarta Sans', system-ui, sans-serif
Loaded via Next.js next/font/google in root layout.
Heading Scale
| Element | Size (Mobile → Desktop) | Weight | Usage |
|---|---|---|---|
| H1 (Marketing) | text-4xl → text-5xl |
font-bold |
Landing pages, about, FAQ, installation |
| H1 (Hero) | text-5xl → text-7xl |
font-bold |
Home page hero only |
| H1 (App) | text-2xl |
font-bold |
Dashboard, settings, app pages |
| H2 (Section) | text-2xl |
font-bold |
Major page sections |
| H2 (Panel) | text-xl |
font-semibold |
Panel headers (realtime, modals) |
| H3 (Section) | text-lg |
font-semibold |
Dashboard widgets, subsections |
| H3 (Card) | text-xl |
font-bold |
Feature cards, integration cards |
| H4 (Label) | text-base |
font-semibold |
Footer sections, small headers |
Body Text
| Size | Class | Usage |
|---|---|---|
| Large | text-xl |
Hero descriptions, intro paragraphs |
| Base | text-base (default) |
Body text, card descriptions |
| Small | text-sm |
Help text, labels, metadata |
| Micro | text-xs |
Badges, timestamps, fine print |
Font Weights
font-bold(700): H1, H2, card titles, stat valuesfont-semibold(600): H2 panels, H3 sections, buttonsfont-medium(500): Links, labels, secondary headingsfont-normal(400): Body text
Line Height
- Headings: Default tight (
leading-tightor none) - Body:
leading-relaxedfor readability
Text Color Patterns
// Headings
className="text-neutral-900 dark:text-white"
// Body text (most readable)
className="text-neutral-600 dark:text-neutral-400"
// Secondary/helper text
className="text-neutral-500 dark:text-neutral-400"
// Disabled/placeholder
className="text-neutral-400 dark:text-neutral-500"
📏 Spacing System
Padding Scale
| Value | Size | Usage |
|---|---|---|
p-3 |
12px | Icon containers, compact badges |
p-4 |
16px | Card headers, small cards, stat displays |
p-6 |
24px | Standard cards, forms, modals |
p-8 |
32px | Feature cards, spacious sections |
p-10 |
40px | Page containers |
p-12 |
48px | Empty states, hero sections |
Vertical Spacing (Sections)
| Value | Size | Usage |
|---|---|---|
py-8 |
32px | App page containers |
py-10 |
40px | Standard pages |
py-16 |
64px | Content sections |
py-20 |
80px | Marketing hero sections |
Gaps (Flexbox/Grid)
| Value | Size | Usage |
|---|---|---|
gap-2 |
8px | Icon+text pairs, inline elements |
gap-3 |
12px | Button groups, small layouts |
gap-4 |
16px | Form fields, button bars |
gap-6 |
24px | Card grids, main layouts |
gap-8 |
32px | Large section spacing |
Margins
| Value | Usage |
|---|---|
mb-2 to mb-4 |
Between related elements |
mb-6 to mb-8 |
Between sections |
mb-16 to mb-32 |
Major page divisions |
🔲 Border Radius
| Element | Radius | Class | Example |
|---|---|---|---|
| Cards | 16px | rounded-2xl |
Dashboard cards, feature cards |
| Buttons | 12px | rounded-xl |
Primary, secondary buttons |
| Inputs | 8px | rounded-lg |
Text inputs, selects, textareas |
| Tabs | 8px | rounded-lg |
Tab buttons |
| Badges | Full | rounded-full |
Status badges, tags |
| Small UI | 8px | rounded-lg |
Small buttons, icons |
Rule: Larger elements = larger radius. Cards are most prominent, so rounded-2xl.
🎭 Shadows
Shadow Scale
// Subtle (cards at rest)
shadow-sm
// Medium (hover states)
shadow-md, shadow-lg
// Prominent (elevated elements, popovers)
shadow-xl, shadow-2xl
// Glow effects (CTAs)
shadow-lg shadow-brand-orange/20
Usage Examples
// Card at rest
className="rounded-2xl border border-neutral-200 dark:border-neutral-800 shadow-sm"
// Card hover
className="hover:shadow-xl hover:-translate-y-1 transition-all"
// Primary CTA
className="shadow-lg shadow-brand-orange/20"
🎯 Focus States
Standard Focus Pattern
For most interactive elements:
focus:outline-none focus:ring-2 focus:ring-brand-orange focus:ring-offset-2
Compact Pattern (Links, Small Elements)
focus:outline-none focus:ring-2 focus:ring-brand-orange focus:rounded
Danger Actions (Delete, Reset)
focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2
Inset Focus (Selected Elements)
focus:outline-none focus:ring-2 focus:ring-brand-orange focus:ring-inset
Rule: ALL interactive elements must have a visible focus state for keyboard accessibility (WCAG 2.1 Level AA).
🧩 Custom CSS Classes
Location: styles/globals.css
.card-glass
Glass card effect with backdrop blur (premium feel):
.card-glass {
@apply bg-white/80 dark:bg-neutral-900/80;
@apply backdrop-blur-xl;
@apply border border-neutral-200/50 dark:border-neutral-800/50;
@apply rounded-2xl;
@apply transition-all duration-300 ease-out;
}
Usage: Feature cards on home page, pricing cards
.gradient-text
Orange gradient for emphasized text:
.gradient-text {
@apply bg-gradient-to-r from-brand-orange to-brand-orange-hover bg-clip-text text-transparent;
}
Usage: Hero headlines ("privacy-conscious" on home page)
.badge-primary
Primary badge style (orange theme):
.badge-primary {
@apply inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-semibold uppercase tracking-wider;
@apply bg-brand-orange/10 text-brand-orange border border-brand-orange/20;
}
Usage: "Privacy-First Analytics" badge, "FAQ" badge, page indicators
.bg-grid-pattern
Subtle dot grid background:
.bg-grid-pattern {
background-image: radial-gradient(circle at 1px 1px, currentColor 1px, transparent 0);
background-size: 32px 32px;
}
Usage: Marketing page backgrounds (with radial mask for fade effect)
🧱 Component Patterns
Button Variants
Primary (Brand Action):
<Button variant="primary">Get Started</Button>
// Orange background, white text, shadow on hover
Secondary (Neutral Action):
<Button variant="secondary">Cancel</Button>
// White background, border, neutral text
Danger (Destructive Action):
<Button variant="danger">Delete</Button>
// Red background, white text (for critical actions)
Empty States
Pattern:
<div className="flex flex-col items-center justify-center text-center gap-3 p-8">
<div className="rounded-full bg-neutral-100 dark:bg-neutral-800 p-3">
<IconComponent className="w-6 h-6 text-neutral-500 dark:text-neutral-400" />
</div>
<p className="text-sm font-medium text-neutral-900 dark:text-white">
No data yet
</p>
<p className="text-xs text-neutral-500 dark:text-neutral-400">
Data will appear here once visitors arrive
</p>
</div>
Usage: Dashboard widgets with no data, empty site lists, etc.
Loading States
Full Page Loading:
<LoadingOverlay
logoSrc="/pulse_icon_no_margins.png"
title="Pulse"
/>
Inline Loading:
<Button isLoading={true}>
Saving...
</Button>
Skeleton Loading:
<div className="animate-pulse rounded-2xl bg-neutral-100 dark:bg-neutral-800 h-48" />
Success/Error States
Success Toast:
toast.success('Site created successfully')
// Green toast with checkmark
Error Toast:
toast.error('Failed to load data')
// Red toast with X icon
Error Display:
<div className="text-red-500 text-sm text-center">
{error}
</div>
🎬 Animations
Framer Motion Patterns
Page Entrance
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
{content}
</motion.div>
Stagger Animation
{items.map((item, i) => (
<motion.div
key={i}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: i * 0.1 }}
>
{item}
</motion.div>
))}
Hover Scale
<motion.div
whileHover={{ scale: 1.05 }}
transition={{ duration: 0.2 }}
>
{content}
</motion.div>
List Item Entry/Exit
<AnimatePresence mode="popLayout">
{items.map(item => (
<motion.div
key={item.id}
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -10 }}
transition={{ duration: 0.2 }}
>
{item}
</motion.div>
))}
</AnimatePresence>
CSS Transitions
Duration Standards:
- Fast (200ms): Input focus, button clicks, immediate feedback
- Medium (300ms): Hover effects, color changes, icon transforms
- Slow (500ms): Page entrances, large movements
Easing:
- Default:
ease-out(feels snappy) - Alternative:
ease-in-out(smooth both ways)
// Standard hover
className="transition-colors duration-200 hover:text-brand-orange"
// Card hover
className="transition-all duration-300 hover:-translate-y-1 hover:shadow-xl"
📱 Responsive Breakpoints
Using Tailwind defaults:
| Breakpoint | Size | Usage |
|---|---|---|
sm: |
640px | Small tablets, large phones |
md: |
768px | Tablets, small laptops |
lg: |
1024px | Laptops, desktops |
xl: |
1280px | Large desktops |
Common Patterns
Grid Layouts:
// 1 col mobile, 2 col tablet, 3 col desktop
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"
Typography:
// Smaller on mobile, larger on desktop
className="text-4xl md:text-5xl"
Padding:
// Less padding on mobile
className="px-4 sm:px-6"
🔘 Buttons
Variant Usage
Primary (Orange):
- Main CTAs ("Get Started", "Create Site", "Save Changes")
- Positive actions
- One per screen section max
Secondary (Neutral):
- Cancel, Back, alternative actions
- Can have multiple per section
Ghost (Minimal):
- Tertiary actions, "View All" links
- Inline actions that shouldn't be prominent
Danger (Red):
- Delete, Reset Data, irreversible actions
- Always confirm before action
Size Variants
// Default
<Button>Save</Button>
// Large (CTAs)
<Button className="px-8 py-4 text-lg">Get Started</Button>
// Small (compact UI)
<Button className="text-sm px-4 py-2">Add</Button>
📦 Cards
Standard Card
<div className="bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 rounded-2xl p-6">
{content}
</div>
Glass Card (Premium Feel)
<div className="card-glass p-8 hover:-translate-y-1 hover:shadow-xl transition-all duration-300">
{content}
</div>
When to use Glass: Marketing pages, feature showcases, hero sections
Hover Effects
// Subtle lift
className="hover:-translate-y-1 hover:shadow-md transition-all"
// Scale
className="hover:scale-105 transition-transform"
🏷️ Badges
Primary Badge
<span className="badge-primary">
<span className="w-1.5 h-1.5 rounded-full bg-brand-orange animate-pulse" />
Privacy-First Analytics
</span>
Status Badges
Active/Live:
<div className="flex items-center gap-2 rounded-full bg-green-50 px-2 py-1 text-xs font-medium text-green-700 dark:bg-green-900/20 dark:text-green-400">
<span className="relative flex h-2 w-2">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"></span>
<span className="relative inline-flex rounded-full h-2 w-2 bg-green-500"></span>
</span>
Active
</div>
🎨 Special Effects
Background Glow (Atmosphere)
<div className="absolute inset-0 -z-10 pointer-events-none">
<div className="absolute top-0 left-1/4 w-[500px] h-[500px] bg-brand-orange/10 rounded-full blur-[128px] opacity-60" />
<div className="absolute bottom-0 right-1/4 w-[500px] h-[500px] bg-neutral-500/10 rounded-full blur-[128px] opacity-40" />
</div>
Grid Pattern Background
<div
className="absolute inset-0 bg-grid-pattern opacity-[0.02] dark:opacity-[0.05]"
style={{ maskImage: 'radial-gradient(ellipse at center, black 0%, transparent 70%)' }}
/>
SVG Underline (Accent)
<svg className="absolute -bottom-2 left-0 w-full h-3 text-brand-orange/30" viewBox="0 0 200 12" preserveAspectRatio="none">
<path d="M0 9C50 3 150 3 200 9" fill="none" stroke="currentColor" strokeWidth="4" strokeLinecap="round" />
</svg>
♿ Accessibility
Focus Indicators
ALL interactive elements must have visible focus states. See "Focus States" section above.
Semantic HTML
- Use proper heading hierarchy (H1 → H2 → H3, no skipping)
- Use
<button>for actions,<Link>for navigation - Use
<nav>for navigation sections - Use
<main>for primary content
Alt Text
- All images must have descriptive
altattributes - Decorative images use
alt=""oraria-hidden="true"
Keyboard Navigation
- Tab order should follow visual order
- All actions accessible via keyboard
- No keyboard traps
- Focus visible at all times
ARIA Attributes
Current usage (limited but correct):
aria-labelon icon-only buttons (social links)aria-hidden="true"on decorative elements (arrows, ornaments)
Could enhance:
role="tablist"on tab navigationaria-live="polite"on realtime countersrole="dialog"on modals
🌓 Dark Mode
Implementation
Using Tailwind's dark: prefix with class-based switching.
Color Adjustments
Backgrounds:
- Light:
bg-white,bg-neutral-50 - Dark:
dark:bg-neutral-900,dark:bg-neutral-800
Text:
- Light:
text-neutral-900,text-neutral-600 - Dark:
dark:text-white,dark:text-neutral-400
Borders:
- Light:
border-neutral-200 - Dark:
dark:border-neutral-800
Testing
Always test both light and dark modes:
- Color contrast (4.5:1 minimum in both modes)
- Focus ring visibility (should stand out in both modes)
- Glass effects (should work in both modes)
📊 Dashboard Widgets
Standard Widget Layout
<div className="bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 rounded-2xl p-6 h-full flex flex-col">
{/* Header */}
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-neutral-900 dark:text-white">
Widget Title
</h3>
<button className="text-xs font-medium text-neutral-500 hover:text-neutral-900 transition-colors focus:...">
View All
</button>
</div>
{/* Content */}
<div className="flex-1 min-h-[200px]">
{hasData ? (
<DataDisplay />
) : (
<EmptyState />
)}
</div>
</div>
Tab Toggles (Inside Widgets)
<div className="flex p-1 bg-neutral-100 dark:bg-neutral-800 rounded-lg">
{tabs.map(tab => (
<button
key={tab}
onClick={() => setActiveTab(tab)}
className={`px-3 py-1 text-xs font-medium rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-brand-orange ${
activeTab === tab
? 'bg-white dark:bg-neutral-700 text-neutral-900 dark:text-white shadow-sm'
: 'text-neutral-600 dark:text-neutral-400 hover:text-neutral-900 dark:hover:text-white'
}`}
>
{tab}
</button>
))}
</div>
📋 Forms
Input Styling
<Input
id="field-name"
placeholder="Enter value"
className="rounded-lg"
/>
// Uses @ciphera-net/ui Input component
// Automatically includes focus states, dark mode
Label Pattern
<label htmlFor="field-name" className="block text-sm font-medium mb-2 text-neutral-700 dark:text-neutral-300">
Field Name
</label>
Help Text
<p className="mt-2 text-sm text-neutral-600 dark:text-neutral-400">
Additional information about this field.
</p>
Error Display
{error && (
<div className="text-red-500 text-sm text-center">
{error}
</div>
)}
🎭 Code Blocks (Integration Pages)
VS Code-Style Syntax Highlighting
<div className="bg-[#1e1e1e] rounded-xl overflow-hidden shadow-2xl border border-neutral-800">
{/* Header bar */}
<div className="flex items-center px-4 py-3 bg-[#252526] border-b border-neutral-800">
<div className="flex gap-2">
<div className="w-3 h-3 rounded-full bg-red-500/20" />
<div className="w-3 h-3 rounded-full bg-yellow-500/20" />
<div className="w-3 h-3 rounded-full bg-green-500/20" />
</div>
<span className="ml-4 text-xs text-neutral-500 font-mono">filename.tsx</span>
</div>
{/* Code content */}
<div className="p-6 overflow-x-auto">
<code className="font-mono text-sm text-neutral-300">
{/* Syntax-highlighted code */}
</code>
</div>
</div>
Colors for syntax:
- Blue
#61AFEF: Keywords, tags - Orange
#D19A66: Strings - Purple
#C678DD: Functions - Green
#98C379: Comments - White
#ABB2BF: Text
🌍 Internationalization (Future)
Currently: English only
When implementing i18n:
- Use
next-intlor similar - Extract all strings to translation files
- Maintain "Swiss infrastructure" messaging
- Support: EN, DE, FR, IT (Swiss languages)
📐 Layout Patterns
Page Container
// App pages
<div className="w-full max-w-6xl mx-auto px-4 sm:px-6 py-8">
{content}
</div>
// Marketing pages
<div className="w-full max-w-4xl mx-auto px-4 pt-20 pb-10">
{content}
</div>
Two-Column Settings Layout
<div className="flex flex-col md:flex-row gap-8">
{/* Sidebar */}
<nav className="w-full md:w-64 flex-shrink-0">
{tabs}
</nav>
{/* Content */}
<div className="flex-1">
{content}
</div>
</div>
🔌 Integration with @ciphera-net/ui
Current Usage
import {
Button,
Input,
Select,
Modal,
LoadingOverlay,
DatePicker,
Captcha,
// Icons
CheckCircleIcon,
SettingsIcon,
// etc.
} from '@ciphera-net/ui'
Version: ^0.0.45
Preset
Pulse uses the Ciphera UI Tailwind preset:
// tailwind.config.ts
presets: [
require('@ciphera-net/ui/dist/tailwind-preset').default,
]
Provides:
- Brand colors (brand-orange)
- Background gradients (bg-ciphera-gradient)
- Neutral scale extension
🎯 Design Principles
1. Privacy-First Visual Language
- Clean, minimal design
- No tracking pixels, no external fonts from CDNs
- Swiss aesthetic (precision, trust, neutrality)
2. Accessibility First
- WCAG 2.1 Level AA compliance
- Keyboard navigation throughout
- Clear focus indicators
- Readable text contrast
3. Performance
- Lightweight animations (opacity, translate only)
- Optimized images
- Code splitting by route
- Fast transitions (200-500ms max)
4. Brand Consistency
- Orange used sparingly (accents only)
- Neutral-heavy palette
- Glass effects for premium feel
- Consistent with other Ciphera products
🛠️ Component Library
Available from @ciphera-net/ui
Buttons: Primary, Secondary, Danger variants
Forms: Input, Select, Checkbox, DatePicker
Feedback: Toast (Sonner), LoadingOverlay
Security: Captcha component
Icons: Full icon set from Lucide
Layout: Modal component
Custom to Pulse
Dashboard: Chart, TopPages, TopReferrers, Locations, TechSpecs, Campaigns, Goals, Performance
Settings: OrganizationSettings, ProfileSettings
Sites: SiteList, VerificationModal
Tools: UtmBuilder
📏 Checklist for New Components
When creating new components, ensure:
- Uses standard color tokens (no hardcoded hex values)
- Follows typography scale (no custom text sizes)
- Has dark mode support (
dark:variants) - Includes focus states on interactive elements
- Uses spacing system (no arbitrary padding values)
- Follows border radius standards
- Has empty state if displays lists
- Has loading state if async
- Has error state if can fail
- Responsive on mobile (<768px)
- Accessible (semantic HTML, ARIA where needed)
- Consistent with existing components
🔍 Testing Checklist
Before deploying changes:
Visual
- Light mode looks correct
- Dark mode looks correct
- Focus rings visible on all interactive elements
- Typography hierarchy is clear
- Spacing feels consistent
Responsive
- Works on mobile (375px)
- Works on tablet (768px)
- Works on desktop (1280px+)
Accessibility
- Can navigate with keyboard only
- Focus visible at all times
- Screen reader friendly (test with VoiceOver/NVDA)
- Color contrast passes WCAG AA
Cross-Browser
- Chrome/Edge (Chromium)
- Firefox
- Safari (macOS/iOS)
📚 References
- Tailwind CSS: https://tailwindcss.com/docs
- Framer Motion: https://www.framer.com/motion/
- WCAG Guidelines: https://www.w3.org/WAI/WCAG21/quickref/
- Ciphera Brand: (internal brand guidelines)
🔄 Changelog
v1.0 (2026-02-06)
- Initial design system documentation
- Documented Tier 1 + Tier 2 standardization results
- Defined all component patterns
- Established accessibility standards
This is a living document. Update as the design system evolves.