Design System
Comprehensive design tokens, components, and patterns for makepostsell.com. Built on CSS custom properties. Mobile-first. Accessible. Respects reduced motion.
Design Tokens
All values live in tokens.css as CSS custom properties. Every component references tokens, never raw values. Change a token, change everything.
/* Include order */
<link rel="stylesheet" href="tokens.css"> /* tokens first */
<link rel="stylesheet" href="common.css"> /* shared app */
<link rel="stylesheet" href="mps.css"> /* ribbon, alerts */
<link rel="stylesheet" href="landing.css"> /* landing page only */
tokens.css also provides base resets (box-sizing, font smoothing, scroll-behavior) and prefers-reduced-motion support.
Typography
Type scale (major third 1.250)
Display
Headline 1
Headline 2
Headline 3
Title Large
Title
Title Small
Body Large — ideal for landing pages and long reads.
Body — default for app interfaces and dashboard text.
Body Small — secondary information, metadata, timestamps.
Label Large
LABEL — SECTION HEADERS, GROUP TITLES
Caption — smallest text, footnotes
OVERLINE — CATEGORY TAGS
.type-display clamp(2.25rem, 5vw, 3.75rem) 700 tight
.type-headline-1 3rem (48px) 700 tight
.type-headline-2 2.25rem (36px) 700 tight
.type-headline-3 1.875rem (30px) 700 snug
.type-title-lg 1.5rem (24px) 600 snug
.type-title 1.25rem (20px) 600 snug
.type-title-sm 1rem (16px) 600 normal
.type-body-lg 1.125rem (18px) 400 relaxed
.type-body 1rem (16px) 400 relaxed
.type-body-sm 0.875rem (14px) 400 relaxed
.type-label-lg 0.875rem (14px) 600 wide
.type-label 0.75rem (12px) 600 UPPERCASE wider
.type-caption 0.75rem (12px) 400 normal
.type-overline 11px 700 UPPERCASE widest
.type-code monospace 0.9em inline code
App typography classes (common.css)
Legacy classes. New code should prefer .type-* classes from tokens.css.
Font rendering
html {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
font-size: 16px; /* 1rem = 16px base */
}
Antialiased rendering on macOS/iOS. optimizeLegibility enables kerning and ligatures. 16px base ensures rem values map cleanly to pixel equivalents.
Elevation
Five shadow levels. Higher = more prominent = closer to the viewer. Every interactive element should shift elevation on hover.
--elevation-0: none
--elevation-1: 0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04) /* cards at rest */
--elevation-2: 0 2px 6px rgba(0,0,0,0.06), 0 2px 4px rgba(0,0,0,0.04) /* cards hovered */
--elevation-3: 0 4px 12px rgba(0,0,0,0.07), 0 2px 4px rgba(0,0,0,0.04) /* dropdown, popover */
--elevation-4: 0 8px 24px rgba(0,0,0,0.08), 0 2px 6px rgba(0,0,0,0.04) /* modal, dialog */
--elevation-5: 0 16px 48px rgba(0,0,0,0.10), 0 4px 12px rgba(0,0,0,0.05) /* toast, overlay */
Usage: element rests at N, hovers at N+1 or N+2
Cards: rest 1, hover 3
Buttons: rest 1, hover 2
Modals: 4
Toasts: 5
Motion
Easing curves
Standard
Decel
Accel
Spring
--ease-standard: cubic-bezier(0.2, 0, 0, 1) /* default for most transitions */
--ease-decelerate: cubic-bezier(0, 0, 0, 1) /* entering elements (fade in, slide in) */
--ease-accelerate: cubic-bezier(0.3, 0, 1, 1) /* exiting elements (fade out) */
--ease-spring: cubic-bezier(0.175, 0.885, 0.32, 1.275) /* playful overshoot */
Hover each box above to feel the curve difference.
Durations
--duration-instant: 50ms /* micro-feedback (opacity toggle) */
--duration-fast: 100ms /* state layer opacity changes */
--duration-normal: 200ms /* color, shadow, border transitions */
--duration-slow: 300ms /* transform transitions (scale, translate) */
--duration-slower: 400ms /* complex animations (ripple) */
--duration-entrance: 250ms /* element enters viewport */
--duration-exit: 200ms /* element leaves (shorter = snappier) */
Entrance animations
fade-in
fade-up
scale
slide-R
.animate-fade-in /* opacity 0 to 1 */
.animate-fade-in-up /* fade + translate up 16px */
.animate-fade-in-down /* fade + translate down 16px */
.animate-fade-in-scale /* fade + scale from 0.95 */
.animate-slide-in-right /* fade + translate from right 24px */
.animate-slide-in-left /* fade + translate from left 24px */
.animate-pulse /* breathing opacity loop */
.animate-spin /* continuous rotation */
/* Stagger delays for lists */
.stagger-1 through .stagger-6 (50ms increments)
Reduced motion
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
Automatically applied. No opt-in needed. All animations and
transitions collapse to instant for users who prefer reduced motion.
Spacing
4px base unit. Consistent spacing prevents the "off by 3px" disease.
2
3
4
5
6
8
10
12
16
20
24
--space-1: 4px --space-8: 32px
--space-2: 8px --space-10: 40px
--space-3: 12px --space-12: 48px
--space-4: 16px --space-16: 64px
--space-5: 20px --space-20: 80px
--space-6: 24px --space-24: 96px
Utilities: .mt-{n} .mb-{n} .p-{n} .gap-{n}
Shape
none
sm 4px
md 8px
lg 12px
xl 16px
2xl 24px
full
--radius-none: 0 /* hard edges */
--radius-sm: 4px /* inputs, code blocks, app buttons */
--radius-md: 8px /* small cards, wells */
--radius-lg: 12px /* feature cards, landing cards */
--radius-xl: 16px /* pricing cards, comparison cards */
--radius-2xl: 24px /* large containers */
--radius-full: 9999px /* pills, avatars, badges */
Rule of thumb: bigger element = bigger radius.
Interactive States
State layer: a semi-transparent overlay that responds to interaction. Apply .state-layer to any interactive element.
.state-layer::after {
content: '';
position: absolute;
inset: 0;
background: currentColor;
opacity: 0;
transition: opacity 100ms ease;
pointer-events: none;
}
.state-layer:hover::after { opacity: 0.04; }
.state-layer:focus-visible::after { opacity: 0.08; }
.state-layer:active::after { opacity: 0.10; }
State overlay tokens:
--state-hover: rgba(0,0,0, 0.04)
--state-focus: rgba(88,113,173, 0.12)
--state-pressed: rgba(0,0,0, 0.08)
--state-dragged: rgba(0,0,0, 0.12)
Focus Rings
Keyboard-only focus rings. Visible on Tab, invisible on click. Navy blue, 2px, 2px offset.
:focus-visible {
outline: 2px solid var(--border-focus); /* navy #5871ad */
outline-offset: 2px;
}
:focus:not(:focus-visible) {
outline: none; /* hide on mouse click */
}
This is set globally in tokens.css. Works on all elements.
Ripple Effect
Material-style touch feedback. Add .ripple class and initialize with JS.
/* CSS */
.ripple { position: relative; overflow: hidden; }
.ripple-wave {
position: absolute;
border-radius: 50%;
background: rgba(255,255,255, 0.35);
transform: scale(0);
animation: ripple-expand 400ms ease-out forwards;
}
.ripple-dark .ripple-wave { background: rgba(0,0,0, 0.12); }
/* JS initialization */
function createRipple(e) {
const el = e.currentTarget;
const rect = el.getBoundingClientRect();
const size = Math.max(rect.width, rect.height);
const wave = document.createElement('span');
wave.className = 'ripple-wave';
wave.style.width = wave.style.height = size + 'px';
wave.style.left = (e.clientX - rect.left - size/2) + 'px';
wave.style.top = (e.clientY - rect.top - size/2) + 'px';
el.appendChild(wave);
wave.addEventListener('animationend', () => wave.remove());
}
Landing CTAs
.landing-cta:
border-radius: 50px (pill)
padding: 16px 32px
box-shadow: elevation-1
hover: translateY(-1px) + elevation-2
.landing-cta-large:
font-size: 18px
padding: 18px 48px
Nav CTA (.nav-cta):
dark pill in nav bar, #0b0b0b bg
Feature Cards
💰
Card Title
Card description with supporting text.
📈
Linked Card
Cards in .landing-content get 12px radius, subtle shadow, hover lift.
Grid: .mps-card-grid (1col mobile, 2col tablet+)
.mps-card-grid.three-col (3col at tablet+)
Card: .mps-card
App: border 1px #e0e0e0, radius 10px, padding 20px
Landing: border 1px #eee, radius 12px, shadow elevation-1
Hover: elevation-3 + translateY(-2px)
Pricing Cards
Monthly
$14/mo
Pay as you go.
Best value
$97/yr
Save 42%.
.landing-pricing-card white, radius-xl, overflow hidden
.landing-pricing-featured 2px green border, green shadow
.landing-pricing-banner gray top strip, uppercase
.landing-pricing-banner-featured green bg, white text
.landing-pricing-amount 3em, weight 700
.landing-pricing-period 0.35em, color faint
Step Cards
1
First
Description.
2
Second
Description.
3
Third
Description.
.landing-step-number: 44px green circle, white number, weight 700
.landing-step-card: centered text alignment
Protection Lists
- ❌ Negative item
- ❌ Another negative
- ✔ Positive — Details
- ✔ Another — More
.landing-list-bad: bg #fef2f2, border #f5d5d5
.landing-list-good: bg #f0f9e8, border #d5e8c0
.landing-x: color --color-cross
.landing-check: color --color-check
Items separated by 1px border-top at 5% opacity
Alerts
Success — --alert-success-bg
Warning — --alert-warning-bg
Danger — --alert-danger-bg
Variants: .alert-success, .alert-info, .alert-warning, .alert-danger
Token pairs: --alert-{variant}-bg, --alert-{variant}-text, --alert-{variant}-border
Loading States
Skeleton
.skeleton shimmer gradient animation (1.5s loop)
.skeleton-text height: 1em, small radius
.skeleton-circle border-radius: full
The shimmer runs through surface-container to surface-container-high.
Spinner
.spinner 20px, 2px border, navy top
.spinner-sm 14px
.spinner-lg 32px, 3px border
Spins at 0.6s linear infinite.
Scroll Reveal
Elements with .reveal start invisible and slide up when scrolled into view. JS adds .revealed.
.reveal {
opacity: 0;
transform: translateY(20px);
transition: opacity 300ms ease-out, transform 300ms ease-out;
}
.reveal.revealed {
opacity: 1;
transform: translateY(0);
}
/* JS — IntersectionObserver */
const observer = new IntersectionObserver((entries) => {
entries.forEach(e => {
if (e.isIntersecting) {
e.target.classList.add('revealed');
observer.unobserve(e.target);
}
});
}, { threshold: 0.15 });
document.querySelectorAll('.reveal, .reveal-scale')
.forEach(el => observer.observe(el));
Wells
Well (gray background)
Content inside a gray well.
Well White
Content inside a white well.
.well bg: surface-dim, radius: 10px
.well-white bg: surface-base
Message Ribbon
Sell digital downloads or physical products — commission free!
.message-ribbon
gradient: navy #5871ad to navy-light #82A8FF
white text, bold, 18px, centered
hidden on mobile, visible at 800px+
Landing Sections
.landing-section-alt (#F9F9FA)
.landing-final-cta (gradient)
.landing-hero (top gradient)
.landing-section padding: 64px mobile, 80px tablet, 100px desktop
.landing-section-alt background: surface-dim
.landing-final-cta gradient: #f4f6fb to #f0f3fa
.landing-hero gradient: #f4f6fb to white (top-down)
Alternating white/dim sections replace HR dividers.
Layout
Containers
Legacy:
.one-column max-width: 800px, centered
.one-column-thin max-width: 400px
Token-based:
.container max-width: var(--content-width) 800px, auto padding
.container-narrow max-width: var(--content-narrow) 400px
.container-wide max-width: var(--content-wide) 1200px
Breakpoints
Mobile first (no media query)
@media (min-width: 800px) — tablet/desktop
@media (min-width: 1200px) — wide desktop
Tokens: --bp-tablet: 800px, --bp-desktop: 1200px
Z-index scale
--z-base: 0 /* default */
--z-dropdown: 100 /* dropdown menus */
--z-sticky: 200 /* sticky headers */
--z-overlay: 300 /* backdrop overlays */
--z-modal: 400 /* modal dialogs */
--z-toast: 500 /* toast notifications */
--z-ribbon: 600 /* top ribbon banner */
CSS files
tokens.css — design tokens, base resets, utilities, animations
common.css — shared app styles (layout, buttons, nav, wells, footer, cards)
mps.css — message ribbon, alerts
landing.css — landing page (hero, sections, pricing, CTAs, lists, steps)