/* ---------------------------------------------------------------------------
   Design tokens.

   Theme is locked dark. Not a preference: photographs are viewed on dark
   surfaces, and dark chrome lets the images supply all the brightness.

   The palette is deliberately ACHROMATIC. Sports photos are full of team
   colors, so any accent hue would clash with roughly half the galleries.
   White is the action color; there is no second hue anywhere on the site.

   Radius system, two values, applied everywhere:
     photos and image tiles -> 0 (rounded photo corners read as consumer app)
     interactive chrome      -> var(--r)
--------------------------------------------------------------------------- */
:root {
  color-scheme: dark;

  --bg: #0b0b0c;
  --bg-1: #141416;
  --bg-2: #1c1c1f;
  --line: #26262a;
  --line-2: #35353b;

  --ink: #fafafa;
  --ink-2: #a4a4ad;
  --ink-3: #6e6e78;

  --r: 8px;
  --gutter: 4px;
  --nav-h: 64px;

  /* iOS sheet curve. Decelerates hard at the end, which is what makes a
     transition feel like it settled rather than stopped. */
  --ease: cubic-bezier(0.32, 0.72, 0, 1);
  --fast: 180ms;
  --slow: 420ms;

  --sans: ui-sans-serif, -apple-system, "SF Pro Text", "Segoe UI Variable",
    "Segoe UI", Roboto, system-ui, sans-serif;
  --mono: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, monospace;

  --page: 1440px;
}

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; }

:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
  border-radius: 2px;
}

.wrap { max-width: var(--page); margin-inline: auto; padding-inline: clamp(16px, 4vw, 40px); }

/* Type -------------------------------------------------------------------- */
/* Tracking tightens as size grows. Optical sizing by hand, since the system
   face gives us the metrics but not the automatic compensation. */
.display {
  font-size: clamp(2.5rem, 1.1rem + 5.4vw, 5.25rem);
  line-height: 1.02;
  letter-spacing: -0.038em;
  font-weight: 600;
  margin: 0;
  text-wrap: balance;
}
.h2 {
  font-size: clamp(1.5rem, 1rem + 1.8vw, 2.25rem);
  line-height: 1.1;
  letter-spacing: -0.028em;
  font-weight: 600;
  margin: 0;
}
.h3 { font-size: 1.0625rem; font-weight: 600; letter-spacing: -0.014em; margin: 0; }
.lede { color: var(--ink-2); font-size: 1.0625rem; max-width: 46ch; margin: 0; }
.meta { color: var(--ink-3); font-size: 0.8125rem; letter-spacing: 0.005em; }
.num { font-variant-numeric: tabular-nums; }

/* Controls ---------------------------------------------------------------- */
.btn {
  display: inline-flex; align-items: center; gap: 8px;
  white-space: nowrap;                    /* CTA labels never wrap */
  padding: 11px 20px;
  border-radius: var(--r);
  border: 1px solid transparent;
  background: var(--ink); color: #0b0b0c;
  font-size: 0.9375rem; font-weight: 550; letter-spacing: -0.01em;
  cursor: pointer;
  transition: transform var(--fast) var(--ease), background var(--fast) var(--ease);
}
.btn:hover { background: #fff; }
.btn:active { transform: translateY(1px) scale(0.985); }

.btn-ghost {
  background: transparent; color: var(--ink);
  border-color: var(--line-2);
}
.btn-ghost:hover { background: var(--bg-2); border-color: var(--ink-3); }

.btn-sm { padding: 7px 13px; font-size: 0.8125rem; }

input, select, textarea {
  font: inherit;
  width: 100%;
  padding: 10px 12px;
  background: var(--bg-1);
  color: var(--ink);
  border: 1px solid var(--line-2);
  border-radius: var(--r);
}
input::placeholder { color: var(--ink-3); }   /* passes AA on --bg-1 */
input:focus-visible, select:focus-visible, textarea:focus-visible {
  outline: 2px solid var(--ink); outline-offset: 1px; border-color: transparent;
}
label { display: grid; gap: 6px; font-size: 0.8125rem; color: var(--ink-2); }

/* Gallery index. First tile spans wide, so the grid has rhythm rather than
   a run of identical cards. -------------------------------------------------*/
.index-grid {
  display: grid; gap: 28px 20px; grid-template-columns: 1fr;
  padding-block: clamp(24px, 4vw, 44px) 96px;
}
@media (min-width: 760px) { .index-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1180px) { .index-grid { grid-template-columns: repeat(3, 1fr); } }

.card { display: grid; gap: 12px; }
.card-art { aspect-ratio: 3 / 2; overflow: hidden; background: var(--bg-1); }
.card-art img {
  width: 100%; height: 100%; object-fit: cover;
  transition: transform var(--slow) var(--ease), opacity var(--slow) var(--ease);
}
.card:hover .card-art img { transform: scale(1.028); }
.card-head { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; }
@media (min-width: 760px) {
  .index-grid > .card:first-child { grid-column: span 2; }
  .index-grid > .card:first-child .card-art { aspect-ratio: 2 / 1; }
}

/* Gallery cover header ---------------------------------------------------- */
.cover {
  position: relative;
  min-height: min(58dvh, 560px);
  display: flex; align-items: flex-end;
  overflow: hidden;
  background: var(--bg-1);
}
.cover > img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.cover::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(to top, rgb(6 6 7 / 0.92) 0%, rgb(6 6 7 / 0.35) 45%, rgb(6 6 7 / 0.15) 100%);
}
.cover .wrap { position: relative; z-index: 1; padding-block: 36px; width: 100%; }
.cover-row { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: 20px; }
.cover-actions { display: grid; gap: 8px; justify-items: end; }
.cover-buttons { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.cover-actions .btn { min-height: 44px; }
.cover-actions .meta { text-align: right; max-width: 34ch; }
@media (max-width: 640px) {
  .cover-actions { justify-items: start; }
  .cover-actions .meta { text-align: left; }
}

/* Photo grid: uniform 3:2 thumbnails.
   A true justified layout (equal-height rows, every aspect ratio preserved)
   needs a per-row height, and CSS cannot vary height per flex line: leftover
   items always stretch, which crops a portrait frame far worse than a fixed
   3:2 cell does. So the browse grid is uniform thumbnails, and the lightbox
   shows every frame whole and uncropped. Same call Pixieset and the Instagram
   profile grid make.
   ponytail: swap in a JS justified layout (row height = width / sum of ratios)
   if the cropped verticals ever bother you. --ar is already on every tile. */
.grid {
  display: grid;
  /* Two columns on every phone, always. Bigger thumbnails beat more of them
     when the job is finding your own face in 200 frames. */
  grid-template-columns: repeat(2, 1fr);
  gap: var(--gutter);
  padding-block: var(--gutter) 96px;
}
@media (min-width: 560px)  { .grid { grid-template-columns: repeat(auto-fill, minmax(165px, 1fr)); } }
@media (min-width: 900px)  { .grid { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); } }
@media (min-width: 1300px) { .grid { grid-template-columns: repeat(auto-fill, minmax(245px, 1fr)); } }

.tile {
  aspect-ratio: 3 / 2;
  position: relative; overflow: hidden;
  background: var(--bg-1);
  cursor: zoom-in;
  padding: 0; border: 0;
}
.tile img {
  width: 100%; height: 100%; object-fit: cover;
  opacity: 0;
  transition: opacity var(--slow) var(--ease);
}
.tile img.ready { opacity: 1; }
.tile::after {
  content: ""; position: absolute; inset: 0;
  box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0);
  transition: box-shadow var(--fast) var(--ease);
}
.tile:hover::after { box-shadow: inset 0 0 0 2px rgb(255 255 255 / 0.9); }

/* Lightbox. Native <dialog>: focus trap, Esc, inert background, all free. -- */
.lb {
  width: 100dvw; max-width: 100dvw; height: 100dvh; max-height: 100dvh;
  margin: 0; padding: 0; border: 0;
  background: #060607;
  color: var(--ink);
  overflow: hidden;
  /* Stops a swipe past the last photo from chaining to the page behind, or
     triggering pull-to-refresh. */
  overscroll-behavior: contain;
}
.lb::backdrop { background: #060607; }
@media (prefers-reduced-motion: no-preference) {
  .lb[open] { animation: lb-in 260ms var(--ease); }
  .lb[open]::backdrop { animation: lb-fade 260ms var(--ease); }
}
@keyframes lb-in { from { opacity: 0; transform: scale(0.97); } }
@keyframes lb-fade { from { opacity: 0; } }
/* Column layout rather than absolute offsets: the bottom bar can grow by a
   line (the tip, a long lens name wrapping) and the photo simply gets less
   room instead of being covered. No magic numbers to keep in sync.
   Scoped to [open] so the UA's display:none on a closed dialog still wins. */
/* Explicit rows, and a middle row that is minmax(0,1fr) so it is definite
   rather than content-sized. Percentage heights inside a flex item sized by
   `flex: 1` do NOT resolve in Chrome (they do in Safari), which is what let the
   photo overflow down across the caption. Nothing here depends on that. */
.lb[open] {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  position: fixed; inset: 0;
}

.lb-stage {
  position: relative;
  min-height: 0;
  overflow: hidden;
  touch-action: pan-y pinch-zoom;
}

/* Three slides: previous, current, next. Sitting at -100% shows the middle one,
   so a drag can reveal a real neighbour in either direction. */
.lb-track {
  position: absolute; inset: 4px 12px;
  display: flex;
  transform: translate3d(-100%, 0, 0);
  will-change: transform;
}
.lb-slide { flex: 0 0 100%; position: relative; }

/* Absolutely positioned against a slide whose box is definite by construction,
   so the size never depends on a percentage resolving. Nothing overlays the
   image and the touch callout stays enabled, so an iOS long-press still offers
   "Add to Photos". */
.lb-slide img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: contain;
  -webkit-touch-callout: default;
  user-select: none;
}
.lb-top, .lb-bot { flex: none; padding: 12px clamp(12px, 3vw, 24px); }
.lb-top {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding-top: max(12px, env(safe-area-inset-top));
}
.lb-bot {
  display: grid; gap: 10px;
  padding-bottom: max(12px, env(safe-area-inset-bottom));
}
.lb-row { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; }
.lb-tip { margin: 0; font-size: 0.8125rem; color: var(--ink-2); text-align: center; }
/* Save is the primary action on a phone, so give it real thumb area. */
.lb-bot .btn { min-height: 44px; }
.lb-actions { display: flex; gap: 8px; flex: none; }
.lb-info { display: grid; gap: 3px; min-width: 0; }

/* Camera, lens, and settings. Spaced, not punctuated. */
.lb-exif {
  display: flex; flex-wrap: wrap; gap: 4px 14px;
  font-size: 0.75rem; line-height: 1.35;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.lb-exif i { font-style: normal; }
.lb-exif i:nth-child(-n + 2) { color: var(--ink-2); }
.lb-nav {
  position: absolute; top: 50%; translate: 0 -50%;
  width: 46px; height: 46px; display: grid; place-items: center;
  border-radius: 50%; border: 1px solid var(--line-2);
  background: rgb(20 20 22 / 0.72); cursor: pointer;
  transition: background var(--fast) var(--ease), transform var(--fast) var(--ease);
}
.lb-nav:hover { background: var(--bg-2); }
.lb-nav:active { transform: translateY(-50%) scale(0.94); }
.lb-prev { left: 12px; } .lb-next { right: 12px; }
@media (max-width: 640px) { .lb-nav { display: none; } }

/* Admin ------------------------------------------------------------------- */
.admin-head { padding-block: 32px 24px; display: flex; flex-wrap: wrap; gap: 16px; align-items: baseline; justify-content: space-between; }
.panel { background: var(--bg-1); border: 1px solid var(--line); border-radius: var(--r); padding: 20px; }
.rows { display: grid; gap: 1px; background: var(--line); border: 1px solid var(--line); border-radius: var(--r); overflow: hidden; }
.rows > * { background: var(--bg-1); padding: 14px 16px; display: flex; align-items: center; gap: 16px; justify-content: space-between; }
.drop {
  border: 1px dashed var(--line-2); border-radius: var(--r);
  padding: 40px 20px; text-align: center; color: var(--ink-2);
  transition: border-color var(--fast) var(--ease), background var(--fast) var(--ease);
}
.drop.hot { border-color: var(--ink); background: var(--bg-2); color: var(--ink); }
.bar { height: 2px; background: var(--bg-2); border-radius: 2px; overflow: hidden; }
.bar > i { display: block; height: 100%; width: 0; background: var(--ink); transition: width 140ms linear; }

/* Stats. Numbers get room to breathe, no card boxes around single figures. - */
.kpis { display: grid; gap: 1px; background: var(--line); border: 1px solid var(--line); border-radius: var(--r); overflow: hidden; grid-template-columns: repeat(2, 1fr); }
@media (min-width: 860px) { .kpis { grid-template-columns: repeat(4, 1fr); } }
.kpi { background: var(--bg-1); padding: 20px; display: grid; gap: 4px; }
.kpi b { font-size: clamp(1.75rem, 1.2rem + 1.4vw, 2.5rem); font-weight: 600; letter-spacing: -0.03em; line-height: 1; font-variant-numeric: tabular-nums; }
.kpi span { font-size: 0.8125rem; color: var(--ink-2); }
.spark { display: flex; align-items: flex-end; gap: 2px; height: 64px; }
.spark i { flex: 1; background: var(--line-2); border-radius: 1px; min-height: 1px; }
.spark i.on { background: var(--ink); }

.grid-2 { display: grid; gap: 20px; grid-template-columns: 1fr; }
@media (min-width: 900px) { .grid-2 { grid-template-columns: 1fr 1fr; } }

.thumbrow { display: flex; align-items: center; gap: 12px; min-width: 0; }
.thumbrow img { width: 56px; height: 38px; object-fit: cover; flex: none; background: var(--bg-2); }
.trunc { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.banner {
  background: #2a1d05; border: 1px solid #6b4a08; color: #ffd88a;
  border-radius: var(--r); padding: 12px 16px; font-size: 0.875rem; margin-block: 16px;
}

/* Motion ------------------------------------------------------------------ */
@media (prefers-reduced-motion: no-preference) {
  @view-transition { navigation: auto; }
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
  .tile img { opacity: 1; }
}
