primitive · cards · v0.1.0

One card primitive, three variants, every surface.

The card pattern used across every gf.cx surface — system landings, status dashboards, dense category tiles — extracted into one CSS file. Import once, write semantic markup, the look is consistent without per-surface tuning.

Import

<link rel="stylesheet" href="https://assets.gf.cx/cards/cards.css">

The three variants

.card--system · thumb + body

Top-level landings where each card introduces a system / subject with a photo, headline, sub, status, and last-touch.

↓ how .card--system renders:
<div class="card-grid">
  <a class="card card--system" href="/foo/">
    <div class="card__thumb">
      <img src="/foo/photo.jpg" alt="Full descriptive sentence per SEO-rewrite pattern">
    </div>
    <div class="card__body">
      <h2 class="card__title">Foo <em>Bar</em> Subsystem</h2>
      <p class="card__sub">Make / model / serial · purchased Jun 2020</p>
      <span class="card__status">In service</span>
      <p class="card__last">Last touched 24 May 2026 · steward action TBD</p>
    </div>
  </a>
</div>

Empty-state thumb variant — use .card__thumb--empty when a photo hasn't been captured yet.

.card--stat · label + big value + sub

Status dashboards, savings rollups, enrichment previews — anywhere you're surfacing one number per tile.

↓ how .card--stat renders:
Completed
5,669
of 5,669
Date span covered
2022 — 2025
Jul 2022 → Nov 2025 · 4 years
Total saved
$1,184
vs sticker · 14% net
<div class="card-grid card-grid--narrow">
  <div class="card card--stat">
    <div class="card__label">Completed</div>
    <div class="card__value">5,669</div>
    <div class="card__sub">of 5,669</div>
    <div class="card__bar"><div class="card__bar-fill" style="width:100%"></div></div>
  </div>
</div>

.card--tile · compact title + meta

Category landings, year breakdowns, dense lists — each tile is mostly text with a single $ / count line.

↓ how .card--tile renders:
<a class="card card--tile" href="/amazon/tags/brand-apple.html">
  <h3 class="card__title">Apple</h3>
  <p class="card__meta">$19,613 · 294 items</p>
  <span class="card__count">brand</span>
</a>

Theming

The CSS reads :root custom properties with sensible defaults. If your page already defines the portfolio palette (--bg-card, --accent, --line-strong, etc.), cards.css picks those up automatically. Otherwise the defaults take over.

:root {
  --card-accent:  #b85c00;   /* override accent on this surface */
  --card-grid-min: 200px;    /* tighter grid */
}

Hover behavior

Anchor-cards shift their border to --card-accent on hover. No transform — the 1px shift was flagged as jarring on portfolio surfaces (2026-05-21 feedback). To opt-in for hover on non-anchor cards, add .card--hoverable.

Thumb images on .card--system start grayscale-with-contrast and restore color on hover — the portfolio's grayscale-thumb pattern, baked in.

Print

Cards flatten to bordered boxes with no background, thumb images preserve grayscale, and each card gets page-break-inside: avoid.

Kicker numbering · singleton vs siblings

The small uppercase kicker line above a card title (.card__label or equivalent) signals the card's category. Drop the trailing · N count when there is only one card of that category in the grid. The numbering only earns its place when 2+ siblings need disambiguating.

<!-- Singleton — bare category, no count -->
<div class="stack-card__kicker">Filtration</div>

<!-- Two-or-more siblings — disambiguate with a qualifier (preferred) -->
<div class="stack-card__kicker">Consumable · chlorine</div>
<div class="stack-card__kicker">Consumable · pH down</div>

<!-- Two-or-more siblings, no natural qualifier — fall back to · N -->
<div class="stack-card__kicker">Source · 1</div>
<div class="stack-card__kicker">Source · 2</div>

Classes

ClassRole
.cardBase — borders, padding, background
.card--systemVariant: thumb + body
.card--statVariant: label + big value + sub
.card--tileVariant: compact title + meta + count
.card--placeholderEmpty-state styling overlay
.card--hoverableOpt-in hover on non-anchor cards
.card-gridWrapper · responsive auto-fit grid
.card-grid--narrowTighter min-width for stat tiles
.card__thumbThumbnail container
.card__thumb--emptyEmpty-state thumb (no image)
.card__bodyContent container
.card__titleMain heading
.card__subSubtitle
.card__labelStat label · uppercase mono
.card__valueStat big number
.card__metaTile meta · $/count line
.card__countTile count badge
.card__statusStatus chip
.card__lastLast-touched line
.card__bar / .card__bar-fillProgress bar inside .card--stat

Cross-references

cards.css · v0.1.0 · first shipped 2026-05-24 · specimen page 2026-05-30