One sign-in, every agent
Sign in to an app once.
Every agent in the workspace reaches it, under the same rules.
Everything below is rendered from @spinrun/ui, the one package the marketing site, the product app and the docs all draw from. The swatches read the value your browser resolved, so a chip here cannot disagree with the CSS.
docs/design.mdthe written source of truthdocs/architecture.mdthe app's shape and its packagesThe one thing to get right: scope
Half the vocabulary — the three grounds, the three inks, the three line weights, the tight radius ladder, the shadow ladder, the easings — is defined inside .site-app, which the product shell puts on the app and a marketing page does not. Reaching for one of those outside the scope does not error. It simply does nothing.
Outside .site-app
bg-canvas rounded-[var(--radius-card)]
shadow-[var(--shadow-tile)]No ground, no radius, no edge. Silently.
Inside .site-app
bg-canvas rounded-[var(--radius-card)]
shadow-[var(--shadow-tile)]A well, rounded at the card rung, with the tile edge.
One palette, at :root, read by marketing, the product app, auth and the docs site alike. White paper, a zinc ramp and one violet: ink carries structure and action, violet marks focus, selection and progress, yellow is the plan meter, and colour otherwise still means state. The only place a hue is decorative is a chart series.
Four steps of one neutral ramp, declared before anything else so every surface below is a reference rather than a literal. Paper, a well, the hover, and the pressed-or-selected ground. Nothing paints with these directly — reach for --background, --muted, --accent or --sidebar-accent, which are the names that say what the surface is.
The page, the rail, a card, a popover.
—
A well: a search field, an idle row.
—
The row you are pointing at.
—
The row you are on, and the one you are pressing.
—
One ink, flat. --brand-top is --brand now: the button stopped running a vertical gradient when the cards stopped lifting. --brand-soft is what an ink surface lightens to under the pointer, and the far stop of the outward gradient a chat bubble still runs.
The ink. Every emphatic surface.
—
An ink surface under the pointer; the far stop of an outward gradient.
—
Alias of --brand, kept so the landing's gradient controls still resolve.
—
A second name for --brand, read by the landing's gradients.
—
The lighter half of those, and the ink button's hover.
—
Brand at 10% — glows and halos.
—
Brand at 5% — the quietest wash.
—
One hue, three jobs: focus, selection and progress. An active nav row, a button, a title and a link are all still ink — violet marks what the system is doing or what you have picked, never what a thing is. It is the brand's --violet-500, so the product and the marketing site are one colour. It is called accent-primary rather than accent because --accent is shadcn's hover ground and cannot be repurposed, and rather than a hue name so the next palette change is a value, not a rename.
The solid. Focus rings, a progress fill, a chart's first series. 5.24:1 on paper.
—
A resting selected border, a soft fill, the composer's running edge. 2.03:1 on paper — never a series, never a word.
—
The wash under a selected row or a count chip.
—
Violet words on that wash. 7.48:1 on the tint, 8.58:1 on paper.
—
White on a violet fill.
—
The plan meter's bar, and the only place this yellow is spent. A fill with a number beside it, never a series and never a word.
—
The shadcn contract. Every primitive is built on these names, so a surface changes by redefining them rather than by editing components.
The page.
—
Body ink.
—
A raised surface.
—
Ink on a card.
—
An overlay surface.
—
Ink on an overlay.
—
The emphatic action.
—
Ink on it.
—
The quiet action.
—
Ink on it.
—
A wash behind something inactive.
—
Secondary ink.
—
Hover ground.
—
Ink on hover.
—
A drawn edge.
—
A field's edge.
—
The focus ring. The solid violet — the soft one is 2.03:1 on paper.
—
Alias of --brand, kept for `hover:text-signal`.
—
Ink on it.
—
The rail is paper, divided from the page by one hairline; its active row is the only thing on it carrying a ground. shadcn ships a single --sidebar-accent and spends it on hover and on active alike, which stopped working the moment the two stopped being the same near-white — so there is a --sidebar-hover, and the row you are pointing at is a step lighter than the row you are on.
The rail.
—
A resting nav row's word.
—
An emphatic mark on the rail.
—
Ink on it.
—
The row you are pointing at.
—
The row you are on, and the one you are pressing.
—
The active row's word.
—
The hairline dividing rail from page.
—
Focus on the rail. The accent, as everywhere.
—
Colour means state and nothing else. Each hue has a tint to sit on — which is what stopped every pill in the product inventing its own bg-emerald-100 — and an ink, which is the same state dark enough to be read as words. The hue is for shapes: a dot, a bar, a glyph, a filled surface. Orange-400 on orange-50 is 2.13:1 and is not a sentence.
Working, healthy, connected.
—
Ink on a filled success surface.
—
The wash a success pill sits on.
—
Success as words. 5.21:1 on its tint.
—
Waiting on something or someone.
—
Ink on a filled warning surface.
—
The wash a warning pill sits on.
—
Warning as words. 4.88:1 on its tint.
—
Broken, failed, denied.
—
The wash a destructive pill sits on.
—
Destructive as words. 5.87:1 on its tint.
—
A fact about this screen, not a state. The glyph in a callout.
—
The words in one. 8.11:1 on its tint.
—
The callout's ground.
—
The one place hue is decorative, reused in order: the violet ramp, stepped so neighbours differ in lightness, then the ink. A series is a graphical object carrying information, so each owes the paper 3:1 — which is why the light end stops at --violet-400 and why --accent-primary-soft and --meter, at 2.03 and 1.34, are not series.
First series. The accent, --violet-500 — 5.24:1 on paper.
—
Second series. --violet-900, far darker than the first — 13.87:1.
—
Third series. --violet-400, the light one — 3.69:1.
—
Fourth series. --violet-700 — 8.58:1.
—
Fifth series. The ink — 14.89:1.
—
A zinc ramp, twelve rungs, ending on the ink. Reached by name only when the semantic set has nothing that fits — most of the time the answer is bg-muted or text-muted-foreground.
White.
—
The lightest wash.
—
A hover ground.
—
The border grey, and the active pill.
—
The half-step zinc does not publish.
—
A touchable edge.
—
Disabled ink.
—
Secondary ink.
—
Strong secondary ink.
—
Near-ink.
—
The ink.
—
Darker than the ink.
—
The deepest stop.
—
What replaced alternating dark and light bands: a dotted rail down each edge of the measure, and a diagonal hatch behind grouped content.
The dotted page rails and section rules.
—
The hatch's ink. Faint by default; a panel may raise it.
—
The marketing system's flat colours: a violet-tinted ink and two greys, a lavender hairline, beige grounds, and two indigos. --mk-secondary is a lead's colour (4.91:1 on white); --mk-muted is an icon's (3.00:1), never a sentence's. A band is a coloured ground, not a dark theme, and a page carries as many as its sections need — most of them one of the gradients below rather than --mk-band's flat navy. Defined at :root because the mobile menu portals outside .site-marketing.
The marketing ink. Headlines and body on a light ground.
—
A lead, a description, a footer link. 4.91:1 on white, 4.53:1 on beige.
—
An icon, a closed chevron. 3.00:1 — never a sentence.
—
The lavender rule: a frame, a card border, a plan card's edge.
—
The first kit's warm hairline on beige, until its pieces move to --mk-hairline.
—
The warm page ground a section alternates with paper.
—
A step lighter: a card resting on beige.
—
The well a segmented tab control slides in.
—
The statement band, and the deep end of every gradient.
—
The closing call-to-action band. One unit of green from --mk-navy.
—
What bg-mk-band (Band tone="ink") paints: the navy.
—
Words on an indigo ground. White, 16.06:1 on the navy.
—
Secondary words on an indigo ground. White at 75%.
—
The warm line between two cards in a flow.
—
A rating's star, and only that.
—
The brand and the product accent, one ramp. 50 … 900 are its ten steps and --violet-500 is --accent-primary. The rest are stops its gradients pass through, named for where they fall: 450 between two rungs, the deep-* run toward the navy (more chroma than 600 … 900, which are the flat, text-safe steps), and two text colours for words on a violet wash. Tailwind keys are mk-violet-*, not violet-*, because Tailwind ships its own violet ramp under that name.
The wash: an eyebrow chip, a selected option, an accent tint.
—
A chip's ground; the pale end of a ramp.
—
A resting selected border. 2.03:1 — never a word.
—
A gradient's light violet.
—
A light series; a gradient stop. 3.69:1.
—
The half step between 400 and 500 in every sweep.
—
The accent and the brand. 5.24:1 on white.
—
A pressed accent.
—
Violet words on a violet wash. 7.48:1 on --violet-50.
—
A deep violet.
—
The darkest flat step before the navy.
—
A gradient's saturated run toward the navy.
—
The same run, a step deeper.
—
The same run, a step deeper.
—
The last stop before the navy.
—
A selected option's words, and the highlighted plan's title.
—
The words in a "+n" chip on --violet-100.
—
The second accent and the stops that carry a gradient from warm to violet. --peach-500 is what a headline's last words turn on an indigo band (9.80:1 on the navy); 300 … 500 are three near-identical peaches that different gradients use, kept apart because they differ. Mauve sits between peach and violet; warm is the yellow-to-apricot card. None of these is a text colour on a light ground — --peach-500 is 1.6:1 on white.
The faintest peach wash.
—
A peach wash.
—
The peach a hero bowl passes through.
—
A gradient's dusty peach.
—
A gradient's peach, and the text gradients' first stop.
—
The second accent: a headline's last words on indigo, a rotating word.
—
A lilac stop between peach and violet.
—
A mauve stop in the security and integration gradients.
—
A pinker mauve in the warm indigo panel.
—
The palest cream.
—
The warm card's yellow.
—
A sand stop in the peach-yellow ring.
—
A honey stop.
—
The warm card's apricot.
—
Named grounds, each a CSS string written once here. Reach for one as bg-[image:var(--gradient-dark2)] (the kit's Band, GradientCard and Slab take its name as a ground), or with bg-clip-text for the text-* ones — never write a gradient into a component. hero-bowl is an SVG gradient transcribed exactly; product-diagonal and security-header-grey are CSS approximations of an SVG or of two end stops, to be tuned when those heroes are built.
An indigo panel lit violet from the top, with a peach haze.
—
Peach at the top falling through violet to navy: a tab panel, an active chip.
—
Peach to navy from far above: a compare card, a heading's accent.
—
Navy with violet rising at the edges: the workspace panel, the example cards.
—
The Resources header's measured editorial gradient.
—
The Resources featured article card's skewed gradient.
—
The Academy lesson panel's violet-to-peach ground.
—
A curriculum chapter's translucent violet ground.
—
The partner and creator pages' warm hero ground.
—
dark4 with a mauve band: the big warm card.
—
A card's art ground, lit from the top.
—
A card's art ground, lit from the bottom.
—
The pastel panel. Drawn blurred 58.3px at 50%.
—
The pastel panel's faint twin on beige.
—
The warm yellow-apricot card.
—
The first pillar card's art panel: peach from the top-left into navy.
—
The third pillar card's art panel: violet from the top centre into navy.
—
The oversight feature's panel: a violet radial from above-left over a peach glow.
—
The gradient tab bar's active pill. An SVG radial, transcribed.
—
The ring around a highlighted card.
—
The soft conic haze behind a glass card.
—
A headline's accent words, as clipped text. dark3.
—
The warmer accent text, with a mauve step.
—
A violet-only accent text.
—
Beige above, an indigo bowl rising below: home, pricing. An SVG, transcribed.
—
A hero whose violet band sits under the fold.
—
A hero whose violet sweeps in from bottom right. Approximation.
—
The footer's beige-to-indigo haze.
—
Our column's header card in a side-by-side.
—
Its greyscale twin, for the other column. Approximation.
—
A peach-to-violet glow fading into deep navy for the credentials panel.
—
The indigo fade along the credentials panel's bottom edge.
—
A light-blue header card.
—
The line into an avatar: navy, violet, peach.
—
An avatar's glow. Drawn at blur 24px.
—
A glass frame's frosted fill, over a 6px backdrop blur.
—
Peach light at a panel's top centre, as a layer over any ground.
—
Peach warming a panel toward its rim, as a layer over any ground.
—
The integrations page's trust glow: violet warming to beige. Drawn at blur 80px.
—
The pastel panel top to bottom: the numbered steps' panel. Drawn blurred 58.3px at 50%.
—
The header's Product card: navy lit violet from below.
—
The same card in the phone menu, with a peach cap.
—
The Academy card's drawn thumbnail: lavender warming to violet.
—
The home hero badge's header strip: violet into the navy run, peach at the top right.
—
The home hero badge's back face: the same ground, its peach wider for the bigger box.
—
--success / --success-ink, and the other threeThe hues are chosen to be seen: a dot, a bar, a glyph, a filled surface. Read as words on their own tint, three of the four fall under 4.5:1 — orange-400 on orange-50 is 2.13, which is the suggestion of a sentence. So each hue has an ink a few steps darker, and StatusPill writes with it. Ratios are measured against the tint beside them.
The three groups below are the product surface’s own, defined inside .site-app. Their swatches wrap themselves in that class so they paint here; on a page that does not, they paint nothing.
Three grounds, two of them the same paper: a card no longer reads as a card because what is behind it is darker, it reads as one because it carries a hairline and the page does not. --canvas is the only one still a step down — the strip over a grouped list, the ground a resting tab sits on. Defined inside .site-app, so reaching for them on a marketing page gets nothing, deliberately.
The gutter behind the content card, and the rail.
—
A well: a code block, a list's header strip, an idle row.
—
The card.
—
A hover ground inside a card.
—
The wash behind a search field and an idle row.
—
The ink at 5%, for code and URL blocks.
—
Ink inside them.
—
Three weights: --ink for what is being read, --ink-2 for what is beside it, --ink-3 for what is only there if you look. --ink-3 is 2.56:1 on paper and is therefore not for copy — a placeholder, a unit, an index number. Anything a person has to read belongs on --ink-2, which is 7.73:1.
What is being read.
—
What is beside it. Every line of secondary copy.
—
A placeholder, a unit, an index number. Never a sentence.
—
Three weights: --line-soft separates rows inside one card, --line is the card's own edge — the one line most surfaces need — and --line-strong the edge of something you can touch.
A rule between rows.
—
A card's edge.
—
A focused field, a button.
—
Two open-licence faces, loaded once in @spinrun/ui/fonts, put on <html> as CSS variables and mapped onto Tailwind's font keys in tokens.css. Figtree fills --font-ui, so --font-sans and everything below is Figtree; Urbanist is the marketing kit's display face, reached through font-mk-display. Calling next/font from inside a workspace package is nowhere promised in the Next docs; it works because every host transpiles this package, and next build in all three is what proves it rather than assumption.
Two open-licence faces and a mono, loaded once in @spinrun/ui/fonts and applied at <html>. Figtree fills the --font-ui seam, so --font-sans — and with it the whole product, marketing body copy and the docs — is Figtree. Urbanist is the marketing kit's display face, reached only through font-mk-display. Host-only faces (JetBrains Mono for .skin-clone) are that host's business. The size registers are utilities rather than tokens — see the scale below.
The body and UI face. Figtree, through --font-ui.
—
Alias of --font-sans. Product headings differ by weight, not face.
—
The marketing display face. Urbanist, set at 700 by the display registers; font-mk-display, on the kit's Display and Statement.
—
Alias of --font-sans. Reads as nothing here: @theme inline emits a key only where something reads it, and this one is only ever a utility.
—
The accent serif. Declared and not loaded — nothing in the product sets it, so it reads as nothing here and falls through to Georgia where it is used.
—
Code, terminal output, tabular readouts. Geist Mono.
—
seven registers · @theme keys in tokens.cssNamed after the job, not the number, so two surfaces showing the same kind of thing cannot drift apart. nav is the product body and .site-app inherits it — an element spells a size only when it means a different one. In the product, weight tops out at 500: font-semibold and font-bold resolve to 500 here. The marketing display registers (text-mk-h1 … text-mk-h6) carry their own 700.
@spinrun/ui/styles/utilities.cssThree recipes a size token cannot express. .font-label and .eyebrow are the two label jobs and they are deliberately separate: making the first one uppercase would have put ninety buttons in caps, so the section label got its own name.
Seven literal rungs — Tailwind's own defaults — and seven more for the product's denser register.
rounded-sm … rounded-4xl4 · 6 · 8 · 12 · 16 · 24 · 32. These used to be seven multiples of --radius, which made the base the one number to turn; no multiplier reproduces this sequence, so the rungs are spelled. --radius stays at the lg rung because the shadcn primitives compute off it.
Seven literal rungs — 4 · 6 · 8 · 12 · 16 · 24 · 32, Tailwind's own defaults — plus seven product rungs for the denser register. They stopped being multiples of --radius because no multiplier reproduces that sequence; --radius stays as the lg rung, since the shadcn primitives compute off it. The product rungs are reached as arbitrary values — rounded-[var(--radius-card)] — because a @theme key may not reference a variable of its own name.
The base, and the lg rung. 0.5rem.
—
An icon-only action. 4px.
—
A chip, a thumbnail. 6px.
—
A control in a dense row. 8px.
—
A card. 16px.
—
A panel or overlay. 16px.
—
A window. 16px.
—
The prompt box, and only that. 24px.
—
The kit's shape, large and soft: a tile's 14, the 19.6 and 25.2 of a row and a field, a comparison card's 20, a menu's 26, the hanging header's 30, a card's 32, a section's 36.4, a feature panel's 40, the FAQ box's 64, a slab's 100 and the tool page's app tile (88, 28 on a phone). A 24 or 16 takes the literal ladder's rounded-3xl or rounded-2xl rather than a rung of its own. Literal @theme keys, so rounded-mk-card works in a portal too. The section, slab and app-tile rungs step down at their own breakpoints (a section to 19.6 below 640px, a slab to 64 below 1024px and 40 below 768px, the app tile to 28 below 1024px); --radius-mk-band is the section rung under the name Band once asked for. Each rung is registered with cn (RADIUS_KEYS), so two of them collapse to the last.
An icon tile, a well, a menu item. 14px.
—
A select option, a trust card. 19.6px.
—
A comparison card, the phone's highlighted comparison row. 20px.
—
A search field, a directory card, a field. 25.2px.
—
A dropdown panel. 26px.
—
The hanging header's bottom corners. 30px.
—
A content card. 32px.
—
A section's or band's corners. 36.4px, 19.6px below 640px.
—
Alias of --radius-mk-section, for Band's corners.
—
A feature block's art panel. 40px.
—
The FAQ box. 64px.
—
A violet slab. 100px, 64px below 1024px, 40px below 768px.
—
The tool page's white app tile. 88px, 28px below 1024px.
—
rounded-[var(--radius-card)]Not a workaround anybody is waiting to fix.
A @theme key may not reference a variable of its own name: --radius-card: var(--radius-card) is a cycle, and Tailwind resolves it to nothing.
Both ways out are worse. Registering the scoped rungs under different names leaves two vocabularies for one ladder, and the CSS stops reading like the ladder it is. Writing literal values into @theme inline inlines them into the emitted utility — that is what inline means — so the .site-app definitions would never be read and scoping them would have no effect at all.
So they are reached the way this codebase already reached --radius-control and --shadow-panel before the package existed. Same reasoning for the shadow ladder and the easings.
Never hand-write one. Every product rung is a 1px ring and nothing else, which is why four of them are now the same declaration — a panel, a tile and a chip are all outlines on paper. Lift survives in exactly one place, --shadow-float, for something that genuinely floats. The two at :root are the marketing page's, and --shadow-card is still five stops deep because it is built to be the only card on a wide page.
Edges come from shadows, not borders: every rung is a 1px ring, so a card in a flex row never steals a pixel of layout. That mechanism is the whole reason these are shadows; the lift is gone. Four of the product rungs are now the same declaration — a panel, a tile and a chip are all outlines on paper — and the names stay because they say what the thing is, and because --shadow-float is genuinely different. The marketing --shadow-card keeps its five stops for the one hero card on a wide page.
One hero card on a marketing page.
—
A hair of lift on a small control.
—
A ring and nothing else.
—
A chip's edge.
—
A tile in a grid.
—
Something floating over the page. The one rung with a drop.
—
An inset ring — a field, a well.
—
A panel's edge.
—
Soft, and some of them tinted violet. Unlike the product's rings these genuinely lift: a header after scroll, a menu, a frosted card, a glass bezel, the neumorphic white card's inset stops, a glossy app tile, a reel's glass card, a dropdown list. Each is a shadow-* utility and is listed in SHADOW_KEYS so cn keeps it.
The header's halo once the page has scrolled.
—
A dropdown panel.
—
A frosted card on a gradient.
—
A white card lifted off beige.
—
A glass frame's outer shadow.
—
The white face inside a glass frame.
—
The neumorphic white card: four inset stops.
—
A glossy app tile: a drop and a warm inner highlight.
—
A reel's glass card, cast down and to the left.
—
A select's open list: a deep, soft drop.
—
All of it is plain CSS — the app surfaces link no animation library — and all of it is flattened by the prefers-reduced-motion guard at the foot of tokens.css.
Two curves. The whole surface moves on the first; anything that changes the shape of the page uses the second.
not defined hereThe default curve.
—
not defined hereWidth, and anything that reflows.
—
@spinrun/ui/styles/utilities.cssThe one component in the package that animates in JS — BlobMascot — calls useReducedMotion() itself, because the CSS guard cannot reach a motion/react transform.
This line is running animate-shimmer-text
.animate-step-ina trace row arriving, staggered by index.animate-pop-ina status icon settling once its step finishes.animate-fade-upa card that interrupts: a failure, a turn that stopped short.animate-fade-ina surface arriving: a chat footer, an auth stage.animate-shimmer-texttext with a highlight travelling through it.stream-tailthe streaming edge, so live text has a moving frontier.app-sketchan App’s sketch while it is built: dashed, filling, scanned, solid; still when inactive.app-build-pulsethe ring on the step an App build is onThe two scope classes used to swap the entire palette. They no longer do; these four numbers are all that is left of them, and they are genuinely different — a fixed centred column under a floating header, against a full-width shell beside a rail.
The layout arithmetic each surface needs — the one thing the two scope classes still carry.
not defined hereThe centred measure.
—
not defined hereHeight the floating nav pill occupies.
—
not defined hereWhere sticky children start under the header.
—
not defined hereThe marketing kit's content width, gutters excluded. 80rem (1280px); BandInner adds --mk-gutter either side.
—
not defined hereThe hanging header's widest. 85rem.
—
not defined hereThe hanging header's height. 65px.
—
not defined hereA kit band's vertical padding. 56px, then 112px from 768px up.
—
not defined hereThe side gutter around --mk-page. 20px, 48px from 768px, 80px from 1024px.
—
not defined hereThe kit's pill button. 56px.
—
not defined hereWhat a page subtracts from the viewport to fill the card exactly.
—
The primary is ink on ink: a vertical gradient from the lighter brand stop down to the brand, inside a foreground-coloured border, so the face reads as lit from above rather than flat. Pressing nudges a button down a pixel — except a menu trigger, whose popover would jump with it.
@spinrun/ui/components/buttonconnect is the one place a hue survives outside status: an OAuth handoff to somebody else's product, where blue is the convention people arrive with.
size · icon sizesFour text heights and four icon squares. The icon sizes tighten their radius with their box so a 24px square does not read as a circle.
@spinrun/ui/components/badgeA label on a thing. Not a status — a status is a StatusPill, because a status has a tone and a badge has a variant.
@spinrun/ui/components/kbdsolid owns its box, its size and its ink, for a key advertised on a surface. bare carries none of the three, for a key named inside someone else's line — including a dark one, which it must not repaint grey.
Press ⌘J to open the assistant, or / to search.
Fields get their edge from --input and their focus from a three-pixel ring in --ring. Nothing here carries a placeholder that a label should have carried.
@spinrun/ui/components/{input,label,textarea}A slug cannot contain a space.
@spinrun/ui/components/selectRequire approval
Destructive tools pause for a person.
@spinrun/ui/components/radio-groupOne answer from a short, closed set, every option in view. The checked mark is the accent, because a selected radio is a selection. Past four options, reach for Select.
@spinrun/ui/components/checkboxAny number of answers from a short set, each one on its own. Checked is the accent, as a selected radio is. A set of them is a fieldset with a legend, one Label per box.
@spinrun/ui/components/input-groupOne field with something attached: an icon, a unit, a button, a key hint. align takes inline-start, inline-end, block-start or block-end — the block pair stacks the addon above or below the control instead of beside it.
How content is grouped. Panel for a product surface, Card for the shadcn recipe, PanelGrid when a set of cards has to read as one board.
@spinrun/ui/components/panelA panel's edge is one shadow — a hairline ring and a whisper of lift — so it carries no border and steals no layout. A grid's rules come from a 1px ring on every cell over a zero gap, so two neighbours never double up and the empty half of the last row below draws nothing at all.
@spinrun/ui/components/tabsTabsList variant: default is a filled track, line is a bare row for a page-level switch that must not read as a control. orientation='vertical' reaches Radix: Up/Down move between the tabs and the list says aria-orientation='vertical'.
@spinrun/ui/components/tableNumbers are tabular and right-aligned; the header is the only row that is not the same weight as the body.
| Agent | Status | Runs | Credits |
|---|---|---|---|
| Weekly digest | Active | 128 | 4.10 |
| Lead researcher | Pending | 12 | 0.90 |
| Invoice sweep | Draft | 0 | 0.00 |
@spinrun/ui/components/{accordion,collapsible}Accordion when the items are a set and one at a time is the point; Collapsible when one block simply folds.
@spinrun/ui/components/separatorA Separator is a drawn line inside one card. Between sections of a page, use a dotted rule instead — see Lines below.
@spinrun/ui/components/sidebarThe app rail's vocabulary, out of its shell. The whole assembly — provider, rail, inset, collapsed state, mobile sheet — is composed once by PlatformShell in apps/web/src/components/platform/shell.tsx; what is worth seeing here is the row: a button, its active state, and a count.
Colour means state and nothing else, and there are exactly five states worth distinguishing. A caller maps its own vocabulary — expired, revoked, never run — onto a tone; the tone owns the colour.
@spinrun/ui/components/status-pillneutral and muted sit on --canvas, which is defined inside .site-app. On a marketing surface use the three coloured tones or wrap the region.
successworking, healthy, connected
warningwaiting on someone
destructivebroken
neutrala state, not coloured
mutedthe absence of a state
@spinrun/ui/components/sparklineA trend beside a number, not a chart. No axes, no legend, no charting library — the figure next to it already says how much, so this says only which way. It strokes in currentColor and stretches to its box, so the caller owns both the tone and the aspect ratio. A flat series draws down the middle, because a line along the floor reads as a decline to nothing rather than as unchanged.
risingfallingflat@spinrun/ui/components/working-indicatorThe gap between pressing Enter and the first token is the whole first impression of the surface, and it used to be blank. A small monochrome orb whose verb follows the turn reads as work without pretending to know how much is left; the counter answers the only question a person actually has. The orb is thinking-orbs, pinned to the light theme.
@spinrun/ui/components/logo-loaderA stroke tracing the mark's own route, clipped to the mark. Three sizes; LogoLoadingScreen is the same loader centred with a label.
@spinrun/ui/components/logoThe lockup, arranged from BrandMark and Wordmark and drawing nothing of its own. LogoMark is the mark alone, size-6 unless the caller sizes it; the old name is kept for the product call sites that reach for it.
@spinrun/ui/components/toolkit-logo · toolkit-tileAn app's vendored mark. ToolkitLogo is the bare mark at 32px; ToolkitTile is the apps directory's rounded, bordered tile with the mark lazy-loaded inside. With no mark, or one that fails to load, each draws a monogram instead (one letter, or the tile's two).
@spinrun/ui/components/step-timelineOne timeline for both harnesses. A long turn folds to its summary — a person scanning it wants the count and the shape of the work, not forty rows — and the icons stack until they overflow into a +N. A short one does not: `autoCloseAbove` is the line between a list worth reading and a wall worth hiding, and the chat draws it at four.
@spinrun/ui/components/skeletonShape first. A skeleton that does not match the row it stands in is a second layout the reader has to learn and then unlearn.
@spinrun/ui/components/empty-stateA dashed box, never a bare sentence: the surface has to look like it is waiting rather than broken. The icon is drawn large and faint.
A run appears here the moment this agent is triggered — by a schedule, a message, or you.
@spinrun/ui/components/{tooltip,hover-card}A tooltip names a thing in a handful of words. A hover card is for a preview with structure in it — and neither may hold something a person needs, because a pointer is not a promise.
@spinrun/ui/components/avatarInitials, not a coloured hash. AvatarGroup overlaps and AvatarGroupCount closes the row; AvatarBadge carries presence.
Organic mosaic gives image creation a visible shape and dissolves into the result. Galleries follow their container, with one animated activity signal per turn and a still grid for reduced motion.
@spinrun/ui/components/media-gallerySingle cards cap at 520px. Groups use two columns from a 480px container width.
@spinrun/ui/components/media-generation-frameSix surfaces that open over the page, plus the toaster. Radix portals every one of them to document.body — outside .site-app — so their copy uses root tokens and not the ink set.
@spinrun/ui/components/alert-dialogThe confirmation in front of something that cannot be undone. It is a Dialog that a click outside does not dismiss, and Radix focuses Cancel first. The action says what it does, in the destructive variant when it deletes.
@spinrun/ui/components/calendarSingle dates and date ranges; two months share one selection.
@spinrun/ui/components/checkboxAn independent selection in a list or filter.
@spinrun/ui/components/dialogA decision that has to be answered before anything else continues. Header, body, footer — and the footer runs cancel then confirm, so the confirm sits under the thumb.
@spinrun/ui/components/navigation-menuThe site header's menus. One viewport morphs between panels of different sizes, so moving from one menu to the next glides rather than blinks. A plain link takes navigationMenuTriggerStyle so it sits on the same line as the triggers.
@/components/apps/composio-disclosureShown before every hop to Composio's hosted connect page: who is asking, who holds the credential afterwards, and how to undo it. Continue opens the popup inside the click, so the browser treats it as a user gesture.
@spinrun/ui/components/sheetA drawer for detail beside the thing it belongs to. side takes top, right, bottom or left; right is the default and the one the product uses.
@spinrun/ui/components/dropdown-menuActions on a row. Items, checkbox items, sub-menus and shortcut hints; a destructive item comes last, under a separator, so it is never the one you hit by momentum.
@spinrun/ui/components/popoverA small surface anchored to what opened it, for a fact or a two-field form. Not for a decision — that is a Dialog.
@spinrun/ui/components/commandThe palette. Inline here so both halves are visible at once; in the product it is a CommandDialog on ⌘K.
@spinrun/ui/components/sonnerMounted once by PlatformShell and called as toast(…) from anywhere. theme is stated, not asked — see docs/design.md §12.
What replaced alternating dark and light bands: a dotted rail, a dotted rule, and a diagonal hatch behind grouped content.
@spinrun/ui/components/dashed-lineAbsolutely positioned, so it needs a positioned parent. Both axes, a pitch, and a fade at whichever ends it runs to. A solid 1px border reads as a box; this reads as a drafting guide.
@spinrun/ui/styles/utilities.cssThe hatch sits behind a panel's content, above its background, and takes its density from --pattern-fg. The dotted rule is the horizontal companion to the page rails.
Grouped content, on texture.
@spinrun/ui/components/blob-mascotThe one component in the package that animates in JS, so the one that calls useReducedMotion() — the CSS guard in tokens.css cannot reach a motion/react transform.
Composed from the outside in: the shell owns the scope and the viewport, PageFrame owns the measure, PageHeader owns the top of a page. On marketing, Section owns a band.
@spinrun/ui/components/page-headerThe title and its actions on one row — the quieter secondaryAction, then the dark primaryAction at the right edge — and the description under both at the full measure. On a narrow window the actions wrap under the title, still above the sentence.
What your agents run on a schedule, and everything they have been asked to do.
@spinrun/ui/components/filter-barThe bar under a list page's header. Search on its own row; then the dropdown filters, each with a count chip, and a violet edge once one narrows the list; the grid/list switch and the sort menu at the right. Every value is the page's, so a filter can set state or ask the router.
@spinrun/ui/components/starter-gridWhat a New … dialog opens on. One button per card: an icon tile or an illustration across the top, a title, one line, the apps it uses with +N, and an Installed or Team badge. The chevron only says the whole card is the target.
Templates
@spinrun/ui/components/page-framemax-w-7xl, px-4 sm:px-6 lg:px-8, pt-8 pb-16. A list page wraps itself in this because the Agents-mode shell deliberately does not pad its content — the agent workspace wants every pixel.
@/components/platform/shellNot in the package: it knows about the app. It owns the .site-app scope, the pinned viewport — h-svh plus overflow-hidden, which is what keeps the rail still while only content moves — and two variants: card for Gateway, where the content floats as a card on the tint, and flush for Agents, where pages wrap themselves.
@/components/marketing/sectionThe marketing band. divider draws the dotted rule at the seam with the section above, tone drops the ground half a step or paints the ink panel, pattern puts the hatch behind the content. Highlight is deliberately rectangular and deliberately rare — one per page, on the phrase carrying the claim.
tone=plaintone=mutedtone=brandThe scoped Pages catalogue and compact output cards use the existing Gellix and Ulm faces. Document content selects its own report theme; this chrome stays consistent.
apps/web/src/components/pages/pages-reference.cssScoped to .pages-reference and .output-reference; other product surfaces keep their existing tokens.
Ulm 30/32 headings, Gellix 14/20 body, 6px controls, 8px cards. This scope follows dialog and menu portals.
The reusable section kit the marketing site composes from — grounds, type, buttons, glass, cards, bands, heroes and blocks — on one system: the marketing palette, its gradients, its measures and its motion, every value a token in tokens.css. Every word, image and vignette here is Spinrun's own.
@/components/marketing/kit/displayThe kit's text jobs, all in Urbanist at 700 from the register itself, never a class. Display is the page's one h1 (text-hero, 64 → 80, 1.1, −0.06em); Statement a section's headline (text-statement, 40 → 48); Heading any other on the six mk-h registers (80 · 64 · 48 · 40 · 32 · 24). A title is a string or segments: accent words in the heading gradient (or -alt, peach, violet), a 32px inline icon, a line break. Lead is 18/25.2 in --mk-secondary, 512px wide. Kicker is the violet eyebrow chip — 12/16 violet-500 on violet-50, padded 2/8, or 4/16 over a hero — or a caps line in violet-700.
The marketing kit
Over a hero
For agencies
h1
h2
h3
h4
h5
h6
Kickers, headlines and leads all read from the same scale, so a hero and a section three screens down still look like the same page.
@/components/marketing/kit/pill-buttonFour fills, two heights, a hairline border on each: primary (ink) and secondary (white, ink/10 border) on a light ground; on-dark (white) and ghost-dark (white/20 border) on an indigo one. lg is 56px with 40px sides, sm 40px with 24. Hover fades primary and on-dark to 80%; a press drops 1px; focus is a 3px violet ring. The first kit's names still draw these — ink → primary, outline → secondary, light → on-dark, md → sm — and ghost stays its arrowed text link.
@/components/marketing/kit/bandA full-bleed ground: paper, beige, beige-light, the two flat indigos, or any --gradient-* token by name — the same vocabulary SplitHero, GradientCard and Slab take. A dark ground turns its text white, re-points --muted-foreground at white 75% and sets the private --mk-lead switch Lead reads, so a Lead or the wordmark follows without a prop; a light surface inside (a glass face, a neumorphic card, a white window) takes ON_LIGHT, which resets --mk-lead and --muted-foreground and gets both greys back. corners rounds the named edges at 36.4 (19.6 below 640px); tone is the first kit's name (ink is the navy).
paper
beige
beige-light
navy
navy-cta
hero-bowl
top-band
dark1
dark2
dark3
dark4
dark4-warm
purple-1
purple-3
radial-warm
tone="ink" — the wordmark and this lead both read the band’s own tone, with no prop of their own.
@/components/marketing/kit/glass-frameThe glass bezel a mockup sits in, in four layers: a frosted fill over a 6px backdrop blur, a 1px diagonal white stroke (60% at two corners, clear between), the same stroke at 4px blurred σ5, and a 4px white rim blurred 4px just outside the edge — then a white face inset 7–15px at radius − inset, or on a rung with faceRadius (the product hero's 38/16 frame holds a 24 face). radius='full' is a pill: the strokes take its measured height. Gradient and filter ids come from useId, so any number of frames can share a page.
@/components/marketing/kit/gradient-cardGradientCard is a panel on any ground (radius 32 by default), with two warm layers on request: cap, peach at the top centre; rim, peach toward the edges. NeumorphicCard (neumorphic-card) is the soft white tile — radius 36.4, padded 32, all its depth in --shadow-mk-neu's four inset stops — pressed out of a beige page.
Credits
Spent only when a run does work.@/components/marketing/kit/slabThe violet slab: a content-width panel with 100px corners (64 from 768px, 40 on a phone), padded 80, on dark1 by default — the shape a band of glass cards sits in on the product pages. Its children stack in a centred column 40px apart (64 from 640px).
@/components/marketing/kit/word-rotatorThe phrase that cycles in place: a clip-path box sized to the longest phrase, the outgoing one sliding up and out as the next slides in (0.6s, cubic-bezier(.77,0,.175,1)), both briefly blurred, one step every 1.75s. Pauses off-screen and with the tab hidden; reduced motion holds the first phrase. accent='gradient' paints the phrases in the heading gradient, clipped to each one as it moves.
@/components/marketing/kit/split-heroThe page-opening hero: the hero-bowl ground (beige above, the indigo bowl rising behind the media's lower half), a 1.15fr/1fr split 56px apart, the h1 left and the lead and actions right, the media in a GlassFrame below, and an optional row under it (the logo drum) drawn only when given. size='page' is the subpage hero: text-hero-page on the top-band ground. variant='product' is the product pages' hero: the product-diagonal ground with rounded bottom corners, the columns bottom-aligned, a 440px lead, pills 12px apart, the media in an 840px 38/16 frame.
Spinrun
Connect once, and every agent reaches the same governed set of tools — no separate keys, no separate audit trail.
Spinrun
Claude, ChatGPT, Cursor and your own agents reach the same tools, under the same rules, in one log.
@/components/marketing/kit/feature-blockThe two-column feature behind the home page's oversight and workspace sections: a 474px text column (eyebrow, the h3-register headline with segments, a 16/22.4 body, bullets 16px apart each after a 24px icon slot, the primary pill) beside an art panel on a named ground — --gradient-oversight by default, radius 40 (rounded-mk-panel) or 32 (panelRadius='card'), 600px tall from lg — holding whatever the page puts in it, here a GlassFrame, with an optional footer row pinned to its bottom. Columns are 1fr/1.2fr from lg, 48 apart (80 from xl); flip swaps them. Without a panel the first kit's framed media tile draws instead. The bullet icon and the panel's art are placeholders until the art system lands.
Approvals
An agent looks things up and plans without asking. By default, anything that writes to your apps waits for someone on the team to approve it.
Placeholder footer row
Connections
Sign in to an app a single time; every agent and every client behind the same gateway can use it, with no key of its own to manage.
@/components/marketing/kit/card-trioThe three pillars: white cards 413×514 at 1440, 20 apart, radius 32, no border; a centred text block (padded 32/24/0, 16 between lines) with a 24/26.4 title whose second line is in the heading gradient, then one 18/25.2 paragraph — lead in ink, body in the secondary grey; under it a 288px art panel, radius 32, flush to the bottom on a named ground (--gradient-pillar-a, radial-warm, --gradient-pillar-c in turn, or artGround). One column below xl. The art here is a neutral placeholder.
Sign in to an app once.
Every agent in the workspace reaches it, under the same rules.
Every plan includes approvals.
A step that changes something waits for a person, on every plan.
Every run has a transcript.
Each tool call and approval lands in one audit trail.
@/components/marketing/kit/step-cardsHow it works: cards 413×490 on beige-light (#fcfaf9), radius 32; a 413×288 art panel clipped by inset(0 round var(--radius-mk-card)) on dark4 (the peach glow over the indigo), then the text padded 24/32/32 — a badge slot (the art system's numbered tile; the step's numeral on dark3 until then) 12px before the 32/38.4 h5 title, and a 16/22.4 body. A step without art has no panel. Three across from lg; the call to action 64px below. variant='integrations' is the toolkit pages' card: white at radius 36.4 (19.6 on a phone), a 438px art top, the badge before a 24/32.4 h6 title.
Sign in to each app a single time; every agent can reach it from then on.
Tell it the job, what it may touch and where to post the result.
Approve the writes it proposes, then read the transcript.
Sign in once; the credential stays in the vault.
Reads run; writes to the app wait for a person by default.
Every client reaches it through one endpoint.
@/components/marketing/kit/statement-bandThe navy band under the home hero: a 66px Coin S tile, a statement at 64/70.4 (40 on a phone) whose last line rotates in peach, and the on-dark pill — 112px of air above and below, rounded only at the bottom, where the hero bowl's navy edge runs into it without a seam.
@/components/marketing/kit/cta-bandThe closing call to action on the CTA indigo: 140px above (64 on a phone), 40 below, rounded only at the top. One line of display type whose last words can be peach, a 16px lead at white 75%, then on-dark and ghost-dark pills. It comes in its own wrapper — 40px of white fading to beige behind the corners — unless the page owns that ground (blend={false}).
Start free, connect the tools you already use, and watch the first run happen.
@/components/marketing/kit/segmented-tabsThe comparison track: a full-width 56px pill on the track grey, padded 4, its tabs equal 48px columns — a 20px icon (muted until active) 16px before a 16/22.4 medium label. The ink pill slides under the active tab (transform .5s cubic-bezier(0,0,.2,1)), placed by arithmetic so the server and client agree; labels turn white over 300ms. variant='gradient' is the white neumorphic bar with a gradient pill (--gradient-tab-pill, a radial), width-capped. Below md the tabs are separate pills in a sideways-scrolling row; from lg a label too long for its column wraps to a second line inside its pill. Vertical is a topic list, a beige card beside the panel (over it, full width, on a phone: a column at every width, so Up/Down hold everywhere); tone='glass' is that list on a dark panel — a glass pill, white labels at 70% with a 20px icon, the active topic's line under it, and an aside (the panel's heading) above it. Built on Radix Tabs: arrow keys and roving focus come from the primitive, Up/Down on a vertical list.
Wire up a tool once and every agent can reach it — no separate key per person.
The catalogue of apps an agent can reach.
A long label wraps inside its pill from lg.
Scoped to the tools they actually need.
tone="glass"
Rules the model cannot talk its way past
@/components/marketing/kit/slot-wordThe pricing headline's reel: a window one item tall (clamp(4.75rem, 7vw, 5.5rem); 4.5rem below md), each item an optional 0.875em icon slot 20px before the word, the list written three times. Every ~1.48s it steps one window (0.6s cubic-bezier(.645,.045,.355,1)) while the width follows the word, blurring to 3.75px and dipping out and back as it travels; the edges fade by mask (10% / 15.81% / 95%). Pauses off-screen and with the tab hidden; reduced motion holds the first item. The icons here are placeholders.
Runs yourstatus updates (also rotates through: status updates, data pulls, access reviews, hand-offs)
@/components/marketing/kit/compare-columnsThe comparison: a SegmentedTabs track over one pair of cards — beige on the left (a 28px icon, a 24px name, an 18px line in the secondary grey), dark3 on the right (a 32px wordmark, a line opening with a violet-50 chip, art in the right 40%), 172px tall at radius 20 (rounded-mk-20), a 120px connector column between. The cards stay put; each scenario's words and art cross-fade inside them (400ms), the inactive layers inert. The row slides in once — left from −28px, right from +28px, 600ms after 120ms — or is placed at once under reduced motion. The connector, the icon and the art are placeholders.
A chat assistant
Writes a template you fill in yourself.
Notes posted in the team channel, built from yesterday's tickets.
@/components/marketing/kit/compare-columnsThe first kit's comparison, kept for the pages written against it: two lists of rows either side of a divider, sliding in on first view the same way.
@/components/marketing/kit/faq-sectionThe FAQ box: beige, radius 64 (squircle where the browser draws one), padded 64, the heading beside the questions at 519fr/630fr from 1024px. Each question is a white card (radius 36.4) that turns violet when open, its one chevron rotating 180° over 200ms; the first starts open. Questions past initialCount wait in a collapsed row that 'show all' — the secondary pill — opens over 700ms, each entering from a 12px blur 90ms after the last. The JSON-LD carries every item regardless. boxed={false} drops the box for a FAQ already standing on beige, its heading on the column's edge.
The short version of what people ask before they connect a tool.
No — a tool connected once is reachable by every agent behind the same gateway.
The short version of what people ask before they connect a tool.
No — a tool connected once is reachable by every agent behind the same gateway.
@/components/marketing/kit/comparison-tableTwo recipes. native (/compare): a real table, columns 32/34/34%, an 81px header row of 32px marks and names, cells padded 24/16/24/0 and top-aligned under 1px ink/8 rules, each value after a 24px mark — the active check for Spinrun, the inactive one for the other, drawn alike (placeholders here; null leaves a column unmarked); below lg the rows stack, each line marked by its column's own icon. highlighted (/for/*): 1104 wide, white, radius 32, columns 306/341/457, a capitals header, 80px rows with 36px wells (beige, violet-100), Spinrun's column in a 2px dark2 ring round on the right. Every value names its column for a screen reader.
| Compared on | Spinrun | A chat assistant |
|---|---|---|
| Runs in | The apps you connect, through one gateway. | A separate chat window. |
| Writing to your apps | Held until a person approves it. | Left for you to copy across. |
| Record kept | A transcript per run, step by step. | The conversation, if it is saved. |
Spinrun: The apps you connect, through one gateway.
A chat assistant: A separate chat window.
Spinrun: Held until a person approves it.
A chat assistant: Left for you to copy across.
Spinrun: A transcript per run, step by step.
A chat assistant: The conversation, if it is saved.
Where it stops: Answers you still have to act on.
Where Spinrun takes it: The work, done in your tools once you approve it.
Where it stops: Rules someone has to keep up to date.
Where Spinrun takes it: A brief an agent follows, with a transcript of every run.
Where it stops: One login passed around the team.
Where Spinrun takes it: One connection, reachable by every agent, revocable in one place.
Where it stops: Answers you still have to act on.
Where Spinrun takes it: The work, done in your tools once you approve it.
Where it stops: Rules someone has to keep up to date.
Where Spinrun takes it: A brief an agent follows, with a transcript of every run.
Where it stops: One login passed around the team.
Where Spinrun takes it: One connection, reachable by every agent, revocable in one place.
@/components/marketing/kit/tier-selectThe pricing card's dropdown, on @spinrun/ui's Select restyled by className: a 56px trigger, radius 25.2, a 1px ink/8 border and a 16px chevron at ink/45; the list 8px below, white, ink/10 border, radius 25.2, padded 8, shadow-mk-dropdown; options 40px at radius 19.6, 14px medium, a check slot leading the label and a detail (and optional peach badge) on the right; the selected one violet-50 with #5c28d7 words. Groups carry a capitals label. Plan names from lib/plans.ts; no prices.
@/components/marketing/kit/artifact-reelThe agencies hero's reel: glass cards (white/24 over a 21px blur, a white/32 border, radius and border scaled with the card — 36.35 and 1.5 at 450 wide) and shadow-mk-reel, with a white face whose top 46.71% is a preview on violet-50, dark4 or beige. The middle card is ×1.2 (×1 on a phone), its neighbours ×0.844 at half opacity; every ~4s the reel steps one card over 1.84s on cubic-bezier(.5,.1,.1,1) — vertically from xl, sideways below. Reduced motion holds the middle card. The previews are placeholders; the art system draws the real ones.
@/components/marketing/kit/sticky-listThe agencies workflows section: from xl, a header that sticks 112px from the top on the section's ground over a numbered list ruled by 1px ink/8 lines (each item 48px top and bottom: an 18px title after a 16px violet marker, a 16px body, an optional chip), and beside it a square dark2 panel, radius 36.4, padded 64, sticking too and showing the active item's panel. The active item is the one crossing the middle of the viewport; the others dim to 20% until hovered. Below xl each item carries its own panel. The panels here are placeholders.
What it takes on
Each item is a kind of task an agent can run, with every step logged and writes to your apps held for approval.
Labels each thread and drafts replies for a person to send.
DailyUpdates rows as tickets move, and notes what changed.
Writes with approvalFinds threads nobody answered and queues a nudge for approval.
ApprovalSearches what you have indexed and cites where each answer came from.
@/components/marketing/kit/testimonial-gridDefaults to data/social-proof.ts, which is empty — no quote ships without a real, permission-cleared attribution — so both render nothing until it has entries. The grid is one large dark4 card (693×533, radius 32, a white border, a 32/40 display quote) beside a column of beige cards (567×257, radius 32, padded 24). The marquee (testimonial-marquee) drifts glass cards 396 wide, radius 36.4, between 32px progressive-blur edges of eight stacked backdrop blurs (40 → 0.5px). Their layouts are covered by kit-c.test.tsx with fixtures that live only in the test.
@/components/marketing/kit/rating-rowRatingRow (a review site's 20px mark, five 16px stars in #ff492c, a 14/20 label, the whole row a link) reads RATING, which is null; LogoDrum (five 220×56 cells of 32px white-mask logos on the indigo band, 1×24 dividers at 15%, one cell turning 56px every ~1.5s) and LogoRow read CUSTOMER_LOGOS, which is empty. All three render nothing until Spinrun has a real rating and customers cleared to show.
@/components/marketing/kit/directoryThe integrations directory: a 74px white search field (radius 25.2, ink/8 border, padded 24/32, a 24px icon at 40%), 40px category chips 8 apart (the chosen one on dark2 with white words), and a grid 16 apart, four columns from lg. Category mirrors to ?category=; the search filters client-side, case- and diacritic-insensitive; every caller wraps it in Suspense. DirectoryCard is its card: 80px, white, radius 25.2, padded 16, a 48px beige well (radius 14) holding a 32px mark before a 16px name.
@/components/marketing/kit/directory-moreA directory's show-more: a CSS collapse over the Directory's results grid — cards six on a phone and twelve from md, tiles eight and twenty-four — so it follows the search and the chips without knowing either, and hides its button when the filtered grid already fits. The button is the kit's secondary pill (buttonClassName replaces it); one-way, like the FAQ's show-all. The label is passed translated.
Loading…
@/components/marketing/kit/az-indexThe A–Z index: an h3-register title, then letters 32px apart — each a 16px medium head in the secondary grey, in the display face — over a grid of 14px links in the same grey (two columns, three from sm, four from lg; gaps 8/24), turning ink and underlined on hover. Anything that isn't A–Z falls under '#'.
@/components/marketing/kit/vignettesSmall static UI illustrations drawn in HTML and CSS — no screenshots, no SVG artwork — for a CardTrio or FeatureBlock media slot. Every line of text is a prop.
Pulled the last 30 days of ad spend.
Flagged 3 campaigns over budget.
Send the weekly report to #ops
Posts to Slack once approved.
@/components/marketing/chat-mockupA scripted, clickable agent-in-your-chat demo, the home hero's media. Click a channel to switch its script; the skin toggle re-skins the whole mockup. Both skins shown here; a page picks one with defaultSkin.
Hey @Spinrun — what moved in our pipeline this week? Flag the deals I should focus on.
Hey @Spinrun — what moved in our pipeline this week? Flag the deals I should focus on.
Original illustration pieces drawn in code — glossy squircles, soft paper tiles, frosted glass, dashed connectors and glows — on the kit's gradients, radii and shadows, with Spinrun's Coin S or neutral shapes on every face. Every composition is generated or laid out in code, and the scattered ones are seeded so a server render and the client agree.
@/components/marketing/art/glossy-tileThe app icon of the art system: a 22% squircle on --gradient-dark3, a sheen across the top half, a light rim, depth pooled at the foot and an under-glow in --gradient-glow-avatar. The S by default, a white lucide glyph, or any child. One component from a 20px check to the 120px avatar, so the lighting scales rather than being re-tuned.
@/components/marketing/art/numbered-badgeA step's number on a 28px glossy tile, the numeral in Urbanist 700.
@/components/marketing/art/check-iconactive is a glossy tile with a white tick; inactive the same squircle as a grey outline. 20 and 24px, the same box either way so a mixed column stays aligned.
@/components/marketing/art/soft-app-tileThe paper counterpart of the glossy tile, carrying a real catalog logo through toolkitMark. soft: 135° off-white wash, near-white rim, shadow-mk-tile, radius 36.4 at 104. white: radius 14 at 44 and a lighter drop. glass: the glass frame's frost over a 6px blur and a white-30% rim, radius 24 at 82 — for a violet card. The row overlaps 32px white tiles by 6px.
@/components/marketing/art/hub-and-spokeSpinrun's S at the centre of six app tiles, each tied to it by a dashed curve (--mk-connector, 1.25, 3/4), a faint orbit, and blurred ghost tiles behind. Our own two-arc layout; shown on --gradient-radial-warm.
@/components/marketing/art/tile-constellationA seeded scatter of glossy and soft tiles at different depths — size, blur and opacity follow depth — with a crisp S tile in front. Each tile floats on its own slow loop; reduced motion leaves them where they were drawn. Shown on --gradient-dark1. layout='cluster' is the credential card's: eighteen glass app tiles, 56 to 80px, close-packed in rows of 3, 4, 4, 4 and 3 round a 96px S — the apps nearest the S first, a glyph past the list — on a fixed 480px board; shown here on dark4-warm at 60%.
@/components/marketing/art/frosted-cardGlass on a gradient panel — --mk-hairline rim, radius 22, white/35 over a 12px backdrop blur, shadow-mk-soft, 8px inset — holding a white face at radius 16. The frame a chat, checklist or report vignette sits in.
@/components/marketing/art/glyph-watermarkThe Coin S as texture: a blurred bezel, a radial fill lit from the top-left and a diagonal edge stroke, drawn at 10–30% (clamped). White on the indigo panels, violet on beige.
@/components/marketing/art/wave-art · line-artPreview tops for segment and artifact cards: seeded gradient curves with bubbles, and the abstract of a report — heading, lines, a status pill, bars and a trend — with no words or figures in it. Four fields: violet, peach, beige and the dark4 indigo.
@/components/marketing/art/connector-line · avatar-glowThe integrations hero's pairing: a 4 × 56 rule on --gradient-connector, a bead running down it, dropping into a 120px glossy S tile in a 196px --gradient-glow-avatar halo blurred 24px. Reduced motion stills the bead and the halo. caps='dot-ring' ends the line with a pair of caps: an 18px navy dot on the top end, a 16px peach ring over the foot.
Chat compositions over the existing Spinrun primitives: sourced cards, comparisons, charts, preferences and linked places.
apps/web/src/components/chat/rich-uiSample data; preferences and itinerary edits in this catalogue are local demonstrations.
Interactive answer specimen · sample comparison values. Changes stay in this demo.
Ten of them. Three are enforced by packages/ui/src/boundaries.test.ts rather than by review — the raw-swatch ratchet catches the first two, and the no-second-copy assertion catches the seventh. The rest are on you.
bg-white, no text-gray-500, no bg-emerald-100. The frozen baseline in the package’s boundary test only ever shrinks.dark: half. The product is light-only; the variant exists so the primitives’ own utilities still compile.shadow-[var(--shadow-tile)].bg-canvas, text-ink-2 or --radius-card outside .site-app — including inside an overlay, which Radix portals to the body.Panel rather than Card on a product surface: --shadow-panel is the app’s edge.Kbd, StatusPill and BrandMark / Wordmark instead of a seventh spelling of each.npx shadcn@latest add <name> -c packages/ui, from the repo root. Never into an app.useReducedMotion() if you animate in JS. The CSS guard cannot reach you.text-warning on bg-warning-tint is 2.13:1. The -ink grade is what a pill, a callout and an error line are set in.Read next
docs/design.md — the written source of truth: every token with its role, the inventory table this page is checked against, and the do-and-don’t list in full.docs/architecture.md — one app and unified Settings, the package graph, the boundary tests as enforced contracts.packages/ui/src/styles/tokens.css — the definitions, with the reasoning in the comments.We would like to switch on analytics, so we can see which pages are worth keeping. Nothing has loaded yet and nothing will until you choose. The cookies that keep you signed in and remember your language are not part of this.
Read the Cookie Policy