Build warm, editorial, production-grade frontend interfaces for RestaurantIQ — clean light theme, tight typography, terracotta accents
Every page, component, and interaction in the RestaurantIQ platform must follow this design system exactly. The aesthetic is warm editorial light — inspired by premium print publications and modern SaaS platforms like Linear, Notion, and NovaSkin. Clean, confident, and unmistakably professional. Never dark, never neon, never generic.
busineess-opts/frontend/src/These are the only colors used across the platform. Do not deviate.
/* ── Backgrounds ─────────────────────────────────────── */
--bg-primary: #FAF9F6 /* Warm cream — default page background */
--bg-secondary: #FFFFFF /* Pure white — cards, modals, panels */
--bg-muted: #F0EDE8 /* Slightly darker cream — alternating sections, inputs */
--bg-subtle: #F7F5F1 /* Between primary and muted — sidebar, hover states */
/* ── Text ────────────────────────────────────────────── */
--text-primary: #1A1A1A /* Near black — headings, body */
--text-secondary:#6B6B6B /* Medium gray — subtitles, descriptions */
--text-muted: #9B9B9B /* Light gray — placeholders, captions */
--text-disabled: #C5C0B8 /* Disabled state text */
/* ── Accents ─────────────────────────────────────────── */
--accent-terra: #C8593A /* Terracotta — primary CTA, icons, highlights */
--accent-terra-light: #F0E6E2 /* Light terracotta — hover bg on accent items */
--accent-terra-dark: #A8482D /* Dark terracotta — hover state on buttons */
--accent-sage: #3D6B35 /* Muted sage green — secondary accent, AI indicators */
--accent-sage-light: #E8EFE6 /* Light sage — hover bg on sage items */
/* ── Borders ─────────────────────────────────────────── */
--border-light: #E8E4DF /* Default border — cards, dividers, inputs */
--border-medium: #D5CFC8 /* Stronger border — focused inputs, separators */
--border-terra: #C8593A /* Accent border — hover on cards, focus rings */
/* ── Shadows ─────────────────────────────────────────── */
--shadow-xs: rgba(26, 26, 26, 0.04)
--shadow-sm: rgba(26, 26, 26, 0.06)
--shadow-md: rgba(26, 26, 26, 0.10)
--shadow-lg: rgba(26, 26, 26, 0.14)
/* ── Status ──────────────────────────────────────────── */
--status-positive: #2D7A3A /* Success green */
--status-warning: #B07D2A /* Warning amber */
--status-negative: #C8593A /* Error/negative — reuse terracotta */
--status-info: #2B5EA7 /* Info blue */
/* ── Status backgrounds (light fills) ────────────────── */
--status-positive-bg: #EBF5EC
--status-warning-bg: #FBF3E2
--status-negative-bg: #F5EAE7
--status-info-bg: #E7EEF9
#000, #111, #1A1A1A as bg) on any pagebox-shadow with colored light (e.g. rgba(255,107,53,0.5))#000000 — always #1A1A1A or darker #0F0F0F maximum for text#FAF9F6 and #F0EDE8 and #FFFFFF for visual rhythm#C8593A sparingly — CTA buttons, icons, label accents, active states#3D6B35 for AI-generated content indicators, success, and secondary tagsfont-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
Inter is already imported in index.css. No other font families should be added.
Display (hero headline):
fontSize: clamp(3.5rem, 7vw, 7rem) /* 56–112px */
fontWeight: 900
letterSpacing: -0.04em
lineHeight: 1.0
textTransform: uppercase /* hero/marketing only */
H1 (page title):
fontSize: clamp(2.5rem, 5vw, 4rem) /* 40–64px */
fontWeight: 900
letterSpacing: -0.03em
lineHeight: 1.1
H2 (section heading):
fontSize: clamp(2rem, 4vw, 3rem) /* 32–48px */
fontWeight: 800
letterSpacing: -0.02em
lineHeight: 1.15
H3 (card / sub-section):
fontSize: 1.25rem /* 20px */
fontWeight: 700
letterSpacing: -0.01em
lineHeight: 1.3
Body (default):
fontSize: 1rem /* 16px */
fontWeight: 400
lineHeight: 1.7
color: #1A1A1A
Body secondary / descriptions:
fontSize: 1rem
color: #6B6B6B
lineHeight: 1.7
Small / caption:
fontSize: 0.875rem /* 14px */
color: #9B9B9B
Label (section eyebrow):
fontSize: 0.7rem /* 11px */
fontWeight: 700
letterSpacing: 0.16em
textTransform: uppercase
color: #C8593A /* or #3D6B35 for secondary */
Every section must begin with a small uppercase label above the heading. This is a critical visual signal.
// ✅ Always use this pattern for section openers
<span style={{
display: 'inline-block',
fontSize: '0.7rem',
fontWeight: 700,
letterSpacing: '0.16em',
textTransform: 'uppercase',
color: '#C8593A', // or '#3D6B35' for AI/success sections
marginBottom: '1rem',
}}>
THE PROBLEM
</span>
<h2 style={{
fontSize: 'clamp(2rem, 4vw, 3rem)',
fontWeight: 900,
letterSpacing: '-0.02em',
lineHeight: 1.15,
color: '#1A1A1A',
margin: '0 0 1.5rem',
}}>
Section Headline Here
</h2>
All major sections use consistent vertical padding. Never use arbitrary values.
Section padding (full-page sections): padding: '7rem 0'
Section padding (compact): padding: '5rem 0'
Container max-width: maxWidth: '1280px'
Container horizontal padding: padding: '0 2rem'
Card internal padding: padding: '2rem'
Card internal padding (large): padding: '2.5rem'
Gap between grid items: gap: '1.5rem' or '2rem'
Gap between stacked text blocks: gap: '1rem'
Space below section label: marginBottom: '1rem'
Space below section heading: marginBottom: '1.5rem' to '3rem'
Space below section sub-text: marginBottom: '3rem'
Every section's content must be wrapped in this container:
<div style={{
maxWidth: '1280px',
margin: '0 auto',
padding: '0 2rem',
}}>
{/* section content */}
</div>
Pill (buttons, badges, tags): borderRadius: '9999px'
Card (standard): borderRadius: '1.25rem' /* 20px */
Card (large/featured): borderRadius: '1.5rem' /* 24px */
Input / small element: borderRadius: '0.625rem' /* 10px */
Icon container (small): borderRadius: '0.5rem' /* 8px */
Icon container (large): borderRadius: '0.875rem' /* 14px */
Modal / overlay: borderRadius: '1.5rem'
// Card resting
boxShadow: '0 2px 12px rgba(26, 26, 26, 0.06)'
// Card hover
boxShadow: '0 12px 40px rgba(26, 26, 26, 0.12)'
// Elevated element (modal, dropdown)
boxShadow: '0 20px 60px rgba(26, 26, 26, 0.14)'
// Browser frame / showcase
boxShadow: '0 40px 100px rgba(26, 26, 26, 0.15)'
// Navbar scroll shadow
boxShadow: '0 2px 20px rgba(26, 26, 26, 0.08)'
// Fixed, 64px tall, cream glassmorphism
// Scroll > 30px adds shadow
<motion.header
initial={{ y: -20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ duration: 0.55, ease: [0.22, 1, 0.36, 1] }}
style={{
position: 'fixed',
top: 0, left: 0, right: 0,
zIndex: 1000,
height: '64px',
background: 'rgba(250, 249, 246, 0.92)',
backdropFilter: 'blur(16px)',
WebkitBackdropFilter: 'blur(16px)',
borderBottom: '1px solid #E8E4DF',
boxShadow: scrolled ? '0 2px 20px rgba(26, 26, 26, 0.08)' : 'none',
transition: 'box-shadow 0.3s ease',
}}
>
{/* Logo: terracotta icon + "Restaurant" + "IQ" in terracotta */}
{/* Center nav links: color #6B6B6B, hover #1A1A1A */}
{/* Right: "Login" text link + "Get Started" terracotta pill */}
</motion.header>
// Add 64px spacer div below nav to offset fixed positioning
// Fixed left sidebar for authenticated app pages
// Width: 240px, background: #FFFFFF, border-right: 1px solid #E8E4DF
// Items: icon + label, active = terracotta bg tint + terracotta text
<aside style={{
width: '240px',
height: '100vh',
position: 'fixed',
top: 0,
left: 0,
background: '#FFFFFF',
borderRight: '1px solid #E8E4DF',
display: 'flex',
flexDirection: 'column',
padding: '1.5rem 0',
zIndex: 100,
}}>
{/* Logo at top */}
{/* Nav items */}
{/* User profile at bottom */}
</aside>
// Nav item pattern:
<a style={{
display: 'flex',
alignItems: 'center',
gap: '0.75rem',
padding: '0.625rem 1.25rem',
margin: '0.125rem 0.75rem',
borderRadius: '0.625rem',
color: isActive ? '#C8593A' : '#6B6B6B',
background: isActive ? '#F0E6E2' : 'transparent',
fontWeight: isActive ? 600 : 400,
fontSize: '0.9375rem',
textDecoration: 'none',
transition: 'all 0.2s ease',
}}>
<Icon size={18} />
Label
</a>
// Terracotta filled pill — main CTA
<motion.button
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
style={{
display: 'inline-flex',
alignItems: 'center',
gap: '0.5rem',
padding: '0.875rem 2rem',
fontSize: '0.9375rem',
fontWeight: 600,
color: '#FFFFFF',
background: '#C8593A',
border: 'none',
borderRadius: '9999px',
cursor: 'pointer',
letterSpacing: '0.01em',
transition: 'background 0.2s ease',
fontFamily: 'Inter, sans-serif',
}}
onMouseEnter={e => e.currentTarget.style.background = '#A8482D'}
onMouseLeave={e => e.currentTarget.style.background = '#C8593A'}
>
Button Label →
</motion.button>
// Ghost button — secondary actions
<motion.button
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
style={{
display: 'inline-flex',
alignItems: 'center',
gap: '0.5rem',
padding: '0.875rem 2rem',
fontSize: '0.9375rem',
fontWeight: 500,
color: '#1A1A1A',
background: 'transparent',
border: '1.5px solid #D5CFC8',
borderRadius: '9999px',
cursor: 'pointer',
letterSpacing: '0.01em',
transition: 'all 0.2s ease',
fontFamily: 'Inter, sans-serif',
}}
onMouseEnter={e => {
e.currentTarget.style.borderColor = '#C8593A';
e.currentTarget.style.color = '#C8593A';
}}
onMouseLeave={e => {
e.currentTarget.style.borderColor = '#D5CFC8';
e.currentTarget.style.color = '#1A1A1A';
}}
>
Secondary Action
</motion.button>
// White card with border — the default container unit
<motion.div
whileHover={{ translateY: -4, boxShadow: '0 12px 40px rgba(26, 26, 26, 0.12)' }}
transition={{ duration: 0.3, ease: 'easeOut' }}
style={{
background: '#FFFFFF',
borderRadius: '1.25rem',
padding: '2rem',
border: '1px solid #E8E4DF',
boxShadow: '0 2px 12px rgba(26, 26, 26, 0.06)',
transition: 'all 0.3s ease',
}}
>
{/* card content */}
</motion.div>
On hover, optionally change border to terracotta:
onMouseEnter={e => e.currentTarget.style.borderColor = '#C8593A'}
onMouseLeave={e => e.currentTarget.style.borderColor = '#E8E4DF'}
// Small square with rounded corners — holds a Lucide icon
// Terracotta variant:
<div style={{
width: '48px',
height: '48px',
borderRadius: '0.75rem',
background: '#F0E6E2',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0,
}}>
<SomeIcon size={22} color="#C8593A" />
</div>
// Sage green variant (AI / intelligence features):
<div style={{
width: '48px',
height: '48px',
borderRadius: '0.75rem',
background: '#E8EFE6',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0,
}}>
<SomeIcon size={22} color="#3D6B35" />
</div>
// Pill badge — status, categories, labels
// Default (muted):
<span style={{
display: 'inline-flex',
alignItems: 'center',
gap: '0.375rem',
padding: '0.25rem 0.75rem',
borderRadius: '9999px',
fontSize: '0.75rem',
fontWeight: 600,
letterSpacing: '0.02em',
background: '#F0EDE8',
color: '#6B6B6B',
border: '1px solid #E8E4DF',
}}>
Label
</span>
// Terracotta accent:
<span style={{ ...above, background: '#F0E6E2', color: '#C8593A', border: '1px solid #E8D5CC' }}>
Active
</span>
// Sage accent:
<span style={{ ...above, background: '#E8EFE6', color: '#3D6B35', border: '1px solid #D0DFCd' }}>
AI Generated
</span>
<input
type="text"
style={{
width: '100%',
padding: '0.75rem 1rem',
fontSize: '0.9375rem',
fontFamily: 'Inter, sans-serif',
color: '#1A1A1A',
background: '#F0EDE8',
border: '1.5px solid #E8E4DF',
borderRadius: '0.625rem',
outline: 'none',
transition: 'border-color 0.2s ease, background 0.2s ease',
}}
onFocus={e => {
e.target.style.borderColor = '#C8593A';
e.target.style.background = '#FFFFFF';
}}
onBlur={e => {
e.target.style.borderColor = '#E8E4DF';
e.target.style.background = '#F0EDE8';
}}
/>
// Label above input:
<label style={{
display: 'block',
fontSize: '0.875rem',
fontWeight: 600,
color: '#1A1A1A',
marginBottom: '0.5rem',
letterSpacing: '0.01em',
}}>
Field Label
</label>
// Small data display card — used in dashboards
<div style={{
background: '#FFFFFF',
border: '1px solid #E8E4DF',
borderRadius: '1.25rem',
padding: '1.5rem',
boxShadow: '0 2px 12px rgba(26, 26, 26, 0.06)',
}}>
<p style={{ fontSize: '0.75rem', fontWeight: 700, letterSpacing: '0.12em', textTransform: 'uppercase', color: '#9B9B9B', margin: '0 0 0.5rem' }}>
Metric Label
</p>
<p style={{ fontSize: '2.25rem', fontWeight: 900, letterSpacing: '-0.02em', color: '#1A1A1A', margin: '0 0 0.25rem' }}>
4.4
</p>
<p style={{ fontSize: '0.875rem', color: '#3D6B35', fontWeight: 500, margin: 0 }}>
↑ +0.3 this month
</p>
</div>
// Horizontal multi-segment bar (positive / neutral / negative)
<div style={{ height: '10px', borderRadius: '9999px', background: '#E8E4DF', overflow: 'hidden', display: 'flex' }}>
<motion.div
initial={{ width: 0 }}
animate={{ width: '68%' }}
transition={{ duration: 0.9, ease: 'easeOut', delay: 0.3 }}
style={{ height: '100%', background: '#2D7A3A', borderRadius: '9999px 0 0 9999px' }}
/>
<motion.div
initial={{ width: 0 }}
animate={{ width: '18%' }}
transition={{ duration: 0.9, ease: 'easeOut', delay: 0.5 }}
style={{ height: '100%', background: '#B07D2A' }}
/>
<motion.div
initial={{ width: 0 }}
animate={{ width: '14%' }}
transition={{ duration: 0.9, ease: 'easeOut', delay: 0.7 }}
style={{ height: '100%', background: '#C8593A', borderRadius: '0 9999px 9999px 0' }}
/>
</div>
// Decorative typographic accent — use sparingly (1-2 per section max)
<span style={{
fontSize: '2rem', // or '4rem' for large hero placement
color: '#C8593A',
lineHeight: 1,
userSelect: 'none',
pointerEvents: 'none',
fontWeight: 900,
}}>
✦
</span>
// Animated rotating version:
<motion.span
animate={{ rotate: 360 }}
transition={{ duration: 20, repeat: Infinity, ease: 'linear' }}
style={{ fontSize: '1.5rem', color: '#C8593A', display: 'inline-block' }}
>
✦
</motion.span>
<div style={{
display: 'grid',
gridTemplateColumns: '1fr 1fr', // or '1.1fr 0.9fr' for slight asymmetry
gap: '5rem',
alignItems: 'center',
}}>
<div>{/* Text content */}</div>
<div>{/* Visual / card composition */}</div>
</div>
// Responsive: on mobile, collapse to single column
// Use inline media queries or CSS class for this
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(3, 1fr)',
gap: '1.5rem',
}}>
{items.map((item, i) => (
<Card key={i} {...item} />
))}
</div>
// Mobile: 1 column. Tablet: 2 column.
// Variable-sized cards in a CSS grid — used for data visualization panels
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(3, 1fr)',
gridTemplateRows: 'repeat(2, 1fr)',
gap: '1rem',
height: '480px',
}}>
<div style={{ gridColumn: 'span 2', gridRow: 'span 1' }}>{/* Wide card */}</div>
<div style={{ gridColumn: 'span 1', gridRow: 'span 2' }}>{/* Tall card */}</div>
<div style={{ gridColumn: 'span 1' }}>{/* Normal card */}</div>
<div style={{ gridColumn: 'span 1' }}>{/* Normal card */}</div>
</div>
// For all authenticated /dashboard, /insights, /frameworks, etc. pages
<div style={{ display: 'flex', minHeight: '100vh', background: '#FAF9F6' }}>
<AppSidebar /> {/* Fixed 240px left sidebar */}
<div style={{
marginLeft: '240px',
flex: 1,
display: 'flex',
flexDirection: 'column',
minHeight: '100vh',
}}>
<TopBar /> {/* 64px top bar with page title + actions */}
<main style={{
flex: 1,
padding: '2rem',
maxWidth: '1280px',
width: '100%',
}}>
{/* Page content */}
</main>
</div>
</div>
Sections must alternate to create visual breathing room. Follow this order:
Section 1 (Hero): #FAF9F6 (cream)
Section 2 (Problem): #F0EDE8 (muted cream)
Section 3 (Solution): #FFFFFF (pure white)
Section 4 (Features): #FAF9F6 (cream)
Section 5 (Demo): #F0EDE8 (muted cream)
Section 6 (Testimonials): #FFFFFF (pure white)
Section 7 (CTA): #F0EDE8 (muted cream)
Footer: #FFFFFF (pure white)
import { motion, useInView, AnimatePresence } from 'motion/react';
// NOT 'framer-motion' — always import from 'motion/react'
// 1. Get ref and isInView hook
const ref = useRef<HTMLDivElement>(null);
const isInView = useInView(ref, { once: true, margin: '-80px' });
// 2. Wrap section in ref div
<section ref={ref}>
// 3. Animate child elements
<motion.div
initial={{ opacity: 0, y: 24 }}
animate={isInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.55, ease: [0.22, 1, 0.36, 1] }}
>
// Parent receives delay increments per child
items.map((item, i) => (
<motion.div
key={i}
initial={{ opacity: 0, y: 24 }}
animate={isInView ? { opacity: 1, y: 0 } : {}}
transition={{
duration: 0.55,
ease: [0.22, 1, 0.36, 1],
delay: 0.1 + i * 0.08,
}}
>
// For elements that animate on first load (Navbar, Hero headline)
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, ease: [0.22, 1, 0.36, 1] }}
>
<motion.div
whileHover={{ translateY: -4 }}
transition={{ duration: 0.25, ease: 'easeOut' }}
>
<motion.button
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.97 }}
transition={{ duration: 0.15 }}
>
<AnimatePresence>
{isOpen && (
<motion.div
key="modal"
initial={{ opacity: 0, scale: 0.97 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.97 }}
transition={{ duration: 0.22, ease: [0.22, 1, 0.36, 1] }}
>
[0.22, 1, 0.36, 1] (custom ease-out-expo) for entrance animationsonce: true on useInView — don't re-animate on scroll back upstiffness, damping springs) on layout elementsconst MarketingPage: React.FC = () => (
<div style={{
background: '#FAF9F6',
color: '#1A1A1A',
fontFamily: 'Inter, -apple-system, sans-serif',
overflowX: 'hidden',
}}>
<Navbar />
{/* Alternating sections: cream → muted cream → white → cream */}
<HeroSection /> {/* bg: #FAF9F6 */}
<ProblemSection /> {/* bg: #F0EDE8 */}
<SolutionSection /> {/* bg: #FFFFFF */}
<FeaturesSection /> {/* bg: #FAF9F6 */}
<CTASection /> {/* bg: #F0EDE8 */}
<Footer /> {/* bg: #FFFFFF */}
</div>
);
// Centered card on cream background
const AuthPage: React.FC = () => (
<div style={{
minHeight: '100vh',
background: '#F0EDE8',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
padding: '2rem',
fontFamily: 'Inter, -apple-system, sans-serif',
}}>
{/* Small top logo */}
<motion.div
initial={{ opacity: 0, y: 24 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.55, ease: [0.22, 1, 0.36, 1] }}
style={{
width: '100%',
maxWidth: '440px',
background: '#FFFFFF',
borderRadius: '1.5rem',
padding: '3rem',
border: '1px solid #E8E4DF',
boxShadow: '0 20px 60px rgba(26, 26, 26, 0.10)',
}}
>
{/* Logo, heading, form, CTA */}
</motion.div>
</div>
);
// Full-screen with persistent step indicator
const OnboardingPage: React.FC = () => (
<div style={{
minHeight: '100vh',
background: '#FAF9F6',
fontFamily: 'Inter, -apple-system, sans-serif',
}}>
{/* Top bar: logo left + step indicator center + exit right */}
<header style={{
height: '64px',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '0 2rem',
borderBottom: '1px solid #E8E4DF',
background: '#FFFFFF',
}}>
{/* Logo | Step 1 ── Step 2 ── Step 3 ── Step 4 ── Step 5 | Exit */}
</header>
{/* Step content: centered, max 640px wide */}
<main style={{
maxWidth: '640px',
margin: '0 auto',
padding: '4rem 2rem',
}}>
{/* AnimatePresence for step transitions */}
</main>
</div>
);
// App shell with sidebar + main content area
const DashboardPage: React.FC = () => (
<div style={{ display: 'flex', minHeight: '100vh', background: '#FAF9F6' }}>
<AppSidebar />
<div style={{ marginLeft: '240px', flex: 1 }}>
<TopBar title="Dashboard" />
<main style={{ padding: '2rem', maxWidth: '1280px' }}>
{/* Page-specific content */}
{/* KPI row → Charts → Tables */}
</main>
</div>
</div>
);
All charts and data visualizations must match the warm editorial theme:
Chart background: #FFFFFF or #F0EDE8
Chart axes / grid lines: #E8E4DF (very light)
Chart labels: #6B6B6B (secondary text)
Positive data (bars): #3D6B35 (sage green)
Negative data (bars): #C8593A (terracotta)
Neutral data: #B07D2A (amber)
Highlight / selected: #C8593A with opacity 0.15 fill
Line chart color: #C8593A (primary line)
Comparison line: #3D6B35 (secondary line)
Use CSS div bars for simple visualizations — avoid heavy chart libraries for small inline charts.
// ✅ All buttons must have accessible labels
<button aria-label="Close modal">×</button>
// ✅ Focus rings — use terracotta outline