/* ==========================================================================
   OREGON — products.css (Phase P4b → P8 SHOWCASE)
   Owned exclusively by this agent. Loaded after site.css — may override it.

   P8 (Tan feedback 2026-09-04): the pinned scrub carousel now carries the
   five flagship PROJECTS (id="projects", renamed from "products") — bigger
   image, stronger zoom, bigger type, per "i like the side scroll on this.
   Bigger zoom in and bigger font as well as bigger image would be nice."
   The eight product lines that used to live in the carousel move to their
   own compact, non-pinned slide directly after it — id="products",
   class ".products-lines" (NOT ".products" — site.css:493 still styles
   that legacy class on the old carousel section and must not be reused).

   Two sections in this file:
   1. #projects — SELECTED PROJECTS (was "products" carousel; §3-C behaviour
      unchanged in kind, tuned bigger). See js/products.js (same rename).
   2. #products — WHAT WE DO (new: static 4×2 typographic grid, no pin,
      no JS — entrance stagger comes free from js/deck.js via
      data-animate="item", same as every other non-pinned slide).

   P10 (Tan, third review, verbatim: "This section on mobile, it doesn't
   seem to scroll to the side when we scroll down. unlike the desktop
   version"): #projects is now pinned + vertical-scroll-drives-horizontal
   on every width, not just desktop — see SECTION 1's updated note and
   js/products.js (no more isMobile() branch on the transform/opacity
   math, only on pin distance/card sizing here and an added touch-swipe
   assist there). Also fixed: the eyebrow/headline landing under the
   fixed logo on mobile (frame 36) — see the .showcase__pin comment.
   Section 2 (#products) grew its thumbnails — see that section's notes.
   ========================================================================== */

/* ==========================================================================
   SECTION 1 — #projects — SELECTED PROJECTS (pinned scrub carousel)
   Behaviour (see js/products.js):
   - #projects is pinned at every width (P10: was desktop-only): it grows
     tall (650vh desktop / 400vh mobile) and a sticky ".showcase__pin"
     viewport holds the visible carousel while JS translates
     ".showcase__track" horizontally in lockstep with scroll progress
     (scrubbed, not eased). Mobile also gets a cheap touch-swipe assist
     (advance one card on a mostly-horizontal drag) — see js/products.js.
   - prefers-reduced-motion: ".reduced-motion" class (set by js/deck.js on
     <html>) forces a plain static vertical list — see bottom of section.
   ========================================================================== */

/* site.css's generic `.slide { overflow: hidden; }` (site.css:61) sits
   between `.showcase__pin` and the viewport and — because overflow:hidden
   establishes its own scroll container regardless of whether it actually
   overflows — breaks position:sticky (the pin sticks to *that* box instead
   of the viewport, since that box itself never scrolls). Clipping for this
   section happens at `.showcase__stage` instead, so it's safe to open this
   back up just for #projects. */
#projects.slide {
  overflow: visible;
}

/* ---------------------------------------------------------------------- */
/* Layout shell (mobile-first: plain stacked flow)                         */
/* ---------------------------------------------------------------------- */
.showcase__pin {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.showcase__head {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.showcase__stage {
  position: relative;
  width: 100%;
  outline-offset: 10px;
}
.showcase__stage:focus-visible {
  outline: 1px solid var(--accent);
}

.showcase__track {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
}

.showcase-card {
  flex: 0 0 auto;
  width: 82vw;
  max-width: 420px;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  /* P10: scroll-snap-align is inert now — the stage no longer scrolls
     natively at any width (JS drives the track transform instead, same
     as desktop always did; see .showcase__stage's overflow:hidden
     above), so nothing here to snap. */
}

.showcase-card__media {
  border-radius: 0;
}

.showcase-card__title {
  font-family: var(--font-display);
  font-variation-settings: "wdth" var(--wdth-label);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.01em;
  line-height: 1.02;
  color: var(--offwhite);
  /* P8: "bigger font" — an --fs-h1-ish clamp so the active card's title
     pops the way Tan asked for; ~68px at 1920 (≥64px floor), ~55px at
     1440. Sized to still clear the pin's 100svh even for the longest
     title, which wraps two lines ("Auditorium, Mahidol University"). */
  font-size: clamp(2rem, 2.9vw + 0.75rem, 4.25rem);
  margin: 0;
}

.showcase-card__location {
  margin: 0.25em 0 0;
}

.showcase-card__spec {
  margin: 0.2em 0 0;
  line-height: 1.35;
}

/* P9: per-project placeholder link ("See all projects →", verbatim wording
   per Tan — data-video-url is reserved on the anchor for the client's future
   per-project YouTube link, see HANDOVER.md "Added in P9"). Unmistakable as
   a link: offwhite, underlined, arrow, well above the 18px floor, and a
   44px tap target — sits inside .showcase-card__copy so it follows the
   same crossfade as the rest of the card's copy. */
.showcase-card__link {
  display: inline-flex;
  align-items: center;
  margin-top: 0.1em;
  min-height: 44px;
  font-family: var(--font-body);
  font-size: var(--fs-small);
  font-weight: 600;
  color: var(--offwhite);
  text-decoration: underline;
  text-decoration-color: var(--steel);
  text-underline-offset: 0.2em;
  transition: color var(--dur-fast) ease, text-decoration-color var(--dur-fast) ease;
}
.showcase-card__link:hover,
.showcase-card__link:focus-visible {
  color: var(--accent-text);
  text-decoration-color: currentColor;
}
.showcase-card__link:focus-visible {
  outline: 1px solid var(--accent);
  outline-offset: 4px;
}

/* Text crossfade block: idle/inactive rest state (about to enter). Full
   card width (not a narrow ch-cap) so the longest spec line (Carlton's)
   wraps as little as possible — the pinned viewport's vertical budget is
   tight now that the image and title are both much bigger (P8). */
.showcase-card__copy {
  opacity: 0;
  transform: translateY(20px);
  transition: opacity 0.4s var(--ease-out), transform 0.4s var(--ease-out);
  max-width: 100%;
}
/* P9 (PM): the link shares the location row (right-aligned) instead of
   adding a fourth text line under the spec — that is the height that lets
   the image stay at its P8 size (Tan: "bigger image"). The spec keeps the
   full card width so the long Carlton line wraps as little as possible. */
@media (min-width: 641px) {
  .showcase-card:not(.showcase-card--cta) .showcase-card__copy {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    column-gap: var(--space-3);
    align-items: end;
  }
  .showcase-card__title { grid-column: 1 / -1; grid-row: 1; }
  .showcase-card__location { grid-column: 1; grid-row: 2; }
  .showcase-card__link { grid-column: 2; grid-row: 2; align-self: end; justify-self: end; margin-top: 0; min-height: 0; white-space: nowrap; }
  .showcase-card__spec { grid-column: 1 / -1; grid-row: 3; }
}
.showcase-card.is-active .showcase-card__copy {
  opacity: 1;
  transform: translateY(0);
}
/* Outgoing card: fades up and out (asymmetric from the +20px entry). */
.showcase-card.is-leaving .showcase-card__copy {
  opacity: 0;
  transform: translateY(-20px);
}

/* ---------------------------------------------------------------------- */
/* P9: sixth, final card — "See all projects" CTA (no photo, no facts)     */
/* Same .showcase-card width/scrub/opacity treatment as the five project   */
/* cards (js/products.js treats it as just another track item), but the    */
/* photo slot is replaced by an ink panel with a hairline border so it     */
/* reads as a deliberate close, not a missing image.                       */
/* ---------------------------------------------------------------------- */
.showcase-card__cta-panel {
  position: relative;
  overflow: hidden;
  width: 100%;
  aspect-ratio: 16 / 9;
  border: 1px solid var(--steel-20);
  background: var(--panel);
  display: flex;
  align-items: center;
  padding: 0 8%;
}
/* P10 item J (Tan, verbatim: "a shadow of a skyscraper as the background of
   the link of the project card to signify... there's something more to find
   out about"): a real photo crop (assets/img/cta-tower, see MANIFEST.md),
   pushed to near-black by CSS filter (not a design/graphic) and buried under
   a dark radial gradient so it reads as a silhouette emerging from the ink,
   never as a legible photo. object-fit:contain on the portrait crop inside
   this 16:9 panel means the tower spans the full panel height with dark
   letterboxing left/right — that empty dark space is the intended "shadow"
   look, not a bug. */
.showcase-card__cta-media {
  position: absolute;
  inset: 0;
  z-index: 0;
  border: none; /* strip the .media.ph placeholder hairline — panel already has one */
  background: none;
}
.showcase-card__cta-media img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center bottom;
  filter: brightness(0.22) saturate(0.2) contrast(1.1);
}
.showcase-card__cta-panel::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: radial-gradient(ellipse 65% 60% at 50% 60%, rgba(10, 11, 13, 0.2) 0%, rgba(10, 11, 13, 0.65) 55%, var(--ink) 100%);
}
.showcase-card--cta .showcase-card__copy {
  /* This card carries no image above it, so its copy block is the whole
     card — always legible, not just on entry/exit (same is-active/is-leaving
     crossfade still applies for the scrub, this only sets the rest state). */
  position: relative;
  z-index: 2;
  opacity: 1;
  transform: none;
}
.showcase-card__cta-kicker {
  font-family: var(--font-mono);
  font-size: var(--fs-mono);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--steel);
  margin: 0 0 0.5em;
}
.showcase-card__cta {
  display: inline-block;
  color: var(--offwhite);
  text-decoration: none;
  transition: color var(--dur-fast) ease;
}
.showcase-card__cta:hover,
.showcase-card__cta:focus-visible {
  color: var(--accent-text);
}
.showcase-card__cta:focus-visible {
  outline: 1px solid var(--accent);
  outline-offset: 6px;
}

/* ---------------------------------------------------------------------- */
/* Progress: hairline fill + mono counter                                  */
/* ---------------------------------------------------------------------- */
.showcase__progress {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.showcase__progress-track {
  flex: 1 1 auto;
  height: var(--rule);
  background: var(--steel-20);
  position: relative;
  overflow: hidden;
}
.showcase__progress-fill {
  position: absolute;
  inset: 0 auto 0 0;
  /* P5 perf fix: this used to animate `width` every scroll frame (js/products.js
     sets it on desktopUpdate/mobileUpdate) — a layout-triggering property,
     which is exactly the "layout thrash" GOAL.md §3-F rules out ("transforms
     & opacity only"). Full-width bar, scaled from the left with a GPU-only
     transform instead — same visual result, no per-frame layout/reflow. */
  width: 100%;
  transform: scaleX(0);
  transform-origin: 0 50%;
  background: var(--steel);
  will-change: transform;
}
.showcase__counter {
  font-family: var(--font-mono);
  font-size: var(--fs-mono);
  color: var(--steel);
  letter-spacing: 0.02em;
  white-space: nowrap;
  flex: 0 0 auto;
}

/* ---------------------------------------------------------------------- */
/* P10 item E: mobile now gets the SAME pinned vertical-scroll-drives-      */
/* horizontal scrub as desktop (Tan: "it doesn't seem to scroll to the     */
/* side when we scroll down. unlike the desktop version"). The old <=640px */
/* native-swipe/scroll-snap-x mode is gone — js/products.js no longer      */
/* branches on isMobile() for the transform/opacity math, only for the     */
/* pin-distance CSS below and the (optional, cheap) touch-swipe assist.    */
/* Pin mechanics (sticky pin, hidden-overflow stage/track, block layout on */
/* the section) are therefore UNSCOPED — same rules at every width — with  */
/* only sizing (pin distance, card width/gap) split per breakpoint below.  */
/* ---------------------------------------------------------------------- */

/* site.css's `.slide` is `display:flex; justify-content:center;` (site.css:57-59)
   — a flex container centring its sticky child breaks position:sticky in
   Chrome/Safari (the child's static position gets fixed at the centred
   offset and never re-evaluates against the viewport as you scroll).
   Switch #projects to block layout so `.showcase__pin` is a normal
   block-level sticky child instead of a centred flex item; vertical
   centring of the pin's own content happens one level down, on
   `.showcase__pin` itself, which is unaffected. P10: was desktop-only —
   mobile is now pinned too, so this must apply at every width. */
#projects.slide[data-pin="true"] {
  display: block;
  /* Padding here would shrink the sticky containing block's content box
     below the pin's own 100svh box, making the CSS sticky range end
     before js/products.js's scroll-progress math (which uses
     section.offsetHeight, i.e. the padding-inclusive border box) expects
     it to — the pin would visibly "unstick" a beat early. Padding moves
     onto .showcase__pin below instead, which doesn't affect the range. */
  padding: 0;
}

.showcase__pin {
  position: sticky;
  top: 0;
  height: 100svh;
  /* P10: chrome-top clearance now matters at every width — this used to
     be desktop-only; on mobile the pin/head previously relied on the
     generic .slide padding-top (fine while unpinned, but a pinned/block
     section no longer gets it — see the [data-pin] rule above), which is
     exactly how "SELECTED PROJECTS"/the headline ended up under the fixed
     logo (Tan, frame 36). --chrome-top itself is already the mobile value
     (smaller logo) under the site.css `max-width:640px` breakpoint, so no
     separate mobile number is needed here. */
  padding: max(var(--space-6), var(--chrome-top)) var(--margin) 0;
  gap: 4px;
  justify-content: center;
  overflow: hidden;
}

.showcase__stage {
  overflow: hidden;
  scroll-snap-type: none;
  -webkit-overflow-scrolling: auto;
}

.showcase__track {
  align-items: center;
}
.showcase__track.is-scrubbing {
  will-change: transform;
}

/* Mobile-only: pin distance. (N−1) transitions (5, for 5 projects + the
   CTA card) × ~0.8 viewport — shorter than desktop's 130vh/transition so
   one flick of the thumb reads as roughly one card (item E.1). */
@media (max-width: 640px) {
  #projects.slide[data-pin="true"] {
    min-height: 400vh;
  }
  /* Card image on mobile: full column width, ~16:10 (item E.3) — was
     16:9 (desktop's ratio, via the shared .media--16-9 utility class on
     the element); a touch shorter reads better full-bleed at phone width. */
  .showcase-card__media {
    aspect-ratio: 16 / 10;
  }
  /* P10 item J (PM): at phone width a 16:9 panel with a `contain`ed
     portrait tower leaves a ~90px-wide sliver behind the label — match the
     sibling cards' 16:10 and let the crop cover the panel, tower to the
     right of the label, so the silhouette actually reads on a phone. */
  .showcase-card__cta-panel {
    aspect-ratio: 16 / 10;
  }
  .showcase-card__cta-media img {
    object-fit: cover;
    object-position: 68% 20%;
  }
}

/* Short phones (390×660-class, address bar always showing): the pin is a
   flex column (head + stage + progress) inside a fixed 100svh box, and
   with no explicit stage height the three compete for the same space —
   below ~700px tall the head+progress no longer leave the 16:10 card
   room to be the stage's natural (unshrunk) height, and the stage's own
   overflow:hidden was silently clipping the bottom of the copy block
   (spec/link) instead of the browser ever actually shrinking the card.
   Shorter media aspect + a smaller title floor reclaim that room
   explicitly instead of relying on that clip. */
@media (max-width: 640px) and (max-height: 700px) {
  .showcase-card__media {
    aspect-ratio: 16 / 7;
  }
  .showcase-card__title {
    font-size: clamp(1.5rem, 5vw + 0.4rem, 2rem);
  }
}

/* ---------------------------------------------------------------------- */
/* Desktop (>640px): pin distance + bigger card (mechanics above, shared)  */
/* ---------------------------------------------------------------------- */
@media (min-width: 641px) {
  /* P8: 5 projects (was 8 products) / 4 transitions. Reduced from 800vh so
     each transition still gets ~130vh — deliberate, not sluggish.
     P9: a 6th (CTA) card adds a 5th transition — scaled up to 650vh
     (5 × 130vh) to keep the same per-transition scroll pace. */
  #projects.slide[data-pin="true"] {
    min-height: 650vh;
  }

  /* Pin mechanics (position/height/overflow/padding/gap) live in the
     shared, unscoped .showcase__pin rule above (P10 — now needed on
     mobile too): the active card is much taller (≥58vw 16:9 image +
     --fs-h1-ish title) than the frame has room to spare, so the
     reclaimed space goes to the card, not the pin — otherwise the copy
     block gets clipped by the stage's overflow:hidden (verified via
     bounding-box measurement). P9: that shared gap was trimmed 6px→4px
     to help reclaim room for the per-card link line below the spec (see
     .showcase-card__link and the card width clamp below — verified via
     bounding-box measurement that the link now clears the stage's own
     overflow:hidden at 1920×1080/1440×900). */

  .showcase__track {
    gap: clamp(2rem, 4vw, 4rem);
    /* Set every frame in px by js/products.js; no CSS transition on this —
       the transform must track scroll 1:1, not ease behind it. */
  }

  .showcase-card {
    /* P8 "bigger image": ≥58vw at 1920 (≈1100px+); clamp keeps 1440 and
       the pin's own vertical budget sane (Carlton's long spec line is the
       tallest copy block — verified via bounding-box measurement that it
       still clears the pin at this width).
       P9: trimmed to 54vw — the new .showcase-card__link line under the
       spec needs the reclaimed ~35-40px of image height to still clear
       the stage's overflow:hidden at 1920×1080/1440×900 (bounding-box
       verified); still comfortably the "bigger image" P8 asked for. */
    width: clamp(340px, 58vw, 1250px);
    max-width: none;
    gap: 4px;
    scroll-snap-align: none;
    /* opacity + transform (scale) set per-frame by JS; no transition here
       either — it must stay locked to scroll position. */
  }
}

/* P9: the pin's vertical budget scales with viewport *height*, not width —
   54vw still overflows the stage at 1440×900 (900px of height vs. 1080 at
   1920, same chrome-top/head/progress overhead) even though it fits fine
   at 1920×1080. Scoped to shorter desktop viewports only, independent of
   width, so 1920×1080 keeps the full "bigger image" size and only actually
   short windows (1440×900, 1366×768, laptop windows generally) get a
   smaller image + tighter rhythm — bounding-box verified the link now
   clears the stage at both required widths. */
@media (min-width: 641px) and (max-height: 1000px) {
  .showcase-card { width: clamp(320px, 54vw, 1100px); gap: 2px; }
  .showcase__pin { gap: 2px; }
  /* Small extra trim so the longest (2-line) title, Mahidol's, keeps a
     safety margin rather than landing at an exact 0px fit. */
  .showcase-card__title { font-size: clamp(1.8rem, 2.6vw + 0.6rem, 3.6rem); }
  /* Width alone can't fix this: a narrower card would only make Carlton's
     (longest) spec line wrap to a 3rd line and get taller, so height is
     trimmed independently via a shorter aspect ratio on the image instead
     of shrinking width further — bounding-box verified this clears every
     project card's link at 1440×900/1366×768, including Carlton's. */
  .showcase-card__media { aspect-ratio: 16 / 8; }
}
/* Even shorter windows (1366×768-class laptops) need a bit more still. */
@media (min-width: 641px) and (max-height: 820px) {
  .showcase-card__media { aspect-ratio: 16 / 6.5; }
}

/* ---------------------------------------------------------------------- */
/* prefers-reduced-motion: plain static vertical list, no pin, no scrub    */
/* ---------------------------------------------------------------------- */
.reduced-motion #projects.slide[data-pin="true"] {
  min-height: 100svh;
}
.reduced-motion .showcase__pin {
  position: static;
  height: auto;
  overflow: visible;
}
.reduced-motion .showcase__stage {
  overflow: visible;
  scroll-snap-type: none;
}
.reduced-motion .showcase__track {
  display: flex;
  flex-direction: column;
  transform: none !important;
  gap: var(--space-5);
}
.reduced-motion .showcase-card {
  width: 100%;
  max-width: 40rem;
  opacity: 1 !important;
  transform: none !important;
}
.reduced-motion .showcase-card__copy {
  opacity: 1 !important;
  transform: none !important;
}
.reduced-motion .showcase__progress {
  display: none;
}

/* ==========================================================================
   SECTION 2 — #products — WHAT WE DO (product-line grid)
   Non-pinned slide. Padding trimmed from site.css's default `.slide`
   (space-6 top/bottom is too generous for an 8-item grid to sit inside
   100svh at 1920×1080/1440×900 alongside the token type floors) —
   everything else about `.slide`/`.slide--ink` is inherited unchanged.
   Entrance stagger is free: js/deck.js already animates every
   [data-animate="item"] inside a non-pinned .slide on scroll-into-view.

   P10 item F (Tan, third review, verbatim: "The images on this section
   need to be bigger. This is not impactful with the images that we
   use."): mobile's card image goes from a fixed ~88px sliver to a real
   full-width 16:10 tile (item F.1; the section already isn't pinned, so
   it's free to run past one viewport there — item F.1 says as much).
   Desktop's 4×2 thumbnails were the same problem in miniature — a
   342×125px strip at 1920 (2.7:1) — because the media box had *both* an
   explicit height and the shared `.media--3-2` utility's aspect-ratio,
   and an explicit height wins: the aspect-ratio was dead weight, and
   the actual on-page ratio was whatever the height clamp happened to
   produce, not 3:2. To grow the desktop thumbnail meaningfully while
   the slide still fits one viewport at 1920×1080/1440×900 (item F.2),
   the reclaimed room comes from three places, none of them the images:
   this section's own heading is stepped down from the deck's full
   --fs-h2 scale (it doesn't need hero-level weight sitting above a
   dense 8-item grid), row/head spacing is tightened, and the title is
   sized closer to its 18px-at-1920 floor instead of well above it (was
   ~27px). See the media-query comments below for the actual numbers —
   arrived at by measuring, not by formula (bounding-box verified: grid
   still clears the slide bottom with real spare at 1920×1080/1440×900).
   ========================================================================== */
.products-lines.slide {
  /* Top padding kept at the site-wide space-6 (96px) so the eyebrow/h2
     clears the fixed .logo mark (site.css:251-258, top:24px, ~106px
     tall) the way every other slide's head does — this section's own
     .slide { justify-content:center } masked the collision at 1920×1080
     (enough centring slack to push the head down clear of the logo) but
     not at 1440×900 (verified via screenshot). Bottom padding stays
     tight so the grid still clears 100svh with the type floors. */
  padding-top: max(var(--space-6), var(--chrome-top));
  padding-bottom: var(--space-3);
}

.products-lines__head {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin-bottom: var(--space-2);
}

/* P10: this heading doesn't need the deck's full --fs-h1-adjacent --fs-h2
   scale (≈72px at 1920) — it sits above a dense 8-item grid, not a lone
   headline, and stepping it down is most of where the bigger thumbnails
   below come from. Still comfortably a display headline (≥18px floor by
   a wide margin at every required width). */
.products-lines__head .display--h2 {
  font-size: clamp(1.7rem, 1.7vw + 0.85rem, 3rem);
}

.products-lines__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4);
}

@media (min-width: 641px) {
  .products-lines__grid {
    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-2) var(--gutter);
  }
}

@media (min-width: 1100px) {
  .products-lines__grid {
    grid-template-columns: repeat(4, 1fr);
    gap: var(--space-2) var(--gutter);
  }
}

.line-card {
  display: flex;
  flex-direction: column;
}

/* P10 item F.1: mobile (single column) — full column width, real 16:10,
   auto height (no explicit height fighting the aspect-ratio here, unlike
   the old shared rule below) so it reads as a proper photo, not a strip.
   The section already isn't pinned/one-viewport, so this is free to make
   the slide taller on mobile (per the brief). */
.line-card__media {
  width: 100%;
  aspect-ratio: 16 / 10;
  border-radius: 0;
  margin-bottom: var(--space-1);
}

/* Desktop (≥641px, 2-col; ≥1100px, 4-col): back to a fixed-height strip
   so 8 cards keep fitting one viewport — but a much taller one than
   before (was clamp(5.5rem,6.5vw,8.75rem) ≈ 88–140px; ~1.85–2.2:1 now,
   not 2.7:1+). Site.css's own short-laptop trim (≤880px tall,
   .products-lines .line-card__media) still takes over below that height
   unchanged — two classes beats this file's one, deliberately, so very
   short laptop windows keep their existing smaller/safer thumbnail. */
@media (min-width: 641px) {
  .line-card__media {
    aspect-ratio: auto;
    height: clamp(8.5rem, 10.6vw, 13.5rem);
  }
}

.line-card__title {
  font-family: var(--font-display);
  font-variation-settings: "wdth" var(--wdth-label);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.01em;
  line-height: 1.1;
  color: var(--offwhite);
  margin: 0;
}
/* Mobile keeps a bigger title (the grid isn't fighting for one-viewport
   room there); desktop's is stepped down toward its 18px-at-1920 floor
   (was ~27px) — reclaimed height that goes to the image instead, per the
   section note above. Still a full step above the 18px floor. */
.line-card__title {
  font-size: clamp(1.3rem, 1.1vw + 0.85rem, 1.9rem);
}
@media (min-width: 641px) {
  .line-card__title {
    font-size: clamp(1.15rem, 0.5vw + 0.7rem, 1.35rem);
  }
}

.line-card__promise {
  font-family: var(--font-body);
  color: var(--offwhite-60);
  font-size: var(--fs-small);
  line-height: 1.3;
  margin: 0.3em 0 0;
}

.line-card__spec {
  line-height: 1.3;
  margin: 0.25em 0 0;
}

/* Weak source photo (prod-doors-windows): a distant grid-trellis tower with
   no door/window close-up. Crop into the grid texture (avoid the empty sky),
   quiet it slightly, and let the type below carry the card. */
.line-card__media--weak img {
  object-position: 50% 78%;
  filter: grayscale(18%) contrast(1.04) brightness(0.94);
}
.line-card__media--weak::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(14, 15, 17, 0) 45%, rgba(14, 15, 17, 0.6) 100%);
  pointer-events: none;
}
