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.
.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.
.card--stat renders:<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.
.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.
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
| Class | Role |
|---|---|
.card | Base — borders, padding, background |
.card--system | Variant: thumb + body |
.card--stat | Variant: label + big value + sub |
.card--tile | Variant: compact title + meta + count |
.card--placeholder | Empty-state styling overlay |
.card--hoverable | Opt-in hover on non-anchor cards |
.card-grid | Wrapper · responsive auto-fit grid |
.card-grid--narrow | Tighter min-width for stat tiles |
.card__thumb | Thumbnail container |
.card__thumb--empty | Empty-state thumb (no image) |
.card__body | Content container |
.card__title | Main heading |
.card__sub | Subtitle |
.card__label | Stat label · uppercase mono |
.card__value | Stat big number |
.card__meta | Tile meta · $/count line |
.card__count | Tile count badge |
.card__status | Status chip |
.card__last | Last-touched line |
.card__bar / .card__bar-fill | Progress bar inside .card--stat |
Cross-references
- patterns.gf.cx — the portable-patterns surface that catalogs why this primitive exists
- media.css — sibling primitive for media-sizing
- page-header.css — sibling primitive for breadcrumb + kicker + h1 typography