This page is the single source of truth for site components, tokens, and signed-off patterns. Tapico (/tapico) is the reference case study; Home, Work, and Play define the vetted portfolio grid. Other project routes are legacy Webflow migrations — do not copy their markup when building or porting content.

Color tokens

ref · palette

--ref-palette-grey-0

#000000

--ref-palette-grey-100

#1f1f1f

--ref-palette-grey-150

#353535

--ref-palette-grey-200

#414141

--ref-palette-grey-300

#5a5a5a

--ref-palette-grey-400

#6d6d6d

--ref-palette-grey-500

#888888

--ref-palette-grey-600

#aeaeaf

--ref-palette-grey-700

#d3d3d6

--ref-palette-grey-800

#e1e1e5

--ref-palette-grey-900

#ededf0

--ref-palette-grey-950

#f5f5f7

--ref-palette-grey-1000

#ffffff

--ref-palette-red-50

#460809

--ref-palette-red-100

#82181a

--ref-palette-red-200

#9f0712

--ref-palette-red-300

#c10007

--ref-palette-red-400

#e7000b

--ref-palette-red-500

#fb2c36

--ref-palette-red-600

#ff6467

--ref-palette-red-700

#ffa2a2

--ref-palette-red-800

#ffc9c9

--ref-palette-red-900

#ffe2e2

--ref-palette-red-950

#fef2f2

--ref-palette-warm-800

#e0ddd8

--ref-palette-brand-traffic-hide

#ff5f57

--ref-palette-brand-traffic-minimize

#febc2e

--ref-palette-orange-50

#441306

--ref-palette-orange-100

#7e2a0c

--ref-palette-orange-200

#9f2d00

--ref-palette-orange-300

#ca3500

--ref-palette-orange-400

#f54900

--ref-palette-orange-500

#ff6900

--ref-palette-orange-600

#ff8904

--ref-palette-orange-700

#ffb86a

--ref-palette-orange-800

#ffd6a7

--ref-palette-orange-900

#ffedd4

--ref-palette-orange-950

#fff7ed

--ref-palette-amber-50

#461901

--ref-palette-amber-100

#7b3306

--ref-palette-amber-200

#973c00

--ref-palette-amber-300

#bb4d00

--ref-palette-amber-400

#e17100

--ref-palette-amber-500

#fe9a00

--ref-palette-amber-600

#ffb900

--ref-palette-amber-700

#ffd230

--ref-palette-amber-800

#fee685

--ref-palette-amber-900

#fef3c6

--ref-palette-amber-950

#fffbeb

--ref-palette-yellow-50

#432004

--ref-palette-yellow-100

#733e0a

--ref-palette-yellow-200

#894b00

--ref-palette-yellow-300

#a65f00

--ref-palette-yellow-400

#d08700

--ref-palette-yellow-500

#f0b100

--ref-palette-yellow-600

#fdc700

--ref-palette-yellow-700

#ffdf20

--ref-palette-yellow-800

#fff085

--ref-palette-yellow-900

#fef9c2

--ref-palette-yellow-950

#fefce8

--ref-palette-lime-50

#192e03

--ref-palette-lime-100

#35530e

--ref-palette-lime-200

#3c6300

--ref-palette-lime-300

#497d00

--ref-palette-lime-400

#5ea500

--ref-palette-lime-500

#7ccf00

--ref-palette-lime-600

#9ae600

--ref-palette-lime-700

#bbf451

--ref-palette-lime-800

#d8f999

--ref-palette-lime-900

#ecfcca

--ref-palette-lime-950

#f7fee7

--ref-palette-green-50

#032e15

--ref-palette-green-100

#0d542b

--ref-palette-green-200

#016630

--ref-palette-green-300

#008236

--ref-palette-green-400

#00a63e

--ref-palette-green-500

#00c950

--ref-palette-green-600

#05df72

--ref-palette-green-700

#7bf1a8

--ref-palette-green-800

#b9f8cf

--ref-palette-green-900

#dcfce7

--ref-palette-green-950

#f0fdf4

--ref-palette-emerald-50

#002c22

--ref-palette-emerald-100

#004f3b

--ref-palette-emerald-200

#006045

--ref-palette-emerald-300

#007a55

--ref-palette-emerald-400

#009966

--ref-palette-emerald-500

#00bc7d

--ref-palette-emerald-600

#00d492

--ref-palette-emerald-700

#5ee9b5

--ref-palette-emerald-800

#a4f4cf

--ref-palette-emerald-900

#d0fae5

--ref-palette-emerald-950

#ecfdf5

--ref-palette-teal-50

#022f2e

--ref-palette-teal-100

#0b4f4a

--ref-palette-teal-200

#005f5a

--ref-palette-teal-300

#00786f

--ref-palette-teal-400

#009689

--ref-palette-teal-500

#00bba7

--ref-palette-teal-600

#00d5be

--ref-palette-teal-700

#46ecd5

--ref-palette-teal-800

#96f7e4

--ref-palette-teal-900

#cbfbf1

--ref-palette-teal-950

#f0fdfa

--ref-palette-cyan-50

#053345

--ref-palette-cyan-100

#104e64

--ref-palette-cyan-200

#005f78

--ref-palette-cyan-300

#007595

--ref-palette-cyan-400

#0092b8

--ref-palette-cyan-500

#00b8db

--ref-palette-cyan-600

#00d3f2

--ref-palette-cyan-700

#53eafd

--ref-palette-cyan-800

#a2f4fd

--ref-palette-cyan-900

#cefafe

--ref-palette-cyan-950

#ecfeff

--ref-palette-sky-50

#052f4a

--ref-palette-sky-100

#024a70

--ref-palette-sky-200

#00598a

--ref-palette-sky-300

#0069a8

--ref-palette-sky-400

#0084d1

--ref-palette-sky-500

#00a6f4

--ref-palette-sky-600

#00bcff

--ref-palette-sky-700

#74d4ff

--ref-palette-sky-800

#b8e6fe

--ref-palette-sky-900

#dff2fe

--ref-palette-sky-950

#f0f9ff

--ref-palette-blue-50

#162456

--ref-palette-blue-100

#1c398e

--ref-palette-blue-200

#193cb8

--ref-palette-blue-300

#1447e6

--ref-palette-blue-400

#155dfc

--ref-palette-blue-500

#2b7fff

--ref-palette-blue-600

#51a2ff

--ref-palette-blue-700

#8ec5ff

--ref-palette-blue-800

#bedbff

--ref-palette-blue-900

#dbeafe

--ref-palette-blue-950

#eff6ff

--ref-palette-indigo-50

#1e1a4d

--ref-palette-indigo-100

#312c85

--ref-palette-indigo-200

#372aac

--ref-palette-indigo-300

#432dd7

--ref-palette-indigo-400

#4f39f6

--ref-palette-indigo-500

#615fff

--ref-palette-indigo-600

#7c86ff

--ref-palette-indigo-700

#a3b3ff

--ref-palette-indigo-800

#c6d2ff

--ref-palette-indigo-900

#e0e7ff

--ref-palette-indigo-950

#eef2ff

--ref-palette-violet-50

#2f0d68

--ref-palette-violet-100

#4d179a

--ref-palette-violet-200

#5d0ec0

--ref-palette-violet-300

#7008e7

--ref-palette-violet-400

#7f22fe

--ref-palette-violet-500

#8e51ff

--ref-palette-violet-600

#a684ff

--ref-palette-violet-700

#c4b4ff

--ref-palette-violet-800

#ddd6ff

--ref-palette-violet-900

#ede9fe

--ref-palette-violet-950

#f5f3ff

--ref-palette-purple-50

#3c0366

--ref-palette-purple-100

#59168b

--ref-palette-purple-200

#6e11b0

--ref-palette-purple-300

#8200db

--ref-palette-purple-400

#9810fa

--ref-palette-purple-500

#ad46ff

--ref-palette-purple-600

#c27aff

--ref-palette-purple-700

#dab2ff

--ref-palette-purple-800

#e9d4ff

--ref-palette-purple-900

#f3e8ff

--ref-palette-purple-950

#faf5ff

--ref-palette-fuchsia-50

#4b004f

--ref-palette-fuchsia-100

#721378

--ref-palette-fuchsia-200

#8a0194

--ref-palette-fuchsia-300

#a800b7

--ref-palette-fuchsia-400

#c800de

--ref-palette-fuchsia-500

#e12afb

--ref-palette-fuchsia-600

#ed6aff

--ref-palette-fuchsia-700

#f4a8ff

--ref-palette-fuchsia-800

#f6cfff

--ref-palette-fuchsia-900

#fae8ff

--ref-palette-fuchsia-950

#fdf4ff

--ref-palette-pink-50

#510424

--ref-palette-pink-100

#861043

--ref-palette-pink-200

#a3004c

--ref-palette-pink-300

#c6005c

--ref-palette-pink-400

#e60076

--ref-palette-pink-500

#f6339a

--ref-palette-pink-600

#fb64b6

--ref-palette-pink-700

#fda5d5

--ref-palette-pink-800

#fccee8

--ref-palette-pink-900

#fce7f3

--ref-palette-pink-950

#fdf2f8

--ref-palette-rose-50

#4d0218

--ref-palette-rose-100

#8b0836

--ref-palette-rose-200

#a50036

--ref-palette-rose-300

#c70036

--ref-palette-rose-400

#ec003f

--ref-palette-rose-500

#ff2056

--ref-palette-rose-600

#ff637e

--ref-palette-rose-700

#ffa1ad

--ref-palette-rose-800

#ffccd3

--ref-palette-rose-900

#ffe4e6

--ref-palette-rose-950

#fff1f2

sys · roles

--sys-color-srf

var(--ref-palette-grey-1000)

The page itself

--sys-color-srf-container-low

var(--ref-palette-grey-950)

Subtly raised — panels, soft button fills

--sys-color-srf-container

var(--ref-palette-grey-900)

Filled control that reads as a shape with no border

--sys-color-srf-container-high

var(--ref-palette-grey-800)

Strongest normal surface — pressed states

--sys-color-inverse-srf

var(--ref-palette-grey-0)

Deliberately opposes the page — solid buttons

--sys-color-on-srf

var(--ref-palette-grey-100)

Default ink

--sys-color-on-srf-variant

var(--ref-palette-grey-300)

Secondary ink — menu links, subtle emphasis

--sys-color-on-srf-variant

var(--ref-palette-grey-300)

Muted ink — captions, field labels

--sys-color-inverse-on-srf

var(--ref-palette-grey-1000)

Ink sitting on inverse-srf

--sys-color-outline

var(--ref-palette-grey-700)

Visible edge — button rings, dividers

--sys-color-outline-variant

var(--ref-palette-warm-800)

Quieter hairline — separators

--sys-color-error

var(--ref-palette-red-500)

Danger accent

--sys-color-error-container

var(--ref-palette-red-900)

Danger surface

--sys-color-on-error-container

var(--ref-palette-red-200)

Ink on a danger surface

--sys-color-link

var(--sys-color-on-srf)

Default link

--sys-color-link-soft

var(--sys-color-on-srf-variant)

Softer link

--sys-color-link-soft-hover

var(--sys-color-on-srf)

Hover for softer links

Typography

Display / h1

clamp(1.95rem, 2.5vw, 2.85rem)

Page-level title and hero intro

Industrial design, made tactile.

Section heading / h2

clamp(1.35rem, 1.8vw, 2rem)

Top-level section breaks

Process and outputs

Subheading / h3

1.1rem

Nested sections and card titles

Materials and ergonomics

Body copy

1rem / 1.55

Long-form description text

Used for narrative sections. Keep width around 52-62ch so dense process writing remains easy to scan.

Caption

1.2em / 1.1 · weight 600 · tracking -0.05ch

White image-overlay style (Styles → Caption). Dev Mode caption tuner also offers Black (overlay) and Below image (body-sized text under the media, always visible; match-height grids leave matching empty space under sibling cells).

Caption style preview Foam model before final chamfer direction

Spacing

--space-unit

Core unit: gutter, header/footer padding, grid gaps, related stack step — 1rem, 0.75rem below 40em

Horizontal

Vertical

--space-related

Continuous / related blocks (= --space-unit)

Horizontal

Vertical

--space-group

Same chapter, new beat (e.g. press → community)

Horizontal

Vertical

--space-section

Gap between imagery and narrative text

Horizontal

Vertical

--space-major

Major section / chapter break

Horizontal

Vertical

Layout rules

Project stack rhythm margin-top on siblings via --space-*

One gap between sections (related / group / section / major). Blocks do not pad top and bottom.

Fluid site gutter max(var(--space-unit), calc((100% - 60em) / 2))

Keeps content comfortable at every size while preserving edge breathing room.

Core unit --space-unit — 1rem, 0.75rem below 40em

Build gutter, chrome padding and gaps on the unit before introducing custom values. Phones step it down together so nothing drifts out of sync.

Page transition 0.5s overlay fade (after visible media)

Shared entry/exit timing from `Layout.astro`; enter waits for viewport images.

Component library

21 signed-off components and patterns across site chrome, portfolio listings, and project pages (vetted on Home/Work/Play + Tapico). Each is built for reuse on future case studies. Cards show a live preview, source file, props, and where it is used today.

Site chrome

Header

Global nav, wordmark swap on inner pages, menu overlay.

Source

src/components/Header.astro

Props

title?, navMenu? (default true)

Class reference

.site-header / .site-header--nav-menu

Used on

All pages

QA checklist

Wordmark kerning pairs, menu open/close, active route state.

Rendered at the top of this page.

SectionSidebarNav

Scroll-spy section nav for long single-column pages.

Source

src/components/SectionSidebarNav.astro

Props

sectionLinks, ariaLabel?, enableEasingDevPanel?

Class reference

.section-sidebar-nav

Used on

/styles, /about, /tapico

QA checklist

Click scroll offset, active section highlight, mobile hide.

Fixed nav on the left of this page (desktop).

ProjectGrid

Project/play listing grid on Home, Work, and Play — cover cards with hover title reveal and optional cover→hover image fade.

Source

src/components/ProjectGrid.astro

Props

items[] (title, image, hoverImage?, bg, dark, subtitle?, href?, full?), variant? (home | work | play)

Class reference

.project-grid / .project-grid__card / .project-grid__cover

Used on

Home, Work, Play

QA checklist

2-col desktop; featured full-width on home/work; titles always visible ≤768px; optional hoverImage fades in on desktop hover (no zoom); play placeholders when href omitted. Not legacy case-study media grids.

Project shell

ProjectLayout

Case-study page shell: Layout + Header + main slot + Footer + project.css.

Source

src/components/ProjectLayout.astro

Props

title, description, outline?, sectionLinks?, sectionNavDocument?

Class reference

.project-page / .project-page__main

Used on

Tapico

QA checklist

Title prop matches header wordmark; description feeds meta. Other case studies are legacy Webflow migrations — rebuild with signed-off components only.

Wraps project pages — see any case study (e.g. Tapico).

Project content

ProjectBodyText

Narrative body copy between media blocks.

Source

src/components/project/ProjectBodyText.astro

Props

paragraphs[], documentKey?, pathPrefix?, spacing?, sectionId?

Class reference

.project-body-text / .project-block

Used on

Tapico

QA checklist

Comfortable line length; stack gap from spacing prop; inline HTML allowed in paragraphs.

Designed for narrative flow between media modules. Keep this block comfortable and readable, then let images carry detail.

ProjectSectionTitle

In-page section break heading (Research, Process, etc.).

Source

src/components/project/ProjectSectionTitle.astro

Props

title, spacing?, tightBottom? (default true → related after), level? (2 | 3), showTitle?, hideWhenSidebarVisible?

Class reference

.project-section-title

Used on

Tapico

QA checklist

Default precede-related so body after a heading sits at --space-unit; per-breakpoint show/hide when sidebar is active.

ProjectPressQuoteTicker

Horizontally scrolling press quotes with outlet logos.

Source

src/components/project/ProjectPressQuoteTicker.astro

Props

items[] (quote, logoSrc, logoAlt, href, name?), documentKey?, sectionPath?, spacing?, speed?, logoOpacity?

Class reference

.press-quote-ticker

Used on

Tapico

QA checklist

Seamless loop; logo opacity; pause on hover/focus; links open in new tab.

"It’s a single-function device that does only one thing but does it to perfection."

"Tapico Typer is a minimalist writer's dream machine"

Project media

ProjectImage

Single project image — lead/crop (variant) or inline media between prose, with optional captions (overlay or below-image).

Source

src/components/project/ProjectImage.astro

Props

src, alt?, kind? (lead | inline), variants?, caption?, captions?, captionReveal?, captionStyle?, captionPosition?, constrained?, maxVh?, spacing?

Class reference

.project-image / .project-media

Used on

Tapico

QA checklist

Lead crop focal point; overlay caption reveal defaults (hover desktop, always touch); Below image style is always visible under media; Dev Mode crop + caption tuners.

ProjectImage lead preview

ProjectImageGrid

Responsive multi-image grid with per-cell crop and captions.

Source

src/components/project/ProjectImageGrid.astro

Props

items[], columns?, defaultAspectRatio?, matchHeight?, constrained?, layouts?, document?, basePath?, spacing?

Class reference

.project-image-grid / .project-image-grid__item

Used on

Tapico

QA checklist

Column count per breakpoint; captions match ProjectImage (overlay or below); match-height equalizes below-slot whitespace across the row; grid tuner in Dev Mode.

Three-quarter view
Screen close-up

ProjectVideo

Self-hosted video player (media-chrome) with a centred play affordance and a minimal seek bar — no embed, no skin.

Source

src/components/project/ProjectVideo.astro

Props

src, poster?, alt?, autoplay? (default false), aspectRatio? (default "16:9"), spacing?

Class reference

.project-video-player / .project-video-player__play / .project-video-player__progress

Used on

QA checklist

Centre play button fades on play / reappears on hover; seek bar floats clear of all three edges and grows on hover/focus; reduced-motion disables autoplay and all transitions; missing src renders an empty black block only.

JSON shape: type: "video" with src, poster?, alt?, autoplay?, aspectRatio?, spacing?. Video files live in public/videos/sample.mp4 isn't in the repo yet, so this preview shows the player chrome over a black frame (no video to decode) until one is added.

UI primitives

Button

Presentational button primitive mirroring the Figma Button (Emphasis × State).

Source

src/components/ui/Button.astro

Props

emphasis? (ghost | soft | filled | page | danger, default soft), label, type?, disabled?, ...rest

Class reference

.ui-button / .ui-button--{ghost,soft,filled,page,danger}

Used on

Not yet adopted — Stage 1 primitive

QA checklist

All five emphasis variants render in both themes; hover/pressed states use comp-button tokens; focus-visible ring; disabled state.

SegmentedControl

Presentational shell mirroring the Figma Segmented Control — composed examples below (Viewport Selector, Axis Selector) show a selected segment; selection state and wiring stay with the caller.

Source

src/components/ui/SegmentedControl.astro

Props

label, ...rest (children passed via default slot)

Class reference

.ui-segmented-control

Used on

Not yet adopted — Stage 1 primitive

QA checklist

Concentric border radius with slotted buttons; role="group" with aria-label; Page emphasis Button with selected renders the selected segment fill; renders correctly in both themes.

Axis

DevPanel

Outer chrome shell (header + body) mirroring the Figma DevPanel Base — used to build dev-mode panels.

Source

src/components/ui/DevPanel.astro

Props

title, ...rest (body content via default slot)

Class reference

.ui-dev-panel / .ui-dev-panel__header / .ui-dev-panel__body

Used on

Not yet adopted — Stage 1 primitive

QA checklist

Fixed 352px width matches assembled Figma panels; header/body divider; renders correctly in both themes.

Panel title

Body content

Field

Shared label-over-value shell mirroring the Figma Field Base — every field-like control composes on top of it.

Source

src/components/ui/Field.astro

Props

label, ...rest (value content via default slot)

Class reference

.ui-field / .ui-field__value

Used on

Not yet adopted — Stage 1 primitive

QA checklist

Label uses on-srf-variant, value box uses field/container and on-srf; renders correctly in both themes.

Label
Value

TextField

Text input composed on Field — mirrors the Figma Text Field.

Source

src/components/ui/TextField.astro

Props

label, value?, placeholder?, disabled?, ...rest

Class reference

.ui-field / .ui-text-field__input

Used on

Not yet adopted — Stage 1 primitive

QA checklist

Input is chromeless (Field supplies background/radius); disabled state dims; renders correctly in both themes.

Label

TextArea

Multi-line text input composed on Field — auto-resizes to typed content, with a native vertical drag handle.

Source

src/components/ui/TextArea.astro

Props

label, value?, placeholder?, disabled?, rows? (default 3), ...rest

Class reference

.ui-field / .ui-text-area__textarea

Used on

Not yet adopted — Stage 1 primitive

QA checklist

Chromeless textarea (Field supplies background/radius); field-sizing: content grows with typed text; native vertical resize handle; disabled state dims; renders correctly in both themes.

Notes

SelectField

Select input composed on Field — uses the system’s native select picker.

Source

src/components/ui/SelectField.astro

Props

label, options ({ value, label }[]), value?, disabled?, ...rest

Class reference

.ui-field / .ui-select-field__select

Used on

Not yet adopted — Stage 1 primitive

QA checklist

Chromeless select (Field supplies background/radius); native OS dropdown chrome left as-is; disabled state dims; renders correctly in both themes.

Label

ToggleRow

Labelled checkbox row mirroring the Figma Toggle Row.

Source

src/components/ui/ToggleRow.astro

Props

label, checked?, ...rest

Class reference

.ui-toggle-row / .ui-toggle-row__checkbox

Used on

Not yet adopted — Stage 1 primitive

QA checklist

Unchecked stroke uses outline-variant; checked fill uses button/filled/container; renders correctly in both themes.

PanelActions

Layout wrapper for a trailing row of Button instances, mirroring the Figma Panel Actions composition.

Source

src/components/ui/PanelActions.astro

Props

...rest (Button instances via default slot)

Class reference

.ui-panel-actions

Used on

Not yet adopted — Stage 1 primitive

QA checklist

Right-aligned row with top divider; renders correctly in both themes.

Legacy patterns

Raw markup still present on older Webflow-migration case studies. These are not signed off — do not copy into new pages or when porting content to Tapico-style layouts.

Case-study media grid .project-grid--2 / .project-grid--3 / .project-grid--5

Used on: The Rams, Conic Collection. Legacy case-study image clusters via legacy/LegacyProjectGrid.astro — not the home/work/play portfolio grid.

Stacked grid cell .project-cell--stack

Used on: The Rams, Conic Collection. Tall two-image stack inside a 5-column grid. Legacy only — not used on Tapico.

Responsive hide / show pair .project-hide-mobile + .project-show-mobile-only

Used on: Conic Collection. Desktop grid cell hidden on mobile, alternate full image shown instead. Legacy only.

Captioned image (legacy) .project-captioned-image

Used on: Conic Collection. Superseded by ProjectImage with caption props. See legacy/LegacyProjectCaptionedImage.astro.

Interaction sample

Hover tone should stay subtle. This link uses the same transition behavior as inline project links: read case-study details.