/* UI-017 product detail on the M11-B2 contract — Phase 2 public content
   surface. Frozen target: docs/design/ui-references/20260915-m11/index.html
   (UI-017 markup). Loads after content.css; tokens resolve via tokens.css. */

/* ---------- head region ---------- */

/* Reference breadcrumbs: quiet muted trail, .82rem, navy links, "/" plain
   separators (the separator itself lives in breadcrumb.ts). */
.breadcrumb {
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem;
  font-size: 0.82rem;
  color: var(--color-text-muted);
}
.breadcrumb-link {
  color: var(--color-text);
  text-decoration: none;
}
.breadcrumb-link:hover {
  color: var(--color-accent-text);
  text-decoration: underline;
  text-underline-offset: 0.18em;
}
/* UI review P2: the detail title's two runs (name, trailing parenthetical)
   are each unbreakable (renderProductTitle), so the balanced wrap falls
   between them — 白金卡 / （示例）, never 白金 / 卡（示例）. Owner revision
   2026-09-21 (R5): the H1 carries the product name only and the institution
   sits on its own muted line below (the old single-H1 institution span and
   its title-width :has() hack are gone — the grid column sizes the title). */
.page-head.detail-head h1 .title-name,
.page-head.detail-head h1 .title-paren { white-space: nowrap; }
/* A leading 「 hangs into the margin (zh titles like 「貸您想」…): the open
   quote optically aligns with neighbouring headings instead of indenting
   half a character. The span is rendered by renderProductTitle. */
.page-head h1 .title-hang { margin-inline-start: -0.5em; }
/* Refine 2026-09-23: the hero's decorative column grid stays (frozen
   language) but dims on the detail head only. Review round 2: 0.55 still
   cut through the logo lockup and the H1 tail glyphs, so the head dims
   again; removing the layer whole stays a contract-level change. */
.layered-detail .grid-bg { opacity: 0.35; }
/* Review round 2 (cmb zh mobile): the balanced wrap could strand a single
   CJK character as the second line (…貸 / 款). Balancing evens the lines,
   so the wrap lands mid-title instead of one hanging glyph. */
.page-head.detail-head h1 { text-wrap: balance; }

/* ---------- head region: two-line title + card cover (R1/R5) ---------- */

.detail-head { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-5); }
.detail-inst-line { margin: 0.55rem 0 0; font-size: var(--text-label); color: var(--color-text-muted); }
/* Institution mark (owner round 2026-09-22): the institution line follows
   the same invisible uniform-footprint doctrine as the list cards — one
   uniform slot, object-fit contain inside (loaded after content.css, so
   this width/height override wins). Refine 2026-09-23: the detail slot
   takes the list slot's landscape ratio (5rem × 2.5rem) so extreme-wide
   logos (ICBC, IBKR, Hang Seng) stop collapsing into a colour dot; the
   height is unchanged, so the head rhythm holds. */
.detail-inst-line--logo { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
/* Refine 2026-09-23 (review round 2): the fixed 5rem × 2.5rem slot still
   collapsed extreme-wide lockups (ICBC ~8:1 renders ~10px tall — 3–4×
   shorter than the near-square marks). The detail line drops the width cap:
   every logo renders at one uniform 2.5rem height and its own natural
   width, so the lockup height matches SC/AEON across samples. The list
   cards keep the frozen uniform box (content.css). */
.inst-logo--detail { width: auto; height: 2.5rem; max-width: 100%; }
.inst-logo--mono.inst-logo--detail { width: 2.5rem; }
@media (min-width: 48rem) {
  .detail-head { grid-template-columns: minmax(0, 1fr) auto; align-items: end; gap: var(--space-8); }
}
/* Uniform card frame (owner 2026-09-22 晚, R1 look restored): every cover —
   fetched landscape, rotated portrait or the typographic fallback — sits in
   ONE fixed frame: the ID-1 ratio on the clamped R1 width. The art crops
   into it (object-fit: cover), so the box is pixel-identical across
   products and the head's auto column never follows the art. */
.detail-cover { position: relative; aspect-ratio: 1.586; }
@media (min-width: 48rem) { .detail-cover { width: clamp(16rem, 22vw, 20rem); } }
@media (max-width: 47.99rem) { .detail-cover { width: min(100%, 18rem); } }

/* ---------- card cover: the typographic credit-card face (R1) ---------- */

.card-cover { position: relative; display: flex; flex-direction: column;
  justify-content: space-between; gap: 0.6rem; aspect-ratio: 1.586; /* ISO ID-1 */
  width: 100%; padding: 0.9rem 1rem; background: var(--color-text);
  color: var(--color-bg); border-radius: var(--radius-md);
  box-shadow: var(--shadow-press); overflow: hidden; }
.card-cover .cc-top { display: flex; justify-content: space-between; gap: 0.6rem; align-items: center; }
.card-cover .cc-inst { font-family: var(--font-mono); font-size: 0.62rem; font-weight: var(--weight-regular);
  letter-spacing: 0.14em; text-transform: uppercase; opacity: 0.82; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.card-cover .cc-net { font-style: normal; font-family: var(--font-mono); font-size: 0.62rem;
  letter-spacing: 0.1em; text-transform: uppercase; border: 1px solid currentColor;
  border-radius: var(--radius-pill); padding: 0.05rem 0.55rem; white-space: nowrap; }
.card-cover .cc-name { font-family: var(--font-display); font-weight: var(--weight-bold);
  font-size: clamp(1.1rem, 2vw, 1.45rem); line-height: 1.15; letter-spacing: -0.01em; }
.card-cover .cc-serial { font-family: var(--font-mono); font-size: 0.6rem; letter-spacing: 0.18em; opacity: 0.6; }
.card-cover--thumb { padding: 0.5rem 0.6rem; border-radius: var(--radius-sm); box-shadow: none; gap: 0.35rem; }
.card-cover--thumb .cc-inst { font-size: 0.52rem; letter-spacing: 0.1em; }
.card-cover--thumb .cc-net { font-size: 0.52rem; padding: 0.02rem 0.4rem; }

/* Fetched issuer hero (owner 2026-09-22 晚, uniform frame): the wrapper IS
   the fixed ID-1 frame and the img simply fills it — width/height 100% with
   object-fit: cover, so every landscape crops identically against the same
   box. The radius rides the base so corners wrap the art; background and
   shadow stay clear so alpha cutouts never sit on a dark plate. Last in
   source order to override the thumb rules below. */
.card-cover--img {
  display: block;
  padding: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: none;
  box-shadow: none;
}
/* List and article thumbs share the fill: their wrappers keep the fixed
   5.25rem × 1.586 slot (content.css / article.css), so the same cover crop
   lands at thumb scale. Only the smaller radius differs. */
.card-cover--img.card-cover--thumb {
  border-radius: var(--radius-sm);
}

/* Portrait rotation into the SAME uniform frame (owner 2026-09-22 晚): a
   card is a horizontal object — a portrait cover stands only because of
   how it was shot. The client script (scripts/client.ts) measures
   naturalWidth<naturalHeight on load and marks the img's wrapper .cc-rot;
   the img then renders as the frame swapped (pre-rotation width = frame
   height, height = frame width) with object-fit: cover, centred and
   rotated 90° — the rotated box maps exactly onto the identical ID-1
   frame and the overflow crops symmetrically before the rotation, exactly
   like a landscape cover. Without JavaScript the portrait renders
   unrotated, centre-cropped by the same cover rule. Last in source order
   so the rotated img wins the specificity tie with
   .card-cover--img.card-cover--thumb above. */
.cc-rot { position: relative; }
/* Server-side portrait rotation (owner 2026-09-24 打橫): identical mapping to
   the .cc-rot client path, applied straight from the stored | rot90 flag —
   deterministic with JavaScript disabled. */
.card-cover--rot {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%) rotate(90deg);
  width: calc(100% / 1.586);
  height: calc(100% * 1.586);
  object-fit: cover;
}

.cc-rot > .card-cover--img {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%) rotate(90deg);
  width: calc(100% / 1.586);
  height: calc(100% * 1.586);
  object-fit: cover;
}

/* ---------- detail sections: breathing rhythm (R8) ---------- */

/* The head leaves more exit air now that the sections carry their own
   spacing; each section then steps in at 3rem desktop / 2rem mobile. */
.layered-detail { padding-bottom: 2.4rem; }
.block-detail { padding-bottom: 3.2rem; }
.detail-sec + .detail-sec { margin-top: var(--space-8); }
@media (max-width: 640px) {
  .detail-sec + .detail-sec { margin-top: var(--space-6); }
}

/* Refine 2026-09-23: prose on this page takes the shared body measure
   token. Review round 2: leaving the data frames full width kept a ~390px
   dead band on the right of every section, so the frames take the same
   measure — intro, data grids and ledgers now share one right edge, and
   the width step stops reading as a layout fault. */
.detail-intro { margin: 0; max-width: var(--size-measure-body); font-size: 1.02rem; }
.facts-group,
.facts,
.rate-ledger,
.detail-fit { max-width: var(--size-measure-body); }
.detail-fit { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.6rem; }
.detail-fit li { border-left: 2px solid var(--color-accent); padding-left: 0.9rem; }
.apply-row { margin: 0; } /* section spacing is the detail-sec's job */

/* Provenance: the source line sits after a hairline of air, still below the
   content it supports (DS-COMP-027). */
.detail-sec:last-of-type .source-line { margin-top: var(--space-8); }

/* ---------- key facts: grouped hairline ledgers + rule rows ---------- */

/* 行式 refine 2026-09-24 (field-rows Phase 2): the grouped fact grids became
   the same hairline ledger language as the list cards — one field per line,
   the dashed hairline running the full measure width, no frame, no fill.
   The cell row geometry mirrors .kv (content.css) at the detail page's 1rem
   value size. */

.facts-group + .facts-group { margin-top: 1.2rem; }
/* Group kicker over the ledger (review round 2: sans — the mono stack has no
   CJK glyphs, so zh pages fell back mid-element while en rendered
   typewriter; one body family reads the same in both locales). */
.facts-group-k { font-size: 0.7rem; letter-spacing: 0.1em;
  color: var(--color-text-muted); margin: 0 0 0.45rem; }
/* One fact row: fixed label column left (10rem, one shared alignment line
   across every group), its value column starting on that line, the dashed
   hairline closing the row — the 直欄帳目 hand (排版盤點 2026-09-25 #2; the
   old no-grid flex let the value start drift per label width). The column
   is minmax(0, 10rem), not max-content: per-row max-content would just
   re-measure each label and keep the drift. Longer labels wrap inside the
   column without breaking the line. Hairline stays 1px dashed (dashed =
   within one record, solid = between records — product-rows.css), padding
   unchanged. The last line of a group keeps its hairline so the ledger
   closes on the line. */
.facts > div { display: grid; grid-template-columns: minmax(0, 10rem) minmax(0, 1fr); column-gap: 0.7rem;
  align-items: baseline; border-bottom: 1px dashed var(--color-border); padding: 0.42rem 0; }
@media (max-width: 640px) {
  .facts > div { grid-template-columns: minmax(0, 8rem) minmax(0, 1fr); }
}
.facts .k {
  /* sans for the same reason as .facts-group-k above (review round 2) */
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  color: var(--color-text-muted);
}
.facts .v {
  font-size: 1rem;
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
}
/* Rule rows read one step quieter on the same ledger: their override ties
   .facts > div's specificity and sits after it in source order, and carries
   display: block back so the flex geometry above cannot flex the prose
   span. The hairline inherits from .facts > div. */
.facts > .fact-rule { display: block; padding: 0.34rem 0; }
/* The rule text takes the prose measure: the value span is inline in the
   markup, so it blockifies first (max-width does nothing on inline) while
   the dashed rule line itself stays full width. Review round 2: pretty
   never removed a CJK orphan (…永久豁免年 / 費), balance evens the lines
   so the wrap cannot strand one hanging glyph; blocks beyond the engine's
   balance window fall back to normal wrapping. */
.fact-rule .v { display: block; max-width: var(--size-measure-body);
  font-size: 0.92rem; font-weight: var(--weight-regular); line-height: 1.55;
  text-wrap: balance; }
/* A group reduced to its effectiveFrom date renders as one bare mono meta
   line instead of a ledger group — same voice as .update-flag
   (public.css). */
.facts-asof { margin: 0.5rem 0 0; font-family: var(--font-mono); font-size: 0.74rem; color: var(--color-text-muted); }

/* ---------- current offers timeline ---------- */

.timeline {
  border-left: 2px solid var(--color-border-strong);
  padding-left: 1.2rem;
  margin: 0.8rem 0 0;
  max-width: var(--size-measure-body); /* offer copy is prose (§ refine 2026-09-23) */
}
.tl-item { position: relative; padding: 0 0 1rem; }
.tl-item::before {
  content: "";
  position: absolute;
  left: -27px;
  top: 0.4rem;
  width: 8px;
  height: 8px;
  background: var(--color-warning-fill);
}
.tl-item .d { font-family: var(--font-mono); font-size: 0.7rem; color: var(--color-text-muted); white-space: nowrap; }
/* An ended offer keeps its row but reads as muted with the 已結束 badge. */
.tl-item.offer-ended::before { background: var(--color-border-strong); }
.tl-item.offer-ended strong { color: var(--color-text-muted); }

/* ---------- apply row: /go hop with the adjacent disclosure ---------- */

.apply-row {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
  margin: 1.6rem 0;
}
/* Refine 2026-09-23: 34ch broke the disclosure into orphan lines (en
   「advice.」, zh single 借/貸); the prose measure lets it wrap whole. */
.apply-row .disc { font-size: 0.76rem; color: var(--color-text-muted); max-width: var(--size-measure-body); text-wrap: pretty; }

/* ---------- apply CTA blocks (article page variant + sticky mobile bar) --- */

.apply-cta {
  border: 2px solid var(--color-text);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  box-shadow: var(--shadow-press);
  padding: var(--space-5);
}
.apply-cta-note {
  margin: var(--space-3) 0 0;
  color: var(--color-text-muted);
  font-size: var(--text-caption);
}

/* E1 finding 2: sticky mobile apply bar. The fixed bottom band exists in
   markup only on product detail and stays display:none above the compact
   breakpoint. While visible, the body reserves the band height as bottom
   padding so the footer's last row — the copyright line — always scrolls
   clear of it (refine 2026-09-23: the reserve moved from .public-main to
   body, which also removes the dead band between the source line and the
   footer; :not(.has-consent-banner) keeps client.ts's larger consent
   reserve intact — this rule must never shrink it). The consent banner
   stays on top: z-30 over z-20. Static markup only — no transitions, so
   reduced motion needs no extra guard. */
.apply-cta-bar {
  display: none;
}
/* The 26em measure keeps the note's last line ≥5 CJK characters on 390 /
   320 viewports instead of a single hanging character. */
.apply-cta-bar-note {
  margin: 0;
  max-width: 26em;
  color: var(--color-text-muted);
  font-size: var(--text-caption);
  text-wrap: pretty;
}
@media (max-width: 47.99rem) {
  .apply-cta-bar {
    position: fixed;
    inset-inline: 0;
    bottom: 0;
    z-index: 20;
    display: grid;
    gap: var(--space-1);
    padding: var(--space-3) var(--space-4);
    background: var(--color-surface);
    border-block-start: 2px solid var(--color-text);
  }
  .apply-cta-bar .btn {
    justify-self: stretch;
  }
  body:has(.apply-cta-bar):not(.has-consent-banner) {
    padding-block-end: var(--space-16);
  }
}

/* ---------- related articles (refine 2026-09-23) ---------- */

/* The detail page's card grid carries a count modifier class from the
   template: one card takes the full reading row (a lone 24rem card left
   two thirds of the band idle and its 2px top rule read as a truncated
   separator — review round 2), two take two columns, three and up keep
   the base grid. Scoped to ≥641px — this sheet loads after public.css at
   equal specificity, so an unscoped rule would override the ≤640px
   single-column breakpoint (public.css) and squeeze mobile cards into
   slivers. */
@media (min-width: 641px) {
  .cardgrid--1 { grid-template-columns: minmax(0, 1fr); }
  .cardgrid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
