/* ============================================================================
   GALLERY PAGE (/gallery/)
   Loaded ONLY by gallery/index.html, after occasion.css, which supplies the
   hero, the timeline walker, the switcher and the type scale. Nothing here
   invents a visual treatment: the palette tokens, the type classes and the
   .reveal entrance are all the kit's own. What this file adds is a photo grid
   and the viewer that opens from it.
   ========================================================================= */

/* --- the hero -------------------------------------------------------------
   Reuses .occ-hero's geometry wholesale and swaps only the photograph, so the
   gallery does not open with the identical frame as the weddings page. The
   scrim keeps the same .64 the occasion hero settled on for WCAG AA over a
   bright plate.
   THE PLATE IS CHOSEN AT THE PHONE CROP, NOT THE DESKTOP ONE. This box is a
   wide band on a laptop and a tall one on a phone, and cover resolves those
   two crops completely differently: the previous two plates both looked fine
   at 1440 and fell apart at 390, one losing every face behind a raised arm and
   the next cropping three guests down to a single open mouth with the hat cut
   off. This one holds both subjects fully framed at both crops and stays
   bright enough to read through the scrim, which a dark plate does not. */
.gal-hero{
  background:
    linear-gradient(rgba(30,29,26,.64), rgba(30,29,26,.64)),
    url('../images/jl-couple-hats.jpg') center / cover no-repeat fixed;
}

/* --- the grid -------------------------------------------------------------
   CSS columns rather than a row grid, deliberately: her photos run from 3:4
   portrait to 4:3 landscape, and a row grid would need a fixed aspect and
   object-fit:cover to line them up, which crops. Cropping a photo booth
   gallery is how a face ends up bisected. Columns let every photo keep its
   own shape, so nothing is cut. */
.gal-grid{
  columns:3;
  column-gap:18px;
  margin-top:52px;
}
/* The JS-balanced form. CSS columns fill column 1, then 2, then 3, so they
   cannot even up the BOTTOM edge: with these 32 photographs the three columns
   finished 504px apart, which reads as a broken grid rather than a designed
   one. gallery.js re-lays the same nodes into explicit columns, choosing the
   shortest column for each next photo, and switches the container to this
   flex form. The columns rule above stays as the no-JS fallback, so a visitor
   without scripts still gets a real gallery, just a raggeder one. */
.gal-grid.gal-cols{
  columns:auto;
  display:flex;
  align-items:flex-start;
  gap:18px;
}
.gal-col{
  flex:1 1 0;
  min-width:0;              /* or a wide photo pushes the row past the frame */
  display:flex;
  flex-direction:column;
  gap:18px;
}
.gal-grid.gal-cols .gal-item{margin:0;}
/* THE GRID MUST NEVER CARRY .reveal, AND THIS RULE IS THE BACKSTOP.
   script.js reveals with IntersectionObserver at threshold 0.12, i.e. 12% of
   the element's own area has to be on screen at once. This grid is ~5700px
   tall, so at a 900px viewport at most 11.4% of it can ever be visible: the
   observer never fires, the class never lands, and the whole gallery stays at
   opacity 0 while laying out perfectly. It shipped exactly that way once and
   the screenshot was a blank cream page. A threshold that assumes an element
   shorter than the viewport is fine for every other band on this site; it is
   not fine for a container that IS the page. */
.gal-grid.reveal{opacity:1; transform:none;}
.gal-item{
  display:block;
  width:100%;
  margin:0 0 18px;
  padding:0;
  border:0;
  background:none;
  cursor:zoom-in;
  break-inside:avoid;      /* standard */
  page-break-inside:avoid; /* the property Safari still reads */
  -webkit-column-break-inside:avoid;
  line-height:0;           /* kills the inline-image descender gap */
  overflow:hidden;
}
.gal-item img{
  width:100%;
  height:auto;
  display:block;
  transition:transform .6s cubic-bezier(.2,.7,.2,1), opacity .4s ease;
}
/* Hover dimming is a pointer-device idiom, exactly as the home page's photo
   wall scopes it. On a touch screen :hover sticks after a tap and would leave
   a photo faded with no way to clear it. */
@media (hover:hover){
  .gal-item:hover img{transform:scale(1.03);}
  .gal-grid:hover .gal-item:not(:hover) img{opacity:.62;}
}
.gal-item:focus-visible{outline:2px solid var(--ink); outline-offset:3px;}

.gal-cta{margin-top:46px; text-align:center;}

/* --- the lightbox ---------------------------------------------------------
   [hidden] is reset !important in style.css for the same reason it is on the
   agreement page: any author display rule outranks the UA's [hidden] on
   origin, and a viewer that cannot close is worse than no viewer. This block
   therefore never sets display on .gal-lb itself except through :not([hidden]). */
.gal-lb{
  position:fixed; inset:0; z-index:120;
  background:rgba(30,29,26,.94);
  align-items:center; justify-content:center;
  padding:64px 20px;
}
.gal-lb:not([hidden]){display:flex;}
.gal-lb-fig{
  margin:0; max-width:1100px; max-height:100%;
  display:flex; flex-direction:column; align-items:center; gap:16px;
}
.gal-lb-img{
  max-width:100%;
  /* leaves room for the caption under it without capping the SECTION height,
     which is the thing that clips on iOS */
  max-height:calc(100vh - 190px);
  width:auto; height:auto; display:block;
  animation:gal-lb-in .28s ease both;
}
@keyframes gal-lb-in{from{opacity:0; transform:scale(.985);}to{opacity:1; transform:none;}}
.gal-lb-cap{
  display:flex; align-items:baseline; gap:14px; flex-wrap:wrap;
  justify-content:center; text-align:center;
  font-family:var(--f-label); font-size:11px; letter-spacing:.14em;
  text-transform:uppercase; color:rgba(249,248,246,.72);
  max-width:760px; line-height:1.6;
}
.gal-lb-count{color:rgba(249,248,246,.5); white-space:nowrap;}
.gal-lb-close,
.gal-lb-nav{
  position:absolute; z-index:2;
  background:transparent; border:1px solid rgba(249,248,246,.4);
  color:var(--cream); cursor:pointer; line-height:1;
  transition:background .3s ease, color .3s ease, border-color .3s ease;
}
.gal-lb-close{top:20px; right:20px; width:46px; height:46px; font-size:26px;}
.gal-lb-nav{top:50%; transform:translateY(-50%); width:52px; height:52px; font-size:30px;}
.gal-lb-prev{left:20px;}
.gal-lb-next{right:20px;}
/* Scoped to pointer devices for the same reason .gal-item's hover above is,
   and the reason every hover state on this site is: iOS reports hover:none and
   LEAVES the state applied after a tap, so tapping next through the gallery
   left that arrow stuck inverted, cream-filled, until you happened to tap
   somewhere else. A control that changes appearance permanently the first time
   you use it reads as broken even though it still works. */
@media (hover:hover){
  .gal-lb-close:hover,.gal-lb-nav:hover{background:var(--cream); color:var(--ink); border-color:var(--cream);}
}
/* Touch gets a real press state instead, which ends when the finger lifts. */
.gal-lb-close:active,.gal-lb-nav:active{background:rgba(249,248,246,.18);}
.gal-lb-close:focus-visible,.gal-lb-nav:focus-visible{outline:2px solid var(--cream); outline-offset:3px;}

/* the page behind the viewer must not scroll under it */
body.gal-lb-open{overflow:hidden;}
/* The site's own menu button is position:fixed in the same corner as the
   viewer's close control and carries the same z-index, so its white plate
   reads THROUGH the .94 backdrop and its three bars land across the x. On a
   phone the two are the same 46x46 rect, so the corner shows a hamburger and
   an x drawn on top of each other. The viewer is a modal covering the page,
   so the page's menu button stands down while it is open and is back the
   instant it closes. Same "the page behind is inert" rule the overflow lock
   above already applies; nothing is removed. */
body.gal-lb-open .nav-toggle{visibility:hidden;}

/* --- tablet --------------------------------------------------------------- */
@media (max-width: 1100px){
  .gal-grid{columns:2;}
}

/* --- phone ----------------------------------------------------------------
   Two columns, not one: a single column turns 32 photographs into a very long
   scroll, and these images are small enough to read at half a phone width.
   The hero's fixed attachment is dropped here for the same reason occasion.css
   drops it, iOS does not composite background-attachment:fixed. */
@media (max-width: 900px){
  .gal-hero{clip-path:inset(0); background:none;}
  .gal-hero::before{
    content:""; position:fixed; inset:0; z-index:0;
    background:
      linear-gradient(rgba(30,29,26,.64), rgba(30,29,26,.64)),
      url('../images/jl-couple-hats.jpg') center / cover no-repeat;
  }
  .gal-grid{columns:2; column-gap:12px; margin-top:34px;}
  .gal-item{margin-bottom:12px;}
  .gal-cta{margin-top:34px;}

  /* The bottom padding RESERVES the arrow band. The arrows are absolute at
     bottom:16px and 46px tall, so they own the last 62px of the viewer; the
     figure is centred inside the padding box, so 78px of bottom padding keeps
     the caption 16px clear of them. Without it the caption's last line ran
     straight through both buttons (measured 60x24px of overlap on every phone
     but 390x844, which is exactly why one phone size is not a test).
     The image cap follows from the same sum and must move with it:
     100vh - 76 (top) - 78 (bottom) - 16 (figure gap) - 20 (the caption, which
     is now just the "n / 32" counter on one line, since the owner removed the
     descriptions) = 100vh - 190px. The 78px reserve stays regardless, because
     it is the ARROWS it is holding back, not the caption. This caps the PHOTO,
     which scales, never a section, which would clip. */
  .gal-lb{padding:76px 12px 78px;}
  .gal-lb-img{max-height:calc(100vh - 190px);}
  .gal-lb-close{top:14px; right:14px;}
  /* thumb-reachable on a phone: the arrows sit at the bottom, not the edges,
     where they would overlap the photograph on a narrow screen */
  .gal-lb-nav{top:auto; bottom:16px; transform:none; width:60px; height:46px;}
  .gal-lb-prev{left:calc(50% - 68px);}
  .gal-lb-next{right:calc(50% - 68px);}
  .gal-lb-cap{font-size:10px;}
}

@media (prefers-reduced-motion: reduce){
  .gal-item img{transition:none;}
  .gal-lb-img{animation:none;}
}
