7.8 KiB

Design System

Complete global.css structure for the design system. Adapt colors, fonts, and effects to match the client's brand.

File Structure

The CSS file follows this exact order:

  1. Tailwind import
  2. Font-face declarations
  3. Tailwind variants and plugins
  4. @theme block (design tokens)
  5. :root variables (shadcn compatibility, optional)
  6. Global element styles
  7. Custom cursor (optional)
  8. Scrollbar theming
  9. Utility classes
  10. Keyframe animations
  11. Reduced motion resets
  12. Safe viewport utilities
  13. @theme inline block (shadcn, optional)
  14. Dark mode overrides (optional)

Complete Template

@import "tailwindcss";

/* ── Custom Font ── */
@font-face {
  font-family: "{{DISPLAY_FONT_NAME}}";
  src: url("/assets/fonts/{{FONT_FILE}}.woff2") format("woff2"),
       url("/assets/fonts/{{FONT_FILE}}.ttf") format("truetype");
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}

@custom-variant dark (&:is(.dark *));
@plugin "@tailwindcss/typography";

/* ── Design Tokens ── */
@theme {
  /* Brand colors — substitute from client brief */
  --color-primary: {{PRIMARY_COLOR}};        /* e.g. #FF006E */
  --color-primary-light: {{PRIMARY_LIGHT}};  /* lighter variant */
  --color-primary-dark: {{PRIMARY_DARK}};    /* darker variant */
  --color-accent: {{ACCENT_COLOR}};          /* e.g. #7B61FF */
  --color-secondary: {{SECONDARY_COLOR}};    /* e.g. #00F0FF */
  --color-highlight: {{HIGHLIGHT_COLOR}};    /* e.g. #FFE600 */
  --color-dark: #0A0A0A;
  --color-darker: #050505;
  --color-gray: #1A1A1A;

  /* Font stacks */
  --font-display: "{{DISPLAY_FONT}}", {{DISPLAY_FALLBACK}};
  --font-body: "{{BODY_FONT}}", sans-serif;
  --font-mono: "{{MONO_FONT}}", monospace;

  /* Pixel shadows (4px offset = retro pixel aesthetic) */
  --shadow-pixel-primary: 4px 4px 0 0 {{PRIMARY_DARK}};
  --shadow-pixel-primary-lg: 8px 8px 0 0 {{PRIMARY_DARK}};
  --shadow-pixel-accent: 4px 4px 0 0 {{ACCENT_DARK}};

  /* Glow effects */
  --shadow-glow-primary: 0 0 20px {{PRIMARY_COLOR}}, 0 0 40px {{PRIMARY_COLOR}};
  --shadow-glow-accent: 0 0 20px {{ACCENT_COLOR}}, 0 0 40px {{ACCENT_COLOR}};
  --shadow-glow-secondary: 0 0 20px {{SECONDARY_COLOR}}, 0 0 40px {{SECONDARY_COLOR}};
}

/* ── Global Styles ── */
:root {
  --cursor-size: 20px;
  --radius: 0px;  /* sharp pixel aesthetic — set to 0.5rem for rounded */
}

html {
  cursor: none;  /* hide for custom cursor — remove if not using custom cursor */
}

@media (pointer: coarse) {
  html { cursor: auto; }
}

body {
  font-family: var(--font-body);
  background-color: var(--color-dark);
  color: #ffffff;
  margin: 0;
  padding: 0;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-display);
  text-transform: uppercase;  /* remove if display font is not pixel/block style */
}

code, pre {
  font-family: var(--font-mono);
}

/* ── Custom Cursor (optional — remove block if not using) ── */
#custom-cursor {
  position: fixed;
  top: 0;
  left: 0;
  width: var(--cursor-size);
  height: var(--cursor-size);
  border: 2px solid var(--color-primary);
  pointer-events: none;
  z-index: 9999;
  transform: translate(-50%, -50%);
  mix-blend-mode: difference;
  transition: width 0.2s, height 0.2s, background-color 0.2s;
}

#custom-cursor::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 4px;
  height: 4px;
  background-color: var(--color-secondary);
  transform: translate(-50%, -50%);
}

@media (pointer: coarse) {
  #custom-cursor { display: none; }
}

/* ── Scrollbar ── */
::-webkit-scrollbar { width: 10px; }
::-webkit-scrollbar-track { background: var(--color-darker); }
::-webkit-scrollbar-thumb {
  background: var(--color-primary-dark);
  border: 2px solid var(--color-darker);
}
::-webkit-scrollbar-thumb:hover { background: var(--color-primary); }

/* ── Utility Classes ── */
.text-glow-primary { text-shadow: 0 0 10px var(--color-primary); }
.text-glow-primary-hover {
  transition: text-shadow 0.3s ease, color 0.3s ease;
}
.text-glow-primary-hover:hover {
  color: var(--color-primary);
  text-shadow: 0 0 10px var(--color-primary);
}
.text-glow-accent { text-shadow: 0 0 10px var(--color-accent); }
.text-glow-secondary { text-shadow: 0 0 10px var(--color-secondary); }

.box-pixel { box-shadow: 4px 4px 0 0 var(--color-primary-dark); }
.box-pixel-accent { box-shadow: 4px 4px 0 0 {{ACCENT_DARK}}; }
.box-pixel-secondary { box-shadow: 4px 4px 0 0 {{SECONDARY_DARK}}; }

.pixel-art {
  image-rendering: pixelated;
  image-rendering: crisp-edges;
}

.scrollbar-none {
  -ms-overflow-style: none;
  scrollbar-width: none;
}
.scrollbar-none::-webkit-scrollbar { display: none; }

/* ── Atmospheric Overlays ── */
.scanlines { position: relative; }
.scanlines::before {
  content: '';
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(
    0deg,
    transparent,
    transparent 2px,
    rgba(0, 0, 0, 0.15) 2px,
    rgba(0, 0, 0, 0.15) 4px
  );
  pointer-events: none;
  z-index: 10;
}

.crt-screen { position: relative; overflow: hidden; }
.crt-screen::before {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.25) 50%);
  background-size: 100% 4px;
  pointer-events: none;
  z-index: 10;
}
.crt-screen::after {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(ellipse at center, transparent 0%, rgba(0, 0, 0, 0.4) 100%);
  pointer-events: none;
  z-index: 11;
}

/* ── Keyframe Animations ── */
@keyframes vhs-flicker {
  0%, 100% { opacity: 1; }
  92% { opacity: 1; }
  93% { opacity: 0.8; }
  94% { opacity: 1; }
  96% { opacity: 0.9; }
  97% { opacity: 1; }
}
.vhs-flicker { animation: vhs-flicker 4s infinite; }

@keyframes blink {
  0%, 50% { opacity: 1; }
  51%, 100% { opacity: 0; }
}
.animate-blink { animation: blink 1s infinite; }

@keyframes glitch {
  0% { transform: translate(0); }
  20% { transform: translate(-2px, 2px); }
  40% { transform: translate(-2px, -2px); }
  60% { transform: translate(2px, 2px); }
  80% { transform: translate(2px, -2px); }
  100% { transform: translate(0); }
}
.animate-glitch { animation: glitch 0.3s cubic-bezier(.25,.46,.45,.94) both infinite; }

@keyframes marquee-scroll {
  0% { transform: translateX(0); }
  100% { transform: translateX(-50%); }
}
.marquee-track { animation: marquee-scroll 20s linear infinite; }

/* ── Reduced Motion ── */
@media (prefers-reduced-motion: reduce) {
  .marquee-track { animation: none; }
  .vhs-flicker { animation: none; }
  .animate-blink { animation: none; }
  .animate-glitch { animation: none; }
}

/* ── Safe Viewport Height ── */
.h-screen-safe { height: 100vh; height: 100dvh; }
.min-h-screen-safe { min-height: 100vh; min-height: 100dvh; }

Color Token Mapping Guide

Map any brand palette to the token system:

Token Purpose Example
--color-primary Main brand color, CTAs, links, highlights #FF006E
--color-primary-light Hover states, lighter accents Lighten primary 20%
--color-primary-dark Shadows, pressed states, dark accents Darken primary 20%
--color-accent Secondary brand color, headings, tags #7B61FF
--color-secondary Tertiary color, links, code text #00F0FF
--color-highlight Warnings, special callouts #FFE600
--color-dark Page backgrounds #0A0A0A
--color-darker Footer, deeper sections #050505
--color-gray Card backgrounds, borders #1A1A1A

Font Strategy

  • Self-host pixel/display fonts as woff2 + ttf — preload the woff2 in BaseHead
  • Google Fonts for body + mono fonts — preconnect in BaseHead
  • Always use font-display: swap to prevent FOUT blocking
  • Preload pattern in BaseHead: <link rel="preload" href="/assets/fonts/Font.woff2" as="font" type="font/woff2" crossorigin />