Design Tokens

All tokens are CSS custom properties defined in @layer tokens on :root. Reference them with var(--token-name).

Color — Base Palette

--navy #080818
--navy-2 #0e0e24 — philosophy/lets-talk bg
--navy-3 #14143a — case study open state
--navy-card #121230 — card/panel bg
--navy-border rgba(255,255,255,0.08) — default dividers & card borders

Color — Text

--text-primary #f0f0ff
--text-secondary #9090b8

Color — Accents & Gradient

--accent-cyan #00dbde
--accent-pink #fc00ff
--gradient cyan → pink
--accent-glow rgba(252,0,255,0.18)

Color — Alpha / Surface Variants

--accent-cyan-subtle rgba(0,219,222,0.07) — hero bg glow
--accent-pink-subtle rgba(252,0,255,0.07) — hero bg glow
--navy-overlay rgba(8,8,24,0.7) — nav desktop backdrop
--navy-overlay-solid rgba(8,8,24,0.96) — nav mobile (no blur)
--surface-hover-md rgba(255,255,255,0.04) — subtle hover/tag bg
--border-hover rgba(255,255,255,0.2) — btn/input hover border

Color — Shadows

--shadow-sm nav scroll shadow
--shadow-lg card lift shadow

Spacing Scale

--space-xs 0.5rem — 8px
--space-sm 1rem — 16px
--space-sm-plus 1.5rem — 24px — component gaps
--space-md 2rem — 32px
--space-lg 4rem — 64px
--space-xl 8rem — 128px

Border Radius

--radius-sm 6px — buttons, inputs
--radius-md 12px — cards, panels
--radius-lg 20px — case studies wrapper
--radius-pill 100px — tags

Measure

--measure 60ch — max line length for text containers

This paragraph is constrained to max-width: var(--measure). Used on .section__header and .lets-talk__content to cap comfortable reading width.

Typography

Font Families

Exo --font-display "Exo", sans-serif — headings, wordmark
Exo --font-body "Exo" + system fallbacks — body text, UI

Font Size Scale

--font-size-xs 0.6875rem / 11px Tag labels, marker arrows
--font-size-sm 0.75rem / 12px Eyebrows, overlines, small labels, btn--sm
--font-size-ui 0.875rem / 14px UI chrome — toggles, footer copy, links & tagline
--font-size-base 0.9375rem / 15px Primary body text
--font-size-md 1.0625rem / 17px Lead text, section subtitle
--font-size-lg 1.125rem / 18px Component headings
--font-size-xl 1.375rem / 22px Nav wordmark, case study title
--font-size-2xl 1.75rem / 28px Footer name, card icon

Font Weight

--weight-normal 400 The quick brown fox
--weight-medium 500 The quick brown fox
--weight-semibold 600 The quick brown fox

Letter Spacing

--tracking-tighter -0.02em Display name
--tracking-tight -0.01em Section titles, wordmark
--tracking-normal 0.01em Default slight open
--tracking-wide 0.06em Hero title, uppercase tags
--tracking-wider 0.1em Org labels, col titles
--tracking-widest 0.12em Section eyebrow

Typography Utility Classes

Gradient Text
<span class="gradient-text">Gradient Text</span>

Applies the --gradient as a clipped background. Used on the hero name, nav wordmark, footer name, and the ✦ glyph. Transitions to --rainbow-gradient in rainbow mode.

Overline Label
<p class="section__eyebrow">Overline Label</p>

font-weight: --weight-semibold, uppercase. Used for section eyebrows and accordion org labels.

Tag Label
<span class="text--tag">Tag Label</span>

font-weight: --weight-medium, uppercase, --tracking-wide. Used on hero subtitle and case study category tags.

This is body text styled with the text--body utility. It sets font-size to --font-size-base and color to --text-secondary.

<p class="text--body">Body text here.</p>

--font-size-base (15px), --text-secondary. Used on card bodies and case study teasers.

  • First item in the list
  • Second item in the list
  • Third item in the list
<ul class="arrow-list">
  <li>First item</li>
  <li>Second item</li>
</ul>

Arrow-bulleted list. Each <li> gets a in --accent-cyan via ::before. Items are spaced with li + li { margin-top } — no flex wrapper needed. Used in case study columns for goals, outcomes, and learnings.

Category Tag
<span class="badge">Category Tag</span>

Pill-shaped label. --font-size-xs, --text-secondary, --surface-hover-md background, --navy-border border, --radius-pill. Pair with .text--tag for uppercase tracking. Used on accordion items to label content type.

Buttons

All buttons share the .btn base class. Variants and size modifiers are additive. Hover states lift and brighten; :focus-visible shows a cyan outline ring.

Default Size

Primary Ghost
<a href="#" class="btn btn--primary">Primary</a>
<a href="#" class="btn btn--ghost">Ghost</a>
<button class="btn btn--sm btn--rainbow" aria-pressed="false">
  <span class="btn__icon" aria-hidden="true">🌈</span> Rainbow Time
</button>

Small — .btn--sm

Primary Ghost
<a href="#" class="btn btn--sm btn--primary">Primary</a>
<a href="#" class="btn btn--sm btn--ghost">Ghost</a>
<button class="btn btn--sm btn--rainbow" aria-pressed="false">
  <span class="btn__icon" aria-hidden="true">🌈 Rainbow Time
</button>

Rainbow — pressed state

<button class="btn btn--sm btn--rainbow" aria-pressed="true">
  <span class="btn__icon" aria-hidden="true">🌈</span> Rainbow Time
</button>

Hover: btn--primary lifts (translateY -2px) + glow shadow, border brightens. btn--ghost border brightens, text becomes primary. btn--rainbow border brightens, subtle background appears, icon rotates 20° and scales. Note: btn--rainbow uses aria-pressed to reflect toggle state — JS sets this on interaction.

Layout

Container

The global page column. Centers content and applies consistent horizontal padding.

max-width1100px padding-inlinevar(--space-md) — 32px margin-inlineauto — horizontally centered
<div class="container">
  <!-- page content -->
</div>

Section

Shared wrapper for every page section. Adds vertical rhythm and a top border separator between adjacent sections.

padding-blockvar(--space-xl) — 128px (var(--space-lg) on mobile) .section + .sectionborder-top: 1px solid var(--navy-border)
<section class="section" id="my-section" aria-labelledby="my-heading">
  <div class="container">
    <header class="section__header">…</header>
    <!-- section content -->
  </div>
</section>

Section Header

The standard header block used at the top of every content section. Constrained to --measure (60ch). Children use margin-bottom for spacing — no flex/gap.

Eyebrow Label

Section Title

A subtitle or brief description that sits below the title and gives a little more context about what follows.

<header class="section__header">
  <p class="section__eyebrow">Eyebrow Label</p>
  <h2 class="section__title" id="section-heading">Section Title</h2>
  <p class="section__subtitle">Subtitle text here.</p>
</header>

Spacing: eyebrow margin-bottom: 0.75rem, title margin-bottom: 1rem, subtitle margin-bottom: var(--space-sm-plus). Header itself: margin-bottom: var(--space-lg).

Components

Card

Used in the Philosophy section. A 4-column grid on desktop, 2-col on tablet, 1-col on mobile. Cards lift on hover with a pink glow.

Systems Over Heroics

Sustainable delivery comes from strong planning rituals, not individual heroics. Build the system, not the hero.

Safety With Accountability

Psychological safety isn't softness — it's the foundation that lets people do their best work without fear.

<article class="card">
  <div class="card__icon" aria-hidden="true">⚙️</div>
  <h3 class="card__title">Title</h3>
  <p class="card__body text--body">Body text.</p>
</article>

In rainbow mode, card borders become a full-spectrum gradient via background-clip: border-box.

Accordion

An accessible accordion. Each item is a <button> trigger with aria-expanded and aria-controls. The body uses the hidden attribute; JS toggles is-open on the wrapper and removes hidden. The title gets a gradient on hover/open. The chevron rotates on open.

Context

Background prose about the problem space and situation.

Goals

  • Deliver the feature on schedule
  • Improve team collaboration rituals
  • Increase engagement metrics

What I Learned

Reflective prose about key takeaways from the engagement.

<div class="accordion">
  <article class="accordion__item" id="cs-id">
    <button class="accordion__trigger" aria-expanded="false" aria-controls="cs-id-body">
      <div class="accordion__meta">
        <span class="section__eyebrow">Org</span>
        <span class="badge">Category</span>
      </div>
      <h3 class="accordion__title">Title</h3>
      <p class="accordion__teaser text--body">Teaser.</p>
      <span class="accordion__chevron" aria-hidden="true"></span>
    </button>
    <div class="accordion__body" id="cs-id-body" hidden>
      <div class="accordion__grid">
        <div class="accordion__col">
          <h4 class="text--overline">Context</h4>
          <p>Prose.</p>
        </div>
        <div class="accordion__col">
          <h4 class="text--overline">Goals</h4>
          <ul class="arrow-list">
            <li>Goal one</li>
          </ul>
        </div>
      </div>
    </div>
  </article>
</div>

The gap: 1px between case study items is achieved by the flex container exposing its background through a 1px gap — not borders. JS toggles .is-open on the article and flips aria-expanded. The expandIn animation plays when the body is revealed.

Nav

Fixed to the top. Glassmorphism backdrop blur on desktop (removed on mobile for performance). The wordmark fades in via JS once the hero name scrolls out of view. The rainbow toggle lives here.

<nav class="nav" aria-label="Main navigation">
  <a href="/" class="nav__wordmark">
    <span class="gradient-text">Aly Fluckey</span>
  </a>
  <div>
    <button class="btn btn--sm btn--rainbow" id="rainbowToggle"
            aria-pressed="false" aria-label="Toggle Rainbow Time mode">
      <span class="btn__icon" aria-hidden="true">🌈</span>
      <span>Rainbow Time</span>
    </button>
  </div>
</nav>

.nav.scrolled adds a border and shadow — applied by JS on scroll. .nav.name-visible fades the wordmark in — applied by JS via IntersectionObserver on the hero name.

2-column grid on desktop (brand left, links right), stacked on mobile. The bottom bar spans full width and holds copyright copy and a second rainbow toggle.

<footer class="footer">
  <div class="container footer__inner">
    <div>
      <span class="footer__name gradient-text">Name</span>
      <p class="footer__tagline">Tagline.</p>
    </div>
    <nav class="footer__links" aria-label="...">
      <a href="#" class="footer__link">Link</a>
    </nav>
    <div class="footer__bottom">
      <span class="footer__copy">Copy text.</span>
      <button class="btn btn--sm btn--rainbow" aria-pressed="false">
        <span class="btn__icon" aria-hidden="true">🌈</span> Rainbow Time
      </button>
    </div>
  </div>
</footer>

Animations & Motion

All animations respect prefers-reduced-motion: reduce — durations are collapsed to 0.01ms and iteration counts to 1.

Motion Tokens

--ease-out cubic-bezier(0.22, 1, 0.36, 1) — snappy deceleration
--duration 0.3s — default: buttons, borders, colours
--duration-medium 0.4s — nav wordmark, toggle icon
--duration-slow 0.5s — body bg, gradient-text transitions

Keyframe Animations

glyphSpin 18s linear infinite Rotates the ✦ glyph in the Let's Talk section. Paused by default, runs only when the element is visible in the viewport (IntersectionObserver sets .playing class).
expandIn var(--duration-medium) var(--ease-out) Fades in and lifts up (translateY -8px → 0) the case study body panel when an accordion item is opened.

Transition Patterns

btn--primary hover translateY(-2px) + box-shadow glow. Gradient border ::before opacity 0.7 → 1.
btn--ghost hover translateY(-2px), border brightens to --border-hover, text becomes --text-primary.
btn--rainbow hover / pressed Border brightens, subtle background appears, .btn__icon rotates 20° and scales 1.15×.
card hover translateY(-2px) + --shadow-lg lift shadow.
nav wordmark opacity 0 → 1 + translateY(-6px → 0) over --duration-medium. Triggered by IntersectionObserver when hero name leaves viewport.
gradient-text background-image transitions over --duration-slow when rainbow mode is toggled.
body background background-color transitions over --duration-slow when rainbow mode is toggled.

Rainbow Mode

Activated by toggling the Rainbow Time button. Applies body.rainbow-mode and overrides key color tokens + specific component styles. Preference is persisted to localStorage under the key af-rainbow-mode.

Overridden Tokens

--navy #05051a (was #080818)
--navy-2 #0a0520 (was #0e0e24)
--navy-3 #0f0a2e (was #14143a)
--navy-card #090520 (was #121230)
--grad-start #ff0080 (was #00dbde)
--grad-end #7928ca (was #fc00ff)
--accent-cyan #0affef (was #00dbde)
--accent-pink #ff6ec7 (was #fc00ff)
--accent-glow rgba(121,40,202,0.25) — purple glow
--accent-cyan-subtle rgba(10,255,239,0.07) — hero bg glow
--accent-pink-subtle rgba(255,110,199,0.07) — hero bg glow
--navy-overlay rgba(5,5,26,0.7) — nav desktop backdrop
--navy-overlay-solid rgba(5,5,26,0.96) — nav mobile
--rainbow-gradient hot pink → orange → yellow → green → sky → purple

Affected Components

.hero__name .gradient-text background-image → --rainbow-gradient
.lets-talk__glyph background-image → --rainbow-gradient
.footer__name Clipped gradient text using --rainbow-gradient
.card Border replaced with full-spectrum gradient via background-clip: border-box trick
.btn--primary::before Gradient border swaps from --gradient to --rainbow-gradient

The two rainbow toggle buttons (#rainbowToggle and #rainbowToggleFooter) both have their aria-pressed attribute managed by JS in index.js → initRainbow().