/* gallery.css — White Sand Salon /gallery/ ("Instagram Gallery").
 * Masonry via CSS columns; coastal editorial luxury. Scoped under .gallery so
 * it's inert elsewhere (loaded globally from base.js). Consumes tokens.css. */

.gallery {
  --edge: clamp(1.25rem, 5vw, 4rem);
  max-width: 1440px;
  margin: 0 auto;
  padding: clamp(4rem, 9vw, 7.5rem) var(--edge) clamp(4.5rem, 11vw, 9rem);
  color: var(--ink);
  overflow-x: clip;
}
.gallery .rule {
  display: block;
  width: 64px;
  height: 1px;
  margin: 0 auto var(--space-6);
  background: linear-gradient(90deg, transparent, var(--gold), transparent);
}
.gallery [data-reveal] {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity var(--dur-slow) var(--ease), transform var(--dur-slow) var(--ease);
  transition-delay: var(--reveal-delay, 0ms);
  will-change: opacity, transform;
}
.gallery [data-reveal].is-visible {
  opacity: 1;
  transform: none;
}

.gallery-head {
  max-width: 900px;
  margin: 0 auto clamp(3rem, 7vw, 5rem);
  text-align: center;
}
.gallery-head__eyebrow {
  font-family: var(--sans);
  font-size: var(--fs-100);
  font-weight: var(--fw-medium);
  letter-spacing: 0.36em;
  text-transform: uppercase;
  color: var(--gold-deep);
  margin: 0 0 var(--space-5);
}
.gallery-head__title {
  font-family: var(--serif);
  font-weight: var(--fw-medium);
  font-size: clamp(2.2rem, 6.46vw, 4.68rem);
  line-height: 0.98;
  letter-spacing: -0.022em;
  margin: 0;
  text-wrap: balance;
}
.gallery-head__intro {
  margin: clamp(1.25rem, 3vw, 2rem) auto 0;
  max-width: 52ch;
  font-family: var(--serif);
  font-style: italic;
  font-size: clamp(1.2rem, 2.2vw, 1.6rem);
  line-height: 1.45;
  color: var(--ink-soft);
}
/* Gold hairline under the masthead, echoing the homepage section rhythm. */
.gallery-head::after {
  content: "";
  display: block;
  width: 64px;
  height: 1px;
  margin: clamp(1.75rem, 4vw, 2.75rem) auto 0;
  background: linear-gradient(90deg, transparent, var(--gold), transparent);
}

/* Instagram square grid ------------------------------------------------------
   Was CSS multi-column masonry — WebKit mislays/blanks animated children inside
   column fragmentation (reproduced in Safari: scattered tiles, blank holes).
   Uniform square grid is Safari-proof and reads as a true IG feed. */
.gallery__grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: clamp(0.75rem, 2vw, 1.25rem);
}
.gallery__tile {
  display: block;
  position: relative;
  aspect-ratio: 1;
  border-radius: var(--radius-lg);
  overflow: hidden;
  border: 1px solid var(--line);
  box-shadow: var(--shadow-soft);
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
    border-color var(--dur-slow) var(--ease);
}
.gallery__tile:hover {
  transform: translateY(-3px);
  box-shadow: var(--shadow);
  border-color: var(--gold-light);
}
.gallery__tile:focus-visible {
  outline: 2px solid var(--gold-deep);
  outline-offset: 3px;
}
.gallery__tile::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  box-shadow: inset 0 0 0 1px rgba(15, 15, 15, 0.08);
  pointer-events: none;
  transition: box-shadow var(--dur-slow) var(--ease);
}
.gallery__tile:hover::before {
  box-shadow: inset 0 0 0 1px var(--gold-light);
}
/* The signature gallery move: muted-warm grade unifies the feed → true colour
   blooms on hover (Move 1). */
.gallery__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  filter: var(--img-grade);
  transition: transform var(--dur-slow) var(--ease), filter var(--dur-slow) var(--ease);
}
.gallery__tile:hover .gallery__img {
  transform: scale(1.05);
}
@media (hover: hover) {
  .gallery__tile:hover .gallery__img {
    filter: var(--img-grade-hover);
  }
}

.gallery-cta {
  margin-top: clamp(2.5rem, 6vw, 4rem);
  text-align: center;
}

@media (min-width: 720px) {
  .gallery__grid {
    grid-template-columns: repeat(3, 1fr);
  }
}
@media (min-width: 1040px) {
  .gallery__grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

@media (prefers-reduced-motion: reduce) {
  .gallery [data-reveal] {
    opacity: 1;
    transform: none;
    transition: none;
  }
  .gallery__tile:hover .gallery__img {
    transform: none;
  }
}
.no-js .gallery [data-reveal] {
  opacity: 1;
  transform: none;
}

/* Editorial outro band — entity-rich prose beneath the masonry (AEO guard). */
.gallery-outro {
  max-width: 60rem;
  margin: clamp(2.5rem, 6vw, 4rem) auto 0;
  padding: 0 var(--edge);
  text-align: center;
}
.gallery-outro__prose {
  font-family: var(--serif);
  font-weight: var(--fw-regular);
  font-size: clamp(1.25rem, 2.8vw, 1.9rem);
  line-height: 1.4;
  color: var(--ink);
  text-wrap: balance;
}
.gallery-outro__prose p { margin: 0; }
@media (prefers-reduced-motion: reduce) {
  .gallery-outro [data-reveal] { opacity: 1; transform: none; }
}

/* ─────────────────────────────────────────────────────────────────────────────
   "Follow along" — the live Instagram section (src/js/gallery.js)
   ─────────────────────────────────────────────────────────────────────────────
   Three states, one layout, no reflow between them:
     data-ig-state="cta"   heading + follow button (the shipped state, and the
                           fallback for a missing feed id or a blocked feed host)
     data-ig-state="feed"  heading + OUR grid + the follow button beneath

   The grid is ours now — Behold's hosted <behold-widget> is gone, along with its
   own typography and its "every tap leaves for instagram.com" popup. What is
   below is the same square-tile language as .gallery__grid above it (2 up on a
   phone, 3 up from 720px, same gap rhythm, same muted-warm grade blooming to
   full colour on hover) so the two grids read as one page rather than as a
   website with a widget bolted to the bottom of it.
   ───────────────────────────────────────────────────────────────────────────── */
.ig {
  max-width: 1200px;
  margin: clamp(3.5rem, 8vw, 6rem) auto 0;
  padding: 0 var(--edge);
  text-align: center;
}
.ig__head {
  margin-bottom: clamp(1.5rem, 4vw, 2.5rem);
}
.ig__eyebrow {
  margin: 0 0 var(--space-2);
  font-family: var(--sans);
  font-size: var(--fs-100);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-wider);
  text-transform: uppercase;
  color: var(--gold-deep);
}
.ig__title {
  margin: 0;
  font-family: var(--serif);
  font-size: clamp(1.6rem, 4vw, 2.4rem);
  font-weight: var(--fw-light);
  letter-spacing: var(--ls-display);
  line-height: var(--lh-tight);
  color: var(--ink);
}
.ig__handle {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.ig__handle:hover,
.ig__handle:focus-visible {
  color: var(--gold-deep);
  border-bottom-color: var(--gold);
}
/* The grid's container. min-height only once we know posts are coming, so a
   blocked feed that never paints doesn't leave a tall empty well behind (the JS
   hides this element entirely in that case — the min-height is for the seconds
   in between). */
.ig__feed {
  margin: 0 auto;
}
.ig[data-ig-state='feed'] .ig__feed {
  min-height: 220px;
}
.ig__feed[hidden] {
  display: none;
}
.ig__cta {
  margin-top: clamp(1.75rem, 4vw, 2.75rem);
}
.ig__cta[hidden] {
  display: none;
}
.ig__cta .rule {
  display: block;
  width: 64px;
  height: 1px;
  margin: 0 auto var(--space-6);
  background: linear-gradient(90deg, transparent, var(--gold), transparent);
}

/* ── The post grid ─────────────────────────────────────────────────────────
   Deliberately the same geometry as .gallery__grid: 2 up / 3 up at 720px, same
   clamped gap, same square tiles, same radius. Tiles are <button>s (they open a
   dialog, not a page), so every default button style has to be unwound here. */
.ig__grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: clamp(0.75rem, 2vw, 1.25rem);
}
@media (min-width: 720px) {
  .ig__grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

.ig__tile {
  appearance: none;
  -webkit-appearance: none;
  padding: 0;
  margin: 0;
  font: inherit;
  color: inherit;
  background: none;
  cursor: pointer;
  display: block;
  position: relative;
  width: 100%;
  aspect-ratio: 1;
  border-radius: var(--radius-lg);
  overflow: hidden;
  border: 1px solid var(--line);
  box-shadow: var(--shadow-soft);
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
    border-color var(--dur-slow) var(--ease);
}
.ig__tile:hover {
  transform: translateY(-3px);
  box-shadow: var(--shadow);
  border-color: var(--gold-light);
}
.ig__tile:focus-visible {
  outline: 2px solid var(--gold-deep);
  outline-offset: 3px;
}
.ig__tile::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  box-shadow: inset 0 0 0 1px rgba(15, 15, 15, 0.08);
  pointer-events: none;
  transition: box-shadow var(--dur-slow) var(--ease);
}
.ig__tile:hover::before {
  box-shadow: inset 0 0 0 1px var(--gold-light);
}
.ig__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  /* Same signature move as the gallery above: the mixed feed is unified by the
     muted-warm grade, and true colour blooms on hover. */
  filter: var(--img-grade);
  transition: transform var(--dur-slow) var(--ease), filter var(--dur-slow) var(--ease);
}
.ig__tile:hover .ig__img {
  transform: scale(1.05);
}
@media (hover: hover) {
  .ig__tile:hover .ig__img {
    filter: var(--img-grade-hover);
  }
}
/* Carousel / video marker, top-right, the way Instagram's own grid marks them.
   Dark plate rather than a bare glyph so it survives a light photograph. */
.ig__badge {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  z-index: 2;
  display: grid;
  place-items: center;
  min-width: 1.6rem;
  height: 1.6rem;
  padding: 0 0.35rem;
  border-radius: 999px;
  background: rgba(15, 15, 15, 0.55);
  color: #fff;
  font-size: 0.7rem;
  line-height: 1;
  pointer-events: none;
  backdrop-filter: blur(2px);
}
/* The batch sentinel. Zero visual weight, real height so it can intersect. */
.ig__more {
  height: 1px;
  margin-top: 1px;
}

/* ─────────────────────────────────────────────────────────────────────────────
   The lightbox (src/js/ig-lightbox.js)
   ─────────────────────────────────────────────────────────────────────────────
   NOT scoped under .gallery: the overlay is appended to <body> so it cannot be
   clipped by the page's overflow:clip, or torn out from under itself by a router
   swap of <main>.

   Contrast, stated because it was checked: caption and stats are --ink /
   --ink-soft on --paper (13.6:1+). The one gold text run, "View on Instagram",
   is --gold-deep — 6.07:1 on white. The decorative --gold appears only as a
   1px hairline, never behind a letter. On the scrim side, the × is white on
   rgba(15,15,15,.55) over an already-dark backdrop.
   ───────────────────────────────────────────────────────────────────────────── */
.iglb {
  position: fixed;
  inset: 0;
  z-index: 120; /* above the sticky header (100) and the chat launcher */
  display: grid;
  place-items: center;
  padding: clamp(0px, 2vw, 2rem);
  opacity: 0;
  transition: opacity var(--dur) var(--ease);
}
.iglb[hidden] {
  display: none;
}
.iglb.is-open {
  opacity: 1;
}
.iglb__scrim {
  position: absolute;
  inset: 0;
  background: rgba(9, 12, 13, 0.88);
  /* A touch of the post's own tone bleeding into the scrim corners — the only
     place IG colour is allowed to touch chrome, and at 10% it reads as depth
     rather than as a second brand. */
  background-image: radial-gradient(
    120% 90% at 50% 0%,
    rgba(var(--iglb-accent, 15, 15, 15), 0.22),
    transparent 70%
  );
  cursor: zoom-out;
}

.iglb__dialog {
  position: relative;
  z-index: 1;
  display: grid;
  width: min(1100px, 100%);
  max-height: min(92vh, 900px);
  background: var(--paper);
  color: var(--ink);
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: 0 40px 120px -40px rgba(0, 0, 0, 0.8);
  /* Phone: media on top, story panel beneath, the whole card scroll-free — the
     caption is the only thing that scrolls.
     minmax(0, …) ON BOTH ROWS IS LOAD-BEARING. With a plain `auto` row the row
     sizes to its CONTENT, the panel grows past the dialog's max-height, and
     overflow:hidden clips it — so a long caption's scroller ends up with
     scrollHeight === clientHeight and does not scroll at all. It just looks cut
     off. The e2e suite asserts the caption really scrolls for exactly this. */
  grid-template-rows: minmax(0, auto) minmax(0, 1fr);
  transform: scale(0.965) translateY(10px);
  transition: transform var(--dur) var(--ease);
}
.iglb.is-open .iglb__dialog {
  transform: none;
}

.iglb__x {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  z-index: 4;
  appearance: none;
  -webkit-appearance: none;
  width: 2.5rem;
  height: 2.5rem;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: rgba(15, 15, 15, 0.55);
  color: #fff;
  font-family: var(--sans);
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease);
}
.iglb__x:hover {
  background: rgba(15, 15, 15, 0.8);
}
.iglb__x:focus-visible {
  outline: 2px solid var(--gold-light);
  outline-offset: 2px;
}

/* Media stage. The photo is CONTAINed, never cropped — a lightbox that cuts the
   top of a haircut off is worse than no lightbox. The letterbox behind it is the
   post's own muted-dark tone at low opacity over near-black. */
.iglb__stage {
  position: relative;
  display: grid;
  place-items: center;
  min-height: 0;
  background: #0d0f10;
  background-image: linear-gradient(
    160deg,
    rgba(var(--iglb-accent, 15, 15, 15), 0.5),
    rgba(var(--iglb-accent, 15, 15, 15), 0.15)
  );
}
.iglb__media {
  display: grid;
  place-items: center;
  width: 100%;
  height: 100%;
  min-height: 0;
}
.iglb__img,
.iglb__video {
  display: block;
  max-width: 100%;
  max-height: 52vh;
  width: auto;
  height: auto;
  object-fit: contain;
}

.iglb__nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 3;
  appearance: none;
  -webkit-appearance: none;
  width: 2.75rem;
  height: 2.75rem;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.9);
  color: var(--ink);
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
  box-shadow: 0 6px 20px -8px rgba(0, 0, 0, 0.6);
  transition: background var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}
.iglb__nav[hidden] {
  display: none;
}
.iglb__nav:hover {
  background: #fff;
}
.iglb__nav:focus-visible {
  outline: 2px solid var(--gold-deep);
  outline-offset: 2px;
}
.iglb__nav--prev {
  left: 0.5rem;
}
.iglb__nav--next {
  right: 0.5rem;
}

.iglb__dots {
  position: absolute;
  bottom: 0.65rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 3;
  display: flex;
  gap: 0.4rem;
}
.iglb__dots[hidden] {
  display: none;
}
.iglb__dot {
  appearance: none;
  -webkit-appearance: none;
  width: 7px;
  height: 7px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.45);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}
.iglb__dot.is-on {
  background: var(--gold-light);
  transform: scale(1.25);
}

/* Story panel — handle, date, caption, counts, permalink. */
.iglb__panel {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  min-height: 0;
  text-align: left;
  padding: clamp(1rem, 2.5vw, 1.5rem);
  gap: var(--space-3);
  background: var(--paper);
}
.iglb__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
}
.iglb__handle {
  margin: 0;
  font-family: var(--sans);
  font-size: var(--fs-200);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--gold-deep);
}
.iglb__date {
  margin: 0;
  font-family: var(--sans);
  font-size: var(--fs-100);
  font-weight: var(--fw-light);
  letter-spacing: 0.06em;
  color: var(--ink-soft);
}
.iglb__date[hidden],
.iglb__handle[hidden],
.iglb__stats[hidden],
.iglb__caption[hidden],
.iglb__link[hidden] {
  display: none;
}

/* THE SCROLLER. Carries [data-lenis-prevent] in the markup — without it Lenis
   eats the wheel event and this never scrolls (see ig-lightbox.js). */
.iglb__caption {
  margin: 0;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  white-space: pre-wrap; /* IG captions are written in line breaks */
  font-family: var(--sans);
  font-size: var(--fs-200);
  font-weight: var(--fw-light);
  line-height: var(--lh-body);
  color: var(--ink);
  padding-right: var(--space-2);
}
.iglb__caption:focus-visible {
  outline: 2px solid var(--gold-deep);
  outline-offset: 2px;
}

.iglb__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
  padding-top: var(--space-3);
  border-top: 1px solid var(--line);
}
.iglb__stats {
  margin: 0;
  font-family: var(--sans);
  font-size: var(--fs-100);
  font-weight: var(--fw-medium);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.iglb__link {
  font-family: var(--sans);
  font-size: var(--fs-100);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--gold-deep);
  text-decoration: none;
  border-bottom: 1px solid var(--gold);
  padding-bottom: 2px;
  transition: color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.iglb__link:hover,
.iglb__link:focus-visible {
  color: var(--ink);
  border-bottom-color: var(--ink);
}

/* Screen-reader-only carousel position. */
.iglb__sr {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Desktop: media left, story right — the caption gets a full column to be long
   in, and the photo stops being letterboxed into a strip. */
@media (min-width: 860px) {
  .iglb__dialog {
    /* One row, explicitly clamped to the dialog — NOT `none`, which leaves an
       implicit content-sized row and reintroduces the clipped-caption bug above. */
    grid-template-rows: minmax(0, 1fr);
    grid-template-columns: minmax(0, 1.35fr) minmax(320px, 0.85fr);
  }
  .iglb__img,
  .iglb__video {
    max-height: min(86vh, 860px);
  }
  .iglb__panel {
    padding: clamp(1.5rem, 2.5vw, 2.25rem);
  }
  /* From here up the panel owns the dialog's top-right corner, which is where
     the × lives — so the date has to get out from under it. (On a phone the ×
     sits over the photo instead, and the header needs no reserve.) */
  .iglb__head {
    padding-right: 2.5rem;
  }
}

/* Body scroll lock while open — the twin of nav.js's .nav-open, kept separate so
   the two overlays can never disagree about who restores the scroll. Lenis is
   stopped and restarted in JS alongside these. */
body.iglb-open {
  overflow: hidden;
}

@media (prefers-reduced-motion: reduce) {
  /* The "explode" is a nicety. A plain fade is the whole animation here. */
  .iglb,
  .iglb__dialog {
    transition: none;
  }
  .iglb__dialog {
    transform: none;
  }
  .ig__tile:hover {
    transform: none;
  }
  .ig__tile:hover .ig__img {
    transform: none;
  }
}
