Portfolio · card deep-dive · v1 · 2026-04-12

Card component
layout options

Deferred from v5 item 4. This doc answers the card-level questions that v5 couldn't cover without derailing the accessibility + critique pass: variant structure (one component or many) and imagery placement. Scope is the card component as a whole, across every surface it ships on — not just the writing variant.

Usage surfaces in scope: landing 2×2 case study grid, landing Writing zone, /writing index, /work index, and the related-work footer inside case study bodies. Density is locked to v5's moderate baseline (three proximity groups). Imagery is optional per Q2 — work cards use screenshots or diagram fragments, writing cards use big numerals or gradient washes.

One component, variants via props

Despite the work/writing register split and five usage surfaces, there's enough structural overlap to justify a single <Card> Astro component with a props API. The alternatives duplicate ~70% of the markup and CSS for differences that are mostly presentational. A prop-driven component lets register and imagery flex independently while keeping the valid combinations explicit. Featured becomes a boolean flag, not a third type.

Proposed API — src/components/Card.astro

// Single component; register + imagery + featured are orthogonal props
interface Props {
  /** Register — drives kicker colour, left rail, arrow colour, tag styling */
  register: "work" | "writing";

  /** Featured — adds gradient border + "FEATURED" pin. At most one per page. */
  featured?: boolean;

  /** Content — always required */
  kicker: string;                      // "Selected work · 01"
  title: string;
  description: string;
  tags: string[];
  href: string;

  /** Optional imagery — if omitted, card is typography-only */
  image?:
    | { kind: "screenshot";    src: string; alt: string }  // work
    | { kind: "diagram";       src: string; alt: string }  // work
    | { kind: "numeral";       content: string }           // writing ("01", "02"…)
    | { kind: "gradient-mark"; motif?: "rules" | "grid" };  // writing
}

// Valid combinations (enforced via narrowing, not brute validation):
//   register "work"    + image.kind "screenshot" | "diagram" | undefined
//   register "writing" + image.kind "numeral"    | "gradient-mark" | undefined

Why not two components? The work/writing split lives in three places only: kicker colour, left rail presence, arrow colour. Padding, proximity, title font, tag shape, hover behaviour are identical. Splitting into WorkCard/WritingCard just means two files drifting apart. Why not modifier classes? Astro component boundaries are stronger than CSS class discipline. Props make "featured writing card with a numeral" either valid or invalid at the type layer; class-based modifiers make it look possible in CSS even if it's meaningless.

Six layouts for optional imagery

Each option shown twice: left card is work register with a screenshot mock; right card is writing register with a numeral or gradient wash. The mocks are CSS-drawn — no real assets — so the question is layout, not craft. Tradeoffs listed under each. Summary table and recommendation at the bottom.

A
Top third — image above, content below
classic blog tile · familiar · image-dominant

The most familiar web pattern. 160px image strip at the top, full content block below. Every card with imagery reads as an editorial tile; cards without imagery read as plain text blocks. The tradeoff: mixing image and non-image cards in the same grid creates two visual species that don't sit together cleanly. Works if you commit to imagery grid-wide, fails if you want imagery to be truly optional.

Selected work · 02

The Weekly App

Meal-kit POC pulling Coles recipes into a consolidated trolley.

REACT VITE NETLIFY
Where it worksAll surfaces, but only if every card in a group has imagery
Author costHigh — every case study needs a crafted hero image
RiskVisual species split when mixing with non-image cards
B
Left half — image left, content right
horizontal list tile · wide-friendly · grid-hostile

150px image on the left, content filling the rest. Reads as a list row rather than a grid tile. Breaks at narrow widths: in a 2×2 landing grid each card is ~420px wide, and giving 150px to imagery leaves only 270px for content — the title wraps aggressively and tags break onto multiple rows. Works well in list contexts (/writing index, related-work footer) but fails in grid contexts.

Selected work · 02

The Weekly App

Meal-kit POC pulling Coles recipes into a consolidated trolley.

REACT VITE NETLIFY
Where it worksList surfaces only — /writing index, related-work footer
Author costMedium — image present but smaller footprint
RiskDoesn't fit the 2×2 landing grid at any breakpoint
C
Background — image fills card, content overlaid
dramatic · image-led · legibility-hostile

Image fills the card; a bottom-up dark gradient scrim makes the content legible over it. Strongest visual impact, highest content risk: body copy contrast depends on where the image's content sits, so every card needs a carefully composed image with dark bottom space. Hover states are invisible (the image covers the background change). Breaks with abstract marks and numerals — those don't read as atmospheric backgrounds.

Selected work · 02

The Weekly App

Meal-kit POC pulling Coles recipes into a consolidated trolley.

REACT VITE NETLIFY
Where it worksLanding hero grid only — when imagery is all-in and curated
Author costVery high — every image must be composed with dark bottom space
RiskHover state invisible; body legibility depends on image content
D
Corner accent — small mark top-right, content dominant
additive · minimal · mix-tolerant

64×64 mark in the top-right corner, content takes the rest. The image is a tag rather than decoration — it identifies the project or essay at a glance without demanding visual weight. Key property: cards with and without the corner mark sit together cleanly in the same grid; the difference reads as "some cards have an icon" rather than "some cards are different animals". Lowest author cost: a 64px square asset for work, a two-character numeral for writing.

Selected work · 02

The Weekly App

Meal-kit POC pulling Coles recipes into a consolidated trolley.

REACT VITE NETLIFY
Where it worksAll surfaces — mixes freely with non-image cards
Author costLow — 64px icon or two-char string
RiskVisual weight is small; corner mark doesn't rescue a dull content block
E
Asymmetric half — image takes 42% of card width
editorial · distinctive · authoring-heavy

58/42 asymmetric split — content on the left, image on the right taking the full height of the card. The split is deliberately off-centre to feel editorial rather than utilitarian. Distinctive look, but has the same mixing problem as A: cards with and without imagery create two visual species. Also needs the card to have a minimum height, which loses the "natural height" property of the current card.

Selected work · 02

The Weekly App

Meal-kit POC pulling Coles recipes into a consolidated trolley.

REACT VITE NETLIFY
Where it worksLanding hero grid — loses its charm in list mode
Author costHigh — image must work at tall/narrow aspect
RiskMixing problem; loses natural card height
F
Full-bleed overlap — image top, content floats up
collage · layered · craft-sensitive

Image fills the top 140px of the card; content sits in a bordered inset that rises 28px into the image. Reads as a two-layer collage — image and content are clearly separate layers, not stacked blocks. Distinctive when it works, but requires precise image framing: the top third of every image is wasted behind the card's visible edge, and the bottom 28px sits behind the content inset.

Selected work · 02

The Weekly App

Meal-kit POC pulling Coles recipes into a consolidated trolley.

REACT VITE NETLIFY
Where it worksLanding grid — novelty wears off in list mode
Author costHigh — every image needs precise top/bottom framing
RiskNovelty trap; craft-sensitive; mixing problem persists

How the options compare

Scored against the five things that actually matter for this portfolio: fit in the 2×2 landing grid, fit in list surfaces (/writing, related-work), author cost, content-first alignment (boring > clever), and tolerance for mixing image and non-image cards in the same group.

Name 2×2 grid List mode Author cost Content-first Mix tolerance
A Top third Pass Pass High Partial Fail
B Left half Fail Pass Medium Pass Partial
C Background Pass Fail Very high Fail Fail
E Asymmetric half Pass Fail High Partial Fail
F Full-bleed overlap Pass Partial High Partial Fail

Recommendation — D (corner accent)

D is the only option that passes all five criteria. The other five each fail at least one — mostly mix tolerance, which matters because "optional imagery" (Q2=B) implies some cards will have imagery and some won't, in the same grid. If that's the answer, only a layout that tolerates mixing survives contact with reality.

The deeper reason D wins: it reframes imagery as a tag rather than a hero. A 64px corner mark identifies the card ("this is the planner app", "this is essay 01") without making a visual claim. Cards without the mark look like cards that haven't been given a mark yet, not cards from a different system. That's the exact property you want when the content itself is the point — which this portfolio's style guide says it is.

The tradeoff to be honest about: D's visual impact is low. If you want the landing grid to feel visually striking, D won't get you there on its own. Striking comes from the hero block, the gradient CTA, and the signature divider — the card grid is supposed to feel orderly, not loud. If you later decide the landing grid needs more weight, the upgrade path is to swap D for A with the rule "when used on the landing grid, all four cards must have imagery" (making A work despite its mix intolerance).

Second choice: A, but only on the landing grid specifically, and only with the all-or-nothing rule. I'd rather ship D everywhere than ship A with a mix of image and non-image cards.

Next step if you pick D: I'll mock the corner accent at higher fidelity — featured variant (with FEATURED pin + gradient border), loading state, imagery-absent fallback, and /writing index density. Then we wire it into the token spec and move to chunk 4b.

If you pick something else: tell me which one and I'll mock that at higher fidelity, same follow-ups.