/* ================================================================
   css/pages/projects.css — /projects page-specific sections.
   Sections built here: her (id="home"), projects (id="areas").
   menu-line / help / inner-annual / footer are shared partials+CSS.

   Values sourced from research/raw/desktop/projects.html inline <style>
   blocks (research/_stylesheet.css is HOME-only, see SHARED-NOTES §11
   landmines) and research/fragments/projects/{her,projects}.html, plus
   research/raw/{tablet,phone}/projects.html for the grid's structural
   variants. Formula-verified against reference-heights.json:
     her:      max(50vh,400) + 1 + max(20vh,200)          desktop/laptop/tablet
               max(40vh,300)                                phone (footer hidden)
     projects: 10vh + max(40vh,400) + max(20vh,200)         desktop
               10vh + 500 (fixed)                            laptop/tablet
               [Left list + 360px thumb], column              phone
   ================================================================ */

/* ---------------------------------------------------------------
   1. her  (data-section="her", DOM id="home")
   --------------------------------------------------------------- */
.her {
  background-color: var(--c-cream);
  z-index: 1;
  display: flex;
  flex-flow: column;
  place-content: center flex-start;
  align-items: center;
  width: 100%;
  height: min-content;
  position: relative;
  overflow: visible;
}

.her__gradient {
  z-index: 3;
  background: linear-gradient(var(--c-cream) 39%, transparent 100%);
  flex: none;
  height: 449px;
  position: absolute;
  top: 0; left: 0; right: 0;
  pointer-events: none;
}

.her__main {
  z-index: 4;
  display: flex;
  flex-flow: row;
  place-content: flex-start center;
  align-items: flex-start;
  width: 100%;
  height: 50vh;
  height: calc(var(--framer-viewport-height, 100vh) * .5);
  min-height: 400px;
  padding: 0 32px;
  position: relative;
  overflow: visible;
}
@media (max-width: 809.98px) {
  .her__main {
    height: 40vh;
    height: calc(var(--framer-viewport-height, 100vh) * .4);
    min-height: 300px;
    padding: 0 26px;
  }
}

.her__content {
  z-index: 1;
  display: flex;
  flex-flow: row;
  flex: 1 0 0;
  place-content: center;
  align-items: center;
  width: 1px;
  max-width: var(--page-max);
  height: 100%;
  position: relative;
  overflow: visible;
}

/* generic "flex slot holding one divider line" — reused for every
   -gap+line- filler the reference scatters through the header grid */
.her__slot {
  height: 100%;
  display: flex;
  flex-flow: row;
  flex: 1 0 0;
  place-content: flex-start;
  align-items: flex-start;
  width: 1px;
  position: relative;
  overflow: var(--overflow-clip-fallback, clip);
}
.her__slot--center { place-content: center flex-start; align-items: center; }
.her__slot--between { place-content: flex-start space-between; align-items: flex-start; }

.her__gaps {
  display: flex;
  flex-flow: row;
  flex: 1 0 0;
  place-content: flex-start center;
  align-items: flex-start;
  width: 1px;
  height: 100%;
  position: relative;
  overflow: visible;
}

.her__headerpart {
  display: flex;
  flex-flow: row;
  flex: 2 0 0;
  place-content: flex-start center;
  align-items: flex-start;
  width: 1px;
  height: 100%;
  position: relative;
  overflow: visible;
}

.her__subheader {
  height: 100%;
  display: flex;
  flex-flow: row;
  flex: 1 0 0;
  place-content: flex-start;
  align-items: flex-start;
  width: 1px;
  position: relative;
  overflow: var(--overflow-clip-fallback, clip);
}
.her__subheader-inner {
  height: 100%;
  display: flex;
  flex-flow: row;
  flex: 1 0 0;
  place-content: center flex-start;
  align-items: center;
  gap: 10px;
  width: 1px;
  padding: 62px 0 0;
  position: relative;
  overflow: var(--overflow-clip-fallback, clip);
}
.her__tag {
  height: 31px;
  display: flex;
  flex: none;
  place-content: center;
  align-items: center;
  width: min-content;
  padding: 0 10px;
  position: relative;
  overflow: var(--overflow-clip-fallback, clip);
  background-color: var(--c-orange);
  color: var(--c-white);
  white-space: pre;
}

/* NOT white-space:pre-wrap here. The h1 is a block-level child sitting on
   its own source line, so pre-wrap renders the surrounding HTML indentation
   as two real line boxes -- one above, one below -- inflating this wrapper
   from 72px to 128px and lifting the title 28px off the hr it should sit
   on (measured: ref title ink rows 394-449, clone 366-421, identical 55px
   ink height = pure translation, not a font-size/line-height error).
   The reference puts pre-wrap on the rich-text container that directly
   holds the text, not on a wrapper around a block child. */
.her__title {
  white-space: normal;
  word-break: break-word;
  z-index: 1;
  flex: none;
  width: 100%;
  max-width: 540px;
  height: auto;
  position: absolute;
  bottom: -8px;
  left: -1px;
}
@media (min-width: 810px) and (max-width: 1439.98px) {
  .her__title { max-width: 440px; bottom: -6px; }
}
@media (max-width: 809.98px) {
  .her__title { width: 90%; max-width: 440px; bottom: -6px; left: 0; }
}

/* The -216/-59 insets are authored against the 450px hero block
   (max(50vh,400px) — the same expression .her__main uses), NOT against the
   full .her section (450+1+200=651px at desktop). Resolving them against
   .her pushes the dune texture ~145px down, where .her__footer's opaque
   cream fill hides it completely (measured: ref dune top y=310, clone
   y=444). Pin height to the hero block and let bottom go auto so the
   insets resolve as authored. Kept as a child of .her -- moving it into
   .her__main would drag it into that element's z-index:4 stacking context
   and make .her__gradient (z-index:3) paint underneath it, killing the
   cream->transparent fade over the top of the dune. */
.her__bgwrap {
  opacity: .9;
  z-index: 0;
  flex: none;
  position: absolute;
  inset: -216px -759px auto -758px;
  height: calc(max(50vh, 400px) + 275px);
  height: calc(max(calc(var(--framer-viewport-height, 100vh) * .5), 400px) + 275px);
  pointer-events: none;
}
@media (max-width: 809.98px) {
  /* phone: hero block is max(40vh,300px) and .her__footer is hidden */
  .her__bgwrap {
    height: calc(max(40vh, 300px) + 275px);
    height: calc(max(calc(var(--framer-viewport-height, 100vh) * .4), 300px) + 275px);
  }
}
.her__bg {
  height: 100%;
  width: 100%;
  position: relative;
  overflow: hidden;
  background: var(--c-cream);
  transform: scale(1.05638);
}

.her__hr {
  height: 1px;
  z-index: 4;
  flex: none;
  width: 100%;
  position: relative;
  overflow: var(--overflow-clip-fallback, clip);
}
.her__hr[data-border] { --border-bottom-width: 1px; --border-color: var(--c-stone); --border-style: solid; }

.her__footer {
  background-color: var(--c-cream);
  z-index: 4;
  display: flex;
  flex-flow: column;
  place-content: center;
  align-items: center;
  width: 100%;
  height: 20vh;
  height: calc(var(--framer-viewport-height, 100vh) * .2);
  min-height: 200px;
  padding: 0 32px;
  position: relative;
  overflow: visible;
}
@media (min-width: 810px) and (max-width: 1439.98px) {
  .her__footer {
    height: 24vh;
    height: calc(var(--framer-viewport-height, 100vh) * .24);
  }
}

.her__footer-inner {
  display: flex;
  flex-flow: row;
  flex: 1 0 0;
  place-content: center;
  align-items: center;
  width: 100%;
  max-width: var(--page-max);
  height: 1px;
  position: relative;
  overflow: visible;
}

.her__footer-gaps { display: flex; flex-flow: column; flex: 1 0 0; place-content: flex-start center; align-items: flex-start; width: 1px; height: 100%; position: relative; overflow: visible; }
@media (min-width: 810px) and (max-width: 1439.98px) { .her__footer-gaps { display: none; } }
.her__footer-gapline { height: 1px; display: flex; flex-flow: row; flex: 1 0 0; place-content: center flex-start; align-items: center; gap: 8px; width: 100%; position: relative; overflow: var(--overflow-clip-fallback, clip); }
.her__footer-hr { height: 1px; z-index: 8; flex: none; position: absolute; bottom: 1px; left: 0; right: 0; }
.her__footer-hr[data-border] { --border-bottom-width: 3px; --border-color: var(--c-stone-85); --border-style: dashed; }

.her__buttons {
  height: min-content;
  z-index: 1;
  display: flex;
  flex-flow: row;
  flex: none;
  place-content: center;
  align-items: center;
  position: absolute;
  bottom: 0;
  /* Starts one column in, aligned with .her__headerpart (x491 of the 1376px
     inner box), NOT at the far left. Measured ref ink: 50+ at x496, label at
     x722, CTA at x950; clone had 50+ at x37, label at x493, CTA at x950 --
     the CTA matched because it is the last column, everything before it was
     one column too far left. 33.3333% == the .her__gaps column (459px). */
  left: calc(33.3333% + 1px);
  right: 0;
  overflow: var(--overflow-clip-fallback, clip);
}
@media (min-width: 810px) and (max-width: 1439.98px) {
  /* Reference (reference/tablet/projects.jpeg) shows num+text sharing one
     row and the full-width button wrapping to a second row underneath —
     not three stacked rows. flex-wrap achieves that: num stays auto-width,
     text fills the rest of row 1, the 100%-wide button forces row 2. */
  /* row-gap only. A column gap also eats into the 1:2 split and pushes the
     label off the 1/3 column: with gap:22px tablet gives (746-22)/3=241 ->
     label at x295, but the reference measures x282, which is the clean
     746/3=248.7 -> x280.7. Laptop likewise lands at x437 vs ref x439. */
  .her__buttons { flex-flow: row wrap; gap: 22px 0; position: absolute; bottom: 0; left: 1px; right: 0; top: auto; }
}

.her__num {
  height: min-content;
  display: flex;
  flex-flow: row;
  flex: 1 0 0;
  place-content: center;
  align-items: center;
  width: 1px;
  padding: 0 0 4px;
  position: relative;
  overflow: var(--overflow-clip-fallback, clip);
}
/* num stays a real 1/3 flex column at laptop/tablet (with .her__text at
   flex:2) -- shrink-to-fit made the label hug "50+" and land at x166/x163
   instead of the reference's x282/x439. */
.her__num-inner {
  height: min-content;
  display: flex;
  flex-flow: row;
  flex: 1 0 0;
  place-content: center flex-start;
  align-items: center;
  width: 1px;
  position: relative;
  overflow: var(--overflow-clip-fallback, clip);
}
/* (The companion content-sizing override here is gone with it: .her__num is
   a real flex column again at every breakpoint, so its flex:1 0 0 child
   resolves against a real width and no longer collapses.) */
.her__num-value {
  display: flex; justify-content: center; align-items: center; overflow: hidden;
  gap: 15px; flex-direction: row; font-size: 48px; font-family: var(--font-sans);
  font-weight: 500; color: var(--c-graphite); height: 40.8px; line-height: 48px;
}
.her__num-value span { letter-spacing: -.05em; }

.her__text {
  height: min-content;
  display: flex;
  flex-flow: row;
  flex: 1 0 0;
  place-content: center flex-start;
  align-items: center;
  gap: 10px;
  width: 1px;
  position: relative;
  overflow: var(--overflow-clip-fallback, clip);
}
@media (min-width: 810px) and (max-width: 1439.98px) { .her__text { flex: 2 0 0; } }
.her__text p { white-space: pre-wrap; }

.her__divider { height: 1px; z-index: 8; flex: none; width: 50%; position: absolute; bottom: 1px; left: 0; }
.her__divider[data-border] { --border-bottom-width: 3px; --border-color: var(--c-stone-85); --border-style: dashed; }
@media (min-width: 810px) and (max-width: 1439.98px) { .her__divider { display: none; } }

/* Present at desktop AND tablet/laptop (only absent at phone, where the
   whole her__footer block is hidden) — verified against
   research/raw/{desktop,tablet,phone}/projects.html: the "how it works"
   button (named "Dark" at desktop, "Dark tab-mob" at tablet/laptop) exists
   in both captures. The hidden-15djxoa marker class on the fragment carries
   no matching CSS rule anywhere in the capture, so it is not a live hide
   mechanism — do not use it to infer visibility. See KNOWN-DIFFERENCES.md. */
/* 1:1:2 across the row -> 229/229/458, matching the reference column
   rhythm (num x491 w229, text x720 w229, cta x950 w458). Equal thirds put
   the label 229px left of where it belongs. */
.her__cta-wrap { z-index: 1; flex: 2 0 0; width: 1px; height: 57px; position: relative; }
@media (min-width: 810px) and (max-width: 1439.98px) {
  .her__cta-wrap { flex: none; width: 100%; }
}

.her__cta {
  cursor: pointer;
  height: 57px;
  width: 459px;
  max-width: 100%;
  display: flex;
  flex-flow: row;
  place-content: center;
  align-items: center;
  gap: 10px;
  padding: 0;
  position: relative;
  overflow: var(--overflow-clip-fallback, clip);
  text-decoration: none;
  background-color: var(--c-graphite);
}
/* Must come AFTER the .her__cta base rule above: same specificity, so source
   order decides (an earlier copy of this override silently lost). The 459px
   base is a desktop column measurement; at laptop/tablet the button spans
   the whole content box (ref ink 33->1247 laptop, 33->777 tablet; the cap
   pinned both to 33->491). */
@media (min-width: 810px) and (max-width: 1439.98px) {
  .her__cta { width: 100%; }
}
.her__cta-edge {
  z-index: 1; flex: none; width: 1px; position: absolute; top: 0; bottom: 0; left: -1px;
  background-color: var(--c-orange);
  transition: width .25s var(--ease-spring0), left .25s var(--ease-spring0);
}
.her__cta:hover .her__cta-edge,
.her__cta:focus-visible .her__cta-edge { width: 100%; left: 0; }
.her__cta-all { display: flex; flex-flow: column; flex: 1 0 0; place-content: center; align-items: center; gap: 10px; width: 1px; height: 100%; position: relative; overflow: visible; }
.her__cta-text { height: min-content; display: flex; flex-flow: row; flex: none; place-content: center; align-items: center; gap: 10px; width: min-content; position: relative; overflow: var(--overflow-clip-fallback, clip); }
.her__cta-text .t-mono { color: var(--c-white); white-space: pre; user-select: none; }
.her__cta-icon { width: 12px; height: 12px; position: absolute; top: 3px; left: -26px; }
.her__cta-icon svg { width: 100%; height: 100%; }
.her__cta:hover .her__cta-text { z-index: 1; }
.her__cta:hover .her__cta-icon { position: relative; top: auto; left: auto; }

.her__blurb {
  z-index: 1;
  display: flex;
  flex-flow: row;
  flex: none;
  place-content: center;
  align-items: center;
  gap: 10px;
  width: 100%;
  max-width: 340px;
  height: min-content;
  padding: 36px 0 0;
  position: absolute;
  top: 0; left: 0;
  overflow: visible;
}
/* Desktop-scoped: the blurb shares the hero-footer column origin with
   .her__buttons (ref x491, clone was x33). Width 320 (not 340) reproduces
   the reference's 3-line wrap -- ref longest line inks 302px and its box
   must be < 330px or "TRANSFORMS" would pull up onto line 1 as it did in
   the clone. Laptop/tablet keep their own max-width and are verified
   separately; phone hides .her__footer entirely. */
@media (min-width: 1440px) {
  .her__blurb { left: 33.3333%; max-width: 320px; }
}
/* 340, not 420: at 420 "TRANSFORMS" pulls onto line 1 and the block runs to
   x417 (laptop) / x325 (tablet), colliding with the label column. Reference
   blurb ink stops at x334 (laptop) / x337 (tablet). */
@media (min-width: 810px) and (max-width: 1439.98px) { .her__blurb { max-width: 320px; } }
.her__blurb p { white-space: pre-wrap; }

.her__vline-orange[data-border] { --border-bottom-width: 3px; --border-left-width: 3px; --border-right-width: 3px; --border-top-width: 3px; --border-color: var(--c-peach); --border-style: dashed; }
.her__vline-orange-wrap { height: 80vh; flex: none; width: 1px; position: relative; overflow: var(--overflow-clip-fallback, clip); }

/* ---------------------------------------------------------------
   2. projects grid  (data-section="projects", DOM id="areas")
   --------------------------------------------------------------- */
.pg {
  background-color: var(--c-cream);
  z-index: 1;
  display: flex;
  flex-flow: column;
  place-content: center flex-start;
  align-items: center;
  width: 100%;
  height: min-content;
  position: relative;
  overflow: hidden;
}

.pg__top {
  z-index: 4;
  display: flex;
  flex-flow: row;
  place-content: flex-start center;
  align-items: flex-start;
  width: 100%;
  height: 10vh;
  height: calc(var(--framer-viewport-height, 100vh) * .1);
  padding: 0 32px;
  position: relative;
  overflow: visible;
}
@media (max-width: 809.98px) { .pg__top { display: none; } }
.pg__top-inner {
  display: flex; flex-flow: row; flex: 1 0 0; place-content: center; align-items: center;
  width: 1px; max-width: var(--page-max); height: 100%; position: relative; overflow: visible;
}
.pg__top-slot { display: flex; flex-flow: row; flex: 1 0 0; place-content: flex-start; align-items: flex-start; width: 1px; height: 100%; position: relative; overflow: visible; }
.pg__top-slots { display: flex; flex-flow: row; flex: 2 0 0; place-content: flex-start space-between; align-items: flex-start; width: 1px; height: 100%; position: relative; overflow: visible; }

.pg__card { width: 100%; height: auto; position: relative; }

.pg__row {
  display: flex;
  flex-flow: row;
  place-content: flex-start center;
  align-items: flex-start;
  width: 100%;
  min-height: 400px;
  height: calc(var(--framer-viewport-height, 100vh) * .4);
  padding: 0 32px;
  position: relative;
  overflow: visible;
}
@media (min-width: 810px) and (max-width: 1439.98px) { .pg__row { height: 500px; } }
@media (max-width: 809.98px) { .pg__row { flex-flow: column; height: min-content; padding: 0 26px; } }

.pg__row-inner {
  display: flex; flex-flow: row; flex: 1 0 0; place-content: flex-start center; align-items: flex-start;
  width: 1px; max-width: var(--page-max); height: 100%; position: relative; overflow: visible;
}
@media (max-width: 809.98px) { .pg__row-inner { flex-flow: column; flex: none; place-content: flex-start; width: 100%; height: min-content; max-width: none; } }

.pg__plus { width: 15px; height: 15px; position: absolute; top: -8px; left: -7px; z-index: 3; }

.pg__left {
  height: 100%; display: flex; flex-flow: column; flex: 1 0 0; place-content: center; align-items: center;
  width: 1px; position: relative; overflow: var(--overflow-clip-fallback, clip);
}
@media (max-width: 809.98px) { .pg__left { flex: none; width: 100%; height: min-content; } }

.pg__left-header {
  display: flex; flex-flow: row; flex: 1 0 0; place-content: flex-start; align-items: flex-start;
  width: 100%; height: 1px; position: relative; overflow: visible;
}
@media (max-width: 809.98px) { .pg__left-header { gap: 0; flex: none; place-content: space-between; height: min-content; min-height: 110px; } }

.pg__left-line { height: 100%; flex: none; width: 1px; position: relative; }
.pg__left-line[data-border] { --border-top-width: 1px; --border-right-width: 1px; --border-bottom-width: 1px; --border-left-width: 1px; --border-color: var(--c-stone-cool-50); --border-style: solid; }
@media (max-width: 809.98px) {
  .pg__left-line { align-self: stretch; height: auto; }
  .pg__left-line[data-border] { --border-top-width: 2px; --border-right-width: 2px; --border-bottom-width: 2px; --border-left-width: 2px; --border-color: var(--c-peach); --border-style: dashed; }
}

.pg__cases {
  display: flex; flex-flow: column; flex: 1 0 0; place-content: flex-start center; align-items: flex-start;
  gap: 28px; width: 1px; height: min-content; position: relative; overflow: visible;
  list-style: none; margin: 0; padding: 0;
}
@media (max-width: 809.98px) { .pg__cases { justify-content: flex-start; padding: 60px 0 52px; } }

.pg__case {
  display: flex; flex-flow: wrap; flex: none; place-content: center flex-start; align-items: center;
  gap: 30px; width: 100%; height: min-content;
}

.pg__case-link {
  display: flex; flex-flow: row; flex: 1 0 0; place-content: center space-between; align-items: center;
  width: 1px; height: min-content; padding: 0 20px 0 0; text-decoration: none; position: relative; overflow: visible;
  cursor: pointer;
}
@media (max-width: 809.98px) { .pg__case-link { padding: 0; } }

.pg__case-name {
  white-space: pre-wrap; word-break: break-word; z-index: 1; flex: 1 0 0; width: 1px; height: auto; position: relative;
  font-family: var(--font-sans); font-weight: 500; font-size: 24px; line-height: 100%;
  letter-spacing: -.05em; color: var(--c-graphite);
}
@media (min-width: 810px) and (max-width: 1439.98px) { .pg__case-name { flex: none; width: 100%; max-width: 250px; } }

.pg__case-meta { white-space: pre; z-index: 1; flex: none; width: auto; height: auto; position: relative; }
.pg__case-meta.t-body-sm { color: var(--c-grey); text-align: right; }
/* research/raw/{tablet,laptop}/projects.html: the year-meta RichTextContainer
   (quote-mark + <time>) is structurally ABSENT from every case row's <a> at
   810-1439.98px — not just hidden, not present at all in that breakpoint's
   component variant. Desktop (>=1440) and phone (<810) both keep it (3
   RichTextContainers per case vs 1 at tablet/laptop). Verified against
   reference/{laptop,tablet}/projects.jpeg: no "'26" anywhere in the case
   list at either bp. See docs/KNOWN-DIFFERENCES.md. */
@media (min-width: 810px) and (max-width: 1439.98px) { .pg__case-meta { display: none; } }

/* Cases render name + quote-mark near-invisible at rest for the trailing
   (unreleased?) entries, and it is BREAKPOINT-DEPENDENT — verified against
   reference/{desktop,tablet,phone}/projects.jpeg:
     desktop (>=1440):        Case 4 (NovaFlow) + Case 5 (Sonora) dimmed
     tablet/laptop (810-1439.98): only Case 5 (Sonora) dimmed
     phone (<=809.98):        none dimmed, all 5 fully visible
   No data-framer-appear-id and no .hover rule govern it in the captured
   CSS, so it is not the standard appear system; reproduced as a static
   resting state per breakpoint, with a hover/focus reveal as a reasonable
   progressive-disclosure approximation. See docs/KNOWN-DIFFERENCES.md. */
.pg__case-link--dim-d .pg__case-name,
.pg__case-link--dim-d .pg__case-quote,
.pg__case-link--dim-dt .pg__case-name,
.pg__case-link--dim-dt .pg__case-quote {
  transition: opacity .25s var(--ease-spring0);
}
/* CORRECTED 2026-08 (live re-probe): the dim rows do NOT light up on mouse
   hover on the reference — hovering NovaFlow/Sonora leaves them dim (and is
   a no-op for the thumbnail/panel too, see js/projects.js). The reveal is
   kept for :focus-visible ONLY, so the entries stay reachable and legible
   for keyboard users; that is a deliberate a11y addition over the
   reference, not an attempt to mirror it. */
@media (min-width: 1440px) {
  .pg__case-link--dim-d .pg__case-name,
  .pg__case-link--dim-d .pg__case-quote { opacity: .001; }
  .pg__case-link--dim-d:focus-visible .pg__case-name,
  .pg__case-link--dim-d:focus-visible .pg__case-quote { opacity: 1; }
}
@media (min-width: 810px) {
  .pg__case-link--dim-dt .pg__case-name,
  .pg__case-link--dim-dt .pg__case-quote { opacity: .001; }
  .pg__case-link--dim-dt:focus-visible .pg__case-name,
  .pg__case-link--dim-dt:focus-visible .pg__case-quote { opacity: 1; }
}

.pg__right {
  flex: 2 0 0; display: flex; flex-flow: column; place-content: flex-start center; align-items: flex-start;
  width: 1px; height: 100%; position: relative; overflow: visible;
}
@media (max-width: 809.98px) { .pg__right { flex: none; width: 100%; height: min-content; } }

.pg__right-header { display: flex; flex-flow: row; flex: 1 0 0; place-content: center; align-items: center; width: 100%; height: 1px; position: relative; overflow: visible; }
@media (max-width: 809.98px) { .pg__right-header { flex: none; height: min-content; } }

.pg__thumb-box {
  height: 100%; display: flex; flex-flow: row; flex: 1 0 0; place-content: flex-start; align-items: flex-start;
  width: 1px; position: relative; overflow: var(--overflow-clip-fallback, clip);
}
@media (max-width: 809.98px) { .pg__thumb-box { height: min-content; } }

.pg__thumb-line { height: 100%; flex: none; width: 1px; position: relative; }
.pg__thumb-line[data-border] { --border-top-width: 3px; --border-right-width: 3px; --border-bottom-width: 3px; --border-left-width: 3px; --border-color: var(--c-peach); --border-style: dashed; }
@media (max-width: 809.98px) { .pg__thumb-line { display: none; } }

/* No opacity:.75 here. The capture had it because the thumbnail was frozen
   mid appear-animation, but the reference SCREENSHOT shows the thumb fully
   opaque (crop stats: min=0, 23.2% of pixels dark). Since the dithered
   thumb asset is cropped straight out of that screenshot, the crop already
   IS the final look -- same reason transform and filter are neutralized on
   .pg__thumb-scale--dithered below. Re-applying .75 composited the image
   against the cream page a second time, lifting blacks 0 -> 61 and cutting
   dark pixels 23.2% -> 5.1% (measured), which alone accounted for the
   ~12% diff in the two thumbnail bands. */
.pg__thumb-wrap {
  display: flex; flex-flow: row; flex: 1 0 0; place-content: center; align-items: center; gap: 10px;
  width: 1px; height: 100%; position: relative; overflow: hidden;
}
@media (max-width: 809.98px) { .pg__thumb-wrap { height: min-content; } }

.pg__thumb-inner {
  display: flex; flex-flow: column; flex: 1 0 0; place-content: center flex-start; align-items: center;
  gap: 28px; width: 1px; height: 100%; position: relative;
}
@media (max-width: 809.98px) { .pg__thumb-inner { height: min-content; } }

.pg__thumb-link {
  display: flex; flex-flow: wrap; flex: 1 0 0; place-content: center flex-start; align-items: center; gap: 30px;
  width: 100%; height: 1px; text-decoration: none; position: relative;
}
@media (max-width: 809.98px) { .pg__thumb-link { flex: none; height: min-content; } }

.pg__thumb-frame { flex: 1 0 0; width: 1px; height: 100%; position: relative; overflow: hidden; }
@media (max-width: 809.98px) { .pg__thumb-frame { height: 360px; } }
.pg__thumb-scale { height: 100%; width: 100%; position: relative; transform-origin: center; transform: scale(1.176); overflow: hidden; }
.pg__thumb-img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 50%; filter: brightness(203%) contrast(221%); display: block; }

/* Reference (research/) points at a plain photo source, but every
   reference/{bp}/projects.jpeg screenshot actually shows the photo run
   through a client-side canvas halftone/dither effect (visible ordered-dot
   pattern, not a CSS filter look) plus the platform's "BUY TEMPLATE" /
   "Made in Framer" badge baked into the frame. Reproducing the canvas
   effect isn't practical here, so this crops the already-dithered pixels
   straight out of each breakpoint's reference screenshot and uses that as
   the local image — pixel-faithful without a JS dependency. Because the
   crop already IS the final rendered look, the scale/filter used for the
   plain-photo source must be neutralized on this wrapper. See
   docs/KNOWN-DIFFERENCES.md. */
.pg__thumb-scale--dithered { transform: none; }
.pg__thumb-scale--dithered .pg__thumb-img { filter: none; }

/* ---- Hover-swap thumbnail layer (desktop >=1440 only) ----
   Cases 2 and 3 of the hover swap cannot reuse the baked crop above: that
   crop is one frozen screenshot of case 1. On the reference the thumbnail
   is a live <canvas> ordered-dot halftone of the raw photo, composited at
   0.8 * 0.75 = 0.6 effective opacity (measured up the ancestor chain at
   1440x900). The raw photos for cases 2 and 3 are already in
   /assets/images, so this layer re-derives the look from them in CSS.

   Recipe: the reference's own pre-halftone img treatment (scale 1.176 +
   brightness 203% / contrast 221%, transcribed from the live rules),
   desaturated, then screened through a 3px dot-grid mask.

   KNOWN DIFFERENCE — this is an approximation of a canvas effect, not a
   reproduction, and it is the one part of the hover swap that is not
   faithful. A real halftone modulates dot AREA by local luminance; a CSS
   mask can only punch a fixed-size dot grid, so flat regions render as an
   even screen instead of resolving into coarse/fine clusters. Twelve
   filter/opacity/dot-size combinations were measured against the live
   thumbnails; this one matched best on key and subject legibility, but the
   texture is finer and more uniform than the reference. Doing better means
   baking per-case dithered PNGs, which is not attempted here — the only
   way to get them is to screenshot the live canvas, which also bakes the
   template vendor's "BUY TEMPLATE / Made in Framer" watermark into the
   asset (as the case-1 crop above unfortunately already does).
   See docs/KNOWN-DIFFERENCES.md.

   `hidden` at rest keeps the at-rest render and the network waterfall
   byte-identical: js/projects.js only assigns a src once a swap happens. */
.pg__thumb-swap {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 50%;
  transform: scale(1.176);
  transform-origin: center;
  filter: grayscale(1) brightness(203%) contrast(221%);
  z-index: 1;
  pointer-events: none;
  -webkit-mask-image: radial-gradient(circle at 50% 50%, #000 55%, transparent 60%);
  mask-image: radial-gradient(circle at 50% 50%, #000 55%, transparent 60%);
  -webkit-mask-size: 3px 3px;
  mask-size: 3px 3px;
  -webkit-mask-repeat: repeat;
  mask-repeat: repeat;
}
.pg__thumb-swap[hidden] { display: none; }

/* While a swapped case is showing, the baked case-1 crop underneath must go,
   or the two images composite together (the swap layer is translucent by
   design). `visibility`, not `display`: the <picture> keeps its box, so the
   frame cannot change measured height mid-interaction. */
.pg__thumb-frame[data-case-swapped] .pg__thumb-scale--dithered { visibility: hidden; }

/* ---- Problem and Result bar — desktop-only ---- */
.pg__bottom {
  z-index: 4;
  display: flex;
  flex-flow: row;
  place-content: flex-start center;
  align-items: flex-start;
  width: 100%;
  min-height: 200px;
  height: 20vh;
  height: calc(var(--framer-viewport-height, 100vh) * .2);
  padding: 0 32px;
  position: relative;
  overflow: visible;
}
@media (min-width: 810px) and (max-width: 1439.98px), (max-width: 809.98px) { .pg__bottom { display: none; } }

/* CORRECTED 2026-08 (live re-probe). This bar was previously hidden at rest
   behind :hover/:focus-within, on the theory that the blank 200px band in
   reference/desktop/projects.jpeg meant an un-fired reveal. It did not.
   Probing the live page at 1440x900: the Problem/Result panel is
   opacity:1 with no interaction, at rest, on first paint — the reference
   screenshot simply caught it before the appear pass. It is now always
   visible, which is the only intentional at-rest pixel change here. */
.pg__bottom-inner {
  display: flex; flex-flow: row; flex: 1 0 0; place-content: center; align-items: center; width: 1px; max-width: var(--page-max); height: 100%; position: relative; overflow: visible;
}
.pg__bottom-line { height: 100%; display: flex; flex-flow: row; flex: 1 0 0; place-content: center flex-start; align-items: center; gap: 0; width: 1px; position: relative; }
.pg__bottom-line-inner { height: 100%; flex: none; width: 1px; position: relative; }
.pg__bottom-line-inner[data-border] { --border-top-width: 1px; --border-right-width: 1px; --border-bottom-width: 1px; --border-left-width: 1px; --border-color: var(--c-stone-cool-50); --border-style: solid; }

.pg__bottom-info { display: flex; flex-flow: row; flex: 2 0 0; place-content: flex-start center; align-items: flex-start; width: 1px; height: 100%; position: relative; overflow: visible; }
.pg__bottom-col { display: flex; flex-flow: column; flex: 1 0 0; place-content: center flex-start; align-items: center; gap: 28px; width: 1px; height: 100%; position: relative; }
.pg__bottom-link { display: flex; flex-flow: wrap; flex: 1 0 0; place-content: center flex-start; align-items: center; width: 100%; height: 1px; text-decoration: none; position: relative; }

.pg__what { height: 100%; display: flex; flex-flow: row; flex: 1 0 0; place-content: flex-start; align-items: flex-start; width: 1px; position: relative; overflow: var(--overflow-clip-fallback, clip); }
.pg__what-line { height: 100%; flex: none; width: 1px; position: relative; }
.pg__what-line[data-border] { --border-top-width: 3px; --border-right-width: 3px; --border-bottom-width: 3px; --border-left-width: 3px; --border-color: var(--c-peach); --border-style: dashed; }
.pg__texts { display: flex; flex-flow: row; flex: 1 0 0; place-content: flex-start; align-items: flex-start; width: 1px; height: 100%; position: relative; overflow: var(--overflow-clip-fallback, clip); }

.pg__pr { height: 100%; display: flex; flex-flow: row; flex: 1 0 0; place-content: flex-start; align-items: flex-start; width: 1px; position: relative; overflow: var(--overflow-clip-fallback, clip); }
.pg__pr-line { height: 100%; flex: none; width: 1px; position: relative; }
.pg__pr-line[data-border] { --border-top-width: 3px; --border-right-width: 3px; --border-bottom-width: 3px; --border-left-width: 3px; --border-color: var(--c-stone-85); --border-style: dashed; }

/* Alignment fix (2026-08-31): was `place-content: flex-start flex-end` —
   everything bottom-anchored. The reference's captions are one-liners so its
   ragged tops never show; our multi-line values pushed the three labels to
   three different heights. space-between keeps the values on the reference's
   shared bottom line and puts the labels on one shared top line. */
.pg__col { height: 50%; display: flex; flex-flow: column; flex: 1 0 0; place-content: flex-start space-between; align-items: flex-start; gap: 10px; width: 1px; position: relative; overflow: var(--overflow-clip-fallback, clip); }

.pg__label-row { max-width: 120px; height: min-content; display: flex; flex-flow: row; flex: none; place-content: flex-end center; align-items: flex-end; width: 100%; position: relative; overflow: var(--overflow-clip-fallback, clip); }
.pg__label { white-space: pre-wrap; word-break: break-word; flex: 1 0 0; width: 1px; height: auto; position: relative; }
.pg__label.pg__label--orange { color: var(--c-orange); }

.pg__value-row { max-width: 170px; height: min-content; display: flex; flex-flow: row; flex: none; place-content: center; align-items: center; gap: 10px; width: 100%; position: relative; overflow: var(--overflow-clip-fallback, clip); }
.pg__value-row--sm { max-width: 170px; } /* was 120px — our "what we did" captions are longer than the template's and wrapped to 3 lines */
.pg__value { white-space: pre-wrap; word-break: break-word; flex: 1 0 0; width: 1px; height: auto; position: relative; }

.pg__result-row { height: min-content; display: flex; flex-flow: row; flex: none; place-content: flex-end flex-start; align-items: flex-end; gap: 12px; width: 100%; position: relative; overflow: var(--overflow-clip-fallback, clip); }
.pg__result-num-wrap { height: min-content; display: flex; flex-flow: row; flex: none; place-content: center; align-items: center; width: min-content; position: relative; overflow: var(--overflow-clip-fallback, clip); }
.pg__result-num { white-space: pre; flex: none; width: auto; height: auto; position: relative; color: var(--c-black); }
.pg__result-caption { max-width: 88px; height: min-content; display: flex; flex-flow: row; flex: none; place-content: flex-start flex-end; align-items: flex-start; gap: 10px; width: 88px; position: relative; overflow: var(--overflow-clip-fallback, clip); }

.pg__end-line { height: 100%; flex: none; width: 1px; position: relative; }
.pg__end-line[data-border] { --border-top-width: 1px; --border-right-width: 1px; --border-bottom-width: 1px; --border-left-width: 1px; --border-color: var(--c-stone-cool-50); --border-style: solid; }

/* ================================================================
   RUNTIME ANIMATION PARITY  (measured on the live reference, 2026-08-19,
   1440x900, style-probes from DOMContentLoaded every 60ms on /projects)
   ----------------------------------------------------------------
   Live fades its mono captions word-by-word (data-split-text="words" is
   now on those elements in the HTML; shared text-reveal.js/.css does the
   work). The shared word preset staggers at .12s; live's stagger shrinks
   with word count (run stays ~450-650ms total):
     hero blurb        12 words  first fade ~436ms, last ~962ms  ≈48ms/word
     "problem >" bar    2-4 words per slot, ~150-190ms/word on scroll
   "perfectly Finished / projects" (3 words) keeps the shared default.
   The h1, case names and the "'"-glyph of each year meta are LETTER
   reveals on live (the year digits themselves never reveal — verified:
   the live "26" is plain text with no spans) — the HTML carries plain
   data-split-text for those.
   ================================================================ */
.her__blurb [data-split-text="words"] { --sr-stagger: .05s; }
.pg__label[data-split-text="words"],
.pg__value[data-split-text="words"],
.pg__result-num[data-split-text="words"] { --sr-stagger: .15s; }
