/* ===== self-hosted fonts — generated by tools-fonts.js ===== */
@font-face{
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 400 900;
  font-stretch:100%;
  font-display: swap;
  src: url('assets/fonts/archivo-400-900.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face{
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('assets/fonts/ibm-plex-mono-400.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face{
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('assets/fonts/ibm-plex-mono-500.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* ===== end self-hosted fonts ===== */

/* ==========================================================================
   FRAMES — Photography Club, Mar Baselios College of Engineering & Technology
   "The Archive" — an exhibition catalogue for a student photography collective.

   Swiss/International typographic style: one grotesque, extreme scale
   contrast, a strict 12-column grid, hairline rules, and photographs given
   the room they need. One accent colour, spent sparingly.

   01  Tokens & themes          07  Practice mosaic
   02  Reset & base             08  Archive (pinned filmstrip)
   03  Typography               09  Occasions index
   04  Grid & structure         10  Execom contact sheet
   05  Motion system            11  Join & footer
   06  Chrome (nav, rails)      12  Lightbox
   13  Hero                     14  Reduced motion
   ========================================================================== */

/* ======================== 01  TOKENS & THEMES ============================ */

:root{
  /* Neutrals carry a faint warm bias toward the accent — never pure grey. */
  --ground:      #0B0A0A;
  --surface:     #141211;
  --surface-2:   #1D1A19;
  --ink:         #F4F1EF;
  --ink-2:       rgba(244,241,239,0.66);
  /* 0.40 resolved to #686666 on --ground, which is 3.46:1 — under the 4.5:1
     WCAG AA asks for, and this token sets 11px mono type (.meta, .foot__title,
     .label-ish captions) where it matters most. 0.52 lands at 4.9:1 and is
     very nearly the same grey to look at. */
  --ink-3:       rgba(244,241,239,0.52);
  --ink-4:       rgba(244,241,239,0.16);
  --rule:        rgba(244,241,239,0.14);
  --rule-strong: rgba(244,241,239,0.28);
  --accent:      #F5232F;
  --accent-ink:  #FFFFFF;
  --scrim:       rgba(11,10,10,0.55);
  --plate:       #1A1717;

  --grade:       grayscale(1) contrast(1.06) brightness(0.96);
  --grade-off:   grayscale(0) contrast(1) brightness(1);

  /* Type */
  --sans: "Archivo", Helvetica, "Helvetica Neue", Arial, sans-serif;
  --mono: "IBM Plex Mono", ui-monospace, "SF Mono", Consolas, monospace;

  --fs-mega:    clamp(4.2rem, 17vw, 17rem);
  --fs-display: clamp(2.8rem, 8.5vw, 8.5rem);
  --fs-h2:      clamp(2rem, 4.6vw, 4.4rem);
  --fs-h3:      clamp(1.15rem, 1.9vw, 1.7rem);
  --fs-lead:    clamp(1.15rem, 1.9vw, 1.65rem);
  --fs-body:    clamp(0.98rem, 1.05vw, 1.09rem);
  --fs-small:   0.82rem;
  --fs-label:   0.6875rem;

  /* Structure */
  --shell:  1720px;
  --gutter: clamp(1rem, 1.8vw, 2rem);
  --margin: clamp(1.25rem, 4.4vw, 4.5rem);
  --bar-h:  clamp(70px, 7.2vw, 92px);   /* sized around the logo */
  --rail-w: clamp(34px, 4vw, 62px);

  /* Motion */
  --ease:     cubic-bezier(0.16, 0.84, 0.24, 1);
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --swift:    0.42s;
  --slow:     0.9s;

  color-scheme: dark;
}

/* =========================== 02  RESET & BASE ============================ */

*, *::before, *::after{ box-sizing: border-box; }

html{
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;

  /* No browser scrollbar. The rail on the right edge is this site's scroll
     indicator, and the chrome one only competed with it. Scrolling itself is
     untouched — wheel, trackpad, keys, and touch all still work. */
  scrollbar-width: none;
  -ms-overflow-style: none;
}
html::-webkit-scrollbar,
body::-webkit-scrollbar{ width: 0; height: 0; display: none; }

body{
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--sans);
  font-size: var(--fs-body);
  line-height: 1.6;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
  transition: background-color .5s var(--ease), color .5s var(--ease);
}
body.is-locked{ overflow: hidden; }

img, svg, video{ display: block; max-width: 100%; }
img{ background: var(--plate); }

button{ font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; }
a{ color: inherit; text-decoration: none; }
ul, ol{ list-style: none; margin: 0; padding: 0; }
figure{ margin: 0; }
[hidden]{ display: none !important; }

::selection{ background: var(--accent); color: var(--accent-ink); }

:focus-visible{
  outline: 2px solid var(--accent);
  outline-offset: 4px;
}

.sr-only{
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ============================ 03  TYPOGRAPHY ============================= */

h1, h2, h3, h4{
  margin: 0;
  font-weight: 800;
  line-height: 0.92;
  letter-spacing: -0.035em;
  text-wrap: balance;
}

.t-mega{
  font-size: var(--fs-mega);
  font-weight: 900;
  line-height: 0.82;
  letter-spacing: -0.05em;
}
.t-display{ font-size: var(--fs-display); letter-spacing: -0.045em; }
.t-h2{ font-size: var(--fs-h2); }
.t-h3{ font-size: var(--fs-h3); font-weight: 700; letter-spacing: -0.02em; line-height: 1.12; }

p{ margin: 0; }

.lead{
  font-size: var(--fs-lead);
  line-height: 1.42;
  letter-spacing: -0.015em;
  font-weight: 400;
  color: var(--ink);
  text-wrap: pretty;
}

.body{ color: var(--ink-2); max-width: 62ch; text-wrap: pretty; }

/* Monospaced data type — labels, counters, coordinates, captions. */
.label,
.meta{
  font-family: var(--mono);
  font-size: var(--fs-label);
  font-weight: 400;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  line-height: 1.4;
  font-variant-numeric: tabular-nums;
}
.label{ color: var(--accent); }
.meta{ color: var(--ink-3); letter-spacing: 0.16em; }

.num{ font-variant-numeric: tabular-nums; }
.em{ color: var(--accent); }

/* An index numeral, set as a design element rather than an ornament. */
.idx{
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: 0.16em;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}

/* ========================= 04  GRID & STRUCTURE ========================== */

.shell{
  width: 100%;
  max-width: var(--shell);
  margin-inline: auto;
  padding-inline: var(--margin);
}

.g12{
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: var(--gutter);
}

.section{ padding-block: clamp(5rem, 12vh, 11rem); position: relative; }
.section--tight{ padding-block: clamp(3.5rem, 8vh, 7rem); }

.rule{ height: 1px; background: var(--rule); border: 0; margin: 0; }

/* Section header: a hairline, a red label, then the statement. Asymmetric. */
.sec-head{
  border-top: 1px solid var(--rule);
  padding-top: 1.4rem;
  margin-bottom: clamp(2.5rem, 6vw, 5rem);
}
.sec-head__label{ grid-column: 1 / 4; }
.sec-head__title{ grid-column: 4 / 13; }
.sec-head__title .body{ margin-top: 1.6rem; }

@media (max-width: 900px){
  .sec-head__label{ grid-column: 1 / -1; margin-bottom: 1.4rem; }
  .sec-head__title{ grid-column: 1 / -1; }
}

/* Media: every photograph sits in a masked, ratio-locked frame. */
.media{
  position: relative;
  display: block;
  width: 100%;
  overflow: hidden;
  background: var(--plate);
}
.media--fill{ height: 100%; }
.media img{
  width: 100%; height: 100%;
  object-fit: cover;
  filter: var(--grade);
  transition: transform 1.2s var(--ease), filter .7s var(--ease);
}
.media--4x5{ aspect-ratio: 4 / 5; }
.media--3x2{ aspect-ratio: 3 / 2; }
.media--1x1{ aspect-ratio: 1 / 1; }
.media--2x3{ aspect-ratio: 2 / 3; }
.media--16x9{ aspect-ratio: 16 / 9; }

/* Colour returns only where you look. */
.reveal-colour:hover .media img,
.reveal-colour:focus-visible .media img{
  filter: var(--grade-off);
  transform: scale(1.03);
}

.caption{
  display: flex;
  gap: 1rem;
  justify-content: space-between;
  align-items: baseline;
  margin-top: 0.85rem;
  padding-top: 0.7rem;
  border-top: 1px solid var(--rule);
}
.caption span{ font-family: var(--mono); font-size: var(--fs-label); letter-spacing: 0.14em; text-transform: uppercase; }
.caption .c-title{ color: var(--ink); }
.caption .c-meta{ color: var(--ink-3); white-space: nowrap; }

/* On narrow screens the meta line ("coordinated by Frames") is longer than
   the plate, so the caption stacks and the nowrap is released. */
@media (max-width: 620px){
  .caption{
    flex-direction: column;
    align-items: flex-start;
    gap: 0.35rem;
  }
  .caption .c-meta{ white-space: normal; overflow-wrap: anywhere; }
  .caption span{ letter-spacing: 0.1em; line-height: 1.45; }
}

/* Buttons */
.btn{
  display: inline-flex;
  align-items: center;
  gap: 0.85rem;
  padding: 1.05rem 1.9rem;
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  border: 1px solid var(--rule-strong);
  border-radius: 999px;
  transition: background-color var(--swift) var(--ease),
              color var(--swift) var(--ease),
              border-color var(--swift) var(--ease),
              transform var(--swift) var(--ease);
}
.btn:hover{ transform: translateY(-2px); }
.btn--solid{ background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.btn--solid:hover{ background: var(--ink); border-color: var(--ink); color: var(--ground); }
.btn--ghost:hover{ background: var(--ink); border-color: var(--ink); color: var(--ground); }
.btn .arrow{ transition: transform var(--swift) var(--ease); }
.btn:hover .arrow{ transform: translateX(4px); }

/* =========================== 05  MOTION SYSTEM =========================== */

/* Text and blocks rise into place. */
[data-reveal]{
  opacity: 0;
  transform: translateY(30px);
  transition: opacity var(--slow) var(--ease), transform var(--slow) var(--ease);
  transition-delay: calc(var(--d, 0) * 1ms);
}
[data-reveal].in{ opacity: 1; transform: none; }

/* Photographs wipe open, and the image inside settles from a slight push-in.

   The wipe is on the CHILD, never on the observed element itself. Chrome
   counts an element's own clip-path when it computes intersection, so an
   element that starts at inset(0 0 100% 0) reports zero intersection, the
   observer never fires, and it stays clipped for good — the reveal
   deadlocks and the photograph simply never appears. Clipping the child
   leaves the observed box its full area, and the wipe looks identical. */
[data-reveal="mask"]{
  opacity: 1;
  transform: none;
}
[data-reveal="mask"] > *{
  display: block;
  clip-path: inset(0 0 100% 0);
  transition: clip-path 1.15s var(--ease-out);
  transition-delay: calc(var(--d, 0) * 1ms);
}
[data-reveal="mask"].in > *{ clip-path: inset(0 0 0 0); }
[data-reveal="mask"] img{ transform: scale(1.14); }
[data-reveal="mask"].in img{ transform: scale(1); }

/* Split lines of display type, masked from below.
   The padding/negative-margin pair keeps descenders from being clipped. */
.line{
  display: block;
  overflow: hidden;
  padding-bottom: 0.14em;
  margin-bottom: -0.14em;
}
.line > span{
  display: block;
  transform: translateY(105%);
  transition: transform 1s var(--ease-out);
  transition-delay: calc(var(--d, 0) * 1ms);
}
.in > .line > span,
.line.in > span{ transform: none; }

/* A hairline that draws itself across. */
.draw{
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 1.1s var(--ease-out);
}
.draw.in{ transform: scaleX(1); }

/* ====================== 06  CHROME: NAV, RAILS, MENU ===================== */

.bar{
  position: fixed;
  inset: 0 0 auto 0;
  /* Above .menu (70), so the close button stays reachable while it is open. */
  z-index: 75;
  height: var(--bar-h);
  display: flex;
  align-items: center;
  padding-inline: var(--margin);
  /* The bar is the element that blends. A fixed element forms its own
     stacking context, so a mix-blend-mode on a *child* only ever sees the
     bar's own (transparent) group and stays flat white. Blending the bar
     itself lets it difference against the hero plate underneath. */
  mix-blend-mode: normal;
  transition: background-color var(--swift) var(--ease),
              border-color var(--swift) var(--ease),
              backdrop-filter var(--swift) var(--ease);
  border-bottom: 1px solid transparent;
}
/* Over the hero: difference against whatever frame is showing, so the bar
   inverts to dark on a light photograph and stays light on a dark one.
   Dropped once the bar is stuck (it gets a real background then) and while
   the menu is open (it would invert the overlay). */
.bar:not(.is-stuck):not(.on-menu){ mix-blend-mode: difference; }
.bar:not(.is-stuck):not(.on-menu) .icon-btn{ color: #fff; }
/* Slightly under pure white: still inverts hard on a light frame, but leaves
   headroom for the hover/current state to go fully white. */
.bar:not(.is-stuck):not(.on-menu) .bar__nav a{ color: #C8C8C8; }
.bar:not(.is-stuck):not(.on-menu) .bar__nav a:hover,
.bar:not(.is-stuck):not(.on-menu) .bar__nav a:focus-visible,
.bar:not(.is-stuck):not(.on-menu) .bar__nav a[aria-current="true"]{ color: #fff; }

.bar.is-stuck{
  background: color-mix(in srgb, var(--ground) 82%, transparent);
  backdrop-filter: blur(18px) saturate(140%);
  -webkit-backdrop-filter: blur(18px) saturate(140%);
  border-bottom-color: var(--rule);
}
.bar__inner{
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2rem;
}

/* The logo lives outside .bar so the bar's difference blend cannot reach it,
   and is pinned over the slot .mark-slot reserves inside the bar's flex row.
   Left edge and centring mirror the bar's own padding and height exactly. */
.mark{
  position: fixed;
  top: 0;
  left: var(--margin);
  height: var(--bar-h);
  z-index: 76;
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font-weight: 900;
  font-size: 1.02rem;
  letter-spacing: -0.03em;
}
.mark__sub{
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: 0.2em;
  color: var(--ink-3);
  font-weight: 400;
}

/* Difference blending solves the white-on-transparent logo in one move:
   against a dark ground it stays white, against light paper it comes out
   dark. No filter, no plate behind it, nothing to keep in sync. */
/* 722 x 159 source, so width is height x 4.541 — the slot below reserves
   exactly that, keeping the nav and tools where they were. */
:root{ --logo-h: clamp(38px, 4.2vw, 50px); }
.mark-slot{ width: calc(var(--logo-h) * 4.541); flex: none; }
/* Painted rather than embedded. A background image is not an <img>, so
   there is no "Save image as" on it, no drag-to-desktop, and nothing for
   a long-press on a phone to offer. It is friction, not protection: the
   file is still fetchable at its own URL, as any file a browser renders
   must be. See README, "About the logo". */
.mark__logo{
  display: block;
  height: var(--logo-h);
  width: calc(var(--logo-h) * 4.54);   /* 722 x 159, kept in proportion */
  background-image: url("assets/frames-logo.png");
  background-repeat: no-repeat;
  background-position: left center;
  background-size: contain;
  /* Never blended: the logo is white on transparent, so a soft shadow rather
     than an inversion is what keeps it off a light hero frame. */
  mix-blend-mode: normal;
  filter: drop-shadow(0 1px 4px rgba(0,0,0,0.45));
  transition: opacity var(--swift) var(--ease);
  -webkit-user-select: none;
  user-select: none;
  -webkit-user-drag: none;
  -webkit-touch-callout: none;   /* no long-press sheet on iOS */
}
.mark:hover .mark__logo{ opacity: 0.72; }

/* Before the bar sticks it floats over the hero photograph, so its contents
   stay light regardless of theme. */
.bar:not(.is-stuck) ~ .mark,
.bar:not(.is-stuck) .icon-btn{ color: #F4F1EF; }
.bar:not(.is-stuck) .icon-btn{ border-color: rgba(255,255,255,0.32); }
.bar:not(.is-stuck) .icon-btn:hover{ border-color: #fff; color: #fff; }
.bar:not(.is-stuck) .burger span{ background: #F4F1EF; }

/* With the index menu open the bar sits on the menu's own ground, not on a
   photograph — so its contents return to the theme's ink. */
.bar.on-menu ~ .mark,
.bar.on-menu .icon-btn{ color: var(--ink); }
.bar.on-menu .icon-btn{ border-color: var(--rule); }
.bar.on-menu .burger span{ background: var(--ink); }

.bar__nav{ display: flex; gap: clamp(1.2rem, 2.4vw, 2.4rem); }
.bar__nav a{
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  /* Colour is overridden while the bar is blending; when stuck the normal
     theme ink takes over. States move colour rather than opacity — a partly
     transparent source dilutes a difference blend back toward the backdrop,
     which is exactly the low contrast this is meant to avoid. */
  color: var(--ink-2);
  position: relative;
  padding-block: 0.4rem;
  transition: color var(--swift) var(--ease);
}
.bar__nav a::after{
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 1px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: right;
  transition: transform var(--swift) var(--ease);
}
.bar__nav a:hover{ color: var(--ink); }
.bar__nav a:hover::after{ transform: scaleX(1); transform-origin: left; }
.bar__nav a[aria-current="true"]{ color: var(--ink); }
.bar__nav a[aria-current="true"]::after{ transform: scaleX(1); }

.bar__tools{ display: flex; align-items: center; gap: 0.9rem; }

.icon-btn{
  width: 40px; height: 40px;
  display: grid; place-items: center;
  border: 1px solid var(--rule);
  border-radius: 999px;
  color: var(--ink-2);
  transition: border-color var(--swift) var(--ease), color var(--swift) var(--ease), transform var(--swift) var(--ease);
}
.icon-btn:hover{ border-color: var(--ink); color: var(--ink); transform: translateY(-2px); }
.icon-btn .sun{ display: none; }

/* The burger. No ring around it: the two rules are the mark, and a circle
   only competed with the logo across the bar. The button keeps a 44px box
   for the thumb, but nothing of it is drawn.

   The rules are deliberately unequal — the lower one short, like a line of
   type that has run out — and they square up on approach. Open, they cross.
   Set tight: 5px apart, which reads as one object rather than two. */
.burger{
  display: none;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  align-content: center;
  justify-items: center;
  gap: 5px;
}
.burger:hover{ border-color: transparent; transform: none; }

.burger span{
  display: block;
  width: 20px;
  height: 1.5px;
  margin: 0;
  background: currentColor;
  transform-origin: 50% 50%;
  transition: transform var(--swift) var(--ease),
              width var(--swift) var(--ease),
              opacity var(--swift) var(--ease);
}
.burger span:nth-child(2){ width: 13px; align-self: start; justify-self: center; margin-right: 7px; }

.burger:hover span:nth-child(2),
.burger:focus-visible span:nth-child(2){ width: 20px; margin-right: 0; }

.burger:hover{ color: var(--ink); }
.burger:focus-visible{ outline: 2px solid var(--accent); outline-offset: 2px; }

/* Open: both rules full length, meeting in the middle to cross. The pair
   sits 6.5px apart centre to centre, so each travels half of that. */
.burger[aria-expanded="true"] span{ width: 20px; margin-right: 0; }
.burger[aria-expanded="true"] span:nth-child(1){ transform: translateY(3.25px) rotate(45deg); }
.burger[aria-expanded="true"] span:nth-child(2){ transform: translateY(-3.25px) rotate(-45deg); }

@media (max-width: 940px){
  .bar__nav{ display: none; }
  .burger{ display: grid; }
}

/* Vertical rails — the running head and folio of a printed catalogue. */
.rail{
  position: fixed;
  top: 0; bottom: 0;
  width: var(--rail-w);
  z-index: 55;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}
.rail--l{ left: 0; }
.rail--r{ right: 0; }
.rail span{
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--ink-3);
  writing-mode: vertical-rl;
}
.rail--l span{ transform: rotate(180deg); }

.rail__track{
  position: absolute;
  top: 22vh; bottom: 22vh;
  width: 1px;
  background: var(--ink-4);
}
.rail__thumb{
  position: absolute;
  top: 0; left: 0;
  width: 100%;
  height: 0%;
  background: var(--accent);
  transform-origin: top;
}
@media (max-width: 1180px){ .rail{ display: none; } }

/* Full-screen index menu */
.menu{
  position: fixed;
  inset: 0;
  z-index: 70;
  background: var(--ground);
  display: grid;
  align-content: center;
  padding: calc(var(--bar-h) + 2rem) var(--margin) 3rem;
  opacity: 0;
  visibility: hidden;
  transition: opacity .5s var(--ease), visibility 0s linear .5s;
}
.menu.is-open{ opacity: 1; visibility: visible; transition: opacity .5s var(--ease); }
.menu__list{ display: grid; gap: 0.1rem; }
.menu__item{ border-top: 1px solid var(--rule); }
.menu__item:last-child{ border-bottom: 1px solid var(--rule); }
.menu__link{
  display: flex;
  align-items: baseline;
  gap: 1.4rem;
  padding-block: clamp(0.7rem, 2vw, 1.3rem);
  font-size: clamp(2rem, 7vw, 4.5rem);
  font-weight: 800;
  letter-spacing: -0.04em;
  line-height: 1;
  color: var(--ink-3);
  transition: color var(--swift) var(--ease), padding-left var(--swift) var(--ease);
}
.menu__link:hover{ color: var(--ink); padding-left: 1rem; }
.menu__foot{
  margin-top: 3rem;
  display: flex; flex-wrap: wrap; gap: 1.6rem;
  justify-content: space-between;
}

/* Reading-progress hairline at the very top */
.progress{
  position: fixed;
  top: 0; left: 0;
  height: 2px; width: 0%;
  background: var(--accent);
  z-index: 80;
}

/* ============================== 13  HERO ================================ */

.hero{
  position: relative;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding-block: calc(var(--bar-h) + 1rem) clamp(1.5rem, 4vh, 3rem);
  overflow: hidden;
}

.hero__slides{ position: absolute; inset: 0; z-index: 0; will-change: transform; }

/* Film advance: frames are pulled through the gate rather than dissolved.
   The outgoing frame lifts away as the next arrives beneath it.          */
.hero__slide{
  position: absolute; inset: 0;
  overflow: hidden;
  transform: translate3d(0, 100%, 0);
  will-change: transform;
}
.hero__slide.is-active{ transform: translate3d(0, 0, 0); }

.hero__slide img{
  width: 100%; height: 100%;
  object-fit: cover;
  filter: contrast(1.06) saturate(0.94);
  transform: scale(1.03);
}

/* Each frame breathes while it is on screen. Varying the origin gives each
   one its own drift without needing four sets of keyframes.              */
.hero__slide.is-active img{ animation: drift 9s linear forwards; }
.hero__slide:nth-child(1) img{ transform-origin: 32% 42%; }
.hero__slide:nth-child(2) img{ transform-origin: 68% 58%; }
.hero__slide:nth-child(3) img{ transform-origin: 50% 28%; }
.hero__slide:nth-child(4) img{ transform-origin: 40% 72%; }

@keyframes drift{
  from{ transform: scale(1.03); }
  to  { transform: scale(1.12); }
}

/* Scrim, vignette and grain: three cheap layers that make stock look graded. */
.hero__scrim{
  position: absolute; inset: 0; z-index: 1;
  background:
    linear-gradient(to top, var(--ground) 2%, color-mix(in srgb, var(--ground) 55%, transparent) 34%, transparent 62%),
    radial-gradient(130% 96% at 50% 42%, transparent 38%, color-mix(in srgb, var(--ground) 78%, transparent) 100%),
    linear-gradient(to right, color-mix(in srgb, var(--ground) 62%, transparent), transparent 46%);
  pointer-events: none;
}
.grain{
  position: absolute; inset: 0; z-index: 2;
  pointer-events: none;
  opacity: 0.30;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='140' height='140'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.86' numOctaves='3' stitchTiles='stitch'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
  background-size: 220px 220px;
}

.hero__body{ position: relative; z-index: 3; width: 100%; }

.hero__eyebrow{
  display: flex; align-items: center; gap: 0.9rem;
  margin-bottom: clamp(1rem, 2.4vw, 2rem);
}
.hero__eyebrow .dot{
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--accent);
  animation: blink 2.6s ease-in-out infinite;
}
@keyframes blink{ 0%,100%{ opacity: 1; } 50%{ opacity: 0.25; } }

/* Solid wordmark. The photograph sits behind it, not inside it. */
.wordmark{
  margin: 0;
  line-height: 0.8;
  color: var(--ink);
}

.hero__sub{
  max-width: 46ch;
  margin-top: clamp(1.4rem, 3vw, 2.4rem);
  color: var(--ink-2);
  font-size: var(--fs-lead);
  line-height: 1.4;
  letter-spacing: -0.015em;
}

.hero__foot{
  position: relative;
  z-index: 3;
  margin-top: clamp(2rem, 6vh, 4.5rem);
  padding-top: 1.2rem;
  border-top: 1px solid var(--rule);
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 2rem;
  flex-wrap: wrap;
}
.hero__caption{ display: grid; gap: 0.4rem; min-width: 0; }
.hero__caption .c-line{
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-2);
  transition: opacity .45s var(--ease), transform .45s var(--ease);
}
.hero__caption.is-swapping .c-line{ opacity: 0; transform: translateY(6px); }

/* Solar readout — where the light is, right now, at the club's coordinates. */
.hero__clock{
  display: grid;
  gap: 0.4rem;
  text-align: center;
  min-width: 0;
}
.hero__clock p{
  margin: 0;
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}
.hero__clock .is-golden{ color: var(--accent); }

@media (max-width: 900px){
  .hero__clock{ text-align: left; }
  .hero__clock .geo{ display: none; }
}

/* The frame ticks are controls, not decoration — so they need a real target. */
.tickbtn{
  display: block;
  padding: 9px 0;
  cursor: pointer;
  background: none;
  border: 0;
}
.tickbtn:hover .hero__tick{ background: var(--ink-3); }

.hero__ticks{ display: flex; align-items: center; gap: 0.5rem; }
.hero__tick{
  display: block;
  width: 34px; height: 2px;
  background: var(--ink-4);
  position: relative;
  overflow: hidden;
  transition: background-color var(--swift) var(--ease);
}
.hero__tick i{
  position: absolute; inset: 0;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
}
.hero__tick.is-active i{ animation: tick 5.5s linear forwards; }
@keyframes tick{ from{ transform: scaleX(0); } to{ transform: scaleX(1); } }
.hero__tick.is-done i{ transform: scaleX(1); }

.scroll-cue{
  display: flex; align-items: center; gap: 0.7rem;
  font-family: var(--mono); font-size: var(--fs-label);
  letter-spacing: 0.2em; text-transform: uppercase; color: var(--ink-3);
}
.scroll-cue svg{ animation: nudge 2.2s var(--ease) infinite; }
@keyframes nudge{ 0%,100%{ transform: translateY(0); } 50%{ transform: translateY(5px); } }

@media (max-width: 720px){
  .hero__foot{ flex-direction: column; align-items: flex-start; gap: 1.2rem; }
  .scroll-cue{ display: none; }
}

/* ======================= PLATE OF THE DAY =============================== */
/* The frontispiece. One photograph, given room, with a wall label beside it. */

.potd{ border-bottom: 1px solid var(--rule); }

.potd__grid{ align-items: start; }
.potd__frame{ grid-column: 1 / 9; }
.potd__label{
  grid-column: 9 / 13;
  border-top: 1px solid var(--accent);
  padding-top: 1.4rem;
}

/* Today's plate is the one exception to the monochrome archive: it is
   already in colour, because it is the frame being shown to you. */
.potd .media img{ filter: none; cursor: zoom-in; }
.potd .media:hover img{ transform: scale(1.02); }

.potd__date{
  margin: 0.9rem 0 0;
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.potd__title{ margin-top: 1.1rem; }

.potd__meta{ margin: 1.6rem 0 0; }
.potd__meta > div{
  display: grid;
  grid-template-columns: 8.5rem 1fr;
  gap: 1rem;
  padding-block: 0.6rem;
  border-top: 1px solid var(--rule);
}
.potd__meta dt{
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.potd__meta dd{
  margin: 0;
  font-size: 0.94rem;
  color: var(--ink);
}
.potd__meta dd.is-placeholder{ color: var(--ink-3); font-style: italic; }

.potd__note{ margin-top: 1.4rem; font-size: 0.96rem; }
.potd__foot{ margin-top: 1.6rem; color: var(--ink-3); }

@media (max-width: 1000px){
  .potd__frame{ grid-column: 1 / -1; }
  .potd__label{ grid-column: 1 / -1; margin-top: 2rem; }
}
@media (max-width: 560px){
  .potd__meta > div{ grid-template-columns: 1fr; gap: 0.25rem; }
}

/* ============================== ABOUT =================================== */

.about__lead{ grid-column: 1 / 8; }

/* Justified, because this block is a solid column of prose and the ragged
   edge was fighting the grid beside it. hyphens are on deliberately: without
   them justification opens rivers of white space between words on narrow
   screens. The motto is left alone — a three-word line cannot be justified. */
.about__lead .lead,
.about__lead .body:not(.about__motto){
  text-align: justify;
  text-justify: inter-word;
  hyphens: auto;
  -webkit-hyphens: auto;
}

/* Below the fold of a phone a justified measure gets too narrow to set
   well, so it goes back to ragged right. */
@media (max-width: 520px){
  .about__lead .lead,
  .about__lead .body:not(.about__motto){
    text-align: left;
    hyphens: manual;
    -webkit-hyphens: manual;
  }
}
/* The execom plate: a centred 16:9 frame under the lead copy, no longer a
   column of the about grid. */
.about__figure{
  margin: clamp(3rem, 6vw, 5.5rem) auto 0;
  max-width: 1000px;
}
.about__figure .caption{ border-color: var(--rule); }

@media (max-width: 1000px){
  .about__lead{ grid-column: 1 / -1; }
  .about__figure{ margin-top: 2.5rem; }
}

/* The old rule padded only the right side and hung the divider off the cell
   edge, so the first number sat flush while the rest drifted. Now: equal
   columns, symmetric space either side of every divider, and the first
   figure aligned to the page margin. */
.stats{
  margin-top: clamp(3rem, 8vw, 6rem);
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
}
.stat{
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.6rem;
  min-width: 0;
  padding: clamp(1.6rem, 3vw, 2.6rem) clamp(1rem, 2vw, 1.8rem);
  border-left: 1px solid var(--rule);
}
.stat:first-child{ border-left: 0; padding-left: 0; }
.stat__num{
  font-size: clamp(2.2rem, 5vw, 4rem);
  font-weight: 800;
  letter-spacing: -0.05em;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.stat__label{
  margin: 0;
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-3);
  text-wrap: balance;
}
@media (max-width: 760px){
  .stats{ grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .stat:nth-child(3){ border-left: 0; padding-left: 0; }
  .stat:nth-child(1), .stat:nth-child(2){ border-bottom: 1px solid var(--rule); }
}
@media (max-width: 430px){
  .stats{ grid-template-columns: minmax(0, 1fr); }
  .stat{ border-left: 0; padding-left: 0; border-bottom: 1px solid var(--rule); }
  .stat:last-child{ border-bottom: 0; }
}

/* ======================== 07  PRACTICE MOSAIC =========================== */

/* Fixed row height + row spans, so every row lines up along the bottom
   regardless of the source photograph's own proportions. */
.mosaic{
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  grid-auto-rows: clamp(150px, 19vw, 265px);
  gap: var(--gutter);
}
.tile{ position: relative; display: block; }
.tile .media{ height: 100%; }
.tile__body{
  position: absolute;
  left: 0; right: 0; bottom: 0;
  padding: clamp(1rem, 2vw, 1.6rem);
  z-index: 2;
  background: linear-gradient(to top, rgba(6,5,5,0.86), rgba(6,5,5,0.30) 55%, transparent);
  color: #F4F1EF;
}
.tile__idx{
  font-family: var(--mono); font-size: var(--fs-label);
  letter-spacing: 0.2em; color: rgba(244,241,239,0.62);
  display: block; margin-bottom: 0.5rem;
}
.tile__title{
  font-size: clamp(1.1rem, 1.7vw, 1.55rem);
  font-weight: 700; letter-spacing: -0.025em; line-height: 1.1;
}
.tile__text{
  margin-top: 0.5rem;
  font-size: 0.92rem;
  line-height: 1.5;
  color: rgba(244,241,239,0.74);
  max-width: 42ch;
  opacity: 0;
  transform: translateY(8px);
  transition: opacity var(--slow) var(--ease), transform var(--slow) var(--ease);
}
.tile:hover .tile__text,
.tile:focus-visible .tile__text{ opacity: 1; transform: none; }

.tile--a{ grid-column: span 7; grid-row: span 2; }
.tile--b{ grid-column: span 5; grid-row: span 2; }
.tile--c{ grid-column: span 4; grid-row: span 2; }
.tile--d{ grid-column: span 4; grid-row: span 2; }
.tile--e{ grid-column: span 4; grid-row: span 2; }
.tile--f{ grid-column: span 12; grid-row: span 1; }

@media (max-width: 900px){
  .tile--a, .tile--b, .tile--c, .tile--d, .tile--e{ grid-column: span 6; }
  .tile__text{ opacity: 1; transform: none; }
}
@media (max-width: 620px){
  .tile--a, .tile--b, .tile--c, .tile--d, .tile--e, .tile--f{ grid-column: span 12; }
  .tile--f{ grid-row: span 2; }
}

/* ==================== 08  ARCHIVE — PINNED FILMSTRIP ==================== */

.archive{ position: relative; background: var(--surface); }
.archive__pin{
  position: sticky;
  top: 0;
  height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: clamp(1.5rem, 4vh, 3rem);
  overflow: hidden;
  padding-block: clamp(4rem, 10vh, 7rem);
}
.archive__head{
  padding-inline: var(--margin);
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 2rem;
  flex-wrap: wrap;
}
.archive__head h2{ max-width: 16ch; }
.archive__hint{ display: flex; align-items: center; gap: 0.7rem; }

.archive__viewport{ overflow: hidden; }
.archive__track{
  display: flex;
  gap: clamp(1rem, 2vw, 2rem);
  padding-inline: var(--margin);
  width: max-content;
  will-change: transform;
}
.plate{
  width: clamp(230px, 25vw, 400px);
  flex: 0 0 auto;
}
.plate--tall .media{ aspect-ratio: 4 / 5; }
.plate--wide{ width: clamp(300px, 36vw, 580px); }
.plate--wide .media{ aspect-ratio: 3 / 2; }

.archive__meter{
  padding-inline: var(--margin);
  display: flex; align-items: center; gap: 1.2rem;
}
.archive__bar{ flex: 1; height: 1px; background: var(--ink-4); position: relative; }
.archive__bar i{
  position: absolute; inset: 0;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
}

/* Below the pin breakpoint the strip becomes an honest swipe gallery. */
@media (max-width: 900px), (prefers-reduced-motion: reduce){
  .archive{ height: auto !important; }
  .archive__pin{ position: static; height: auto; }
  .archive__viewport{
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    /* Same bargain as the feed rail: hold the horizontal overscroll,
       hand every vertical gesture to the page. */
    overscroll-behavior-inline: contain;
    overscroll-behavior-block: auto;
    touch-action: pan-x pan-y pinch-zoom;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .archive__viewport::-webkit-scrollbar{ display: none; }
  .archive__track{ transform: none !important; }
  .plate{ scroll-snap-align: center; }
}

/* ======================= 09  OCCASIONS INDEX ============================ */

.occ__list{ border-top: 1px solid var(--rule); }

.occ__row{ border-bottom: 1px solid var(--rule); }

.occ__link{
  position: relative;
  display: grid;
  grid-template-columns: 3.5rem 1fr auto auto;
  align-items: center;
  gap: clamp(1rem, 3vw, 2.5rem);
  width: 100%;
  padding-block: clamp(1.1rem, 3vw, 2.1rem);
  text-align: left;
  color: var(--ink-3);
  transition: color var(--swift) var(--ease), padding-left var(--swift) var(--ease);
}
.occ__link:hover,
.occ__link:focus-visible{ color: var(--ink); padding-left: clamp(0.5rem, 1.6vw, 1.4rem); }

.occ__name{
  font-size: clamp(1.6rem, 4.6vw, 3.6rem);
  font-weight: 800;
  letter-spacing: -0.04em;
  line-height: 1;
  color: currentColor;
}
.occ__year,
.occ__count,
.occ__go{
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-3);
  white-space: nowrap;
}
.occ__link:hover .occ__count,
.occ__link:hover .occ__go{ color: var(--accent); }
/* The arrow leans out on approach, the way every other link here does. */
.occ__go{ transition: transform var(--swift) var(--ease), color var(--swift) var(--ease); }
.occ__link:hover .occ__go{ transform: translateX(4px); }

/* Thumbnail rides the cursor — the point of an index is to look things up. */
.occ__peek{
  position: fixed;
  top: 0; left: 0;
  width: clamp(180px, 17vw, 270px);
  aspect-ratio: 4 / 5;
  z-index: 40;
  pointer-events: none;
  opacity: 0;
  transform: translate3d(-50%, -50%, 0) scale(0.9);
  transition: opacity .4s var(--ease), transform .5s var(--ease);
  overflow: hidden;
  background: var(--plate);
}
.occ__peek.is-on{ opacity: 1; transform: translate3d(-50%, -50%, 0) scale(1); }
.occ__peek img{ width: 100%; height: 100%; object-fit: cover; filter: var(--grade-off); }

@media (max-width: 900px){
  .occ__peek{ display: none; }
  .occ__link{
    grid-template-columns: 2.6rem 1fr;
    grid-template-areas:
      "idx name"
      ".   meta";
    row-gap: 0.6rem;
  }
  .occ__link .idx{ grid-area: idx; }
  .occ__name{ grid-area: name; }
  .occ__year{ grid-area: meta; }
  .occ__count,
  .occ__go{ display: none; }
}

/* ==================== 10  EXECOM — CONTACT SHEET ======================== */

.sheet{
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(1rem, 2vw, 2rem) var(--gutter);
}
.person{ position: relative; }
.person .media{ aspect-ratio: 4 / 5; }
.person__no{
  position: absolute;
  top: 0.7rem; left: 0.7rem;
  z-index: 2;
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: 0.16em;
  color: #F4F1EF;
  mix-blend-mode: difference;
}
.person__role{
  margin-top: 0.9rem;
  padding-top: 0.7rem;
  border-top: 1px solid var(--rule);
  font-size: 0.98rem;
  font-weight: 700;
  letter-spacing: -0.015em;
}
@media (max-width: 900px){ .sheet{ grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* ===================== PERSON CARD — REVEAL PANEL ====================== */
/* Hover on a pointer device, tap or Enter on touch and keyboard. The panel
   is a real toggle so it is reachable without a mouse.                   */

.person{ position: relative; }
.person__shot{ position: relative; overflow: hidden; }

/* Six core members read better as two rows of three than one row of four. */
.sheet--core{ grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 900px){ .sheet--core{ grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 480px){ .sheet--core{ grid-template-columns: minmax(0, 1fr); } }

.person__panel{
  position: absolute;
  left: 0; right: 0; bottom: 0;
  z-index: 2;
  padding: clamp(0.9rem, 1.6vw, 1.3rem);
  background: linear-gradient(to top, rgba(6,5,5,0.96), rgba(6,5,5,0.80) 58%, rgba(6,5,5,0.28) 84%, transparent);
  color: #F4F1EF;
  opacity: 0;
  transform: translateY(12px);
  transition: opacity var(--swift) var(--ease), transform var(--swift) var(--ease);
  pointer-events: none;
}
.person:hover .person__panel,
.person:focus-within .person__panel,
.person.is-open .person__panel{
  opacity: 1;
  transform: none;
  pointer-events: auto;
}

.team-more{
  margin-top: clamp(2.5rem, 5vw, 3.5rem);
  padding-top: 1.6rem;
  border-top: 1px solid var(--rule);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  flex-wrap: wrap;
}

/* ============================ CONTACT FORM ============================= */

.contact{ border-top: 1px solid var(--rule); }
.contact__intro{ grid-column: 1 / 6; }
.contact__form{ grid-column: 7 / 13; }

@media (max-width: 1000px){
  .contact__intro, .contact__form{ grid-column: 1 / -1; }
  .contact__form{ margin-top: 2.5rem; }
}

.field{ display: grid; gap: 0.5rem; margin-bottom: 1.4rem; }
.field label{
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.field input,
.field select,
.field textarea{
  width: 100%;
  padding: 0.9rem 0;
  font-family: var(--sans);
  font-size: 1rem;
  color: var(--ink);
  background: none;
  border: 0;
  border-bottom: 1px solid var(--rule);
  border-radius: 0;
  transition: border-color var(--swift) var(--ease);
}
.field textarea{ resize: vertical; min-height: 110px; line-height: 1.6; }
.field select{ cursor: pointer; }
.field select option{ background: var(--surface); color: var(--ink); }
.field input:focus,
.field select:focus,
.field textarea:focus{ outline: none; border-bottom-color: var(--accent); }
.field input::placeholder,
.field textarea::placeholder{ color: var(--ink-3); }

.field.is-invalid input,
.field.is-invalid textarea{ border-bottom-color: var(--accent); }
.field__error{
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: 0.1em;
  color: var(--accent);
  min-height: 1em;
}

/* Bot trap — never shown, never announced. */
.field--trap{
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden; clip: rect(0 0 0 0);
  white-space: nowrap;
}

.form__foot{
  display: flex;
  align-items: center;
  gap: 1.4rem;
  flex-wrap: wrap;
  margin-top: 2rem;
}
.form__status{
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.form__status.is-good{ color: var(--accent); }
.form__status.is-bad{ color: var(--accent); }

.btn[disabled]{ opacity: 0.45; pointer-events: none; }

.form__done{
  padding: clamp(1.6rem, 4vw, 2.6rem);
  border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--rule));
}
.form__done h3{ margin-bottom: 0.8rem; }
.form__done:focus{ outline: none; }

/* ============================ WORKSHOP PAGE ============================= */
/* Poster on the left, application on the right; stacked on a phone. */

.ws__grid{ align-items: start; margin-top: clamp(2.5rem, 6vw, 5rem); }
.ws__poster{ grid-column: 1 / 6; position: sticky; top: calc(var(--bar-h) + 1.5rem); }
.ws__poster img{
  width: 100%; height: auto;
  aspect-ratio: 4 / 5;
  background: var(--surface);
  border: 1px solid var(--rule);
}
.ws__form{ grid-column: 7 / 13; }

.ws__step{ margin: 0 0 1.4rem; }
.ws__step:not(:first-child){ margin-top: 2.6rem; padding-top: 1.6rem; border-top: 1px solid var(--rule); }

.ws__pair{ display: grid; grid-template-columns: 1fr 1fr; gap: 0 var(--gutter); }

.ws__skills{ border: 0; padding: 0; margin: 0 0 1.4rem; gap: 0.2rem; }
.ws__skills legend{
  padding: 0;
  margin-bottom: 0.9rem;
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.ws__skills legend span{ letter-spacing: 0.1em; text-transform: none; color: var(--ink-3); opacity: 0.8; margin-left: 0.5rem; }

.check{
  display: flex; align-items: center; gap: 0.9rem;
  padding: 0.75rem 0;
  border-bottom: 1px solid var(--rule);
  cursor: pointer;
  font-size: 1rem;
  color: var(--ink-2);
  transition: color var(--swift) var(--ease);
}
.check:hover, .check:has(input:checked){ color: var(--ink); }
.check input{
  appearance: none;
  flex: none;
  width: 18px; height: 18px;
  margin: 0;
  border: 1px solid var(--rule-strong);
  background: none;
  cursor: pointer;
  display: grid; place-items: center;
  transition: background-color var(--swift) var(--ease), border-color var(--swift) var(--ease);
}
.check input::after{
  content: "";
  width: 9px; height: 5px;
  border-left: 2px solid var(--accent-ink);
  border-bottom: 2px solid var(--accent-ink);
  transform: translateY(-1px) rotate(-45deg) scale(0);
  transition: transform var(--swift) var(--ease);
}
.check input:checked{ background: var(--accent); border-color: var(--accent); }
.check input:checked::after{ transform: translateY(-1px) rotate(-45deg) scale(1); }
.check input:focus-visible{ outline: 2px solid var(--accent); outline-offset: 3px; }

.field.is-invalid select{ border-bottom-color: var(--accent); }

/* Year of study: four buttons in a row instead of a dropdown. */
.ws__years{ border: 0; padding: 0; min-width: 0; }
.ws__years legend{
  padding: 0;
  margin-bottom: 0.75rem;
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.ws__years:focus{ outline: none; }
.ws__yearrow{ display: grid; grid-template-columns: repeat(4, 1fr); gap: 0.5rem; }
.ws__year{ position: relative; cursor: pointer; }
.ws__year input{ position: absolute; inset: 0; opacity: 0; margin: 0; cursor: pointer; }
.ws__year span{
  display: block;
  padding: 0.8rem 0;
  text-align: center;
  border: 1px solid var(--rule-strong);
  font-family: var(--mono);
  font-size: 0.85rem;
  letter-spacing: 0.12em;
  color: var(--ink-2);
  transition: background-color var(--swift) var(--ease), border-color var(--swift) var(--ease), color var(--swift) var(--ease);
}
.ws__year:hover span{ border-color: var(--ink-3); color: var(--ink); }
.ws__year input:checked + span{ background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.ws__year input:focus-visible + span{ outline: 2px solid var(--accent); outline-offset: 3px; }
.ws__years.is-invalid .ws__year span{ border-color: color-mix(in srgb, var(--accent) 70%, var(--rule)); }

.ws__fine{ margin-top: 1.6rem; max-width: 52ch; line-height: 1.7; }

/* Payment: the bank's QR card beside the UPI details, shown as issued. */
.ws__pay{
  display: grid;
  grid-template-columns: 300px minmax(0, 1fr);
  gap: 1.4rem var(--gutter);
  align-items: start;
  margin-bottom: 2rem;
}
.ws__qr{ width: 300px; }
.ws__qr img{ display: block; width: 100%; height: auto; border: 1px solid var(--rule); }
.ws__payactions{ display: flex; flex-wrap: wrap; gap: 0.7rem; }
.ws__payactions [hidden]{ display: none; }
.ws__phonehint{ margin-top: 0.9rem; }
.ws__phonehint b{ color: var(--ink-2); }
.ws__phonehint[hidden]{ display: none; }

.ws__preview{
  margin: 0 0 1.6rem;
  padding: 0.7rem 0.9rem;
  border: 1px dashed var(--amber, #F5A623);
  color: #F5A623;
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.ws__preview[hidden]{ display: none; }

/* "Details saved" — the top of the payment step. Loud on purpose: the
   student must not think they are done before paying. */
.ws__held{
  margin-bottom: 2.4rem;
  padding: 1.1rem 1.2rem;
  border: 1px solid color-mix(in srgb, var(--accent) 55%, var(--rule));
  background: color-mix(in srgb, var(--accent) 8%, transparent);
}
.ws__held:focus{ outline: none; }
.ws__held .label{ margin: 0 0 0.6rem; }
.ws__held p{ margin: 0; color: var(--ink-2); line-height: 1.55; }
.ws__held strong{ color: var(--ink); }
.ws__held .ws__heldwho{ margin-top: 0.7rem; font-size: 0.9rem; }
.ws__heldwho[hidden]{ display: none; }
#wsRegId{ color: var(--ink); letter-spacing: 0.12em; }

.ws__linkbtn{
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink-2);
  font: inherit;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.ws__linkbtn:hover{ color: var(--accent); }

/* Back from the UPI app: pull the eye to the UTR box. */
.field.is-nudged{ animation: ws-nudge 1.6s var(--ease) 1; }
@keyframes ws-nudge{
  0%, 100%{ box-shadow: 0 0 0 0 transparent; }
  20%, 60%{ box-shadow: 0 0 0 10px color-mix(in srgb, var(--accent) 22%, transparent); }
}
@media (prefers-reduced-motion: reduce){ .field.is-nudged{ animation: none; } }
.ws__payinfo .body{ margin: 0 0 1rem; font-size: 0.95rem; }
.ws__payinfo strong{ color: var(--ink); }
.ws__amountnote{
  margin: 0 0 1.1rem;
  padding: 0.7rem 0.9rem;
  border-left: 2px solid var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  font-size: 0.92rem;
  line-height: 1.5;
  color: var(--ink-2);
}
.ws__amountnote strong{ color: var(--accent); font-size: 1.05em; }

.ws__upi{ margin: 0 0 1.2rem; display: grid; gap: 0.5rem; }
.ws__upi > div{ display: grid; grid-template-columns: 5.5rem minmax(0, 1fr); align-items: baseline; gap: 0.6rem; }
.ws__upi dt{
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.ws__upi dd{ margin: 0; color: var(--ink); overflow-wrap: anywhere; }
.ws__copy{
  margin-left: 0.5rem;
  padding: 0.2rem 0.55rem;
  border: 1px solid var(--rule-strong);
  background: none;
  color: var(--ink-2);
  font-family: var(--mono);
  font-size: 0.62rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  cursor: pointer;
  vertical-align: 1px;
}
.ws__copy:hover{ border-color: var(--accent); color: var(--ink); }

.ws__hint{ margin: 0; font-size: 0.82rem; line-height: 1.5; color: var(--ink-3); }

.ws__confirm{ border-top: 1px solid var(--rule); margin-top: 0.4rem; }
.ws__confirm + .field__error{ margin-top: 0.5rem; }

@media (max-width: 560px){
  .ws__pay{ grid-template-columns: 1fr; }
  .ws__qr{ width: min(300px, 100%); justify-self: center; }
}

@media (max-width: 1000px){
  .ws__poster, .ws__form{ grid-column: 1 / -1; }
  .ws__poster{ position: static; max-width: 560px; }
  .ws__form{ margin-top: 3rem; }
}
@media (max-width: 560px){
  .ws__pair{ grid-template-columns: 1fr; }
}

/* ------------------- the workshop popup, bottom right ------------------ */
/* Above the rails (55), below the menu (70) and the photo viewer. */

.wspop{
  position: fixed;
  right: calc(var(--rail-w) + 0.6rem);
  bottom: clamp(1rem, 2.4vw, 1.8rem);
  z-index: 65;
  opacity: 0;
  transform: translateY(16px);
  pointer-events: none;
  transition: opacity 0.5s var(--ease), transform 0.5s var(--ease);
}
.wspop.is-in{ opacity: 1; transform: none; pointer-events: auto; }

.wspop__card{
  display: block;
  width: 210px;
  background: var(--surface);
  border: 1px solid color-mix(in srgb, var(--accent) 55%, var(--rule));
  box-shadow: 0 18px 40px rgba(0,0,0,0.55);
  color: var(--ink);
  transition: border-color var(--swift) var(--ease), transform var(--swift) var(--ease);
}
.wspop__card:hover{ border-color: var(--accent); transform: translateY(-3px); }
.wspop__poster{ display: block; width: 100%; height: auto; aspect-ratio: 4 / 5; background: var(--surface-2); }
.wspop__body{ display: grid; gap: 0.35rem; padding: 0.8rem 0.9rem 0.9rem; }
.wspop__tag,
.wspop__go{
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: 0.16em;
  text-transform: uppercase;
}
.wspop__tag{ color: var(--accent); }
.wspop__title{ font-size: 0.92rem; font-weight: 700; line-height: 1.25; letter-spacing: -0.01em; }
.wspop__go{ margin-top: 0.2rem; color: var(--ink-2); }
.wspop__card:hover .wspop__go{ color: var(--accent); }

.wspop__close{
  position: absolute;
  top: 6px; right: 6px;
  width: 28px; height: 28px;
  display: grid; place-items: center;
  border: 0;
  border-radius: 50%;
  background: rgba(11,10,10,0.72);
  color: var(--ink);
  cursor: pointer;
}
.wspop__close:hover{ background: var(--accent); }

.wspop__tab{
  display: none;
  align-items: center; gap: 0.6rem;
  padding: 0.7rem 1rem;
  border: 1px solid var(--accent);
  background: var(--surface);
  box-shadow: 0 10px 28px rgba(0,0,0,0.5);
  color: var(--ink);
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  cursor: pointer;
}
.wspop__tab:hover{ background: var(--accent); color: var(--accent-ink); }
.wspop__dot{ width: 7px; height: 7px; border-radius: 50%; background: var(--accent); animation: blink 2.6s ease-in-out infinite; }
.wspop__tab:hover .wspop__dot{ background: var(--accent-ink); }

.wspop.is-folded .wspop__card,
.wspop.is-folded .wspop__close{ display: none; }
.wspop.is-folded .wspop__tab{ display: inline-flex; }

/* On a phone the poster would cover the page, so the card turns into a
   short strip: thumbnail, title, apply. */
@media (max-width: 640px){
  .wspop{ right: 0.75rem; bottom: 0.75rem; }
  .wspop__card{ display: flex; align-items: center; width: min(320px, calc(100vw - 1.5rem)); }
  .wspop__poster{ width: 64px; flex: none; }
  .wspop__body{ padding: 0.6rem 2.4rem 0.6rem 0.8rem; gap: 0.2rem; }
  .wspop__title{ font-size: 0.85rem; }
  .wspop__close{ top: 50%; transform: translateY(-50%); }
}

@media (prefers-reduced-motion: reduce){
  .wspop{ transition: none; }
}

/* ========================= 11  JOIN & FOOTER ============================ */

.join{
  position: relative;
  min-height: 86svh;
  display: grid;
  align-items: center;
  overflow: hidden;
}
.join__bg{ position: absolute; inset: 0; z-index: 0; }
.join__bg img{ width: 100%; height: 100%; object-fit: cover; filter: var(--grade) brightness(0.62); }
.join__bg::after{
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(to top, var(--ground), color-mix(in srgb, var(--ground) 42%, transparent));
}
.join__body{ position: relative; z-index: 2; }
.join__body h2{ max-width: 14ch; }
.join__actions{ margin-top: clamp(2rem, 5vw, 3.2rem); display: flex; gap: 1rem; flex-wrap: wrap; }

.foot{ border-top: 1px solid var(--rule); padding-block: clamp(3rem, 7vw, 5rem) 2rem; }
.foot__grid{ align-items: start; }
.foot__brand{ grid-column: 1 / 6; }
.foot__col{ grid-column: span 2; }
.foot__col:first-of-type{ grid-column: 7 / 9; }
.foot__title{
  font-family: var(--mono); font-size: var(--fs-label);
  letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--ink-3); margin-bottom: 1.1rem;
}
.foot__col a{ display: block; padding-block: 0.35rem; color: var(--ink-2); transition: color var(--swift) var(--ease); }
.foot__col a:hover{ color: var(--accent); }
.foot__outline{
  font-size: clamp(3rem, 13vw, 12rem);
  font-weight: 900;
  letter-spacing: -0.05em;
  line-height: 0.8;
  color: transparent;
  -webkit-text-stroke: 1px var(--rule-strong);
  margin-top: clamp(2rem, 6vw, 4rem);
}
/* Who built the site. A credit, not a notice — so it is given the
   weight of a name rather than the weight of fine print, with the
   people who helped set quietly underneath. */
.foot__credit{ margin-top: 2.6rem; }

.foot__credit-label{ color: var(--accent); }

.foot__by{
  margin: 0.55rem 0 0;
  font-size: clamp(1.15rem, 2vw, 1.6rem);
  font-weight: 800;
  letter-spacing: -0.025em;
  line-height: 1.1;
  color: var(--ink);
}

.foot__helped{
  margin: 0.45rem 0 0;
  font-size: clamp(0.9rem, 1.35vw, 1.08rem);
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1.2;
  color: var(--ink-2);
}

.foot__bottom{
  margin-top: 2rem;
  padding-top: 1.4rem;
  border-top: 1px solid var(--rule);
  display: flex; justify-content: space-between; gap: 1.5rem; flex-wrap: wrap;
}
@media (max-width: 900px){
  .foot__brand{ grid-column: 1 / -1; margin-bottom: 2.5rem; }
  .foot__col, .foot__col:first-of-type{ grid-column: span 6; }
}

/* ========================= GALLERY HERO BANNER ========================= */
/* Figma artwork in a fixed 16:5 frame, with a line of copy along the
   bottom pointing at the latest album. */

.banner{
  position: relative;
  overflow: hidden;
  border-bottom: 1px solid var(--rule);

  /* The frame the Figma artwork is designed to: 1600 x 500. Holding the
     ratio here means what the team draws is what ships, uncropped, on a
     desktop-width screen. */
  aspect-ratio: 16 / 5;
  min-height: 360px;
  display: grid;
}
.banner__bg{ position: absolute; inset: 0; z-index: 0; }
.banner__bg img{ width: 100%; height: 100%; object-fit: cover; }
/* A light scrim only where the copy sits, so the artwork reads as drawn. */
.banner__bg::after{
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(to top, rgba(6,5,5,0.62), rgba(6,5,5,0.18) 38%, transparent 62%);
}

/* banner--art: the artwork carries its own title and QR, so the site copy
   would only fight it. Everything but the album link steps aside, and the
   scrim goes with it. */
.banner--art .banner__label,
.banner--art .banner__title,
.banner--art .banner__note{ display: none; }
.banner--art .banner__link{ margin-top: 0; }
.banner--art .banner__bg::after{
  background: linear-gradient(to top, rgba(6,5,5,0.42), transparent 30%);
}

/* The shell stretches so the copy can sit against the bottom of the frame,
   leaving the top two-thirds of the artwork clear. */
.banner .shell{ display: grid; align-content: end; }

.banner__inner{
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: end;
  gap: clamp(2rem, 5vw, 4.5rem);
  padding-top: clamp(3rem, 12vw, 9rem);
  padding-bottom: clamp(2rem, 4vw, 3.4rem);
  color: #F4F1EF;
}
.banner__copy{ min-width: 0; }
.banner__label{ color: var(--accent); }
.banner__title{
  margin-top: 1rem;
  font-size: clamp(1.7rem, 3.6vw, 3rem);
  font-weight: 800;
  letter-spacing: -0.035em;
  line-height: 1.02;
  color: #F4F1EF;
}
.banner__note{
  margin-top: 1rem;
  max-width: 46ch;
  color: rgba(244,241,239,0.76);
  font-size: 0.98rem;
  line-height: 1.55;
}
.banner__link{
  display: inline-flex;
  align-items: center;
  gap: 0.7rem;
  margin-top: 1.7rem;
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: #F4F1EF;
  border-bottom: 1px solid rgba(244,241,239,0.45);
  padding-bottom: 4px;
  transition: color var(--swift) var(--ease), border-color var(--swift) var(--ease);
}
.banner__link:hover{ color: var(--accent); border-color: var(--accent); }

/* On a phone the artwork stops being a backdrop and becomes the thing
   itself: shown whole at its own 16:5, with the copy underneath on the
   page rather than laid over it. Cropping to fill a tall band would cut
   off the title and the QR — the two parts of the design that have to be
   read — and a scrim over a 130px-tall strip only muddies it. */
@media (max-width: 760px){
  .banner{
    aspect-ratio: auto;
    min-height: 0;
    display: block;
  }

  .banner__bg{ position: relative; inset: auto; }
  .banner__bg img{
    aspect-ratio: 16 / 5;
    height: auto;
    object-fit: contain;
    background: var(--surface-2);
  }
  /* Nothing to protect the copy from any more — it has its own ground. */
  .banner__bg::after{ display: none; }

  .banner .shell{ display: block; }
  .banner__inner{
    justify-items: start;
    padding-top: clamp(1.4rem, 6vw, 2rem);
    padding-bottom: clamp(1.6rem, 6vw, 2.2rem);
    color: var(--ink);
  }
  .banner__title,
  .banner__link{ color: var(--ink); }
  .banner__note{ color: var(--ink-2); }
  .banner__link{ border-bottom-color: var(--rule-strong); }

  /* banner--art hides the copy over the artwork on desktop; on a phone the
     link sits below it instead, where there is room for it. */
  .banner--art .banner__link{ margin-top: 0; }
}

/* A 16:5 band is very short on a narrow phone. Below this the artwork is
   still whole, just small — which is the right trade against losing half
   of it to a crop. */
@media (max-width: 420px){
  .banner__inner{ padding-top: 1.2rem; }
}

/* ============================== GALLERY ================================= */
/* Folder index, then the frames inside one folder. Both fed from Drive. */

.gal__head{
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 2rem;
  flex-wrap: wrap;
}

.gal__grid{
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 250px), 1fr));
  gap: clamp(1.2rem, 2.4vw, 2.2rem);
  margin-top: clamp(2.5rem, 5vw, 3.5rem);
}

.gal__card{ display: block; }
.gal__cover{ background: var(--plate); }
.gal__cover img{ width: 100%; height: 100%; object-fit: cover; }

.gal__cardbody{
  margin-top: 0.9rem;
  padding-top: 0.7rem;
  border-top: 1px solid var(--rule);
  transition: border-color var(--swift) var(--ease);
}
.gal__card:hover .gal__cardbody{ border-color: var(--accent); }

.gal__cardtitle{
  font-size: clamp(1rem, 1.5vw, 1.25rem);
  font-weight: 700;
  letter-spacing: -0.02em;
}
.gal__cardmeta{
  margin-top: 0.4rem;
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}

/* Frames inside a folder: a masonry-ish flow that respects each shape. */
.gal__photos{
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 260px), 1fr));
  gap: clamp(0.7rem, 1.4vw, 1.1rem);
  margin-top: clamp(2rem, 4vw, 3rem);
}

/* A floor under the gallery grid, held from the very first paint.

   The photographs arrive from Drive a second or two after the page does, and
   the grid is empty until they do. Without a floor the footer paints just
   below the fold, then gets thrown eleven screens down the moment the frames
   land — a 0.9 layout shift, and the single worst thing on this page.

   It has to be here rather than set from gallery.js: JavaScript cannot get
   there before the first paint, and setting it afterwards *is* the shift.
   Once the real frames are in they are far taller than this and it stops
   mattering; gallery.js clears it only when what replaces the skeletons is
   shorter than they were — an empty folder, or an error. */
#galPhotos,
#galFolders{ min-height: 150vh; }
.gal__shot{
  position: relative;
  display: block;
  padding: 0;
  border: 0;
  overflow: hidden;
  cursor: zoom-in;
  background: var(--plate);
  aspect-ratio: var(--ar, 1.5);
  opacity: 0;
  transform: translateY(16px);
  transition: opacity .7s var(--ease), transform .7s var(--ease);
  transition-delay: calc(var(--d, 0) * 1ms);
}

/* The reveal is flipped on the grid, not on each plate.
   gallery.js used to add .in to every cell from its own requestAnimationFrame
   — several hundred callbacks, several hundred style recalculations, and
   several hundred simultaneous opacity+transform transitions for an animation
   that is only ever watched on the first screenful. One class on the parent
   does the same work in one pass. */
.gal__photos.is-in .gal__shot{ opacity: 1; transform: none; }

/* Plates past the first screenful skip the animation entirely. They are below
   the fold when the folder lands, so there is nothing to see — only layers to
   composite. This has to come after the rule above to win on order. */
.gal__shot--still{
  transition: none;
  transition-delay: 0s;
}
.gal__photos.is-in .gal__shot--still{ opacity: 1; transform: none; }
.gal__shot img{
  width: 100%; height: 100%;
  object-fit: cover;
  filter: var(--grade);
  transition: transform 1s var(--ease), filter .6s var(--ease);
}
.gal__shot:hover img,
.gal__shot:focus-visible img{ filter: var(--grade-off); transform: scale(1.04); }

.gal__back{
  display: inline-flex;
  align-items: center;
  gap: 0.7rem;
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: 1.6rem;
  transition: color var(--swift) var(--ease);
}
.gal__back:hover{ color: var(--accent); }

/* Loading placeholders — a quiet pulse, not a spinner. */
.gal__skeleton{
  aspect-ratio: 4 / 5;
  background: var(--surface-2);
  animation: breathe 1.6s var(--ease) infinite;
}
@keyframes breathe{ 0%,100%{ opacity: 0.5; } 50%{ opacity: 0.9; } }

/* Empty / error panel */
.gal__state{
  margin-top: clamp(2.5rem, 5vw, 3.5rem);
  padding: clamp(2rem, 5vw, 4rem);
  border: 1px solid var(--rule);
  text-align: center;
}
.gal__state.is-error{ border-color: color-mix(in srgb, var(--accent) 45%, var(--rule)); }
.gal__state h2{ font-size: clamp(1.2rem, 2vw, 1.6rem); }
.gal__state p{ margin: 1rem auto 0; max-width: 52ch; color: var(--ink-2); }

/* ============================== THE FEED ================================ */
/* Instagram, without Instagram's code. Copy on the left, the club's own
   frames on the right in a bento that keeps one plate dominant, so the
   block reads as a photograph first and a social link second. */

.feed{ border-top: 1px solid var(--rule); }

.feed__inner{
  display: grid;
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
  gap: clamp(2.4rem, 6vw, 5rem);
  align-items: center;
}

.feed__copy{ min-width: 0; }

/* The handle is the one thing here that must look pressable. */
.feed__handle{
  display: inline-flex;
  align-items: center;
  gap: 0.85rem;
  margin-top: 2rem;
  padding: 0.85rem 1.3rem;
  border: 1px solid var(--rule);
  border-radius: 999px;
  font-family: var(--mono);
  font-size: 0.92rem;
  letter-spacing: 0.02em;
  color: var(--ink);
  transition: border-color var(--swift) var(--ease),
              color var(--swift) var(--ease),
              transform var(--swift) var(--ease),
              background-color var(--swift) var(--ease);
}
.feed__handle:hover{
  border-color: var(--ink);
  background: color-mix(in srgb, var(--ink) 6%, transparent);
  transform: translateY(-2px);
}
.feed__handle:hover .feed__mark{ color: var(--accent); }
.feed__handle .arrow{ transition: transform var(--swift) var(--ease); }
.feed__handle:hover .arrow{ transform: translateX(4px); }

.feed__mark{
  display: grid;
  place-items: center;
  color: var(--ink-2);
  transition: color var(--swift) var(--ease);
}
.feed__at{ font-weight: 500; }

.feed__note{ margin-top: 1.4rem; color: var(--ink-3); }

/* Six frames: the first holds two columns and two rows, the rest fill in
   around it. Square cells, because that is how the feed itself reads. */
.feed__grid{
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-auto-rows: 1fr;
  gap: clamp(0.5rem, 1vw, 0.9rem);
}
.feed__tile{
  position: relative;
  display: block;
  overflow: hidden;
  aspect-ratio: 1;
  background: var(--plate);
}
.feed__tile:first-child{ grid-column: span 2; grid-row: span 2; }

.feed__tile img{
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: var(--grade);
  transition: transform var(--slow) var(--ease-out), filter var(--slow) var(--ease-out);
}
.feed__tile:hover img,
.feed__tile:focus-visible img{ transform: scale(1.05); filter: none; }

/* The glyph only appears on approach — the photograph is the point. */
.feed__veil{
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  color: #F4F1EF;
  background: color-mix(in srgb, var(--ground) 55%, transparent);
  opacity: 0;
  transition: opacity var(--swift) var(--ease);
}
.feed__tile:hover .feed__veil,
.feed__tile:focus-visible .feed__veil{ opacity: 1; }
.feed__veil svg{ transform: translateY(6px); transition: transform var(--swift) var(--ease-out); }
.feed__tile:hover .feed__veil svg,
.feed__tile:focus-visible .feed__veil svg{ transform: translateY(0); }/* The deck. Cards overlap and lean,
the way a handful of prints does when
   you put them down — the top one readable,
the rest promising more. Each
   card is an Instagram frame{
  --card-w: min(320px, 72%);
  --swing: 26px;
  position: relative;
  height: 560px;
  margin-left: clamp(0px, 2vw, 28px);
}/* A live card is ours: the photograph,
the date,
one line of the caption.
   Set in the site's type rather than Instagram's{ background: var(--surface); }

/* --------------------------- the feed cards ----------------------------- */
/* Drawn here rather than embedded, so they can be set in the site's own
   language: the photograph in monochrome until you approach it, a number in
   mono, one line of the caption, and the mark in the corner. The whole card
   is the link.

   instagram.posts.json holds the pictures and captions, collected by
   npm run instagram. */

.feed__deck{ position: relative; }

.feed__rail{
  display: flex;
  gap: clamp(0.7rem, 1.2vw, 1.1rem);
  overflow-x: auto;
  /* contain sideways so a swipe past the end does not trigger the
     browser's back gesture, but let vertical chain straight to the page:
     the pointer resting here must never trap a page scroll. */
  overscroll-behavior-inline: contain;
  overscroll-behavior-block: auto;
  /* Name both pans explicitly. Left to itself the container can claim a
     near-vertical drag as a horizontal one, which is what made scrolling
     past the cards feel like it was fighting back. */
  touch-action: pan-x pan-y pinch-zoom;
  /* Snap so a swipe comes to rest on one post rather than between two.
     One set is an exact whole number of card+gap steps, so the position
     the loop recentres to is itself a snap point and the two do not
     fight — see recentre() in script.js, which mutes snap for the
     instant of the jump so the browser does not animate it. */
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  padding-bottom: 2px;
}
.feed__rail::-webkit-scrollbar{ display: none; }
.feed__rail:focus-visible{ outline: 2px solid var(--accent); outline-offset: 6px; }

.igcard{
  position: relative;
  scroll-snap-align: start;
  flex: 0 0 min(260px, 66%);
  width: min(260px, 66%);
  display: block;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--rule);
  opacity: 0;
  transform: translateY(14px);
  transition: opacity var(--slow) var(--ease), transform var(--slow) var(--ease),
              border-color var(--swift) var(--ease);
  transition-delay: calc(var(--i, 0) * 60ms);
}
.igcard.in{ opacity: 1; transform: none; }
.igcard:hover{ border-color: var(--ink); }
.igcard:focus-visible{ outline: 2px solid var(--accent); outline-offset: 3px; }

.igcard__media{
  position: relative;
  display: block;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  background: var(--plate);
}
.igcard__media img{
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: var(--grade);
  transition: filter var(--slow) var(--ease-out), transform var(--slow) var(--ease-out);
}
.igcard:hover .igcard__media img,
.igcard:focus-visible .igcard__media img{ filter: none; transform: scale(1.04); }

/* The mark sits in the corner, and only colours on approach. */
.igcard__glyph{
  position: absolute;
  top: 0.7rem;
  right: 0.7rem;
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  color: #F4F1EF;
  background: color-mix(in srgb, var(--ground) 55%, transparent);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  transition: color var(--swift) var(--ease), background-color var(--swift) var(--ease);
}
.igcard:hover .igcard__glyph{ color: var(--accent); background: color-mix(in srgb, var(--ground) 72%, transparent); }

.igcard__body{
  display: grid;
  gap: 0.45rem;
  padding: 0.85rem 0.95rem 1rem;
  border-top: 1px solid var(--rule);
}
.igcard__no{
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: 0.2em;
  color: var(--accent);
}
.igcard__cap{
  font-size: 0.9rem;
  line-height: 1.45;
  color: var(--ink-2);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  min-height: 2.6em;
}

/* ---- the arrows ---- */
.feed__arrows{
  display: flex;
  gap: 0.6rem;
  margin-top: clamp(1.1rem, 2vw, 1.5rem);
}
.feed__arrow{
  width: 46px;
  height: 46px;
  display: grid;
  place-items: center;
  border: 1px solid var(--rule);
  background: none;
  color: var(--ink-2);
  cursor: pointer;
  transition: color var(--swift) var(--ease), border-color var(--swift) var(--ease),
              background-color var(--swift) var(--ease), opacity var(--swift) var(--ease);
}
.feed__arrow:hover:not(:disabled){
  color: var(--ground);
  background: var(--ink);
  border-color: var(--ink);
}
.feed__arrow:focus-visible{ outline: 2px solid var(--accent); outline-offset: 2px; }
.feed__arrow:disabled{ opacity: 0.28; cursor: default; }

@media (max-width: 1000px){
  .igcard{ flex-basis: min(240px, 62%); width: min(240px, 62%); }
}
@media (prefers-reduced-motion: reduce){
  .igcard{ transition: none; }
  .igcard:hover .igcard__media img{ transform: none; }
}

/* Below this the copy and the frames stop competing for width and
   simply stack, copy first. */
@media (max-width: 900px){
  .feed__inner{
    grid-template-columns: minmax(0, 1fr);
    gap: clamp(2rem, 6vw, 2.8rem);
    align-items: start;
  }
  .feed__copy{ max-width: 60ch; }
  .feed__handle{ margin-top: 1.6rem; }
  .feed__note{ margin-top: 1.1rem; }
  .igcard{ flex-basis: min(240px, 58%); width: min(240px, 58%); }
}

@media (max-width: 560px){
  .feed__grid{
    grid-template-columns: repeat(2, 1fr);
    gap: 0.5rem;
  }
  /* The hero tile keeps its weight but no longer eats the whole viewport. */
  .feed__tile:first-child{ grid-column: span 2; grid-row: span 2; }
  .feed__handle{
    width: 100%;
    justify-content: center;
    padding: 0.8rem 1rem;
    font-size: 0.86rem;
  }
  .igcard{ flex-basis: 78%; width: 78%; }
  .feed__arrow{ width: 42px; height: 42px; }
}

@media (max-width: 400px){
  /* Four square tiles read better than a lopsided bento this narrow. */
  .feed__tile:first-child{ grid-column: span 2; grid-row: auto; aspect-ratio: 16 / 10; }
}

@media (prefers-reduced-motion: reduce){
  .feed__tile:hover img{ transform: none; }
  .feed__veil svg{ transform: none; }
}

/* ============================ 12  LIGHTBOX ============================== */
/* lightGallery, dressed in the site's own tokens. The vendor stylesheet is
   loaded first (vendor/lightgallery.css); everything here overrides it, so
   the viewer reads as part of the site rather than a bolted-on library.
   Scoped to .lb-lg, the addClass the viewer is opened with.              */

.lb-lg .lg-backdrop{
  background: color-mix(in srgb, var(--ground) 96%, transparent);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
.lb-lg.lg-container{ font-family: var(--mono); }

/* Chrome: flat, no vendor gradients, icons on the site's ink. */
.lb-lg .lg-toolbar,
.lb-lg .lg-sub-html,
.lb-lg .lg-thumb-outer{ background: transparent; }
/* The counter is type and takes the site's label treatment. The controls
   are an icon font, so they take a size you can actually read and a hit
   area you can actually land on — mono metrics and letter-spacing would
   only shove the glyphs off centre. */
.lb-lg .lg-counter{
  color: var(--ink);
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: 0.16em;
  text-shadow: none;
}
.lb-lg .lg-toolbar .lg-icon,
.lb-lg .lg-actions .lg-icon{
  color: var(--ink);
  font-size: 20px;
  line-height: 1;
  letter-spacing: 0;
  text-shadow: none;
  width: 46px;
  height: 46px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.lb-lg .lg-toolbar .lg-icon:hover,
.lb-lg .lg-actions .lg-icon:hover{ color: var(--accent); }

/* Previous / next carry the plate between frames, so they sit a step above
   the toolbar icons. */
.lb-lg .lg-actions .lg-next,
.lb-lg .lg-actions .lg-prev{
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: 0;
  font-size: 24px;
  width: 54px;
  height: 54px;
}
.lb-lg .lg-actions .lg-next:hover,
.lb-lg .lg-actions .lg-prev:hover{ border-color: var(--ink); }

/* Toolbar as a row rather than a stack of floats, so the taller icons stay
   on one baseline. The counter keeps its place at the left. */
.lb-lg .lg-toolbar{
  display: flex;
  align-items: center;
  gap: 0.25rem;
  padding-right: 0.5rem;
}
.lb-lg .lg-toolbar .lg-icon{ float: none; }
.lb-lg .lg-counter{
  margin-right: auto;
  padding: 0 0 0 clamp(1rem, 3vw, 1.6rem);
  height: auto;
}

@media (max-width: 640px){
  .lb-lg .lg-toolbar .lg-icon,
  .lb-lg .lg-actions .lg-icon{ width: 44px; height: 44px; font-size: 19px; }
  .lb-lg .lg-actions .lg-next,
  .lb-lg .lg-actions .lg-prev{ width: 48px; height: 48px; font-size: 22px; }
}

/* Caption: the heading the viewer was opened with, then the frame's own. */
.lb-lg .lg-sub-html{ padding: 1rem clamp(1rem, 3vw, 2rem) 1.4rem; }
.lb-cap{ display: flex; flex-direction: column; gap: 0.35rem; text-align: left; }
.lb-cap__title{
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.lb-cap__text{ color: var(--ink-2); }

/* Thumbnail strip. */
.lb-lg .lg-thumb-item{ border: 1px solid transparent; border-radius: 0; }
.lb-lg .lg-thumb-item.active,
.lb-lg .lg-thumb-item:hover{ border-color: var(--ink); }

/* Slideshow: the vendor progress bar is a bright accent strip by default. */
.lb-lg .lg-progress-bar{ background: var(--rule); height: 2px; }
.lb-lg .lg-progress-bar .lg-progress{ background: var(--accent); }

/* Share menu. Ships as a white rounded card with a pointer arrow — squared
   off and put on the site's surface colour instead.

   Written against .lb-lg .lg-outer (three classes deep, `.lb-lg` sits on the container above `.lg-outer`) on purpose: the vendor
   stylesheet is appended after this one and styles the same nodes as
   `.lg-outer .lg-dropdown`, which ties on specificity and wins on order —
   that left a white card carrying this file's pale text. */
.lb-lg .lg-outer .lg-dropdown{
  background: var(--surface-2);
  border: 1px solid var(--rule);
  border-radius: 0;
  box-shadow: 0 12px 32px rgba(0,0,0,0.5);
  padding: 6px 0;
  min-width: 11rem;
}
.lb-lg .lg-outer .lg-dropdown:after{ display: none; }
.lb-lg .lg-outer .lg-dropdown > li > a{
  display: flex;
  align-items: center;
  padding: 0.55rem 0.9rem;
  color: var(--ink);
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  cursor: pointer;
}
.lb-lg .lg-outer .lg-dropdown > li:hover > a{ color: var(--ink); background: var(--surface); }
.lb-lg .lg-outer .lg-dropdown > li:last-child{ margin-bottom: 0; }
.lb-lg .lg-outer .lg-dropdown .lg-icon{
  color: var(--ink);
  width: auto;
  height: auto;
  margin-right: 0.7rem;
  font-size: 18px;
}
.lb-lg .lg-outer .lg-dropdown .lg-dropdown-text{ color: var(--ink); margin-top: 0; }

/* Instagram has no icon in lightGallery's font, so it is drawn as a mask. */
.lb-lg .lg-outer .lg-dropdown .lg-share-instagram .lg-icon:after{
  content: "";
  display: block;
  width: 1em;
  height: 1em;
  background: currentColor;
  -webkit-mask: var(--ig-mark) center / contain no-repeat;
  mask: var(--ig-mark) center / contain no-repeat;
}
.lb-lg .lg-outer{
  --ig-mark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='3' width='18' height='18' rx='5'/%3E%3Ccircle cx='12' cy='12' r='4'/%3E%3Ccircle cx='17.5' cy='6.5' r='1' fill='black'/%3E%3C/svg%3E");
}

/* Dot pager, only ever shown for small sets. */
.lb-lg .lg-pager{ background: var(--rule); box-shadow: none; }
.lb-lg .lg-pager-cont.lg-pager-active .lg-pager,
.lb-lg .lg-pager:hover{ background: var(--ink); }
.lb-lg .lg-pager-thumb-cont{ border-radius: 0; }

/* The host node only exists to give dynamic mode something to attach to. */
.lb-host{ display: none; }

/* ====================== 15  ENTRY GATE — THE OPTICS ===================== */
/* A six-blade aperture over the page, and the hero racking into focus
   behind it. Additive only: nothing above this block is altered.
   The gate is `hidden` in the markup and unhidden by an inline script,
   so a JS failure can never leave the page covered.                      */

html.is-gated,
html.is-gated body{ overflow: hidden; }

/* Out of focus, and a little past the frame, until the lens finds it. */
html.is-gated .hero__slides{
  filter: blur(30px);
  transform: scale(1.14);
  will-change: filter, transform;
}
html.is-gated .hero__body > *,
html.is-gated .hero__foot{ opacity: 0; }

.gate{
  position: fixed;
  inset: 0;
  z-index: 200;
}

.gate__blades{ position: absolute; inset: 0; overflow: hidden; }

/* Each blade is a half-plane hinged on the centre of the viewport; six of
   them at 60° cover it completely, then swing out along their own axis. */
.gate__blade{
  position: absolute;
  left: 50%; top: 50%;
  width: 400vmax; height: 400vmax;
  margin-left: -200vmax;
  background: var(--ground);
  transform-origin: 50% 0;
  will-change: transform;
}
.gate__blade:nth-child(1){ transform: rotate(0deg); }
.gate__blade:nth-child(2){ transform: rotate(60deg); }
.gate__blade:nth-child(3){ transform: rotate(120deg); }
.gate__blade:nth-child(4){ transform: rotate(180deg); }
.gate__blade:nth-child(5){ transform: rotate(240deg); }
.gate__blade:nth-child(6){ transform: rotate(300deg); }

/* Centre focus bracket: the lens hunting, then locking, before it opens. */
.gate__af{
  position: absolute;
  left: 50%; top: 50%;
  width: clamp(88px, 12vw, 148px);
  aspect-ratio: 1;
  transform: translate(-50%, -50%) scale(1.28);
  animation: hunt 1.9s var(--ease) infinite;
}
.gate__af i{
  position: absolute;
  width: 17px; height: 17px;
  border: 2px solid var(--ink-3);
  transition: border-color .35s var(--ease);
}
.gate__af i:nth-child(1){ top: 0; left: 0; border-right: 0; border-bottom: 0; }
.gate__af i:nth-child(2){ top: 0; right: 0; border-left: 0; border-bottom: 0; }
.gate__af i:nth-child(3){ bottom: 0; left: 0; border-right: 0; border-top: 0; }
.gate__af i:nth-child(4){ bottom: 0; right: 0; border-left: 0; border-top: 0; }

@keyframes hunt{
  0%, 100%{ transform: translate(-50%, -50%) scale(1.28); }
  50%     { transform: translate(-50%, -50%) scale(1.13); }
}

.gate__af.is-locked{
  animation: none;
  transform: translate(-50%, -50%) scale(1);
  transition: transform .45s var(--ease-out);
}
.gate__af.is-locked i{ border-color: var(--accent); }

.gate__panel{
  position: absolute;
  left: var(--margin); right: var(--margin);
  bottom: clamp(1.5rem, 4vh, 2.8rem);
  will-change: opacity;
}
.gate__rule{
  position: relative;
  height: 1px;
  background: var(--ink-4);
  overflow: hidden;
  margin-bottom: clamp(1rem, 2vw, 1.7rem);
}
.gate__rule i{
  position: absolute; inset: 0;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
}
.gate__ui{
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 2rem;
}
.gate__count{
  margin: 0;
  font-family: var(--mono);
  font-weight: 500;
  font-size: clamp(2.2rem, 6.5vw, 4.6rem);
  line-height: 0.86;
  letter-spacing: -0.045em;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.gate__count sup{
  font-size: 0.26em;
  letter-spacing: 0.16em;
  color: var(--ink-3);
  margin-left: 0.5em;
  vertical-align: super;
}
.gate__id{ text-align: right; display: grid; gap: 0.35rem; }

@media (max-width: 620px){
  .gate__id{ display: none; }
}

/* Should anyone reach this with reduced motion on, there is no gate. */
@media (prefers-reduced-motion: reduce){
  .gate{ display: none !important; }
  html.is-gated,
  html.is-gated body{ overflow: auto; }
  html.is-gated .hero__slides{ filter: none; transform: none; }
  html.is-gated .hero__body > *,
  html.is-gated .hero__foot{ opacity: 1; }
}

/* ------------------------- practice, as type ---------------------------- */
/* Without photographs behind them the tiles cannot rely on a gradient for
   contrast, so they become ruled cards: a number, a name, a line of what it
   actually involves. Four equal columns that fold to two, then one. */

.mosaic--text{
  grid-template-columns: repeat(4, minmax(0, 1fr));
  grid-auto-rows: auto;
}
.mosaic--text .tile{
  grid-column: span 1 !important;
  grid-row: auto !important;
  border-top: 1px solid var(--rule);
  padding-top: clamp(1.2rem, 2vw, 1.8rem);
  transition: border-color var(--swift) var(--ease);
}
.mosaic--text .tile:hover{ border-color: var(--ink); }

.mosaic--text .tile__body{
  position: static;
  padding: 0;
  background: none;
  color: inherit;
}
.mosaic--text .tile__idx{
  display: block;
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: 0.2em;
  color: var(--accent);
}
.mosaic--text .tile__title{
  margin: 0.9rem 0 0;
  font-size: clamp(1.15rem, 1.8vw, 1.5rem);
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--ink);
}
.mosaic--text .tile__text{
  margin: 0.7rem 0 0;
  color: var(--ink-2);
  font-size: 0.95rem;
  line-height: 1.6;
  opacity: 1;
  transform: none;
}

@media (max-width: 900px){
  .mosaic--text{ grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 560px){
  .mosaic--text{ grid-template-columns: minmax(0, 1fr); }
}

/* =============================== VIDEO ================================== */
/* A poster frame we host, with the player swapped in on click. Nothing of
   YouTube's — no script, no frame, no cookie — reaches a visitor who never
   presses play, and the page does not carry a megabyte of player it may
   never use. */

.video{
  position: relative;
  width: 100%;
  height: 100%;
  background: var(--plate);
  overflow: hidden;
}
.video__poster{
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: var(--grade);
  transition: filter var(--slow) var(--ease-out), transform var(--slow) var(--ease-out);
}
.video:hover .video__poster{ filter: none; transform: scale(1.02); }

.video__play{
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  width: 100%;
  border: 0;
  padding: 0;
  background: linear-gradient(to top, rgba(6,5,5,0.55), rgba(6,5,5,0.12) 55%, rgba(6,5,5,0.3));
  color: #F4F1EF;
  cursor: pointer;
  transition: background var(--swift) var(--ease);
}
.video__play:hover{ background: linear-gradient(to top, rgba(6,5,5,0.42), rgba(6,5,5,0.06) 55%, rgba(6,5,5,0.22)); }
.video__play:focus-visible{ outline: 2px solid var(--accent); outline-offset: -6px; }

.video__disc{
  display: grid;
  place-items: center;
  width: clamp(58px, 7vw, 78px);
  aspect-ratio: 1;
  border: 1px solid rgba(244,241,239,0.5);
  border-radius: 50%;
  background: color-mix(in srgb, var(--ground) 42%, transparent);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  padding-left: 4px;                 /* optical centring of the triangle */
  transition: transform var(--swift) var(--ease), border-color var(--swift) var(--ease),
              background-color var(--swift) var(--ease), color var(--swift) var(--ease);
}
.video__play:hover .video__disc{
  transform: scale(1.06);
  border-color: var(--accent);
  color: var(--accent);
}

.video__label{
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: 0.2em;
  text-transform: uppercase;
}

/* Once play is pressed the iframe fills the same box. */
.video iframe{
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}
.video.is-playing .video__poster,
.video.is-playing .video__play{ display: none; }

@media (prefers-reduced-motion: reduce){
  .video:hover .video__poster{ transform: none; }
  .video__play:hover .video__disc{ transform: none; }
}

/* ================================ FAQ =================================== */
/* Native <details>, so it opens without JavaScript and a crawler reads the
   answers whether or not it runs any. */

.faq__list{
  margin-top: clamp(2.5rem, 5vw, 3.5rem);
  border-top: 1px solid var(--rule);
}
.faq__item{ border-bottom: 1px solid var(--rule); }

.faq__q{
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2rem;
  padding-block: clamp(1.1rem, 2.6vw, 1.6rem);
  cursor: pointer;
  list-style: none;
  font-size: clamp(1rem, 1.5vw, 1.18rem);
  font-weight: 500;
  letter-spacing: -0.01em;
  color: var(--ink-2);
  transition: color var(--swift) var(--ease);
}
.faq__q::-webkit-details-marker{ display: none; }
.faq__item:hover .faq__q,
.faq__item[open] .faq__q{ color: var(--ink); }
.faq__q:focus-visible{ outline: 2px solid var(--accent); outline-offset: 4px; }

/* A plus that becomes a minus. Two rules, no icon font. */
.faq__sign{
  position: relative;
  flex: 0 0 14px;
  width: 14px;
  height: 14px;
}
.faq__sign::before,
.faq__sign::after{
  content: "";
  position: absolute;
  inset: 50% 0 auto 0;
  height: 1.5px;
  background: currentColor;
  transition: transform var(--swift) var(--ease);
}
.faq__sign::after{ transform: rotate(90deg); }
.faq__item[open] .faq__sign::after{ transform: rotate(0deg); }

.faq__a{
  padding-bottom: clamp(1.2rem, 2.6vw, 1.8rem);
  max-width: 68ch;
}
.faq__a p{
  margin: 0;
  color: var(--ink-2);
  line-height: 1.65;
}
.faq__a a{ color: var(--ink); border-bottom: 1px solid var(--rule-strong); }
.faq__a a:hover{ color: var(--accent); border-color: var(--accent); }

@media (max-width: 620px){
  .faq__q{ gap: 1.2rem; align-items: flex-start; }
  .faq__sign{ margin-top: 0.35rem; }
}

/* ============================ 13b  PHONES =============================== */
/* Everything above is written mobile-tolerant; this is the pass that makes
   a 390px screen feel deliberate rather than survived. Kept together and
   last so it is one place to look when something reads badly on a phone. */

@media (max-width: 620px){

  /* An occasion is four columns of type on a desktop — index, name, year,
     count. At this width that squeezes "Freshers Talent Show" into two
     characters a line. Two rows instead: the name gets the width it needs,
     the numbers sit under it. */
  .occ__link{
    grid-template-columns: 2.4rem minmax(0, 1fr);
    row-gap: 0.4rem;
    column-gap: 1rem;
    align-items: baseline;
  }
  .occ__name{ grid-column: 2; }
  .occ__year{ grid-column: 2; grid-row: 2; }
  .occ__count,
  .occ__go{ grid-column: 2; grid-row: 2; justify-self: end; }
  .occ__link .idx{ grid-row: 1 / span 2; align-self: start; }

  /* The folder heading and its tools stop competing for one line. */
  .gal__head{ align-items: flex-start; gap: 1.2rem; }
  .gal__headtools{ width: 100%; justify-content: space-between; }
}

@media (max-width: 480px){

  /* Thumb targets. Anything tapped should be at least 44px through — the
     icon buttons and view switches were sized for a cursor. */
  .icon-btn{ min-width: 44px; min-height: 44px; }
  .viewbar__btn{ min-height: 44px; padding-inline: 1rem; }
  .gal__back{ min-height: 44px; display: inline-flex; align-items: center; }

  /* Long unbroken things — a filename, a URL in a caption — must not push
     the page sideways. */
  .gal__name,
  .banner__note,
  .body{ overflow-wrap: anywhere; }

  /* The deck's cards are sized for a desktop hover; on a phone they are a
     swipe row, so they take the width rather than a fixed measure. */
  .igcard{ flex-basis: 82%; width: 82%; }
}

/* Landscape on a phone is short, not narrow: a full-height hero leaves
   nothing on screen but the hero. */
@media (max-height: 520px) and (orientation: landscape){
  .hero{ min-height: 0; padding-block: calc(var(--bar-h) + 1rem) 3rem; }
}

/* ========================= 14  REDUCED MOTION =========================== */

@media (prefers-reduced-motion: reduce){
  html{ scroll-behavior: auto; }

  *,
  *::before,
  *::after{
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }

  [data-reveal]{ opacity: 1 !important; transform: none !important; }
  [data-reveal="mask"],
  [data-reveal="mask"] > *{ clip-path: none !important; }
  [data-reveal="mask"] img{ transform: none !important; }
  .line > span{ transform: none !important; }
  .draw{ transform: scaleX(1) !important; }
  .tile__text{ opacity: 1; transform: none; }
}

/* ======================================================================== */
/* ================= 16  PERSON CARDS (PROFILES FROZEN) =================== */
/* Member pages are on hold, so a portrait is no longer a link and the panel
   is no longer a preview of somewhere to go. It carries the name, set bold
   on the gradient, which is the one thing worth reading over a face. */

.person__frame{ display: block; }

.person__panelname{
  margin: 0;
  /* The name is the thing worth reading on a face, so it is set larger
     and heavier than the role beneath the card and carries a deeper
     shadow — a portrait can be bright anywhere behind it. */
  font-size: clamp(1.3rem, 2.15vw, 1.72rem);
  font-weight: 800;
  letter-spacing: -0.022em;
  line-height: 1.14;
  color: #FFFFFF;
  text-shadow: 0 1px 2px rgba(0,0,0,0.55), 0 2px 18px rgba(0,0,0,0.62);
}
.person__panelname.is-placeholder{ opacity: 0.6; font-weight: 500; letter-spacing: -0.01em; }

/* The handle. A real one is a link out; an empty one is a marked slot, so
   it is obvious which people still need theirs. */
.person__ig{
  display: inline-block;
  margin-top: 0.4rem;
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: 0.14em;
  color: rgba(244,241,239,0.82);
  transition: color var(--swift) var(--ease);
}
a.person__ig:hover{ color: var(--accent); }
.person__ig.is-placeholder{ color: rgba(244,241,239,0.42); font-style: italic; letter-spacing: 0.08em; }

/* No hover on touch, so the name simply sits on the photograph. */
.no-hover .person__panel{
  opacity: 1;
  transform: none;
  pointer-events: none;
}

/* ========================= 17  MEMBER PAGE ============================== */

.member__grid{
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: start;
  margin-top: clamp(2rem, 4vw, 3.2rem);
}
@media (max-width: 900px){
  .member__grid{ grid-template-columns: minmax(0, 1fr); gap: 2.2rem; }
}

.member__shot{ position: relative; }
.member__shot .media{
  position: relative;
  width: 100%;
  padding: 0;
  border: 0;
  background: var(--plate);
  cursor: zoom-in;
  overflow: hidden;
}
.member__shot img{ width: 100%; height: 100%; object-fit: cover; }

.member__name{ margin-top: 0.6rem; }
.member__name.is-placeholder{ opacity: 0.5; }

.member__bio{ margin-top: clamp(1.4rem, 2.5vw, 2rem); max-width: 56ch; }
.member__bio .body{ text-align: justify; text-justify: inter-word; }
.member__bio .body + .body{ margin-top: 1rem; }

.member__facts{ margin-top: clamp(1.6rem, 3vw, 2.4rem); }

.member__gear{ margin-top: clamp(1.6rem, 3vw, 2.2rem); }
.member__gearlist{
  margin-top: 0.8rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  list-style: none;
  padding: 0;
}
.member__gearlist li{
  padding: 0.4rem 0.8rem;
  border: 1px solid var(--rule);
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-2);
}

.member__links{
  margin-top: clamp(1.8rem, 3vw, 2.4rem);
  display: flex;
  flex-wrap: wrap;
  gap: 0.8rem;
}

.member__work{ margin-top: clamp(3.5rem, 7vw, 6rem); }

.member__nav{
  margin-top: clamp(3.5rem, 7vw, 6rem);
  padding-top: 1.6rem;
  border-top: 1px solid var(--rule);
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1.5rem;
}
.mnav{
  display: block;
  padding: 1.2rem 0;
  transition: color var(--swift) var(--ease);
}
.mnav--next{ text-align: right; }
.mnav__label{ display: block; }
.mnav__name{
  display: block;
  margin-top: 0.4rem;
  font-size: clamp(1.05rem, 1.8vw, 1.4rem);
  font-weight: 700;
  letter-spacing: -0.02em;
}
.mnav__role{ display: block; margin-top: 0.2rem; }
.mnav:hover .mnav__name{ color: var(--accent); }

/* ===================== 18  GALLERY — VIEW MODES ========================= */

.gal__headtools{
  display: flex;
  align-items: center;
  gap: clamp(1rem, 2vw, 1.8rem);
  flex-wrap: wrap;
}

.viewbar{
  display: inline-flex;
  border: 1px solid var(--rule);
  /* An opaque background inside an isolated group is what makes the buttons'
     difference blend deterministic: white against --ground resolves to near
     black in the light theme and stays near white in the dark one. Without
     it the blend has nothing to invert and the labels disappear. */
  background: var(--ground);
  isolation: isolate;
}
.viewbar__btn{
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.55rem 0.9rem;
  border: 0;
  background: none;
  cursor: pointer;
  color: var(--ink-3);
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  transition: color var(--swift) var(--ease), background-color var(--swift) var(--ease);
}
.viewbar__btn + .viewbar__btn{ border-left: 1px solid var(--rule); }
/* Same difference blend as the top-bar labels, against the opaque --ground
   set on .viewbar above. Full opacity throughout — see .bar__nav a. */
.viewbar__btn{ color: #C8C8C8; mix-blend-mode: difference; }
.viewbar__btn:hover{ color: #fff; }
/* The selected button opts out of the blend. It paints its own --ink panel,
   and a blended element inverts its own background along with its text: in
   the dark theme that turned the panel light grey while the label stayed
   near white. Solid colours here, blend only on the unselected labels. */
.viewbar__btn.is-on{
  mix-blend-mode: normal;
  color: var(--ground);
  background: var(--ink);
}
.viewbar__btn:focus-visible{ outline: 2px solid var(--accent); outline-offset: -2px; }
@media (max-width: 560px){
  .viewbar__btn span{ display: none; }
  .viewbar__btn{ padding: 0.6rem 0.8rem; }
}

/* The thumbnail wrapper is shared by all three views; only its box changes. */
.gal__thumb{
  display: block;
  position: relative;
  overflow: hidden;
  background: var(--plate);
}
.gal__thumb img{ width: 100%; height: 100%; object-fit: cover; display: block; }

/* A frame the CDN would not give us, after the retry ladder in gallery.js
   has run out. An empty plate with a hairline reads as intentional; a
   broken-image glyph does not. */
.gal__thumb.is-missing,
.gal__cover.is-missing{
  background: var(--plate);
  box-shadow: inset 0 0 0 1px var(--rule);
}
.gal__thumb.is-missing::after,
.gal__cover.is-missing::after{
  content: "";
  position: absolute;
  inset: 50% auto auto 50%;
  width: 18px;
  height: 1px;
  transform: translate(-50%, -50%);
  background: var(--ink-3);
  opacity: 0.5;
}
.gal__cover.is-missing{ position: relative; }

.gal__info{
  display: none;
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: 0.12em;
  color: var(--ink-3);
}
.gal__idx{ color: var(--ink-3); }
.gal__name{ color: var(--ink); text-transform: none; letter-spacing: 0.02em; }

/* ---------------------------- bento ------------------------------------ */
/* A dense mosaic: portraits run tall, panoramas run wide, and every seventh
   frame takes a double square so the wall has somewhere to rest.          */

/* Columns rather than a grid of fixed cells. The old version put every
   frame into a ~190px square and cropped whatever did not fit, which
   flattened panoramas and cut the top off portraits — the two things this
   archive is mostly made of. Here each plate keeps the shape it was shot
   in, and the column simply grows. */
.gal__photos--bento{
  display: block;
  columns: 4;
  column-gap: clamp(0.5rem, 1vw, 0.9rem);
}
.gal__photos--bento .gal__shot{
  /* A plate must never be split down the middle across a column break. */
  break-inside: avoid;
  -webkit-column-break-inside: avoid;
  page-break-inside: avoid;
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: auto;
  margin: 0 0 clamp(0.5rem, 1vw, 0.9rem);
}
/* --ar is set per cell in gallery.js from the frame's own dimensions, so the
   plate reserves the right height before the photograph arrives and nothing
   below it jumps when it does. */
.gal__photos--bento .gal__thumb{
  height: auto;
  aspect-ratio: var(--ar, 1.5);
}
.gal__photos--bento .gal__shot img{
  width: 100%;
  height: 100%;
  object-fit: cover;
}

@media (max-width: 1100px){
  .gal__photos--bento{ columns: 3; }
}
@media (max-width: 760px){
  .gal__photos--bento{ columns: 2; }
}
@media (max-width: 420px){
  .gal__photos--bento{ columns: 1; }
}

/* ----------------------------- list ------------------------------------ */

.gal__photos--list{
  display: block;
  border-top: 1px solid var(--rule);
}
.gal__photos--list .gal__shot{
  display: grid;
  grid-template-columns: 74px minmax(0, 1fr) auto;
  align-items: center;
  gap: clamp(1rem, 2.5vw, 2rem);
  width: 100%;
  aspect-ratio: auto;
  padding: 0.7rem 0;
  border-bottom: 1px solid var(--rule);
  background: none;
  text-align: left;
  transition: background-color var(--swift) var(--ease);
}
.gal__photos--list .gal__shot:hover{ background: var(--surface-2); }
.gal__photos--list .gal__thumb{ aspect-ratio: 3 / 2; }
.gal__photos--list .gal__shot img{ transform: none; }
.gal__photos--list .gal__shot:hover img{ transform: scale(1.04); }
.gal__photos--list .gal__info{
  display: contents;
}
.gal__photos--list .gal__name{
  font-size: 0.95rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.gal__photos--list .gal__idx{ display: none; }
.gal__photos--list .gal__dims{ padding-right: 0.4rem; }
@media (max-width: 560px){
  .gal__photos--list .gal__shot{ grid-template-columns: 56px minmax(0, 1fr); }
  .gal__photos--list .gal__dims{ display: none; }
}

/* ---------------------------- detailed --------------------------------- */
/* One plate per row, at size, with its caption set underneath — the way a
   contact sheet becomes a portfolio.                                      */

.gal__photos--detail{
  display: block;
  max-width: 62rem;
  margin-inline: auto;
}
.gal__photos--detail .gal__shot{
  display: block;
  width: 100%;
  aspect-ratio: auto;
  margin-bottom: clamp(3rem, 6vw, 5rem);
  background: none;
}
.gal__photos--detail .gal__thumb{ aspect-ratio: var(--ar, 1.5); }
.gal__photos--detail .gal__info{
  display: flex;
  align-items: baseline;
  gap: 1rem;
  margin-top: 0.9rem;
  padding-top: 0.7rem;
  border-top: 1px solid var(--rule);
}
.gal__photos--detail .gal__name{ flex: 1 1 auto; font-size: 1rem; }

/* ======================== SCREENSHOT / SAVE NOTICE ===================== */
/* Raised by guardPhotos() in script.js. Worth repeating here: this notice
   cannot actually stop a screenshot — nothing on the web can. It asks. */

.shot-note{
  position: fixed;
  left: 50%;
  bottom: clamp(1.2rem, 4vw, 2.4rem);
  z-index: 120;
  display: grid;
  gap: 0.3rem;
  width: min(30rem, calc(100vw - 2.4rem));
  padding: 0.95rem 1.2rem;
  background: var(--ink);
  color: var(--ground);
  border-radius: 2px;
  box-shadow: 0 14px 40px rgb(0 0 0 / 28%);
  opacity: 0;
  transform: translate(-50%, 12px);
  pointer-events: none;
  transition: opacity var(--swift) var(--ease), transform var(--swift) var(--ease);
}
.shot-note.in{ opacity: 1; transform: translate(-50%, 0); }
.shot-note strong{
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  font-weight: 500;
}
.shot-note span{ font-size: 0.9rem; line-height: 1.45; opacity: 0.82; }

@media (prefers-reduced-motion: reduce){
  .shot-note{ transition: none; }
}

/* The three-word motto that closes the About lead. Set apart from the body
   copy it follows — heavier, tracked out, accent-coloured — so it reads as
   a signature rather than another sentence. */
.about__motto{
  margin-top: 1.6rem;
  font-family: var(--mono);
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--accent);
}

