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.

ClassDefaultHover
.btn--primaryDark bg, offwhite text, dark borderTeal bg, dark text, teal border
.btn--outlineTransparent bg, dark text, dark borderTeal bg, offwhite text, teal border
.btn--whiteOffwhite bg, dark text, invisible borderTeal 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)).

One primary per context. Using more than one .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

PropertyValueToken
Font size1rem--text-base
Padding0.618rem 1.618rem--space-sm / --space-lg
Font weight600
Border2px solid
Border radius0.375rem
Hover lifttranslateY(-2px)
Hover shadow0 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.

Bordered card

Border shifts from neutral to teal on hover. Lifts 2–3px. Used for navigational cards.

Another card

Hover both to compare the consistent lift and border treatment.

ComponentBorderHover
.blog-card2px solid --color-borderTeal border + translateY(-3px) + shadow
.docs__card2px solid --color-borderTeal border + translateY(-2px) + shadow
.resource-card1.5px solid --color-borderTeal 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.

Feature card

Hover to see the title go teal and the underline grow from center.

Another feature

No border at rest. The full hover suite signals interactivity without chrome.

ElementDefaultHover
CardNo borderGradient 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); }
Why no border? Feature cards appear in sparse homepage sections with three items in open space. A border would add visual noise with nothing to separate. The multi-element hover suite (title, icon, underline) provides ample interactivity signal without chrome. Bordered cards are for information-dense grids where the border makes each card read as a discrete item. Both patterns navigate; the choice is about density and visual weight.

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.

PreviousStep 1: Prepare NextStep 3: Set the Target
StateBorderTextChevron
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: pager, external link indicator
Caret: nav dropdowns

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/height attributes 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
Icons should be rare. The site uses two. An icon alongside text rarely adds more than the text already communicates; it adds visual noise instead. Use icons only where they add meaning text alone cannot convey (the chevron implies direction) or where space makes text impractical.

Hover vocabulary

Four patterns across the site. Pick the one that matches the element’s nature; never mix patterns on the same component type.

PatternCSSUsed on
LifttranslateY(-2–3px) + drop shadowAll bordered cards, buttons
Teal borderborder-color → --color-primaryAll bordered cards
Teal tintbackground: rgba(--color-primary-rgb, 0.07)Sidebar nav items, table rows
Gradient underlinebackground-size: 100% 2pxFeature cards
Color shiftcolorvar(--color-primary)Header nav, footer nav
Underlinetext-decoration: underlineProse links (always), mobile stream headings
BF color shift--bf-color--bf-color-lightBusiness 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);