Components
Buttons
Three variants share a single base class (.btn) and the same hover transformation: teal background fill. The text color on hover differs, not arbitrarily, but because each variant inverts its default text color.
The flip rule: .btn--primary starts with offwhite text on a dark background; on hover it flips to dark text on teal. .btn--outline and .btn--white start with dark text on a light base; on hover they flip to offwhite text on teal. Every button’s foreground and background both change on hover, making the state change unmistakable regardless of ambient color.
| Class | Default | Hover |
|---|---|---|
.btn--primary | Dark bg, offwhite text, dark border | Teal bg, dark text, teal border |
.btn--outline | Transparent bg, dark text, dark border | Teal bg, offwhite text, teal border |
.btn--white | Offwhite bg, dark text, invisible border | Teal bg, offwhite text, teal border + teal glow shadow |
All three also inherit from .btn:hover: translateY(-2px) lift and a dark drop shadow. .btn--white overrides the shadow with a teal-tinted glow (rgba(--color-primary-rgb, 0.4)).
.btn--primary in a single view dilutes hierarchy: the user's eye has no obvious anchor. Secondary actions use .btn--outline. Never override button colors per-context.Button spec
| Property | Value | Token |
|---|---|---|
| Font size | 1rem | --text-base |
| Padding | 0.618rem 1.618rem | --space-sm / --space-lg |
| Font weight | 600 | — |
| Border | 2px solid | — |
| Border radius | 0.375rem | — |
| Hover lift | translateY(-2px) | — |
| Hover shadow | 0 6px 16px rgba(0,0,0,0.12) | — |
Cards
Two distinct card patterns. Both navigate; the difference is density and visual context, not function.
Bordered cards
Used for: blog cards, docs section cards, resource cards, and docs pager links. All share the same hover behavior: border turns teal, slight lift.
Border shifts from neutral to teal on hover. Lifts 2–3px. Used for navigational cards.
Hover both to compare the consistent lift and border treatment.
| Component | Border | Hover |
|---|---|---|
.blog-card | 2px solid --color-border | Teal border + translateY(-3px) + shadow |
.docs__card | 2px solid --color-border | Teal border + translateY(-2px) + shadow |
.resource-card | 1.5px solid --color-border | Teal border + translateY(-2px) + shadow |
Feature cards
Used on the homepage. No border. Hover triggers three simultaneous transitions: the title shifts from dark to primary teal, the icon scales up and deepens to mid-teal, and the bottom gradient underline grows from center. The paragraph text also darkens slightly.
Hover to see the title go teal and the underline grow from center.
No border at rest. The full hover suite signals interactivity without chrome.
| Element | Default | Hover |
|---|---|---|
| Card | No border | Gradient underline (center → full width) |
h3 | --color-dark | --color-primary |
| Icon | --color-primary, 1× | --color-mid, scale(1.15) |
p | --color-text-light | --color-text |
The gradient underline matches the section title accent, tying the cards into the page structure. Icon placement is intentionally below the title (unconventional) to read icon as punctuation rather than label.
/* Bottom underline */
background-image: linear-gradient(90deg, transparent, var(--color-primary), transparent);
background-size: 0% 2px; /* rest */
background-size: 100% 2px; /* hover */
/* Title */
.feature-card h3 { transition: color var(--duration) var(--ease); }
.feature-card:hover h3 { color: var(--color-primary); }
/* Icon */
.feature-card:hover .feature-card__icon { color: var(--color-mid); transform: scale(1.15); }
Pager links
Docs pages use prev/next pager links at the bottom of the content. Bordered card shape; hover lifts and shifts border to teal. No underline.
| State | Border | Text | Chevron |
|---|---|---|---|
| Default | --color-border | --color-dark | --color-primary, stroke-width: 1.5 |
| Hover | --color-primary | --color-dark | --color-primary, stroke-width: 2.5 |
Specificity note: the pager <nav> is rendered outside <article class="docs__body content prose"> — a sibling, not a descendant. The .prose a rule does not reach it. The only global rule to beat is a:hover { color: primary; text-decoration: underline } in base.css at 0-1-1. Simple class selectors win:
.docs-pager__link { ... color: var(--color-dark); text-decoration: none; } /* 0-1-0 > a 0-0-1 */
.docs-pager__link:hover { ... color: var(--color-dark); text-decoration: none; } /* 0-2-0 > a:hover 0-1-1 */
Never add .docs__body.content to the ancestor chain — the pager is outside the article, so that selector never matches.
Icons
Two stroke-based SVG icons used across the site. Both use fill="none" stroke="currentColor", inheriting color from their parent and working on any background.
Rounded chevron (right): copy this into a template:
<svg width="8" height="12" viewBox="0 0 8 12" fill="none"
stroke="currentColor" stroke-width="1.5" stroke-linecap="round" aria-hidden="true">
<path d="M0.5 0.5 L4.5 4 Q7.5 6 4.5 8 L0.5 11.5"/>
</svg>
Caret (down): copy this into a template:
<svg width="12" height="9" viewBox="0 0 12 9" fill="none"
stroke="currentColor" stroke-width="1.5" stroke-linecap="round" aria-hidden="true">
<path d="M0.5 0.5 L4 4.5 Q6 8 8 4.5 L11.5 0.5"/>
</svg>
Both tips are quadratic bezier curves (Q), which gives them the rounded corner.
Rules:
- Always include
aria-hidden="true"on decorative icons - Set size with
width/heightattributes on the<svg>, not with CSS font-size - Never hardcode a color.
stroke="currentColor"means the parent controls it. - Focus indicators go on the parent
<a>or<button>, not the SVG
Hover vocabulary
Four patterns across the site. Pick the one that matches the element’s nature; never mix patterns on the same component type.
| Pattern | CSS | Used on |
|---|---|---|
| Lift | translateY(-2–3px) + drop shadow | All bordered cards, buttons |
| Teal border | border-color → --color-primary | All bordered cards |
| Teal tint | background: rgba(--color-primary-rgb, 0.07) | Sidebar nav items, table rows |
| Gradient underline | background-size: 100% 2px | Feature cards |
| Color shift | color → var(--color-primary) | Header nav, footer nav |
| Underline | text-decoration: underline | Prose links (always), mobile stream headings |
| BF color shift | --bf-color → --bf-color-light | Business function links on model pages |
Touch devices (@media (hover: none)): lift, opacity transitions, and grayscale filters are suppressed. Elements that rely on hover for visual interest carry their state in their default appearance: blog cards get the primary border color pre-applied, sponsor logos show at full color and opacity.
All transitions use the shared tokens:
transition: property var(--duration) var(--ease);