/* ==========================================================================
   KRESTA TEXTILE — Photography
   --------------------------------------------------------------------------
   Every image here is a SLOT. Until a real file exists at the path, the
   page falls back to something deliberate — the hero to its canvas warp
   field, the cards to their interlacing diagrams — so a missing image is
   never a broken image. js/photo.js hides any <img> that fails to load.
   ========================================================================== */

/* ----------------------------------------------------------------- hero -- */

.hero__media {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
}

/* --- stage reveals -------------------------------------------------------
   Each stage in the hero chain sits above the one before it and is masked
   in as --hero-p runs 0 -> 1 (set by js/hero-reveal.js, one value per
   [data-hero-layer]). Four distinct mask motifs, one per stage in markup
   order, so the chain reads as four deliberate moments rather than one
   effect repeated: centre door -> iris -> diagonal sweep -> blinds. Each
   is a plain mask-image driven by the same --hero-p, no per-effect JS. */

/* Note: --hero-p is NOT declared here. It is set on .hero__stage and
   inherits down; redeclaring it locally would shadow the inherited value
   and freeze the mask at zero. The fallback lives in the var() instead. */
.hero__reveal {
  position: absolute;
  inset: 0;
  z-index: 1;
  overflow: hidden;
}

/* Stage 1 — centre door: opens like a pair of doors, a narrow centre band
   first, then wider outward to the edges.

   Two bugs lived here, both making the "closed" door not actually close:

   1. A 4% MINIMUM door width ("so the reveal is never a hard seam") meant
      the door was never fully shut — every layer showed a permanent ~4%
      sliver of the next image at rest, with zero scroll. That is what the
      client circled as looking "very bad". Fixed by dropping the floor:
      --door-half now reaches exactly 0 when --hero-p is 0.

   2. Less obvious: even with the floor gone, --door-feather was a FIXED
      1%. A CSS gradient always interpolates between two differently-
      coloured stops, so a constant-width transparent-to-opaque transition
      band existed around the centre regardless of how closed the door
      was — a permanent soft column, confirmed by pixel-diffing the centre
      column against the cloth layer alone (30-114 values off, not photo
      noise). The feather has to shrink to 0 together with the door, not
      stay constant, or "closed" is never actually fully transparent.
      min(1%, door-half) does that: no transition zone exists until the
      door has genuinely started opening, and it caps at 1% once open,
      keeping the soft edge from before. */
.hero__reveal--center {
  --door-half: calc(var(--hero-p, 0) * 50%);
  --door-feather: min(1%, var(--door-half));
  -webkit-mask-image: linear-gradient(
    to right,
    transparent 0%,
    transparent calc(50% - var(--door-half) - var(--door-feather)),
    #000 calc(50% - var(--door-half)),
    #000 calc(50% + var(--door-half)),
    transparent calc(50% + var(--door-half) + var(--door-feather)),
    transparent 100%
  );
  mask-image: linear-gradient(
    to right,
    transparent 0%,
    transparent calc(50% - var(--door-half) - var(--door-feather)),
    #000 calc(50% - var(--door-half)),
    #000 calc(50% + var(--door-half)),
    transparent calc(50% + var(--door-half) + var(--door-feather)),
    transparent 100%
  );
}

/* Stage 2 — iris: a circle opens from the centre point outward, like a
   camera aperture or a spotlight widening. Grows to 140% rather than
   stopping at 100% so it clears the far corners of a wide 16:9-ish frame
   regardless of viewport aspect ratio — mask-image's percentage radius
   isn't guaranteed to reach the corner at exactly 100%. */
.hero__reveal--iris {
  --iris-r: calc(var(--hero-p, 0) * 140%);
  --iris-feather: min(6%, var(--iris-r));
  -webkit-mask-image: radial-gradient(
    circle at 50% 50%,
    #000 calc(var(--iris-r) - var(--iris-feather)),
    transparent var(--iris-r)
  );
  mask-image: radial-gradient(
    circle at 50% 50%,
    #000 calc(var(--iris-r) - var(--iris-feather)),
    transparent var(--iris-r)
  );
}

/* Stage 3 — diagonal sweep: a soft edge crosses the frame corner to
   corner, closer to a projected light sweep than a hard wipe.

   --sweep runs from 112% (both stops past the box -> box reads entirely
   "before transparent" -> fully hidden) down to -12% (both stops before
   the box -> box reads entirely "after #000" -> fully visible). It has
   to count DOWN as --hero-p counts up, not up — an ascending sweep here
   leaves the layer fully visible at rest and fully hidden at the end of
   the stage, exactly backwards. */
.hero__reveal--diagonal {
  --sweep: calc(112% - var(--hero-p, 0) * 124%);
  -webkit-mask-image: linear-gradient(
    115deg,
    transparent var(--sweep),
    #000 calc(var(--sweep) + 12%)
  );
  mask-image: linear-gradient(
    115deg,
    transparent var(--sweep),
    #000 calc(var(--sweep) + 12%)
  );
}

/* Stage 4 — blinds: eight vertical bands, each filling in from its own
   left edge in lockstep. One repeating gradient, one progress variable —
   the repeat unit is what turns a single wipe into eight. */
.hero__reveal--blinds {
  --blind-band: 12.5%;
  --blind-open: calc(var(--hero-p, 0) * var(--blind-band));
  -webkit-mask-image: repeating-linear-gradient(
    90deg,
    #000 0,
    #000 var(--blind-open),
    transparent var(--blind-open),
    transparent var(--blind-band)
  );
  mask-image: repeating-linear-gradient(
    90deg,
    #000 0,
    #000 var(--blind-open),
    transparent var(--blind-open),
    transparent var(--blind-band)
  );
}

/* The two images drift at slightly different rates so the swap reads as
   depth rather than a flat dissolve. */
.hero__media .hero__img {
  transform: scale(calc(1 + var(--hero-p, 0) * 0.08));
}

.hero__img--garment {
  transform: scale(calc(1.06 - var(--hero-p, 0) * 0.06));
}

/* The supplied photograph is already deep slate-green and already low-key
   (mean luminance falls 69 -> 24 top to bottom). So the heavy correction
   this rule used to carry — grayscale(0.55) brightness(0.62) — was working
   against it: the desaturation stripped the exact green that makes the shot
   sit in the palette, and the darkening buried the weave.

   Only a light touch now: a little grey to unify, a little contrast to hold
   the fold edges, brightness almost untouched. */
.hero__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 38%;
  filter: grayscale(0.12) contrast(1.06) brightness(0.94) saturate(1.05);
}

.hero__img[hidden] {
  display: none;
}

/* The headline is centred, so the scrim is a soft vignette rather than a
   one-sided gradient: clearest behind the type, closing down to the edges.

   Eased back from 0.34/0.72/0.92 now a real photograph is in place — at the
   old strength the weave was almost entirely lost. The bottom band stays
   strong because the hero note sits on it.

   Anything lighter than this and the wordmark starts to lose its edge; the
   image's mid-frame luminance is ~57/255 before the scrim. */
.hero__scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: radial-gradient(
      115% 85% at 50% 44%,
      rgba(35, 53, 51, 0.2) 0%,
      rgba(35, 53, 51, 0.5) 58%,
      rgba(22, 43, 38, 0.8) 100%
    ),
    linear-gradient(to top, rgba(22, 43, 38, 0.9) 2%, rgba(35, 53, 51, 0) 42%);
}

/* No photograph supplied yet: the canvas warp field is doing the work and
   the full scrim would bury it. Set by js/photo.js; disappears the moment
   a real hero image exists. */
.hero--nophoto .hero__scrim {
  background: radial-gradient(
    120% 90% at 50% 42%,
    rgba(35, 53, 51, 0) 0%,
    rgba(35, 53, 51, 0.28) 62%,
    rgba(22, 43, 38, 0.6) 100%
  );
}

.hero--nophoto .hero__canvas {
  opacity: 0.4;
}

/* ------------------------------------------------------- process band ----
   Full-bleed photographic interstitial. Breaks the page rhythm on purpose:
   the sections above and below are dark and typographic, this is image.   */

/* A card like every other section, not a full-bleed break. */
.band {
  position: relative;
  z-index: var(--z-content);
  min-height: clamp(380px, 58vh, 620px);
  display: flex;
  align-items: flex-end;
  margin-bottom: var(--card-gap);
  border-radius: var(--radius-card);
  overflow: hidden;
  background: var(--card-deep-bot);
}

.band__media {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
}

.band__frame {
  position: relative;
  overflow: hidden;
  border-right: 1px solid rgba(35, 53, 51, 0.55);
}

.band__frame:last-child {
  border-right: 0;
}

/* Extra vertical headroom for js/parallax.js's translateY drift (now up to
   +-55px) plus its scroll-linked zoom, so the moving image never exposes an
   edge — .band__frame's overflow:hidden clips it. Kept separate from
   .band__img so the drift transform never collides with .band__img's own
   hover scale/filter below. */
.band__parallax {
  position: absolute;
  inset: -18% 0;
}

.band__parallax img {
  width: 100%;
  height: 100%;
}

/* Deliberate departure from the rest of the site's palette-only-slate-green
   rule: this band is designed to carry a warm amber/brass accent (tungsten
   work-lamps, machine fittings, a lit inspection table) against the cool
   green everywhere else — the standard teal/orange complementary pairing.
   That only reads if the accent survives the filter, so this is much
   lighter-touch than the old grayscale(0.55) — that was tuned for muted,
   on-palette photography and would have crushed any amber straight back to
   near-invisible. Provisional until real photos land; expect to retune
   once there is an actual highlight to calibrate against (same pattern as
   the hero image). */
.band__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: grayscale(0.15) contrast(1.08) brightness(0.72) saturate(1.15);
  transition: transform 1600ms var(--ease-out), filter var(--dur-slow) var(--ease-out);
}

.band__img[hidden] {
  display: none;
}

/* Hover blooms the warmth further rather than just brightening flatly —
   the amber should visibly deepen, not just get lighter. */
.band__frame:hover .band__img {
  transform: scale(1.04);
  filter: grayscale(0.05) contrast(1.1) brightness(0.9) saturate(1.3);
}

.band__scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: linear-gradient(
    to top,
    rgba(22, 43, 38, 0.94) 0%,
    rgba(22, 43, 38, 0.5) 38%,
    rgba(22, 43, 38, 0.22) 100%
  );
}

.band__caption {
  position: relative;
  z-index: 2;
  width: 100%;
  max-width: var(--page-max);
  margin-inline: auto;
  padding: var(--s-7) var(--page-pad);
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--s-5);
}

.band__text {
  max-width: 46ch;
}

/* Placeholder shown wherever no photograph has been supplied yet. Reads as
   an instruction, not as a broken asset. */
.slot {
  position: absolute;
  inset: 0;
  display: grid;
  place-content: center;
  gap: var(--s-2);
  text-align: center;
  padding: var(--s-4);
  background: repeating-linear-gradient(
    -45deg,
    rgba(244, 251, 250, 0.03) 0 10px,
    rgba(244, 251, 250, 0.06) 10px 20px
  );
  border: 1px dashed rgba(244, 251, 250, 0.22);
}

.slot__path {
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  letter-spacing: 0.06em;
  color: var(--silver);
  word-break: break-all;
}

.slot__spec {
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--silver-dim);
}

/* Visible by default and removed once js/photo.js confirms the image
   loaded. Defaulting to visible means a JS failure still shows the slot
   rather than an empty rectangle. */
[data-slot-state="filled"] .slot {
  display: none;
}

/* ---------------------------------------------------------- card media ---
   The photograph is the face of the card. The interlacing diagram — a real
   structural drawing, not decoration — crossfades in on hover or keyboard
   focus. Photo of the cloth; hover to see how it is made.                  */

.card__photo {
  position: absolute;
  inset: 0;
  z-index: 1;
}

.card__photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: grayscale(0.45) brightness(0.72);
}

.card__photo img[hidden] {
  display: none;
}

/* The diagram lives beneath the photo and is revealed by fading the photo
   out, so there is no second copy to keep in sync. */
.card__photo {
  opacity: 1;
  transition: opacity var(--dur-slow) var(--ease-out);
}

.card:hover .card__photo,
.card:focus-within .card__photo {
  opacity: 0;
}

/* When no photograph exists the diagram is the permanent face, so it must
   not fade to nothing on hover. */
.card--nophoto .card__photo {
  opacity: 0;
}

.card__structure {
  position: absolute;
  left: var(--s-4);
  bottom: var(--s-4);
  z-index: 3;
  padding: var(--s-2) var(--s-3);
  background: rgba(22, 43, 38, 0.78);
  border: 1px solid var(--hairline);
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--silver);
  opacity: 0;
  transform: translateY(6px);
  transition: opacity var(--dur-mid) var(--ease-out),
    transform var(--dur-mid) var(--ease-out);
}

.card:hover .card__structure,
.card:focus-within .card__structure {
  opacity: 1;
  transform: none;
}

/* Sheen must ride above the photograph */
.card__sheen {
  z-index: 2;
}

/* --------------------------------------------------------- breakpoints -- */

@media (max-width: 900px) {
  .band__media {
    grid-template-columns: 1fr;
  }

  /* Only the first frame survives on narrow screens — three slivers of
     photograph read as noise rather than as images. */
  .band__frame:nth-child(n + 2) {
    display: none;
  }
}



@media (prefers-reduced-motion: reduce) {
.band__img,
  .band__frame:hover .band__img {
    transform: none;
  }
}
