/* Lightbox — Exterior Services LLC (Brief 26)
   Loaded by Our Work and the five service pages. Everything here is scoped to
   .lb (the dialog) or [data-lightbox] (the wired tile containers), so it cannot
   reach the heroes, the before/after sliders or the homepage. */

/* ══════════════════════════════════════════════════════════════════════════════
   THE TRIGGER — created by lightbox.js around each tile's <img>
   Brief 20's affordance rule stands: NOTHING is laid over the photograph. The
   hover state is the same lift the service cards use, and the persistent touch
   cue lives in the caption row, below the image, never on it.
   ══════════════════════════════════════════════════════════════════════════════ */
[data-lightbox] .lb__trigger {
  display: block; width: 100%;
  padding: 0; border: 0; background: none;
  cursor: zoom-in;
  border-radius: var(--r);
}
[data-lightbox] .lb__trigger .shot {
  transition: translate 0.25s, box-shadow 0.25s, filter 0.25s;
}
@media (hover: hover) {
  [data-lightbox] .lb__trigger:hover .shot {
    translate: 0 -4px;
    box-shadow: var(--shadow-card);
    filter: brightness(1.04);
  }
}
[data-lightbox] .lb__trigger:active .shot { translate: 0 -1px; }
[data-lightbox] .lb__trigger:focus-visible {
  outline: 2px solid var(--green); outline-offset: 6px;
}

/* PERSISTENT TOUCH CUE. :hover does not exist on touch, so the affordance has
   to be visible at rest. A magnifier glyph is prepended to the caption line —
   below the photograph, so the frame stays unobstructed. Inline SVG data URI
   rather than a glyph character: "⤢" and friends fall back to tofu on some
   Android builds, and a missing affordance is worse than none. */
[data-lightbox] figcaption::before {
  content: "";
  display: inline-block; vertical-align: -0.15em;
  width: 0.95em; height: 0.95em; margin-right: var(--sp-2);
  background: currentColor;
  opacity: 0.75;
  -webkit-mask: var(--lb-cue) center / contain no-repeat;
          mask: var(--lb-cue) center / contain no-repeat;
}
/* GALLERY CUE (Brief 36). The Our Work gallery tiles no longer carry captions,
   so the caption-line magnifier above has nothing to attach to there. The cue
   relocates to a small right-aligned icon row BELOW each tile image — still
   never over the photograph — rendered as ::after on the trigger button. The
   mosaics keep their captions, so they keep the caption-line cue unchanged. */
.gallery-grid .lb__trigger::after {
  content: "";
  display: block;
  width: 0.95em; height: 0.95em;
  margin: var(--sp-1) 0 0 auto;
  background: currentColor;
  color: var(--mut-d);
  opacity: 0.75;
  -webkit-mask: var(--lb-cue) center / contain no-repeat;
          mask: var(--lb-cue) center / contain no-repeat;
}
@media (hover: hover) {
  .gallery-grid .lb__trigger:hover::after { opacity: 1; color: var(--green-lt); }
}

:root {
  --lb-cue: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2.2" stroke-linecap="round"><circle cx="10.5" cy="10.5" r="6.5"/><line x1="15.5" y1="15.5" x2="21" y2="21"/><line x1="10.5" y1="7.5" x2="10.5" y2="13.5"/><line x1="7.5" y1="10.5" x2="13.5" y2="10.5"/></svg>');
}
@media (hover: hover) {
  [data-lightbox] .lb__trigger:hover + figcaption::before,
  [data-lightbox] figure:hover figcaption::before { opacity: 1; color: var(--green); }
}

/* ══════════════════════════════════════════════════════════════════════════════
   THE DIALOG
   ══════════════════════════════════════════════════════════════════════════════ */
.lb {
  padding: 0; border: 0; background: none;
  width: 100vw; max-width: 100vw;
  height: 100dvh; max-height: 100dvh;
  overflow: hidden;
  color: var(--paper);
}
.lb::backdrop { background: color-mix(in srgb, #000 88%, transparent); }

/* The scrim is ALSO painted on the stage, not left to ::backdrop alone.
   ::backdrop only exists for a dialog in the top layer, so it is missing on the
   showModal-unavailable fallback path — and it was observed not painting in
   testing, which left the page legible straight through an "open" viewer.
   The stage fills the dialog either way, so this is the reliable surface;
   ::backdrop above stays as the belt to this pair of braces. */
.lb__stage {
  position: relative;
  background: color-mix(in srgb, #000 88%, transparent);
  width: 100vw; height: 100dvh;
  display: grid; place-items: center;
  padding: clamp(3.25rem, 7vw, 5rem) clamp(0.75rem, 4vw, 4.5rem);
  box-sizing: border-box;
}

.lb__fig {
  display: flex; flex-direction: column; align-items: center;
  gap: var(--sp-4);
  max-width: 100%; max-height: 100%;
  margin: 0;
}
/* contain, never crop. The intrinsic width ceiling is what stops a 1600w file
   being blown up to a 2560 display — the image renders at its own size or
   smaller, never larger. */
.lb__img {
  display: block;
  max-width: 100%;
  max-height: calc(100dvh - clamp(8rem, 18vw, 12rem));
  width: auto; height: auto;
  object-fit: contain;
  border-radius: var(--r);
  background: color-mix(in srgb, var(--paper) 8%, transparent);
}
.lb__cap {
  max-width: 62ch; text-align: center;
  color: var(--mut-d);
  font-family: var(--font-display); font-style: italic;
  font-size: var(--fs-2); line-height: 1.5;
}

/* ── Controls ────────────────────────────────────────────────────────────────
   THE FAILURE CASE THIS AVOIDS: a thin ghost X over a bright sky. Every control
   carries a SOLID scrimmed disc with its own border, so it reads against a
   blown-out cloud and against a black shadow equally. Not a hairline outline,
   not a bare glyph. Verified in the 390 screenshot over the brightest frame.
   ────────────────────────────────────────────────────────────────────────── */
.lb__close, .lb__nav {
  position: absolute; z-index: 2;
  display: grid; place-items: center;
  border-radius: var(--r-pill);
  background: color-mix(in srgb, var(--char) 82%, transparent);
  border: 1px solid color-mix(in srgb, var(--paper) 45%, transparent);
  color: var(--paper);
  cursor: pointer;
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  transition: background 0.2s, border-color 0.2s, translate 0.2s;
}
.lb__close:hover, .lb__nav:hover {
  background: var(--green); border-color: var(--green);
}
.lb__close:focus-visible, .lb__nav:focus-visible {
  outline: 2px solid var(--green-lt); outline-offset: 3px;
}
.lb__close {
  top: clamp(0.6rem, 2.2vw, 1.4rem); right: clamp(0.6rem, 2.2vw, 1.4rem);
  width: 3rem; height: 3rem;                 /* 48px tap target */
  font-size: 1.9rem; line-height: 1;
}
.lb__nav {
  top: 50%; translate: 0 -50%;
  width: 3rem; height: 3rem;
  font-size: 1.75rem; line-height: 1;
}
.lb__nav--prev { left: clamp(0.5rem, 2vw, 1.4rem); }
.lb__nav--next { right: clamp(0.5rem, 2vw, 1.4rem); }
.lb__nav[hidden] { display: none; }
.lb__nav--prev:hover { translate: -3px -50%; }
.lb__nav--next:hover { translate: 3px -50%; }

/* ── Motion ──────────────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: no-preference) {
  .lb[open] { animation: lb-in 0.22s ease-out; }
  .lb[open]::backdrop { animation: lb-fade 0.22s ease-out; }
  @keyframes lb-in   { from { opacity: 0; scale: 0.985; } }
  @keyframes lb-fade { from { opacity: 0; } }
}
@media (prefers-reduced-motion: reduce) {
  .lb, .lb::backdrop, .lb__img,
  [data-lightbox] .lb__trigger .shot,
  .lb__close, .lb__nav { transition: none; animation: none; }
}

/* ── Mobile ──────────────────────────────────────────────────────────────── */
@media (max-width: 880px) {
  /* Side arrows would sit over the photograph at this width and there is a
     swipe gesture for the same job, so they drop to the bottom corners —
     still visible, still tappable, no longer covering the frame. */
  .lb__stage { padding: clamp(3.25rem, 7vw, 5rem) 0.75rem 5.25rem; }
  .lb__nav { top: auto; bottom: 0.9rem; translate: none; }
  .lb__nav--prev { left: 0.9rem; }
  .lb__nav--next { right: 0.9rem; }
  .lb__nav--prev:hover { translate: none; }
  .lb__nav--next:hover { translate: none; }
  .lb__img { max-height: calc(100dvh - 13rem); }
}
