/* The gallery.
   Enqueued after main.css, like every other page stylesheet — see the note
   above svaflo_assets() in functions.php before changing an enqueue order.

   The page is photographs, which is the one thing on this site that is allowed
   to be the subject. So it gets the least chrome of any page: a hero, a row of
   pills, and then pictures with their workshop named above them. No new
   vocabulary — the hero is `.archive-hero` from archive.css's family, the
   eyebrows and hairlines are main.css's, and the pills are the only new object.

   ⚠ THE PILLS ARE THE ONLY THING HERE WITH A BACKGROUND, on purpose. Nothing
   else on this site is a filled shape except the `.btn` outline pill and the
   admin bar, so a filled control reads immediately as a control. */

/* ── THE FILTER RAIL ──────────────────────────────────────────────────────────
   ⚠ SHIPPED WITH `hidden` AND REVEALED BY assets/js/gallery.js. A filter that
   cannot filter should not be drawn — see the note at the top of that file.
   `[hidden]` is stated because this rule sets `display:flex`, and an author
   `display` beats the UA stylesheet's `[hidden]{display:none}`. That exact
   collision cost a release on 29 August with `.fold-l`. */
/* ⚠ `min-width:0` IS A GUARD, NOT DECORATION, and it is here because the
   absence of it cost a release. In 0.4.45 this section was `class="gallery"`,
   which main.css has used for the home page's quote-and-pictures grid since the
   beginning — so `.wrap` became a grid item, a grid item's `min-width` is
   `auto`, and `auto` refuses to shrink below min-content. The min-content of a
   `flex-wrap:nowrap` rail is every pill laid end to end, so the wrap grew to
   577px inside a 390px phone and the whole document scrolled sideways.

   The section is `gwall` now and the collision is gone. This line means that if
   this rail is ever dropped inside another flex or grid container, it shrinks
   and scrolls instead of pushing the page wide. */
/* ⚠ BACK TO ONE WRAPPING ROW. 0.4.50 made this a column to stack a search box
   above the pills; the search box was removed the same day and the column went
   with it. Three label groups still wrap onto several lines on a phone — see
   the `.gfilter-label` rule below, which is the part of 0.4.50 that stayed. */
.gfilters{display:flex;flex-wrap:wrap;align-items:center;gap:10px 8px;
  min-width:0;
  margin-bottom:clamp(26px,3.4vw,44px);padding-bottom:18px;
  border-bottom:1px solid var(--line)}
.gfilters[hidden]{display:none}

.gfilter-label{font:400 11px/1 Mulish,sans-serif;letter-spacing:.18em;
  text-transform:uppercase;color:var(--soft);margin-left:12px}
.gfilter-label:first-child{margin-left:0}
/* ⚠ A BLOCK THAT SET `flex-basis:100%` ON THE LABELS STOOD HERE FOR TWO
   RELEASES AND NEVER DID ANYTHING. The phone rules 140 lines below already make
   this rail a nowrap sideways scroller, and they come later, so they won this
   on source order — same specificity, same media condition, opposite intent.
   Written in 0.4.50 by someone who had not read to the bottom of the file, and
   removed in 0.4.52 once a phone screenshot showed the rail scrolling rather
   than stacking. Two `@media (max-width:760px)` blocks in one stylesheet is the
   fault underneath it; the phone rules are all in the one at the bottom. */

/* 13px over 15px of padding is 43px, and the floor is 44 — so 16px. The three
   tap-target releases on 29 August were all one or two pixels short of this
   number; built to it here rather than patched up to it later. */
.gpill{display:inline-block;padding:16px 18px;border-radius:999px;cursor:pointer;
  border:1px solid var(--line);background:none;color:var(--soft);
  font:400 13px/1 Mulish,sans-serif;letter-spacing:.04em;
  transition:background .2s,color .2s,border-color .2s}
.gpill:hover{border-color:var(--sage);color:var(--sage)}
.gpill.is-active{background:var(--sage);border-color:var(--sage);color:var(--paper)}
.gpill:focus-visible{outline:2px solid var(--sage);outline-offset:3px}

.gcount{font:400 12px/1 Mulish,sans-serif;letter-spacing:.14em;
  text-transform:uppercase;color:var(--soft);margin-bottom:clamp(24px,3vw,38px)}

/* ── A WORKSHOP AND ITS PICTURES ─────────────────────────────────────────────*/
/* ⚠ `padding:0` IS LOAD-BEARING AND ITS ABSENCE SHIPPED IN 0.4.46. Each
   workshop is a `<section>`, because each has its own heading and that is what
   a section is for — but main.css carries a BARE `section{padding:clamp(64px,
   9vw,132px) 0}`, written for the page's top-level bands, and an element
   selector does not know it was meant for those. Every workshop was therefore
   inheriting 132px above and 132px below on a desktop.

   Measured on the live page: a set 587px tall containing a 243px grid. 264px of
   it was padding nobody asked for, per workshop — on a page whose entire
   purpose is looking at photographs, and which will hold ten or twenty of these.

   Second time in one build that a bare rule in main.css reached into this
   markup; the first was `.gallery` being an existing grid. The lesson is the
   same both times: new markup dropped into an old stylesheet inherits things
   nobody wrote down, and the only way to find it is to measure the box tree. */
.gset{margin-bottom:clamp(44px,6vw,84px);padding:0}
.gset:last-of-type{margin-bottom:0}
/* ⚠ `[hidden]` AGAIN, AND FOR THE SAME REASON. gallery.js hides a whole set by
   setting `hidden`; nothing here sets `display` on `.gset`, so the UA rule
   would win anyway — this is stated so that adding one later cannot break the
   filter silently. */
.gset[hidden]{display:none}

/* The workshop name. h2, so the page can be moved through by heading — the
   fault found on Activities and Reviews on 29 August, where 22 screens carried
   a single landmark. Sized well below main.css's bare h2 because there are many
   of them and they label rather than announce. */
.gset-title{font-size:clamp(22px,2.4vw,30px);margin:0 0 6px}
.gset-title a{text-decoration:none;border-bottom:1px solid var(--line)}
.gset-title a:hover{border-bottom-color:var(--ink)}
.gset-meta{font:400 12px/1.6 Mulish,sans-serif;letter-spacing:.13em;
  text-transform:uppercase;color:var(--soft);margin:0 0 22px;max-width:none}

/* ⚠ `align-items:start` IS WHAT MAKES A MIXED ROW READ AS DELIBERATE. Tiles no
   longer share a height, so a 16:9 frame beside a 3:2 one is 34px shorter on a
   desktop column and 17px shorter on a phone. Top-aligned, the columns stay
   dead straight and the difference falls as a gap underneath; stretched, the
   shorter tile would float in the middle of its cell and look like a mistake. */
.ggrid{display:grid;grid-template-columns:repeat(3,1fr);align-items:start;
  gap:clamp(10px,1.4vw,20px);margin:0;padding:0;list-style:none}
.gitem{margin:0}

/* ⚠ 3:2, AND IT SHIPPED AS 4:3 IN 0.4.45–0.4.47 WHICH WAS SIMPLY WRONG FOR THIS
   LIBRARY. RJ asked whether the ratio could be fixed and in line with what has
   already been uploaded. Counted, rather than chosen:

     Assets/04_session-photography (55 frames)   3:2 × 35 · 1:1 × 9 · 1.91:1 × 7
                                                 16:9 × 3 · 4:3 × 1
     Site Uploads/Uploaded — every landscape     3:2 × 13, and nothing else
     filename tags across the project            3x2 × 18 · 2x3 × 15

   **4:3 occurs once in fifty-five photographs and in no filename anywhere.** It
   was the one shape the library does not use. 3:2 is what the session
   photography IS, it is every landscape already on the site, and main.css has
   carried `.ratio-3x2` since 0.4.9.

   ⚠ AND THE 2:3 PORTRAITS ARE NOT GALLERY CONTENT, which is what makes a single
   fixed ratio safe here. Every 2:3 file on the site is a person — Angira, and
   the two advisors — used as heroes, bylines and board photographs. Every 3:2
   file is a room: the drum circle, participants with arms raised, bowls on the
   floor, the outdoor mat session. The gallery is rooms. So a fixed landscape
   tile crops almost nothing, where it would have halved a portrait.

   A fixed ratio at all, rather than `height:auto`: a grid of mixed shapes reads
   as a ragged wall, rows never line up and the eye has nothing to follow.
   `object-fit:cover` does the cropping; without it the pictures would stretch,
   which is worse than any crop.

   ⚠ THE SPEC AND THE PRACTICE DISAGREE, AND PRACTICE WON HERE. Web/IMAGE-SPEC.md
   lists 16:9, 4:5 and 1:1 and never mentions 3:2 — it predates the home grid's
   move to 3:2 in 0.4.9. Flagged for RJ rather than quietly rewritten. */
/* ⚠ NO `aspect-ratio` HERE ANY MORE, AND THAT IS THE WHOLE CHANGE IN 0.4.55.
   The tile used to be locked to 3:2 with `object-fit:cover`, which cropped
   whatever did not match. It now takes its height from the picture, so a 16:9
   frame shows as 16:9 and a 3:2 frame as 3:2.

   ⚠ THE BOX STILL RESERVES ITS SPACE BEFORE THE PICTURE ARRIVES. Height comes
   from the img's own `width`/`height` attributes, which both branches of
   `svaflo_photo()` and `wp_get_attachment_image()` emit — the browser derives
   the ratio from them, so a lazy-loaded tile does not collapse to nothing and
   then shove the page down when it loads. If those attributes are ever missing
   the tile has no height until the image decodes, which is the failure mode to
   look for. */
.gthumb{display:block;position:relative;overflow:hidden;
  background:var(--paper-2)}
.gthumb-img{width:100%;height:auto;display:block;
  transition:transform .5s ease,opacity .3s}
.gthumb:hover .gthumb-img{transform:scale(1.03)}
.gthumb:focus-visible{outline:2px solid var(--sage);outline-offset:3px}

.gempty{font-family:'Cormorant Garamond',serif;font-size:20px;color:var(--soft)}

/* ── THE LIGHTBOX ─────────────────────────────────────────────────────────────
   Built by gallery.js and appended to <body>, so it is outside `.wrap` and sets
   its own bounds. Paper rather than the black every lightbox defaults to: the
   whole site is one warm ground and a black overlay would be the only pure
   black on it. */
.glightbox{position:fixed;inset:0;z-index:200;display:flex;align-items:center;
  justify-content:center;gap:clamp(6px,2vw,26px);padding:clamp(14px,4vw,54px);
  background:rgba(251,248,242,.97)}
.glightbox[hidden]{display:none}
.glb-figure{margin:0;display:flex;flex-direction:column;align-items:center;
  gap:10px;max-width:100%;max-height:100%}
.glb-img{max-width:100%;max-height:78vh;width:auto;height:auto;display:block}

/* ── the usage notice ─────────────────────────────────────────────────────────
   ⚠ THIS WAS STYLED AS A CAPTION AND IT IS NOT ONE. Until 0.4.59 it was 12px
   small-caps, letterspaced and centred under the photograph — the styling the
   slot inherited from when it held a description. RJ, 30 August: *"tuck it to
   the left in smaller size like a legal footnote."* A rights line should be
   findable and quiet, not announced.

   ⚠ `align-self:flex-start` IS WHAT TUCKS IT, not `text-align`. The figure is
   `align-items:center` and sized to the picture, so the caption box was being
   centred as a flex item first — text-align inside a centred box that is only
   as wide as its own text moves nothing. This makes the box start at the
   picture's left edge instead.

   ⚠ AND NO `opacity` ON IT. Quieter was the brief, and opacity is the obvious
   way to get there — but `--soft` on `--paper` is already close enough to the
   AA floor that thinning it would put 11px legal text under the line. Smaller
   and lighter in weight, full strength in colour. */
.glb-cap{align-self:flex-start;text-align:left;
  font:400 11px/1.5 Mulish,sans-serif;letter-spacing:.01em;
  text-transform:none;color:var(--soft);max-width:60ch}

/* 48px, above the 44 floor, because these are the only controls on screen when
   the lightbox is open and there is no reason to be tight with the space. */
.glb-close,.glb-prev,.glb-next{width:48px;height:48px;flex:0 0 48px;
  border:1px solid var(--line);border-radius:999px;background:var(--paper);
  color:var(--ink);cursor:pointer;font:400 20px/1 Mulish,sans-serif;
  display:flex;align-items:center;justify-content:center}
.glb-close:hover,.glb-prev:hover,.glb-next:hover{border-color:var(--sage);color:var(--sage)}
.glb-close:focus-visible,.glb-prev:focus-visible,.glb-next:focus-visible{
  outline:2px solid var(--sage);outline-offset:3px}
.glb-close{position:absolute;top:clamp(12px,3vw,28px);right:clamp(12px,3vw,28px)}

@media (max-width:1023px){
  /* Two across on a tablet. Three 300px tiles do not fit a 768px column with
     the page's own 24px gutters. */
  .ggrid{grid-template-columns:repeat(2,1fr)}
}

@media (max-width:760px){
  /* ⚠ TWO ACROSS ON A PHONE, NOT ONE. A 312px column one-up gives a 234px tall
     tile and about six per screen of scrolling; two-up gives 151px tiles and
     twelve. On a page whose whole purpose is browsing photographs, the smaller
     tile that shows twice as many is the better one — and tapping opens it full
     size anyway. Measured against the 29 August finding that this site's pages
     already run to 10 and 22 screens. */
  .ggrid{grid-template-columns:repeat(2,1fr);gap:8px}
  .gset{margin-bottom:40px}
  .gset-meta{margin-bottom:16px}
  /* The rail scrolls sideways rather than stacking into four rows of pills and
     pushing the photographs off the screen.

     ⚠ AND IT HAD NO WAY OF SAYING SO. Measured on the live page at a 440px rail:
     678px of pills, 238px of them off-screen — and the first thing cut was a
     workshop name, mid-word. A scroller with no visible edge reads as a broken
     layout, which is exactly how RJ read it from his phone on 30 August.

     Two changes, and the first does most of the work:

     ⚠ THE GROUP LABELS GO ON A PHONE. Workshop 82px + Year 37 + City 32, plus
     gaps, is 175px — a quarter of the rail spent on words that the pills
     underneath already say. Without them `All` and the first workshop fit
     before the edge, so what a reader sees first is a whole pill rather than
     half of one. They stay on desktop, where there is room. */
  .gfilters{flex-wrap:nowrap;overflow-x:auto;padding-bottom:14px;
    -webkit-overflow-scrolling:touch;scrollbar-width:none}
  .gfilters::-webkit-scrollbar{display:none}
  .gpill{flex:0 0 auto}
  .gfilter-label{display:none}

  /* ── SNAP — 0.4.88 ────────────────────────────────────────────────────────
     The one thing Home's reviews scroller has that this rail did not. A flick
     now lands on a pill edge instead of halfway through a workshop name, which
     is the same complaint the fade above was built to answer — RJ read the rail
     as a broken layout on 30 August precisely because the first thing cut was a
     name, mid-word. The fade says there is more; the snap stops the cut landing
     mid-word in the first place. Costs no height.

     `proximity`, not `mandatory`: with nineteen pills of nine different widths,
     mandatory fights a reader who wants to drag past several at once. */
  .gfilters{scroll-snap-type:x proximity}
  .gfilters > *{scroll-snap-align:start}

  /* ⚠⚠ AND THE RAIL STILL DOES NOT WRAP — MEASURED AGAIN ON 4 SEPTEMBER, AND
     THE MEASUREMENT IS THE REASON. The mobile audit recommended wrapping it:
     three pills are reachable without scrolling and nineteen would be, for what
     was quoted as +0.65 screens of page length. That number is true and it is
     the wrong number.

         rail                62px  →  581px
         FIRST PHOTOGRAPH   0.86 screens  →  1.51 screens

     A reader would scroll a screen and a half before meeting a photograph, on
     the page whose entire purpose is photographs. The comment above this block
     already said stacking "pushes the photographs off the screen" — this is
     that sentence with a number attached, and the number is worse than the
     sentence implied because the whole cost lands at the TOP of the page rather
     than being spread through it.

     ⚠ AND IT HAS BEEN TRIED. A block setting `flex-basis:100%` on the labels
     stood in this file for two releases doing nothing (0.4.50–0.4.52, see the
     note beside `.gfilter-label`) — written above these rules, beaten by them
     on source order. Anything that wraps this rail must be written HERE, inside
     this block, or it will ship dead the same way. */

  /* ⚠ A MASK, NOT AN `::after`. A pseudo-element inside a scrolling box scrolls
     WITH the content, so it would sit at the end of the pills instead of at the
     edge of the window onto them — visible only once you had already scrolled
     to where it was no longer needed. `mask-image` paints against the element's
     own box, which is the window, so the fade stays at the edge.

     `.is-scroll-end` is added by gallery.js at the end of the scroll and when
     there is nothing to scroll, because a fade that never lifts is a promise of
     more pills that do not exist. Progressive enhancement as everywhere else
     here: with script off the fade simply stays, and every pill is still
     reachable by dragging. */
  .gfilters{-webkit-mask-image:linear-gradient(to right,#000 calc(100% - 40px),transparent);
    mask-image:linear-gradient(to right,#000 calc(100% - 40px),transparent)}
  .gfilters.is-scroll-end{-webkit-mask-image:none;mask-image:none}
  .glb-img{max-height:68vh}
}
