/*
 * card-styles.css — the hand-pinned pixel chrome for the collectible card.
 *
 * The pixel source of truth is docs/specs/2026-07-17_card-render-drift/reference-card.html
 * (REQ-1192; it supersedes the earlier editor page-map "Card build spec" table for every
 * pixel value). This file pins the CONTRACTUAL pixels onto the stable class hooks that
 * card-render.js emits: the 366×518 frame, the 1px black stroke, the one 10px rounded
 * corner per face, the paper fill, and — the REQ-1192 rebuild — every element's ABSOLUTE
 * pinned position inside the frame (both faces are pinned, no flow layout; that is what
 * makes the card match Figma to the pixel). It also ships the game's signature halftone
 * ruled-line texture as ONE CSS `repeating-linear-gradient` (no image file, no <canvas>)
 * so a wall of hundreds of cards costs the same as one (proposal §07 golden rule).
 *
 * CAP-HEIGHT ANCHORING (REQ-1192 build rule 2). Figma measures text position to the top
 * of the capital letters, not to the browser's line box. Every pinned text hook carries
 * `text-box-trim: trim-both` and defaults to `text-box-edge: cap alphabetic`; the three
 * overflow-bearing title/caption/body hooks override only the under edge to `cap text`.
 * In supporting browsers `top: N` therefore puts the caps exactly at N. Firefox does not
 * support the trim yet (there the text sits a few pixels low, which is accepted). Never
 * compensate an outer hook's contractual `top` value.
 *
 * HAND-PINNED, NO BUILD STEP (proposal §05 "The card is exempt"). Plain CSS — no Tailwind,
 * no compiler, no compiled output.
 *
 * THE SEAM WITH card-render.js. The renderer sets each text element's TYPE inline
 * (font-family / weight / size / line-height / tracking / style), plus the title-fit
 * (`.card-title-line` fitted size + width:324 + nowrap) and the pinned widths
 * (`.card-back-headline` 172, `.card-caption-text` 323). Inline wins, so this file does NOT
 * re-declare those; it pins POSITIONS, boxes, colours, and textures. Adding letter-spacing
 * or width to `.card-title-line` here would fight the title-fit math — don't.
 *
 * PHYSICAL SEPARATION (proposal §05). This stylesheet lives ONLY in game/cards/ and imports
 * ONLY its own-folder card-fonts.css. Positioned card rules are scoped under `.card-face*`
 * so a shared class name (e.g. the CMS catalogue's own `.card-article-link`) never inherits
 * the card's absolute layout.
 */

@import url("card-fonts.css");

/* ═══════════════════════════════════════════════════════════════════════════
 * HALFTONE DIALS — LIVE FIGMA VALUES (Game-Chrome, 2026-07-11) — DO NOT TOUCH
 * ═══════════════════════════════════════════════════════════════════════════
 * The card's ruled-line texture is a Figma SHADER: a tiny program + five tuned
 * dials, NOT a picture. These values were read live from the Game-Chrome card
 * layers on 2026-07-11. Never measure shader values from a Figma export: an
 * export bakes the shader into a stale image rather than exposing its settings.
 *
 * The front layer stores 45° and the back stores 135° as mirrored-layer
 * bookkeeping, not as a front/back texture difference. Both card faces use the
 * one shared 135deg texture below; do not derive per-face angles from those
 * layer values.
 *
 * They live on `.card-face` (not :root) so the card carries its own texture
 * without leaking custom properties into whatever page embeds it.
 * ═══════════════════════════════════════════════════════════════════════════ */

.card-face {
  /* --- THE FIVE HALFTONE DIALS (frequency / angle / weight / two colours) --- */
  --halftone-frequency: 27;        /* ruled lines per 100px */
  --halftone-angle: 135deg;        /* direction of the shared front/back lines */
  --halftone-weight: 0.30;         /* inked fraction of each stripe (0..1) */
  --halftone-ink: #FFF3E5;         /* warm-cream line colour (colour 1 of 2) */
  --halftone-paper: #FFFBF7;       /* paper / gap colour, the card cream (colour 2 of 2) */

  /* Derived from the dials — do NOT hand-edit these; edit a dial above.
   * period = one line + one gap; inked = the line's share of that period. */
  --halftone-period: calc(100px / var(--halftone-frequency));
  --halftone-inked: calc(var(--halftone-period) * var(--halftone-weight));

  /* --- Contractual card chrome tokens (reference-card.html) --- */
  --card-frame-width: 366px;
  --card-frame-height: 518px;
  --card-stroke-colour: #000000;   /* 1px black stroke, both faces */
  --card-ink: #000000;             /* black type, all faces */
  --card-navy: #010B23;            /* ET navy — replay border, story button, first-day ring text */
  --card-credit-ink: #5d5d5d;      /* photo credit colour */
  --card-corner-radius: 10px;      /* the one rounded corner per face */
  --card-image-width: 328px;
  --card-image-height: 209px;

  /* --- Frame: the exact 366×518 rectangle with its 1px black stroke.
   * position:relative makes it the containing block for every pinned child. --- */
  box-sizing: border-box;          /* the 1px stroke sits INSIDE the 366×518 box */
  width: var(--card-frame-width);
  height: var(--card-frame-height);
  border: 1px solid var(--card-stroke-colour);
  position: relative;
  overflow: hidden;                /* clip the paper + any content to the rounded corner */
  color: var(--card-ink);

  /* --- The halftone paper: ONE repeating-linear-gradient, both colours named.
   * background-color is the same paper so corners/edges never flash a gap. --- */
  background-color: var(--halftone-paper);
  background-image: repeating-linear-gradient(
    var(--halftone-angle),
    var(--halftone-ink) 0 var(--halftone-inked),
    var(--halftone-paper) var(--halftone-inked) var(--halftone-period)
  );
}

/* The one rounded corner differs per face (front top-right, back top-left).
 * order: top-left top-right bottom-right bottom-left. */
.card-face-front { border-radius: 0 var(--card-corner-radius) 0 0; }
.card-face-back  { border-radius: var(--card-corner-radius) 0 0 0; }

/* Shared cap-height anchoring for every pinned text hook (see file header). */
.card-publish-date,
.card-credit-line,
.card-title-line,
.card-caption-text,
.card-category-label,
.card-best-time-label,
.card-best-time-value,
.card-back-headline,
.card-back-body,
.card-flip-label,
.card-first-day-year {
  text-box-trim: trim-both;
  text-box-edge: cap alphabetic;
}

/* ─────────────────────────────  FRONT FACE  ───────────────────────────── */

/* F1 — category tag. Base is the black-pill fallback for an unknown category
 * (kept per spec §03); the named colourways below texture it and square the corner. */
.card-category-badge {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 24px;
  padding: 0 13px;
  border-radius: 999px;
  background: var(--card-ink);
  color: #FFF;                     /* label + icon (currentColor) read white on the tag */
  white-space: nowrap;
}
.card-face-front .card-category-badge { position: absolute; left: 19px; top: 15px; }
.card-category-icon { width: 16px; height: 16px; display: block; flex: none; }
.card-category-label { text-transform: uppercase; }

/* Eleven colourways: ten assignable categories plus Archive as the no-category fallback. The canonical list is
 * server/modules/cms/ingestion/ingestion-gate.js CMS_CATALOGUE_CATEGORIES ("category
 * must be one of the ten"), mirrored by card-render.js CARD_CATEGORIES. Archive,
 * `archive`, is a COLOURWAY, not a category — the neutral-grey badge an EMPTY
 * category resolves to (card-render.js EMPTY_CATEGORY_BADGE_FALLBACK, REQ-1350),
 * iconless by design. An unknown-but-present category is the different, deliberate
 * case: it keeps the F1 black pill above (spec §03), even though the envelope maps
 * it to Archive in the collection view too —
 * each surface follows its own spec, so don't "fix" one to match the other.
 *
 * The live Figma treatment applies only to named colourways; an unknown
 * `.card-category-*` class keeps the black pill above. Read live from Game-Chrome
 * node 348:252 (2026-07-11); 135deg shader, 1px line + 3px gap. */
.card-category-badge:is(
  .card-category-history,
  .card-category-tradition,
  .card-category-culture,
  .card-category-hope,
  .card-category-literature,
  .card-category-art,
  .card-category-nature,
  .card-category-places,
  .card-category-food,
  .card-category-dev,
  .card-category-archive
) {
  border-radius: 2px;
  background-color: var(--category-tag-background);
  background-image: repeating-linear-gradient(
    135deg,
    var(--category-tag-line) 0 1px,
    var(--category-tag-background) 1px 4px
  );
}

.card-category-history { --category-tag-line: #442152; --category-tag-background: #5C396A; }
.card-category-tradition { --category-tag-line: #64381B; --category-tag-background: #7A431F; }
.card-category-culture { --category-tag-line: #315221; --category-tag-background: #456C32; }
.card-category-hope { --category-tag-line: #B58500; --category-tag-background: #BE931A; }
.card-category-literature { --category-tag-line: #004E97; --category-tag-background: #286AA8; }
.card-category-art { --category-tag-line: #97006F; --category-tag-background: #A93189; }
/* Nature retuned in the tag tuner 2026-07-16 (ahead of the Figma sheet; reference wins). */
.card-category-nature { --category-tag-line: #288F61; --category-tag-background: #36A170; }
.card-category-places { --category-tag-line: #0099B0; --category-tag-background: #1AACC2; }
.card-category-food { --category-tag-line: #B05500; --category-tag-background: #BA6B21; }
.card-category-dev { --category-tag-line: #2E3843; --category-tag-background: #4E5E6C; }
.card-category-archive { --category-tag-line: #514E49; --category-tag-background: #605E5B; }

/* F2 — date, right edge 19 from the right, cap at 22. Type is inline. */
.card-face-front .card-publish-date {
  position: absolute;
  right: 19px;
  top: 22px;
  color: var(--card-ink);
  white-space: nowrap;
}

/* F3 — photo: 328×209, pinned, with a 0.75px black hairline (border-box, so the box
 * is 328×209 total, matching the reference). */
.card-face-front .card-front-photo {
  position: absolute;
  left: 19px;
  top: 47px;
  box-sizing: border-box;
  width: var(--card-image-width);
  height: var(--card-image-height);
  border: 0.75px solid #000;
  overflow: hidden;
  background: var(--halftone-paper);     /* neutral bed before the photo paints */
}
.card-photo-image {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;                     /* fill the box without distortion (cover crop) */
}

/* F4 — photo credit: right-aligned, 20 from the right, cap at 265. #5D5D5D. */
.card-face-front .card-credit-line {
  position: absolute;
  right: 20px;
  top: 265px;
  margin: 0;
  text-align: right;
  white-space: nowrap;
  color: var(--card-credit-ink);         /* #5D5D5D — the one type colour the renderer leaves to us */
}

/* F5 — front title: left 23, cap at 285 (width 324 + fitted size come inline).
 *
 * REQ-1349: card-render.js's fitTitleFontSize shrinks the one-line title to fit the
 * 324px box but FLOORS at 12px, so a title long enough to overflow at that size was
 * hard-clipped mid-word by .card-face's overflow:hidden — one live card read
 * "…Rows Of Hanging Red L" and stopped, with no sign that text was missing (931px of
 * text in a 324px box). Ellipsize instead. Note the file header's rule: never add
 * letter-spacing or width to this hook, because the title-fit measures real
 * letterforms via live layout and the two must not fight. overflow/text-overflow
 * change NOTHING the measurer reads, which is why they are admissible here — they
 * alter presentation after the fit, not the metrics the fit is computed from. */
.card-face-front .card-title-line {
  position: absolute;
  left: 23px;
  top: 285px;
  margin: 0;
  color: var(--card-ink);
  /* Keep the cap-height pin on this semantic hook. The untrimmed child owns clipping so accented
     capitals can paint above this box without changing the fitted type or pinned top (REQ-1670). */
  text-box-edge: cap text;
}
.card-title-metric {
  visibility: hidden;
}
.card-title-metric::before,
.card-caption-metric::before,
.card-back-body-metric-paragraph::before {
  content: attr(data-card-metric-text);
}
.card-title-clip {
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* F6 — caption: left 23, cap at 322 (width 323 + type come inline); 2 lines max. */
.card-face-front .card-caption-text {
  position: absolute;
  left: 23px;
  top: 322px;
  margin: 0;
  color: var(--card-ink);
  /* Keep the cap-height pin on the outer hook; the untrimmed child owns the two-line clip. */
  text-box-edge: cap text;
}
.card-caption-metric,
.card-caption-clip {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
}
.card-caption-metric {
  visibility: hidden;
}
.card-caption-clip {
  position: absolute;
  left: 0;
  /* Unsupported text-box-trim engines keep their normal line box, so the portable default
     aligns the clipping descendant directly to that box. Supporting engines receive the
     measured cap-trim adjustment below without moving this hook's contractual top. */
  bottom: 0;
  width: 100%;
}
@supports (text-box-trim: trim-both) {
  .card-caption-clip {
    /* The clamped -webkit-box keeps 3px more under-leading than the trimmed ET Text hook.
       Align its painted text-bottom against the legacy direct-content control so the first
       cap row stays at the y=322 source-of-truth pin. */
    bottom: -3px;
  }
}

/* F7 — best-time row: group at left 24 / top 398, all bottom-aligned so the label
 * and value baselines meet the clock's visible bottom edge (the 2px lift). */
.card-face-front .card-best-time-row {
  position: absolute;
  left: 24px;
  top: 398px;
  display: flex;
  align-items: flex-end;
  color: var(--card-ink);
}
.card-best-time-icon { width: 20px; height: 20px; display: block; flex: none; }
.card-best-time-label { margin: 0 0 2px 4px; color: var(--card-ink); }
.card-best-time-value { margin: 0 0 2px 13px; color: var(--card-ink); }

/* F8 — replay: a bordered button, left 247 / top 393, 85×31, 1px navy, radius 4.
 * Display-only chrome; #0F0F0F carries to the label + icon (currentColor). */
.card-face-front .card-replay-button {
  position: absolute;
  left: 247px;
  top: 393px;
  box-sizing: border-box;
  width: 85px;
  height: 31px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  border: 1px solid var(--card-navy);
  border-radius: 4px;
  background: transparent;
  color: #0F0F0F;
}
.card-replay-icon { width: 18px; height: 18px; display: block; flex: none; }
.card-replay-label { color: #0F0F0F; }

/* F9 — story button (new): navy bar at the foot, left 19 / top 460, 328×36, radius 4.
 * Display-only; the game flips the card later. White label + chevron (currentColor). */
.card-face-front .card-story-button {
  position: absolute;
  left: 19px;
  top: 460px;
  box-sizing: border-box;
  width: 328px;
  height: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  background: var(--card-navy);
  border-radius: 4px;
  color: #FFF;
}
.card-story-label { color: #FFF; }
.card-story-icon { width: 20px; height: 20px; display: block; flex: none; }

/* ─────────────────────────────  BACK FACE  ───────────────────────────── */

/* B1 — ruled note lines (new): 16 faint blue lines, 20px apart, left 0 / top 139. */
.card-face-back .card-note-lines {
  position: absolute;
  left: 0;
  top: 139px;
  width: 100%;
  height: 301px;
  background-image: repeating-linear-gradient(
    to bottom,
    rgba(92, 118, 179, 0.14) 0 1px,
    transparent 1px 20px
  );
}

/* B2 — red margin rule (new): thin vertical line, 27 from the left, full height. */
.card-face-back .card-red-margin {
  position: absolute;
  left: 27px;
  top: 0;
  width: 1px;
  height: 100%;
  background: rgba(166, 79, 79, 0.48);
}

/* B3 — headline: pinned to the notebook margin, left 48 / base cap 69 (width 172
 * inline). The live top is computed by card-render.js (REQ-1199 auto-flow: base 69
 * + editor nudge in 20px ruled-line steps); the fallback keeps the base layout when
 * the stylesheet is used without the renderer. */
.card-face-back .card-back-headline {
  position: absolute;
  left: 48px;
  top: var(--card-back-headline-top, 69px);
  margin: 0;
  color: var(--card-ink);
}

/* B4 — the editor's text field: left 48, width 269, sitting on the ruled lines.
 * The top is computed by card-render.js (REQ-1199): the first ruled-grid position
 * (base 127 + k×20) clearing the measured headline, plus the editor's nudge — so a
 * long headline pushes the excerpt down instead of overlapping it. The build's ONE
 * boundary is unchanged: the box clips at the 15th of the 16 lines (y 419), now
 * derived from the live top so a pushed-down body still can't crowd the
 * read-article link below. Which line the text ends on is the editor's call; that
 * it never passes the 15th is the build's. No per-paragraph positioning: the
 * editor owns the breaks. */
.card-face-back .card-back-body {
  position: absolute;
  left: 48px;
  top: var(--card-back-body-top, 127px);
  width: 269px;
  /* Keep cap-height positioning on the outer hook. The descendant below clips at the unchanged
     y=419 boundary without clipping ink above the cap line (REQ-1670). */
  text-box-edge: cap text;
  color: var(--card-ink);
}
.card-back-body-metric,
.card-back-body-clip {
  max-height: calc(419px - var(--card-back-body-top, 127px)); /* 15th-line hard cap (B4) */
  overflow: hidden;
}
.card-back-body-metric {
  visibility: hidden;
}
.card-back-body-clip {
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  /* Keep the over-edge untrimmed so accents can paint above the cap-height hook.
     The guarded max-height below preserves this overhang when the body cap engages. */
  text-box-trim: trim-end;
  text-box-edge: text;
}
@supports (text-box-trim: trim-both) {
  .card-back-body-clip {
    /* ET Text 12px/20px leaves 5.59375px above the cap on this untrimmed child.
       Preserve that overhang when the height cap engages, just as when copy fits.
       The metric stays capped at y=419 and bottom:0 keeps this clip's bottom there. */
    max-height: calc(419px - var(--card-back-body-top, 127px) + 5.6px);
  }
}
.card-paragraph-text { margin: 0; color: var(--card-ink); }
.card-back-body-metric-paragraph { margin: 0; }
/* Paragraph gap = one blank ruled line (20px), not the old 12px. */
.card-back-body-clip p + p,
.card-back-body-metric p + p { margin-top: 20px; }

/* B5 — read-article link. A minimal unscoped base keeps the CMS catalogue's own
 * `.card-article-link` element (which shares this class name, out of scope) rendering
 * as it did; the card's composed-row layout is scoped to the back face only. */
.card-article-link {
  font-family: "ET Sans Medium";
  font-weight: 500;
  color: var(--card-ink);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.card-face-back .card-article-link {
  position: absolute;
  left: 99px;
  top: 453px;
  display: flex;
  align-items: center;
  gap: 8px;
  text-decoration: none;           /* only the label underlines, not the whole row */
}
.card-et-logo { width: 25.34px; height: 25.34px; display: block; flex: none; color: var(--card-ink); }
.card-article-label { color: var(--card-ink); text-decoration: underline; text-underline-offset: 2px; }
/* The exported arrow points → (it lost its Figma rotation); re-rotate to ↗. */
.card-article-arrow { width: 8px; height: 8px; display: block; flex: none; transform: rotate(-45deg); color: var(--card-ink); }

/* B6 — flip hint (new): refresh icon + FLIP, top-left, left 48 / top 11. #4F4F4F. */
.card-face-back .card-flip-hint {
  position: absolute;
  left: 48px;
  top: 11px;
  height: 16px;
  display: flex;
  align-items: center;
  gap: 3px;
}
.card-flip-icon { width: 16px; height: 16px; display: block; flex: none; color: #4F4F4F; }
.card-flip-label { color: #4F4F4F; }

/* B7 — send-to-a-friend stamp (new): a rotated postage stamp, top-right. */
.card-face-back .card-send-stamp-wrap {
  position: absolute;
  left: 263.77px;
  top: 15.53px;
  width: 70.59px;
  height: 80.94px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.card-send-stamp {
  position: relative;
  width: 62.13px;
  height: 74px;
  transform: rotate(6.92deg);
  display: flex;
  align-items: center;
  justify-content: center;
}
.card-send-stamp-image { position: absolute; inset: 0; width: 100%; height: 100%; }
.card-send-stamp-caption { position: relative; width: 42px; text-align: center; color: #FFF; white-space: pre-line; }

/* B8 — first-day stamp (new): round ink stamp, bottom-right, left 279 / top 415.
 * The ring is baked artwork (do not recolour); the year is live text over it. */
.card-face-back .card-first-day-stamp {
  position: absolute;
  left: 279px;
  top: 415px;
  width: 75.06px;
  height: 75.06px;
}
.card-first-day-ring { position: absolute; inset: 0; width: 100%; height: 100%; }
.card-first-day-year {
  position: absolute;
  left: 25.17px;
  top: 30.7px;
  transform: rotate(1.63deg);
  white-space: nowrap;
  color: rgba(19, 12, 148, 0.63);        /* #130C94 @ 63% */
}
