Skip to content
DelTech MUNDocs

Design system

The token set, the custom dark variant, and the editorial utilities.

Everything is in one file: src/app/globals.css. There is no tailwind.config.ts and no src/styles/tokens.ts. Tailwind v4 is configured in CSS.

The custom dark variant

@custom-variant dark (&:where(.dark, .dark *):not(:where(.theme-light, .theme-light *)));

Two things follow from it. .dark matches the carrying element and its descendants, and an inner .theme-light opts back out. That is what lets the admin console render light while a marketing page renders dark, in the same document. :where() keeps specificity at zero so these never fight your own selectors.

Token layers

:root, .theme-light defines the light set, .dark overrides it. Between them:

  • A teal ramp, --teal-50 to --teal-900, the brand anchor;
  • A gold ramp, --gold-300, --gold-500, --gold-700;
  • Diplomatic hues: --signal, --signal-soft, --ink-soft;
  • Semantic pairs: background, foreground, card, popover, primary, secondary, muted, accent, destructive, border, input, ring;
  • Charts: --chart-1 to --chart-5;
  • Sidebar: its own eight-token set.

Light is warm paper. Dark is deliberately neutral near-black with zero chroma, not a tinted dark.

Tokens not redefined in dark

The teal and gold ramps, --radius, and --ink / --paper. That is intentional in each case; do not "fix" it.

The ink band rule

--ink and --paper are a fixed always-dark-surface, always-light-text pair, used for footers, hero bands and section headers. They are zero chroma and identical in both themes.

scripts/check-ink-band-tokens.ts fails the build if bg-foreground and text-background appear on the same line anywhere under src/, because that pair inverts between themes and produces an unreadable band in one of them. Use bg-ink text-paper.

Radius

One base, six derived:

--radius: 0.375rem
sm 0.6x   md 0.8x   lg 1x   xl 1.4x   2xl 1.8x   3xl 2.2x   4xl 2.6x

Change the base and the whole scale moves together.

Fonts

Loaded with next/font/google in src/app/layout.tsx:

FamilyVariableUsed for
Geist--font-sansBody
Geist Mono--font-geist-monoCode, identifiers, aligned numbers
Spectral (400, 600)--font-displayOne editorial headline per public page; article bodies

Every h1 to h4 is Geist at weight 600. A public page may set one headline in Spectral with .headline: the event or society name on the home page, "Meet the team", "The Dispatch" and an article title. Forms, navigation, cards, section headings and every task page (sign-in, quiz join, registration, payment) stay sans.

Editorial utilities

ClassFor
.headlineThe single Spectral headline on a public page
.eyebrowSmall sentence-case label above a block. Not uppercase.
.data-label, .section-labelSentence-case labels
.rule, .rule-goldHairline dividers
.editorial-card1px ink border, card background. No drop shadow.
.section-shellResponsive page width
.paper-gridFaint grid, used on the live-quiz screens only

Add to globals.css only when a pattern is used across routes. Otherwise keep styling beside the component.

Operations surfaces differ deliberately

Inside .admin-shell and .recruitment-shell, .editorial-card loses its radius and shadow, labels grow slightly, and controls are forced to 16px. Dense operational screens are not supposed to look like the marketing site.

Motion

Keyframes for orbit, signal pulse and the quiz animations, all disabled under prefers-reduced-motion, except the quiz drain bar, which is kept because it carries information rather than decoration.

Framer Motion helpers live in src/app/(marketing)/_components/motion.tsx: FadeUp, StaggerList, StaggerItem, all reduced-motion aware.

Test site · not live