/* dp-consultant.css — DP block 8 rebuild (issue #14). Owned entirely by Consultant.astro; no
   selector here reaches outside .dpc-* / [data-dpc-*]. Loaded via <link> emitted by the
   component itself (double-include guarded in dp-consultant.js, not here).
   The owner's repeated rule: the consultant is a transparent cut-out that blends into the
   section colour — never a boxed rectangle, never a vignette, never a mask. This file adds
   NO background, NO radius, NO box-shadow, NO mask/gradient of any kind to .dpc-person or its
   image; it only positions the image and lets its own transparent pixels show the green through. */

.dpc-section {
  position: relative;
  /* ROUND 10 #62: an explicit z-index (not `auto`) so this section is a STACKING CONTEXT and its
     `.dpc-grid { z-index: 1 }` child stays inside it instead of competing in the root context with
     the next section's cover. 0 keeps it above `.dpc-ground` (fixed, z-index -1) and below
     `.dp-cases` (z-index 1, case-carousel.css), which is the whole cover mechanism. */
  z-index: 0;
  overflow: clip;
  background: var(--green);
  color: var(--paper);
  border-radius: var(--r-sec) var(--r-sec) 0 0;
}
.dpc-section .eyebrow.light,
.dpc-section .eyebrow { color: var(--mute-d); }
.dpc-section .eyebrow i { background: var(--amber); }
/* #39: `.lead` (site.css) defaults to `color: var(--mute)` — an INK-toned translucent colour meant
   for paper/white grounds. This section's background is the deep green, so that default rendered
   as dark text on dark green (measured well under 4.5:1). `.dark .lead` already has the paper-
   toned fix (`var(--mute-d)`) for other dark sections; this section never carried the `.dark`
   class, so it never got it. Scoped override, same token every other dark-ground section already
   uses (rgba(242,243,238,.68) over #0F3B2E measures ~5.6:1). */
.dpc-section .lead { margin-top: 18px; color: var(--mute-d); }

/* ---- ROUND 9 #59: the makro ground change, done the way makro actually does it ---------------
   SECOND rejection. Round 8 faded a small band at the top of this section; the owner: "far from
   what it looks like... just like a light switch". makro does not tint a seam - it puts ONE
   full-viewport layer behind everything and runs its OPACITY 0 -> 1, so the entire visible ground
   changes colour while the sections in front stay where they are. That is what this is.
   `.dpc-ground` is a sibling of the section (not a child): `.dpc-section` sets `overflow: clip`,
   and a clipping ancestor can cut a fixed-position descendant, so the layer lives outside it.
   The old `.dpc-blend` band and its `--dpc-blend` property are DELETED in this same change - one
   controller for this effect, not two. dp-consultant.js toggles exactly one class on <html> from
   the same single IntersectionObserver line (-49%/-51%) it already used.
   Compositor-only: a fixed layer's `opacity` never touches `background-color` on body and never
   invalidates layout, so PROBE-PERF-BUDGET is unaffected. */
.dpc-ground {
  position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background: var(--green); opacity: 0;
  transition: opacity .8s cubic-bezier(.22, 1, .36, 1);
}
html.dpc-ground-on .dpc-ground { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .dpc-ground { transition: none; } }

/* For the ground to be visible at all, the blocks immediately before and after the consultant must
   not paint their own paper over it. Both are paper-on-paper today, so dropping their background
   changes nothing while the layer is off (the body's paper shows through unchanged) and reveals
   the green while it is on. Panels that carry their OWN opaque colour - the cream phase tray, the
   white case cards, the charcoal open card - keep theirs, exactly as makro keeps its cards. */
/* ROUND 10 #62 (THIRD rejection): `.dp-cases` is NO LONGER on this list, and none of the recolour
   rules below name it any more. The owner: "it poluted the next session turn all thigns to green
   so the segemetn feel is gone". SPEC.md §C line 57 says makro's colour change EXITS because the
   NEXT SECTION'S OPAQUE BACKGROUND ARRIVES FROM BELOW — the layer itself never fades back and the
   next section is never tinted. So 客戶個案 keeps its own paper ground and ink text at every scroll
   position (case-carousel.css gives it the opaque ground + the z-index that lets it cover both
   this section and `.dpc-ground`), and the runway below does the sliding. Only the section BEFORE
   this one still rides the ground, which is the entry the owner accepted in round 9. */
.dpp-section { background: transparent; }

/* ...and their text switches to the light ink on the SAME class and the SAME .8s clock, which is
   what makro does with its heading. Measured on #0F3B2E: --paper 11.2:1, --mute-d 6.1:1; on
   --paper ground the originals are --ink 14.6:1 and --mute 4.8:1. Both ends clear 4.5:1. */
.dpp-section .h2, .dpp-section .lead, .dpp-section .eyebrow,
.dpp-points > h3, .dpp-points-grid > p, .dpp-points-grid > p strong, .dpp-video figcaption {
  transition: color .8s cubic-bezier(.22, 1, .36, 1);
}
html.dpc-ground-on .dpp-section .h2,
html.dpc-ground-on .dpp-points > h3,
html.dpc-ground-on .dpp-points-grid > p strong { color: var(--paper); }
html.dpc-ground-on .dpp-section .lead,
html.dpc-ground-on .dpp-section .eyebrow,
html.dpc-ground-on .dpp-points-grid > p,
html.dpc-ground-on .dpp-video figcaption { color: var(--mute-d); }
html.dpc-ground-on .dpp-section .hi { color: var(--amber); }
@media (prefers-reduced-motion: reduce) {
  .dpp-section .h2, .dpp-section .lead, .dpp-section .eyebrow,
  .dpp-points > h3, .dpp-points-grid > p, .dpp-points-grid > p strong,
  .dpp-video figcaption { transition: none; }
}

/* #40: vertical rhythm now uses the page's own section-spacing token (var(--sec), the same one
   .block-numbers/.dpp-section use) top AND bottom, on the grid wrapper itself - the section no
   longer carries a hand-tuned 100px top / 100px bottom pair. Height falls out of whatever the
   copy column (.dpc-copy: heading + lead + 4 cards) actually needs; the portrait never sets it
   (see .dpc-person below). */
/* R8 issue #51 (owner: "the human (the staff) tucked right into the bottom line so that it won't
   look a little bit cut off"). The bottom padding is GONE from the grid and moved onto the copy
   column. That is the whole mechanism: `.dpc-section` has no padding of its own and `.dpc-grid` is
   its only child, so with `padding-bottom: 0` the grid's content-box bottom, its row's end line,
   the grid's border-box bottom and the section's own bottom edge are all the same y — and the
   absolutely-positioned figure's `bottom: 0` (resolved against that row) lands exactly on it.
   Measured by lab/probe-consultant-floor.mjs at 1440x900 and 390x844 (in the battery).
   This SUPERSEDES #40's "section too tall" note, per the round-8 brief. */
.dpc-grid {
  position: relative; z-index: 1;
  /* ROUND 9 #60: the left column is now wide enough for a person as TALL as the block. The
     cut-out's real alpha bounding box IS its file box (measured: 1205x1600, no transparent margin
     above the hair), ratio 0.753 - so a person filling a ~780px block needs ~590px of column. At
     1440 this resolves to 600px left / ~549px right, which still fits the right column's 2-up
     cards and 3-up strip. Width is deliberately the NON-binding constraint so the person's height
     is set by the block, which is what puts the top of the hair level with the eyebrow. */
  /* ROUND 10 #61 (SECOND rejection of #60). Round 9's 600px cap was measured against the block's
     then-height; it is still a WIDTH cap, and `.dpc-person img` carries `max-width: 100%`, so at
     any viewport where the row is taller than 600 / 0.7531 = 797px the width silently capped her
     PAINTED height (measured on the round-9 preview: ~470px of paint in a ~700px copy column).
     Column 1 is now derived from the height she has to reach instead of guessed: the row is
     `--screen` minus the grid's own top padding (the grid fills the section, see the >=1024 block
     at the foot of this file), and the file's box ratio is 1205/1600 = 0.7531 (measured with sharp
     on public/assets/people/dp-consultant-cut.webp; its alpha bbox is x 12..1204, y 2..1599, i.e.
     2px of transparent margin above the hair = 1.2px at a 976px render, inside the 8px assert, so
     nothing is compensated for here). +8px absorbs sub-pixel track rounding. The 760px ceiling
     keeps the copy column usable; above ~1110px of viewport height the width caps her again. */
  /* R11 issue #64: named so the desktop-only extra clearance below (owner: right column text a
     bit lower, less compact under the fixed header) can add to the SAME value this width calc
     reads, instead of drifting out of sync with it. Default = the original var(--sec) alone. */
  --dpc-top-pad: var(--sec);
  --dpc-col: min(760px, calc((var(--screen) - var(--dpc-top-pad)) * 0.7531 + 8px));
  display: grid; grid-template-columns: minmax(300px, var(--dpc-col)) minmax(0, 1fr);
  /* #40: gap reduced from clamp(36px,6vw,82px) so the portrait sits closer to the copy, per the
     owner's "slightly move it to the right, closer to the information". */
  gap: clamp(20px, 3.4vw, 48px);
  /* ROUND 10 #61: `center` was a no-op while the row was exactly as tall as the copy; once the
     section carries `min-height: var(--screen)` it would centre the copy inside a taller row and
     push the eyebrow BELOW the row's top line — and the row's top line is where the cut-out's hair
     starts. `stretch` keeps the eyebrow's top edge and the figure's top edge on the same y. */
  align-items: stretch;
  padding: var(--dpc-top-pad) 0 0;
}

.dpc-copy { grid-column: 2; grid-row: 1; padding-bottom: var(--sec); }

.dpc-cards { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; margin-top: 28px; }
.dpc-cards article { padding: 18px; border-radius: var(--r-sm); background: rgba(242,243,238,.08); border: 1px solid rgba(242,243,238,.4); }
.dpc-cards h3 { margin: 0; font-size: 19px; color: var(--paper); }
.dpc-cards p { margin-top: 7px; color: var(--mute-d); font-size: 13px; }

/* R8 #51: the closing strip + CTA that fill the taller section (see Consultant.astro). Same
   paper/--mute-d pair every other text box on this green ground uses (measured ~5.6:1). */
.dpc-strip {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px;
  margin: 26px 0 0; padding: 20px 0 0; list-style: none;
  border-top: 1px solid rgba(242, 243, 238, .28);
}
.dpc-strip li { display: flex; flex-direction: column; gap: 4px; }
.dpc-strip strong { font-family: var(--serif); font-size: 17px; font-weight: 500; color: var(--paper); }
.dpc-strip span { color: var(--mute-d); font-size: 13px; line-height: 1.6; }
.dpc-cta { margin-top: 26px; }

/* ---- The cut-out person: absolute WITHIN the column-1 grid area, bottom-anchored, centred ----
   #40 rebuild, take 2: an in-flow `align-self: stretch` grid item (the first attempt) still lets
   the figure's MAX-CONTENT contribution feed the row's `auto` track-sizing pass (measured: the row
   sized itself to the figure's own max-height, 634px, even though .dpc-copy only needed 402px -
   `overflow: clip`'s automatic-minimum-size-0 trick only zeroes the row's MINIMUM contribution,
   not its max-content one, so it did not stop the photo from being the tall item after all).
   An absolutely positioned item takes NO part in track sizing at all, which is the only way to
   guarantee the row (and therefore the section) is sized purely from .dpc-copy. Giving it an
   explicit `grid-column/grid-row` still lets its containing block be that grid AREA (not the whole
   section) per the CSS Grid spec's absolute-positioning rules, which is what makes `height: 100%`
   below resolve against column 1's (copy-driven) row height instead of the page. */
.dpc-person {
  /* Explicit END lines (1 / 2), not just `grid-row: 1` (= start:1, end:auto): an `auto` end line
     makes that edge of the containing block fall back to the grid container's PADDING box instead
     of the row's own computed end line (measured: bottom:0 landed at the container's padding-box
     bottom, past its bottom padding, instead of the row's real bottom) - only the explicit-start
     edge (top) was actually using the row's line. Both edges need an explicit line to both use the
     row's real box. */
  position: absolute; grid-column: 1 / 2; grid-row: 1 / 2;
  left: 50%; bottom: 0; margin: 0; z-index: 0; pointer-events: none;
  /* ROUND 9 #60 (owner: at 1440 the person was ~1/3 of the block's height). The old
     `clamp(320px, 44vw, 640px)` capped her at 634px inside a 778px block. `height: 100%` against
     the copy-driven row is the whole point; the only cap left is the asset's own natural height,
     so she is never rendered larger than the file. */
  height: 100%; max-height: 1600px;
  /* ROUND 10 #61, the actual bug behind the second rejection. An absolutely positioned box with
     `left: 50%` and no width gets SHRINK-TO-FIT width, and its available width is only the half of
     the grid area to the right of that 50% line — measured 300px inside a 600px column. The img's
     `max-width: 100%` then resolved against 300px and, per CSS2.1 §10.4, a width cap on a
     ratio-locked replaced box drags its HEIGHT down with it: 300 / 0.7531 = 398px of paint in a
     ~780px block, which is exactly the "she is a third of the block" the owner rejected twice.
     An explicit `width: 100%` makes the figure the full column again (the -50% in `--dpc-tx`
     still resolves against the figure's own width, so the centring is unchanged) and lets
     `height: 100%` be the binding constraint it was always meant to be. */
  width: 100%;
  /* Single source of truth for the horizontal offset, consumed by every rule below (base, armed,
     in-view, reduced-motion) via `var(--dpc-tx)` - so the mobile override only ever needs to touch
     THIS one custom property instead of re-stating the transform in each state (which would need
     to out-specificity the `[data-dpc-armed] .dpc-in` rule, the highest-specificity one here). */
  --dpc-tx: calc(-50% + clamp(4px, 1.6vw, 22px));
  transform: translateX(var(--dpc-tx));
}
/* `width:auto; height:100%` means the <img> BOX already has the file's own aspect ratio, so
   `object-fit: contain` is a no-op and the box's bottom edge IS the painted image's bottom edge —
   which is what lab/probe-consultant-floor.mjs measures. `margin-inline:auto` centres it inside a
   stretched figure (the <1024px single-column case, where the figure is a plain grid item). */
.dpc-person img {
  display: block; width: auto; height: 100%; max-width: 100%; margin-inline: auto;
  object-fit: contain; object-position: bottom;
}

/* Entrance: gated behind the root attribute dp-consultant.js sets, never CSS-only. `--dpc-tx` is
   part of BOTH the hidden and finished transform so the entrance only animates translateY - it
   must never reset to `transform: none`, or the figure would jump sideways the instant it
   finishes arriving. */
.dpc-section[data-dpc-armed] .dpc-person { opacity: 0; transform: translateX(var(--dpc-tx)) translateY(40px); }
.dpc-section[data-dpc-armed] .dpc-person.dpc-in {
  opacity: 1; transform: translateX(var(--dpc-tx));
  transition: opacity .7s cubic-bezier(.22,1,.36,1), transform .7s cubic-bezier(.22,1,.36,1);
}
@media (prefers-reduced-motion: reduce) {
  .dpc-person { opacity: 1 !important; transform: translateX(var(--dpc-tx)) !important; transition: none !important; }
}

@media (max-width: 1023px) {
  /* R8 #51: the single-column order flips — copy first, cut-out LAST — because "tucked right into
     the bottom line" has to hold at 390 too. With `row-gap: 0` and the grid's own bottom padding
     already removed above, the figure is the last grid item and its bottom edge is the grid's (and
     therefore the section's) bottom edge. The copy column carries the spacing instead. */
  .dpc-grid { grid-template-columns: 1fr; text-align: center; row-gap: 0; }
  .dpc-copy { grid-column: 1; grid-row: 1; padding-bottom: 30px; }
  .dpc-person {
    position: static; grid-column: 1; grid-row: 2;
    height: clamp(340px, 115vw, 460px); max-height: 1600px; margin: 0 auto;
    --dpc-tx: 0px; /* single-column layout: no absolute positioning, no left:50% to cancel out */
  }
  .dpc-cards { grid-template-columns: 1fr; text-align: left; }
  .dpc-strip { grid-template-columns: 1fr; text-align: left; }
}

@media (max-width: 560px) {
  .dpc-person { height: clamp(320px, 112vw, 440px); }
}

/* ---- ROUND 10 #62: the focus pin + the cover runway (>=1024 only) ---------------------------
   Owner: "i want this section shall be the focus and it shall go with the scroll ... and once the
   section is done scrolling and next session shall go back to it's color". Mechanism, straight
   from the measured makro decode (SPEC.md §C line 57): the section is PINNED and the next section
   is an OPAQUE COVER that slides up over it. Nothing new animates — sticky is layout, not motion,
   so this is identical under prefers-reduced-motion and adds no scroll handler or rAF.
   Geometry, in screens (1 screen = var(--screen) = 100svh), all three numbers deliberate:
     runway box   2.30  = 1.00 section + 0.30 hold + 1.00 cover travel
     margin-bottom -1.00  pulls 客戶個案 up by exactly the cover travel, so the cover OVERLAPS the
                          pinned section instead of pushing it (this is why the travel is not just
                          extra page height). Net page growth over round 9: ~0.43 screen.
   Read as scrolling: the section lands under the pointer after one screen of scroll, HOLDS for
   0.30 screen (~270px at 900px tall = three wheel notches), then the cover arrives and takes one
   full screen to close over it — "one scroll to focus, one or two more and the cover is arriving".
   No scroll-snap anywhere: the pinned steps and the statement stack own their own scroll ranges.
   `overflow` is deliberately NOT set on the runway — any of clip/hidden/auto here would make it a
   clipping/scroll container and silently kill the sticky. (`body` is `overflow-x: clip` with
   overflow-y visible, which does NOT create a scroll container; site.css's own `.hero-stage` /
   `.svc-pin` sticky pins prove the page scrolls on the viewport.) */
@media (min-width: 1024px) {
  /* R11 issue #64 (owner: "right side the text can be a bit lower ... won't look too compact ...
     just bit lower with the padding"). Pinned, the eyebrow sat ~5px under the header (measured:
     var(--sec) top padding vs the 96px fixed header). Adds to --dpc-top-pad, which both the grid's
     own padding-top AND the width calc above read, so the person's height (bottom-anchored,
     height:100% of the row) shrinks by the same amount and column 1's width stays derived from her
     real height instead of the old, now-stale premise. #61/#62's 8px hair-to-eyebrow tolerance is
     unaffected either way: she is bottom:0 + height:100% of the row, so her painted top always
     equals the row top, whatever the row's height turns out to be - only her FOOT position and the
     column width are height-sensitive, and the foot never moves (still bottom:0 on the section
     floor). Desktop only, per the owner's ruling; phone/tablet keep the mobile media query's own
     explicit height below and are untouched. */
  .dpc-grid { --dpc-top-pad: calc(var(--sec) + 56px); }
  .dpc-runway {
    position: relative;
    min-height: calc(var(--screen) * 2.3);
    margin-bottom: calc(var(--screen) * -1);
  }
  .dpc-section {
    position: sticky; top: 0;
    min-height: var(--screen);
    /* the grid must FILL the taller section, or the row (and with it the figure's `height: 100%`
       and the copy's top line) would still be only as tall as the copy's own content. */
    display: flex; flex-direction: column;
  }
  .dpc-grid { flex: 1 1 auto; }
  /* With the row now taller than the copy's natural height, the copy column distributes the slack
     ONCE, between the cards and the closing strip, instead of letting it pool under the CTA. The
     eyebrow therefore stays welded to the top line (issue #61) and the CTA to the bottom. */
  .dpc-copy { display: flex; flex-direction: column; }
  .dpc-cards { margin-bottom: auto; }
  /* blockified flex items: the CTA is an inline-block <a> and would otherwise stretch full width. */
  .dpc-cta { align-self: flex-start; }
  .dpc-copy > .eyebrow { align-self: flex-start; }
}
