Full design system: tokens.css + comprehensive styleguide

tokens.css: CSS custom properties for colors, surfaces, text, borders,
typography scale (major third), spacing (4px base), shape (7 radius levels),
elevation (5 shadow levels), motion (4 easing curves, 7 durations),
state layers, ripple effect, entrance animations (6 types + stagger),
skeleton loading, spinner, scroll reveal, disabled states, spacing/margin
utilities, prefers-reduced-motion, font smoothing, box-sizing reset.

styleguide.html: live interactive demos for every token and component.
Elevation boxes, motion curve hover demos, ripple click demo, skeleton
shimmer, spinner sizes, focus ring demo, state layer demo, full TOC
with anchor links.
This commit is contained in:
russell@unturf.com 2026-03-06 08:17:47 -05:00
parent a24b0717e8
commit d8425867c5
3 changed files with 1507 additions and 408 deletions

View file

@ -9,6 +9,7 @@
<meta name="theme-color" content="#5871ad">
<meta name="description" content="Open your own commission-free shop. Sell digital downloads, physical products, and media. Accept Stripe, PayPal, Monero, and Dogecoin. Open source and public domain.">
<link rel="stylesheet" href="tokens.css">
<link rel="stylesheet" href="common.css">
<link rel="stylesheet" href="mps.css">
<link rel="stylesheet" href="landing.css">

File diff suppressed because it is too large Load diff

651
tokens.css Normal file
View file

@ -0,0 +1,651 @@
/* ============================================
DESIGN TOKENS Make Post Sell
Single source of truth for the design system.
CSS custom properties, consumed everywhere.
============================================ */
:root {
/* ---- Color: Brand ---- */
--color-green: #a3c765;
--color-green-dark: #8ab34e;
--color-green-light: #e8f5d4;
--color-blue: #98b6fa;
--color-blue-dark: #7a9de8;
--color-navy: #5871ad;
--color-navy-light: #82A8FF;
--color-purple: #9B59B6;
--color-purple-dark: #7B2D8E;
--color-danger: #CC6958;
--color-danger-dark: #b5503f;
--color-check: #678731;
--color-cross: #c77765;
--color-gold: #d4a843;
/* ---- Color: Surface ---- */
--surface-base: #FFFFFF;
--surface-dim: #F9F9FA;
--surface-container: #F4F4F5;
--surface-container-high: #EEEEEF;
--surface-inverse: #0b0b0b;
/* ---- Color: Text ---- */
--text-primary: #0b0b0b;
--text-body: #515151;
--text-secondary: #666666;
--text-muted: #777777;
--text-faint: #999999;
--text-disabled: #BBBBBB;
--text-inverse: #FFFFFF;
--text-on-green: #FFFFFF;
--text-on-navy: #FFFFFF;
/* ---- Color: Border ---- */
--border-default: #e0e0e0;
--border-light: #eeeeee;
--border-focus: #5871ad;
--border-error: #CC6958;
/* ---- Color: State overlays ---- */
--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);
--state-disabled-bg: rgba(0, 0, 0, 0.04);
--state-disabled-text: rgba(0, 0, 0, 0.26);
/* ---- Color: Alerts ---- */
--alert-success-bg: #e8f5d4;
--alert-success-text: #155724;
--alert-success-border: #c3e6cb;
--alert-info-bg: #dce8ff;
--alert-info-text: #0c5460;
--alert-info-border: #bee5eb;
--alert-warning-bg: #fff3cd;
--alert-warning-text: #856404;
--alert-warning-border: #ffeeba;
--alert-danger-bg: #f8d7da;
--alert-danger-text: #721c24;
--alert-danger-border: #f5c6cb;
/* ---- Typography ---- */
--font-family: helvetica, arial, sans-serif;
--font-mono: 'SF Mono', 'Menlo', 'Consolas', monospace;
/* Type scale — major third (1.250) */
--text-xs: 0.75rem; /* 12px */
--text-sm: 0.875rem; /* 14px */
--text-base: 1rem; /* 16px */
--text-md: 1.125rem; /* 18px */
--text-lg: 1.25rem; /* 20px */
--text-xl: 1.5rem; /* 24px */
--text-2xl: 1.875rem; /* 30px */
--text-3xl: 2.25rem; /* 36px */
--text-4xl: 3rem; /* 48px */
--text-5xl: 3.75rem; /* 60px */
/* Line heights */
--leading-none: 1;
--leading-tight: 1.15;
--leading-snug: 1.3;
--leading-normal: 1.5;
--leading-relaxed: 1.65;
--leading-loose: 1.8;
/* Font weights */
--weight-regular: 400;
--weight-medium: 500;
--weight-semibold: 600;
--weight-bold: 700;
/* Letter spacing */
--tracking-tight: -0.02em;
--tracking-normal: 0;
--tracking-wide: 0.02em;
--tracking-wider: 0.05em;
--tracking-widest: 0.08em;
/* ---- Spacing scale (4px base) ---- */
--space-0: 0;
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-5: 20px;
--space-6: 24px;
--space-8: 32px;
--space-10: 40px;
--space-12: 48px;
--space-16: 64px;
--space-20: 80px;
--space-24: 96px;
/* ---- Shape (border radius) ---- */
--radius-none: 0;
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 12px;
--radius-xl: 16px;
--radius-2xl: 24px;
--radius-full: 9999px;
/* ---- Elevation (box-shadow) ---- */
--elevation-0: none;
--elevation-1: 0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04);
--elevation-2: 0 2px 6px rgba(0,0,0,0.06), 0 2px 4px rgba(0,0,0,0.04);
--elevation-3: 0 4px 12px rgba(0,0,0,0.07), 0 2px 4px rgba(0,0,0,0.04);
--elevation-4: 0 8px 24px rgba(0,0,0,0.08), 0 2px 6px rgba(0,0,0,0.04);
--elevation-5: 0 16px 48px rgba(0,0,0,0.10), 0 4px 12px rgba(0,0,0,0.05);
/* ---- Motion ---- */
/* Durations */
--duration-instant: 50ms;
--duration-fast: 100ms;
--duration-normal: 200ms;
--duration-slow: 300ms;
--duration-slower: 400ms;
--duration-entrance: 250ms;
--duration-exit: 200ms;
/* Easing — based on material curves */
--ease-standard: cubic-bezier(0.2, 0, 0, 1);
--ease-decelerate: cubic-bezier(0, 0, 0, 1);
--ease-accelerate: cubic-bezier(0.3, 0, 1, 1);
--ease-emphasize: cubic-bezier(0.2, 0, 0, 1);
--ease-spring: cubic-bezier(0.175, 0.885, 0.32, 1.275);
/* ---- Z-index scale ---- */
--z-base: 0;
--z-dropdown: 100;
--z-sticky: 200;
--z-overlay: 300;
--z-modal: 400;
--z-toast: 500;
--z-ribbon: 600;
/* ---- Layout ---- */
--content-width: 800px;
--content-narrow: 400px;
--content-wide: 1200px;
--bp-tablet: 800px;
--bp-desktop: 1200px;
}
/* ============================================
BASE RESETS & DEFAULTS
============================================ */
*, *::before, *::after {
box-sizing: border-box;
}
html {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
font-size: 16px;
scroll-behavior: smooth;
}
body {
font-family: var(--font-family);
font-size: var(--text-base);
font-weight: var(--weight-regular);
line-height: var(--leading-normal);
color: var(--text-body);
background-color: var(--surface-dim);
margin: 0;
}
/* ============================================
FOCUS Accessible, visible, consistent
============================================ */
:focus-visible {
outline: 2px solid var(--border-focus);
outline-offset: 2px;
}
:focus:not(:focus-visible) {
outline: none;
}
/* ============================================
SELECTION
============================================ */
::selection {
background: var(--color-navy-light);
color: var(--text-inverse);
}
/* ============================================
TYPOGRAPHY UTILITIES
============================================ */
.type-display {
font-size: clamp(var(--text-3xl), 5vw, var(--text-5xl));
font-weight: var(--weight-bold);
line-height: var(--leading-tight);
letter-spacing: var(--tracking-tight);
color: var(--text-primary);
}
.type-headline-1 {
font-size: var(--text-4xl);
font-weight: var(--weight-bold);
line-height: var(--leading-tight);
letter-spacing: var(--tracking-tight);
color: var(--text-primary);
}
.type-headline-2 {
font-size: var(--text-3xl);
font-weight: var(--weight-bold);
line-height: var(--leading-tight);
letter-spacing: var(--tracking-tight);
color: var(--text-primary);
}
.type-headline-3 {
font-size: var(--text-2xl);
font-weight: var(--weight-bold);
line-height: var(--leading-snug);
color: var(--text-primary);
}
.type-title-lg {
font-size: var(--text-xl);
font-weight: var(--weight-semibold);
line-height: var(--leading-snug);
color: var(--text-primary);
}
.type-title {
font-size: var(--text-lg);
font-weight: var(--weight-semibold);
line-height: var(--leading-snug);
color: var(--text-primary);
}
.type-title-sm {
font-size: var(--text-base);
font-weight: var(--weight-semibold);
line-height: var(--leading-normal);
color: var(--text-primary);
}
.type-body-lg {
font-size: var(--text-md);
font-weight: var(--weight-regular);
line-height: var(--leading-relaxed);
color: var(--text-body);
}
.type-body {
font-size: var(--text-base);
font-weight: var(--weight-regular);
line-height: var(--leading-relaxed);
color: var(--text-body);
}
.type-body-sm {
font-size: var(--text-sm);
font-weight: var(--weight-regular);
line-height: var(--leading-relaxed);
color: var(--text-body);
}
.type-label-lg {
font-size: var(--text-sm);
font-weight: var(--weight-semibold);
line-height: var(--leading-normal);
letter-spacing: var(--tracking-wide);
color: var(--text-primary);
}
.type-label {
font-size: var(--text-xs);
font-weight: var(--weight-semibold);
line-height: var(--leading-normal);
letter-spacing: var(--tracking-wider);
text-transform: uppercase;
color: var(--text-muted);
}
.type-caption {
font-size: var(--text-xs);
font-weight: var(--weight-regular);
line-height: var(--leading-normal);
color: var(--text-faint);
}
.type-overline {
font-size: 11px;
font-weight: var(--weight-bold);
line-height: var(--leading-normal);
letter-spacing: var(--tracking-widest);
text-transform: uppercase;
color: var(--text-muted);
}
.type-code {
font-family: var(--font-mono);
font-size: 0.9em;
background: var(--surface-container);
padding: 2px 6px;
border-radius: var(--radius-sm);
}
/* ============================================
ELEVATION UTILITIES
============================================ */
.elevation-0 { box-shadow: var(--elevation-0); }
.elevation-1 { box-shadow: var(--elevation-1); }
.elevation-2 { box-shadow: var(--elevation-2); }
.elevation-3 { box-shadow: var(--elevation-3); }
.elevation-4 { box-shadow: var(--elevation-4); }
.elevation-5 { box-shadow: var(--elevation-5); }
/* ============================================
SURFACE UTILITIES
============================================ */
.surface-base { background-color: var(--surface-base); }
.surface-dim { background-color: var(--surface-dim); }
.surface-container { background-color: var(--surface-container); }
.surface-inverse { background-color: var(--surface-inverse); color: var(--text-inverse); }
/* ============================================
INTERACTIVE STATE LAYER
Overlay that reacts to hover/focus/press.
Apply to any interactive element.
============================================ */
.state-layer {
position: relative;
overflow: hidden;
}
.state-layer::after {
content: '';
position: absolute;
inset: 0;
background: currentColor;
opacity: 0;
transition: opacity var(--duration-fast) var(--ease-standard);
pointer-events: none;
border-radius: inherit;
}
.state-layer:hover::after {
opacity: 0.04;
}
.state-layer:focus-visible::after {
opacity: 0.08;
}
.state-layer:active::after {
opacity: 0.10;
}
/* ============================================
RIPPLE EFFECT
Add .ripple to any interactive element.
Requires JS initialization (see styleguide).
============================================ */
.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 var(--duration-slower) var(--ease-decelerate) forwards;
pointer-events: none;
}
@keyframes ripple-expand {
to {
transform: scale(2.5);
opacity: 0;
}
}
/* Dark ripple for light backgrounds */
.ripple-dark .ripple-wave {
background: rgba(0, 0, 0, 0.12);
}
/* ============================================
TRANSITION UTILITIES
============================================ */
.transition-all { transition: all var(--duration-normal) var(--ease-standard); }
.transition-colors { transition: color var(--duration-normal) var(--ease-standard), background-color var(--duration-normal) var(--ease-standard), border-color var(--duration-normal) var(--ease-standard); }
.transition-shadow { transition: box-shadow var(--duration-normal) var(--ease-standard); }
.transition-transform { transition: transform var(--duration-normal) var(--ease-standard); }
/* ============================================
ENTRANCE ANIMATIONS
============================================ */
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes fade-in-up {
from { opacity: 0; transform: translateY(16px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes fade-in-down {
from { opacity: 0; transform: translateY(-16px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes fade-in-scale {
from { opacity: 0; transform: scale(0.95); }
to { opacity: 1; transform: scale(1); }
}
@keyframes slide-in-right {
from { opacity: 0; transform: translateX(24px); }
to { opacity: 1; transform: translateX(0); }
}
@keyframes slide-in-left {
from { opacity: 0; transform: translateX(-24px); }
to { opacity: 1; transform: translateX(0); }
}
@keyframes scale-in {
from { transform: scale(0); }
to { transform: scale(1); }
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
@keyframes spin {
to { transform: rotate(360deg); }
}
@keyframes shimmer {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
.animate-fade-in { animation: fade-in var(--duration-entrance) var(--ease-decelerate) both; }
.animate-fade-in-up { animation: fade-in-up var(--duration-entrance) var(--ease-decelerate) both; }
.animate-fade-in-down { animation: fade-in-down var(--duration-entrance) var(--ease-decelerate) both; }
.animate-fade-in-scale { animation: fade-in-scale var(--duration-entrance) var(--ease-decelerate) both; }
.animate-slide-in-right { animation: slide-in-right var(--duration-slow) var(--ease-decelerate) both; }
.animate-slide-in-left { animation: slide-in-left var(--duration-slow) var(--ease-decelerate) both; }
.animate-pulse { animation: pulse 2s var(--ease-standard) infinite; }
.animate-spin { animation: spin 1s linear infinite; }
/* Staggered delays for lists */
.stagger-1 { animation-delay: 50ms; }
.stagger-2 { animation-delay: 100ms; }
.stagger-3 { animation-delay: 150ms; }
.stagger-4 { animation-delay: 200ms; }
.stagger-5 { animation-delay: 250ms; }
.stagger-6 { animation-delay: 300ms; }
/* Respect 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;
}
}
/* ============================================
SKELETON LOADING
============================================ */
.skeleton {
background: linear-gradient(90deg,
var(--surface-container) 25%,
var(--surface-container-high) 50%,
var(--surface-container) 75%
);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
border-radius: var(--radius-md);
color: transparent !important;
}
.skeleton-text {
height: 1em;
margin-bottom: 0.5em;
border-radius: var(--radius-sm);
}
.skeleton-circle {
border-radius: var(--radius-full);
}
/* ============================================
SPINNER
============================================ */
.spinner {
display: inline-block;
width: 20px;
height: 20px;
border: 2px solid var(--border-default);
border-top-color: var(--color-navy);
border-radius: var(--radius-full);
animation: spin 0.6s linear infinite;
}
.spinner-sm { width: 14px; height: 14px; }
.spinner-lg { width: 32px; height: 32px; border-width: 3px; }
/* ============================================
DISABLED STATE
============================================ */
[disabled],
.disabled {
opacity: 0.38;
pointer-events: none;
cursor: not-allowed;
}
/* ============================================
SCROLL-TRIGGERED REVEAL
Elements start hidden, JS adds .revealed
============================================ */
.reveal {
opacity: 0;
transform: translateY(20px);
transition: opacity var(--duration-slow) var(--ease-decelerate),
transform var(--duration-slow) var(--ease-decelerate);
}
.reveal.revealed {
opacity: 1;
transform: translateY(0);
}
.reveal-scale {
opacity: 0;
transform: scale(0.96);
transition: opacity var(--duration-slow) var(--ease-decelerate),
transform var(--duration-slow) var(--ease-decelerate);
}
.reveal-scale.revealed {
opacity: 1;
transform: scale(1);
}
/* ============================================
SPACING UTILITIES
============================================ */
.mt-0 { margin-top: var(--space-0); }
.mt-1 { margin-top: var(--space-1); }
.mt-2 { margin-top: var(--space-2); }
.mt-3 { margin-top: var(--space-3); }
.mt-4 { margin-top: var(--space-4); }
.mt-6 { margin-top: var(--space-6); }
.mt-8 { margin-top: var(--space-8); }
.mt-10 { margin-top: var(--space-10); }
.mb-0 { margin-bottom: var(--space-0); }
.mb-1 { margin-bottom: var(--space-1); }
.mb-2 { margin-bottom: var(--space-2); }
.mb-3 { margin-bottom: var(--space-3); }
.mb-4 { margin-bottom: var(--space-4); }
.mb-6 { margin-bottom: var(--space-6); }
.mb-8 { margin-bottom: var(--space-8); }
.mb-10 { margin-bottom: var(--space-10); }
.p-0 { padding: var(--space-0); }
.p-2 { padding: var(--space-2); }
.p-4 { padding: var(--space-4); }
.p-6 { padding: var(--space-6); }
.p-8 { padding: var(--space-8); }
.gap-2 { gap: var(--space-2); }
.gap-3 { gap: var(--space-3); }
.gap-4 { gap: var(--space-4); }
.gap-6 { gap: var(--space-6); }
.gap-8 { gap: var(--space-8); }
/* ============================================
RESPONSIVE CONTAINER
============================================ */
.container {
width: 100%;
max-width: var(--content-width);
margin-left: auto;
margin-right: auto;
padding-left: var(--space-6);
padding-right: var(--space-6);
}
.container-narrow {
max-width: var(--content-narrow);
}
.container-wide {
max-width: var(--content-wide);
}