/* card-primitives-v1 — W-63-1 — Saadjie standard card component layer. */
/*
 * Planning: .archon/queued-builds/WAVE-63-card-primitives.md
 *
 * WHY this file exists: public/css/radius-shadow.css ships the surface
 * composites (.surface-card / .surface-raised / .surface-floating) that
 * define SHAPE + ELEVATION for any surface. Component CSS has been
 * reaching for that layer with one-off "card-like" rules
 * (.dashboard__child padding+bg+border, .dashboard__contrib padding+bg+
 * border, .profile__hero padding+bg+border — three sources of truth for
 * "a contribution-flavoured card"). This file is the *component* layer
 * (one rung above the .surface-* utilities) that defines the THREE
 * named card layouts the wave brief mandates:
 *
 *   .contribution-card       — single contribution row (dashboard +
 *                              emails + receipts)
 *   .child-card              — child summary tile (dashboard grid +
 *                              profile hero)
 *   .contributor-feed-item   — public profile recent-contributor row
 *
 * The shared .card base class wraps the .surface-card pairing PLUS the
 * padding / background / text-colour opinion that every card shares —
 * surface-card is just shape + elevation, .card is the full card body.
 *
 * WHY .card and the three named cards live in ONE file: the wave brief
 * calls for "composable card components with consistent padding, radius,
 * shadow, hover state". Splitting the base from the named variants
 * across three files would make a future padding rotation (e.g.
 * "tighten cards on mobile") touch three places. One file = one
 * audit surface.
 *
 * WHY .card--interactive is a separate modifier (not baked into .card):
 * not every card is interactive. A receipt-email card or a static
 * marketing card should NOT lift on hover — the user has no way to
 * "click" it. .card--interactive is opt-in so the lift is meaningful
 * (it signals "this card is clickable"). The contribution-card always
 * gets the modifier in the EJS partial because every dashboard contrib
 * row links to a detail page; the child-card does NOT auto-apply it
 * (the partial sets the modifier only when an action link is present —
 * but this CSS file just defines the BEHAVIOUR, not when to apply it).
 *
 * WHY hover lift = transform + shadow-md (not just shadow change):
 * a 2px translateY paired with a shadow upgrade reads as "this card
 * is reaching toward the cursor" — the same metaphor used by Linear,
 * Vercel, Stripe. A shadow-only change reads as a click affordance
 * but the motion is too subtle on low-end Android (less than one
 * frame's-worth of visual delta). Pairing both is the established
 * pattern.
 *
 * GOTCHA: NO raw hex or raw colour-function values appear below.
 * Every colour goes through var(--color-*); every shape value through
 * var(--radius-*) / var(--shadow-*); every spacing through
 * var(--space-*); every duration / easing through var(--motion-*).
 * The W56 / W58 ANTI-RAW-VALUE git-diff gate ignores public/css/
 * tokens/** but ENFORCES on this file — anything raw will fail the
 * gate. tests/card-primitives.test.js replicates the same regex
 * inside jest so the bad commit fails locally too.
 *
 * GOTCHA: this file is the COMPONENT layer. Multi-property layouts
 * for a specific surface that aren't a generic "card" go in the
 * matching feature CSS file (dashboard.css, public-profile.css). The
 * three named cards here are GENERIC — they render the same in every
 * surface that includes the EJS partial. Don't add page-specific
 * overrides to this file; override per page via more specific
 * selectors in the feature CSS instead.
 *
 * CONSTRAINT: WCAG 2.3.3 (Animation from Interactions) — the hover
 * lift must respect prefers-reduced-motion. Routing the transition
 * through var(--motion-duration-*) + var(--motion-ease-out) lets the
 * universal selector override in public/css/motion.css neutralise
 * the lift automatically when the user has the OS preference on.
 *
 * FLOW: tokens.css → spacing.css → radius-shadow.css → motion.css →
 *       cards.css → feature CSS (dashboard.css, public-profile.css).
 *       cards.css sits ABOVE the utility layers so a feature stylesheet
 *       can still win with a more specific selector, but the bare
 *       <article class="card"> already paints correctly.
 */

/* ============================================================
 * Shared .card base — padding + background + shape + elevation
 * ============================================================ */

.card {
  display: block;
  padding: var(--space-5);                            /* 24px — comfortable card */
  background-color: var(--color-bg-raised);
  color: var(--color-text);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);                    /*  8px — workhorse card */
  box-shadow: var(--shadow-sm);                       /* rests on page */
  /* WHY the transition is declared on the base (not the modifier):
     re-declaring a transition under .card--interactive would create a
     specificity ladder for a future "card--quiet" variant. Declaring
     once means every variant inherits the same motion. The transition
     fires only when transform / box-shadow actually change, which
     they only do under :hover on the .card--interactive modifier. */
  transition:
    transform   var(--motion-duration-fast) var(--motion-ease-out),
    box-shadow  var(--motion-duration-fast) var(--motion-ease-out);
}

.card + .card {
  margin-top: var(--space-4);
}

/* ============================================================
 * .card--interactive — opt-in hover lift
 * WHY :focus-within (not just :hover): a card that includes a focusable
 * link inside (e.g. child-card with an "Edit" button) should also lift
 * when the inner element is keyboard-focused, so keyboard users get
 * the same affordance as mouse users.
 * ============================================================ */

.card--interactive {
  cursor: pointer;
}

.card--interactive:hover,
.card--interactive:focus-within {
  transform: translateY(calc(-1 * var(--space-1)));   /* -4px lift */
  box-shadow: var(--shadow-md);
}

/* ============================================================
 * .contribution-card — single contribution row
 * WHY a coloured edge on the inline-start (not a full border colour):
 * status (succeeded / pending / failed / refunded / cancelled) is a
 * scannable signal that should read at a glance without overwhelming
 * the card body. A 4px coloured inline-start border is the established
 * dashboard pattern (see .dashboard__contrib in dashboard.css) — we
 * lift it into the component layer so every surface that renders a
 * contribution gets the same affordance.
 * ============================================================ */

.contribution-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  border-inline-start-width: var(--space-1);          /* 4px coloured edge */
  border-inline-start-style: solid;
  border-inline-start-color: var(--color-border-strong); /* overridden per status */
}

.contribution-card__heading {
  margin: 0;
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.contribution-card__row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2);
  justify-content: space-between;
}

.contribution-card__name {
  font-weight: var(--font-weight-medium);
  word-break: break-word;
}

.contribution-card__amount {
  font-weight: var(--font-weight-bold);
  color: var(--color-brand-dark);
}

.contribution-card__status {
  margin: 0;
  font-size: var(--font-size-sm);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-muted);
}

.contribution-card__when {
  margin: 0;
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
}

.contribution-card__message {
  margin: 0;
  color: var(--color-text-muted);
  font-style: italic;
  line-height: var(--line-height-base);
}

.contribution-card__reference {
  margin: 0;
  font-size: var(--font-size-sm);
  color: var(--color-text-subtle);
}

.contribution-card__reference code {
  word-break: break-all;
}

/* WHY status colours target the inline-start border (not background):
   a fully tinted background drowns the card body in a single colour
   and reads as an alert banner. A coloured edge stays scannable
   without dominating. */
.contribution-card--succeeded { border-inline-start-color: var(--color-success); }
.contribution-card--pending   { border-inline-start-color: var(--color-warning); }
.contribution-card--failed    { border-inline-start-color: var(--color-danger); }
.contribution-card--refunded  { border-inline-start-color: var(--color-border-strong); }
.contribution-card--cancelled { border-inline-start-color: var(--color-border-strong); }

/* ============================================================
 * .child-card — child summary tile
 * WHY the photo sits above the name (vertical stack, not horizontal):
 * on a 320px mobile viewport the child cards stack one-per-row; a
 * horizontal name+photo split eats too much width for the name to
 * stay legible. Vertical keeps the rhythm consistent from mobile to
 * desktop where multiple cards sit side-by-side in a grid.
 * ============================================================ */

.child-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-2);
}

.child-card__photo {
  width: var(--space-7);                              /* 48px */
  height: var(--space-7);
  border-radius: var(--radius-circle);
  overflow: hidden;
  background-color: var(--color-bg-muted);
  display: flex;
  align-items: center;
  justify-content: center;
}

.child-card__photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.child-card__photo--placeholder {
  color: var(--color-text-muted);
  font-weight: var(--font-weight-bold);
  font-size: var(--font-size-xl);
}

.child-card__name {
  margin: 0;
  font-family: var(--font-family-heading);
  font-size: var(--font-size-xl);
  line-height: var(--line-height-tight);
  word-break: break-word;
}

.child-card__slug {
  margin: 0;
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
}

.child-card__slug code {
  word-break: break-all;
}

.child-card__slug a {
  color: var(--color-text-link);
  text-decoration: none;
}

.child-card__slug a:hover,
.child-card__slug a:focus {
  text-decoration: underline;
}

.child-card__total {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.child-card__total-label {
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
}

.child-card__total-amount {
  font-size: var(--font-size-xl);
  font-weight: var(--font-weight-bold);
  color: var(--color-brand-dark);
}

.child-card__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-2);
}

/* ============================================================
 * .contributor-feed-item — public profile recent-contributor row
 * WHY this is a LI (and the .card base is NOT applied): feed items
 * sit inside a <ul> of recent contributors. Wrapping each in the
 * full card surface (background + shadow + padding) would look like
 * a stack of business cards instead of a feed. The compact row
 * pattern (hairline divider between rows, baseline-aligned spans)
 * is what reads as a "feed" — borrowed from .profile__feed-item.
 * Lifting it into a component-layer class means the dashboard's
 * future "latest contributors" widget can drop it in unchanged.
 * ============================================================ */

.contributor-feed-item {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2);
  padding: var(--space-3) 0;
  border-top: 1px solid var(--color-border);
  list-style: none;
}

.contributor-feed-item:first-child {
  border-top: 0;
  padding-top: 0;
}

.contributor-feed-item__name {
  font-weight: var(--font-weight-medium);
  word-break: break-word;
}

.contributor-feed-item__amount {
  font-weight: var(--font-weight-bold);
  color: var(--color-brand-dark);
}

.contributor-feed-item__when {
  font-size: var(--font-size-sm);
  color: var(--color-text-muted);
  margin-inline-start: auto;
}

.contributor-feed-item__message {
  flex-basis: 100%;
  margin: var(--space-1) 0 0;
  color: var(--color-text-muted);
  font-style: italic;
  line-height: var(--line-height-base);
}

/* ============================================================
 * Print override
 * WHY the lift collapses on print: a transformed element prints at
 * its un-transformed position (translateY is GPU-only), so a hover
 * state on a print preview just looks like an extra gap. Strip it.
 * ============================================================ */

@media print {
  .card--interactive:hover,
  .card--interactive:focus-within {
    transform: none;
    box-shadow: none;
  }
}
