withSeismic — brand booklet — v1

The system, not the screenshot.

Base14px body, everything em
TypePP Neue Montreal / mono
Grid12 → 6, 1.25em gutter
AccentOne red, used sparingly
01

Foundations

The whole system hangs off one number. body is 14px, and every size, gutter, radius and rhythm below is stated in em — so a single font-size change rescales the entire page, and a component dropped inside a smaller context shrinks with it instead of breaking out.

The em contractwhy nothing here is in px

Sizes are relative, not absolute. A tier row set in 1em is 14px on the page and 10px inside the nav rail, because the rail re-declares font-size: 0.520833vw on itself. Nothing needed a second stylesheet for that.

Only three things are absolute. The 1px hairline (a hairline that scales stops being a hairline), the shell offsets --shell-pad-left / --shell-pad-top in vw so any band can offset by the rail without inheriting its font-size, and the fluid scaling head-room below.

The fluid ceiling. --size-container clamps 100vw between 992px and 2440px against a 1440px ideal, so the page keeps growing on very wide screens instead of stranding the content in the middle — but stops before the display type becomes a poster.

Resolved right now
body 14px--grid-gutter 1.25em = 17.5px--font-size-h2 4.25em = 59.5px--font-size-display 10.5em = 147px
Breakpointsfour tiers, one of them counter-intuitive
TierQueryColumnsWhat moves
Desktop≥ 991px12The design width. Display 10.5em, H1 7.5em, H2 4.25em. Nav rail is a left column from 1200px.
Tablet≤ 991px6The grid halves to six and STAYS there for every tier below. Only H2 steps down (4.25 → 3.5em). Rail becomes a top bar.
Mobile landscape≤ 767px6Type compresses — display 6.5em, H2 2.25em, H3 2em — but the grid does not move.
Mobile≤ 479px6The scale bottoms out: display 5em, H1 4em, H2/H3 1.5em. H2 and H3 converge — pick by rank, not size.

The trap: webflow.css re-declares --grid-columns on body at all four tiers, and three of those declarations do nothing. A custom property substitutes its own var() at the element it is declared on, and --grid is declared on :root — so it only ever sees :root's value. Read the painted column count, not the declaration nearest the element.

02

Colour

Six tokens and two shell-only extras. The palette is deliberately starved: one warm black, one white, one accent, one quiet grey, one hairline. Every surface in the product is built from that, which is why adding a seventh colour is the single fastest way to make the site look like someone else's.

Ink black#0E0D0D · --color-backgroundEvery band's ground. Not #000 — the two points of warmth stop the video bands looking like a hole.
Paper white#FFFFFF · --colorBody ink on dark bands. Also the ground of the one light band, where --color-background becomes the ink.
Signal#C75D5F · --color-accent-red-lightPrices, kickers, the live tier, the rail's progress. One accent, used for the thing you want clicked.
Signal deep#7D1316 · --color-accent-red-darkBorders and gradients only. Too dark to carry text at any size — never set it as a colour.
Quiet ink#919191 · --color-gray-lightSecondary copy: dates, notes, meta, sold-out rows. Passes on ink black, fails on paper white.
Hairline#2F2F2F · --color-gray-darkEvery rule and every border in the system is 1px of this. There is no second border colour.
Warm cream#FEE9CE · rgb(254, 233, 206)Shell-only: the rail's active label, and hero subcopy that must hold against a bright video frame.
Rail black#050505 · rgb(5, 5, 5)The nav rail's own ground, one step below the page so the shell reads as behind the content.
Ink ruleswhat may sit on what

Founder Weekend — Prague

Two days, eight founders, one room.

14–16 March · Vinohrady · 8 seats

Dark band — the default--color on --color-background; grey for secondary

Founder Weekend — Prague

Two days, eight founders, one room.

Secondary ink on light is a darkened grey, not --color-gray-light.

Light band — the inversion--color-background becomes the ink; grey fails here

The one that bites: shell surfaces float over arbitrary page content, so they must declare their ink. The sticky bar shipped with black-on-glass prices for exactly this reason — it was inheriting the page's rgb(14,13,13). Any fixed or translucent surface sets color: var(--color) on itself.

03

Typography

One sans (PP Neue Montreal) and one mono. Eleven utility classes, and no size that isn't one of them. The rule that keeps it coherent: the bigger the type, the tighter the leading and the tracking — display runs at 0.9 leading and -0.04em, body at 1.2 and -0.02em.

Specimendesktop values, live
.u-display--font-size-display
10.5em · lh (0.9) · wght 500
Day one, not one day
.u-h1--font-size-h1
7.5em · lh (0.9) · wght inherit
Day one, not one day
.u-h2--font-size-h2
4.25em · lh (0.9) · wght 500
Day one, not one day
.u-h3--font-size-h3
3em · lh (1) · wght 500
Day one, not one day
.u-h4--font-size-h4
1.875em · lh (1) · wght 500
Day one, not one day
.u-h5--font-size-h5
1.5em · lh (1) · wght 500
Day one, not one day
.u-body--font-size-body
1em · lh (1.2) · wght 500
Day one, not one day
.u-details--font-size-details
1em · lh (1.05) · wght 500
Day one, not one day
.u-button--font-size-button
1em · lh (1.05) · wght 600
Day one, not one day
.u-mono-large--font-size-large-mono
0.875em · lh (1) · wght 400
Day one, not one day
.u-mono-small--font-size-small-mono
0.625em · lh (1.2) · wght 400
Day one, not one day
The scale, per tierem against a 14px body
Class≥992≤991≤767≤479LeadingTracking
.u-display10.5em10.5em6.5em5em--line-height-90 (0.9)--letter-spacing-tight-04 (-0.04em)
.u-h17.5em7.5em5em4em--line-height-90 (0.9)--letter-spacing-tight-02 (-0.02em)
.u-h24.25em3.5em2.25em1.5em--line-height-90 (0.9)--letter-spacing-tight-02 (-0.02em)
.u-h33em3em2em1.5em--line-height-100 (1)--letter-spacing-tight-01 (-0.01em)
.u-h41.875em1.875em1.5em1.25em--line-height-100 (1)--letter-spacing-tight-02 (-0.02em)
.u-h51.5em1.5em1.5em1.25em--line-height-100 (1)--letter-spacing-tight-02 (-0.02em)
.u-body1em1em1em1em--line-height-120 (1.2)--letter-spacing-tight-02 (-0.02em)
.u-details1em1em1em1em--line-height-105 (1.05)--letter-spacing-tight-02 (-0.02em)
.u-button1em1em1em1em--line-height-105 (1.05)0
.u-mono-large0.875em0.875em0.875em0.875em--line-height-100 (1)--letter-spacing-0 (0)
.u-mono-small0.625em0.625em0.625em0.625em--line-height-120 (1.2)--letter-spacing-loose-05 (0.05em)
Clampinghow the big sizes survive small screens

The scale steps, it doesn't interpolate. There is no clamp() on any type token. Sizes are re-declared at four breakpoints, which means the type is predictable at every width and there is no band where a heading is an awkward in-between size nobody designed.

What clamps instead is the container. --size-container is a clamp, and above 1440px it grows the whole fluid scale together. Below 992px it is pinned, and the breakpoint steps take over.

Line length is clamped in em, not px. Standfirsts cap at 34em, hero subcopy at 24em, prose at 38em. That is roughly 60–75 characters at every size, automatically.

Weights are static, not variable. The original export drove weight through font-variation-settings: "wght" 500. PP Neue Montreal ships static cuts, where that declaration is inert — so fonts.css restates font-weight: 500 for the headings. If new type looks unexpectedly light, that is the cause.

Use the ratio, not the pixel. Leading is stored unitless (--line-height-90 is 0.9), so it survives every re-scale. Never copy a resolved px line-height into a component.

Mono is a label typeface. .u-mono-small is uppercase at 0.625em with +0.05em tracking. It carries kickers, keys, counts and captions. It never sets a sentence.

Pairings that work
Next weekend

Eight founders, one room

Product, growth engineering and positioning, worked through in person over two days.

Kicker → H2 → standfirstmono-small accent, then heading, then grey body
Date14–16 March
WhereVinohrady, Prague
Seats8
FormatTwo full days
Meta rowmono key over details value, hairline above
04

Grid

Twelve columns on desktop, six on tablet, a 1.25em gutter and a 1.25em page margin — and the margin and the gutter are the same value on purpose, so a full-bleed edge and an internal gap read as one rhythm. Every band is a .u-container wrapping one or more .u-grid rows.

The columnslive — resize to see it halve
123456789101112

At 991px --grid-columns drops to 6 and columns 7–12 wrap to a second row — and it stays at 6 all the way down. There is one grid change in the whole system, so a twelve-cell row only ever has to read as two rows of six.

Spans in use
span 12 — full bleed row, band headers
span 8 — the standfirst column
span 4 — sidenote
span 6 — the card default
span 6
span 3 — swatch
span 3
span 3
span 3
span 2 — label
Rules

Bands do not nest containers. One .u-container per band, then as many .u-grid rows as the band needs. Nesting a second container doubles the page margin and the band silently narrows.

The shell owns the left edge. --shell-pad-left is applied once on the page wrapper. Bands never offset for the rail themselves — if a band looks pushed in twice, that is why.

Column width is derivable. --grid-column-width computes a single column from viewport, margin, gutter and count — use it when something must be exactly one column wide outside the grid (a pinned label, an absolutely-positioned figure).

Offsets use start/span, not margins. grid-column: 5 / span 6 beats a left margin, because it survives the column count halving.

Declare the count where the grid is. A route that does not load slater.css — the funnel, which drops the motion stack — has to restate --grid and --grid-columns at :root. Restating only the first silently gives every row twelve columns at every width.

05

Spacing & rhythm

There is no numbered spacing scale in the tokens — there is a set of steps the bands actually use, and staying on them is what makes pages feel like one site. Below is that set, measured out of the shipped CSS rather than invented for this page.

The ladderem, so it scales with the base
gutter1.25em--grid-gutter and --grid-margin. The base unit — column gap, page inset, small stack gaps.
row1.5emPadding inside listing rows, drawer sections, plate feet.
block2emGap between a header and its first row; padding inside bordered panels.
group3.75emBetween a heading block and a CTA cluster (.overview-cta_wrapper).
band-tight7.5emTop of a band that follows a related band (.gear-up_container).
band9emThe booklet's own band padding — the median of the site's bands.
band-loose15emA band that must read as a full stop (.faq).
Hairlinesthe system's only divider

One border, everywhere. 1px solid var(--color-gray-dark). There is no second weight, no second colour and no shadow anywhere in the product — depth is done with blur and gradient, never with a drop shadow.

Above for headers, below for rows. A section header takes a border-top and 1.25em of padding-top. A list row takes a border-bottom and symmetric padding. Mixing the two is what produces doubled rules between stacked blocks.

Radii. 0.1875em for surfaces (chips, panels, plates), 0.125em for tags and inputs, 0.25em for the two emphatic cases (the newsletter button, the offer card). Nothing is fully round except the booster check.

06

Buttons

One button component, five variants, and a scribble. The hover state is a hand-drawn stroke masked over the label — it is the site's signature gesture, and it only works because the anatomy is identical everywhere. Never hand-roll a button; the motion layer keys off the data attributes.

Variantsthe real component, on the ground each one is for
Primary — on darkbase: .button_bg takes --color, so the pill is white
Primary — on lightblack: --color inverts, so the same pill goes ink

The variant only centres the default label and drops the fill. Use it where the band behind it is already doing the work — never on flat --color-background, where it reads as a dead link.

Full-widthdark-gradient: no fill of its own — it borrows the band's
Quiet actionsnot every action is a button
Text actiongrey → white on hover, 0.25s, no underline
Sold outAvailable nowOpens 1 Feb
Tagstate, never a control — 5% ink fill
Anatomy

The markup is load-bearing. data-button, data-button-mask and data-button-svg are what the motion layer animates: it draws the scribble path to reveal the hover label through an SVG mask. Drop any one of them and the button still renders but stops moving.

Two labels, always. One in .button_text.is-default, one inside the masked foreignObject. They must be the same string or the hover reads as a glitch.

Buttons never wrap. In tight rows set white-space: nowrap — a two-line button breaks the mask geometry.

07

Cards & surfaces

Five surface treatments, each with a job. The rule that stops a page turning to mush: a surface either has a fill or a border, never both, and only one surface per screen gets to be translucent.

Included
Positioning teardown

Your message, pulled apart and rebuilt on the Saturday morning.

Hairline cardthe default — border, no fill
Next eventPrague · 14–16 March

Floats over video and arbitrary content, so it declares its own ink.

Glass chipblur(50px) over #0003 — one per screen, max
Early birdFirst four seats
Live£1,200
StandardOpens 1 Feb
Locked£1,600

Unavailable rows drop to 0.45 opacity — they are never removed, because the ladder is the pricing argument.

Listing rowborder-bottom only — tiers, basket, schedule
Take the whole packageEverything above, £400 off

Dashed = an offer not yet taken. It goes solid and tints on accept, which is the only place the accent is used as a fill.

Offer cardthe only dashed border in the system
Weekend kitIncluded
The workbookIncluded
Post-weekend auditIncluded
Media cardimage, then a hairline foot — never text over the image
08

Forms

Inputs are filled, not outlined — a --color-gray-dark field on the black ground, 2.8125em tall, with a 1.25em left inset that lines up with the page margin. Labels are mono, above, and always present.

Placeholder is a hint, never the label. The label stays.

Text fieldfilled, 0.125em radius, no outline
01Pre-weekend teardown60 minutes, the week before+£250Added ✓02Follow-up audit30 days after+£400Add
Order bump card1px rule, accent border and 6% tint when added

Errors print in --color-accent-red-light beneath the field at .u-details, never as a toast. Disabled is 0.45 opacity, matching the locked tier row, so “not yet” looks the same everywhere.

09

Motion & parallax

Three easings, five durations, and a declarative parallax layer driven entirely by data attributes. The house style is fast out, long settle, no bounce — the site should feel weighty, not springy.

Easingslive, looping
--ease-opacity
cubic-bezier(0.4, 0, 0.2, 1)Fades and colour changes. Symmetrical, so it reads as a state change, not a movement.
--ease-smooth
cubic-bezier(0.32, 0.72, 0, 1)Anything that travels: drawers, the sticky bar, the rail. Fast out, long settle.
--ease-out-gentle
cubic-bezier(0.17, 1, 0.35, 0.96)Reveals and scale-ins. Almost no overshoot — the system never bounces.
Durations
DurationWhere it is used
0.2sBooster check, hover fills — anything under the cursor.
0.25sInk-only hover on quiet controls (drawer close, tier add).
0.4sDrawer scrim fade.
0.45sSticky book bar in/out, drawer panel slide.
0.6sNav rail reveal and its background cross-fade.
Parallaxscroll — both panels are live
default · 20 → -20, vertical, scrub
restrained · 8 → -8, scrub 0.5, off on mobile
AttributeDefaultNotes
data-parallax="trigger"Marks the element ScrollTrigger measures. Required.
data-parallax="target"the trigger itselfThe element that actually moves. Give it inset overflow to move into.
data-parallax-directionvertical"horizontal" switches the tween from yPercent to xPercent.
data-parallax-start20Percent offset at scroll-start. Positive = starts pushed down.
data-parallax-end-20Percent offset at scroll-end. The default 20 → -20 is a 40% travel.
data-parallax-scrubtrueA number becomes GSAP's smoothed scrub, in seconds. 0.5 is the house value when you want lag.
data-parallax-scroll-starttop bottomWrapped in clamp() so it never overshoots at the top of the page.
data-parallax-scroll-endbottom topAlso clamped.
data-parallax-disable"mobile" | "mobileLandscape" | "tablet" — opt a band out per tier.

The target needs somewhere to go. A parallax target must be larger than its trigger — the panels above use inset: -20% 0 so a 40% travel never exposes an edge. Matching the default travel to the overflow is the whole trick.

Scrub is not a speed. true pins the tween to the scrollbar exactly; a number is the seconds of catch-up lag. Use 0.5 for large soft imagery, and true for anything the eye can check against a hard edge.

Everything is scoped and re-initialised. The layer re-runs per band scope, so a band added to a route picks up its motion with no registration step — and a band removed tears its triggers down with it.

10

The funnel's own devices

Six pieces that only exist between the basket and the receipt. They are the same system as everything above — the only new idiom is the dashed border, and it means exactly one thing.

Progressthree steps, never four, and never a percentage
01Your seat02Payment03Confirmed

A completed step gets a rule through its number. The active one goes to full ink. Nothing animates: a progress bar that moves on its own reads as a loading state, and this is not one.

Funnel step navtells you where you are, then gets out of the way
The offerthe only dashed border in the system
$1,497 · save $600$897One charge to the card you just used. No new details.
Post-purchase price blockdashed accent-red-dark, 0.25em — post-purchase only

A funnel that hides its exit is one people learn to distrust. The decline is legible, keyboard-reachable, and plainly not the primary action — which is the whole of the design.

Declinea real labelled button, never a close icon
Moneyinteger cents, formatted once, at the edge
SeatFounder Weekend — Early Bird$1,490
Add-onGrowth Engineering From Scratch — the guide$27
Added after checkoutThe Inner Circle — twelve months$897

Every figure here is an integer number of cents until the last possible moment. There is no float anywhere in the money path.

Order linehairline under, price right
Measurementthe report reads like the rest of the site
Landed1,240 of the step before
Took a seat17414% of the step before
Paid7442.5% of the step before
Funnel step rowone bar scale for every row
11

What works, what doesn't

The short version. Everything on the right is something that has actually gone wrong in this codebase, not a hypothetical.

Works
  • Reach for a token firstIf the value you want isn't in the token set, the question is usually whether the design is right, not whether the token list is short.
  • One accent per viewThe red marks the thing you want clicked. Two red things on a screen means neither reads as the action.
  • Write the 991px case explicitlyIt is the only grid change in the system — 12 above, 6 below, all the way down. Write it once and nothing else needs a per-tier span.
  • Fixed surfaces declare their inkAnything positioned or translucent sets its own color.
  • Let the em cascade do the workSize a component once, then let its container decide how big it is in context.
  • Hairline above for headers, below for rowsConsistent divider direction is most of what makes long pages feel calm.
Doesn't
  • New greysThere are two. A third one always looks like a mistake next to the other two, because it is.
  • px inside a componentIt survives until the first time the component is used at a different scale, then it is the only thing that didn't move.
  • Hand-rolled buttonsMissing the data attributes gets you a button that renders and never animates — which reads as a broken build, not a variant.
  • Drop shadowsDepth here is blur and gradient. A shadow instantly makes the page look like a different product.
  • Resolved line-heightsCopying 52.5px instead of 0.9 pins the leading and breaks every other breakpoint.
  • Nested containersTwo .u-container elements double the page margin, and the band quietly stops lining up with its neighbours.