/* ------------------------------------------------------------
   PhotoSite — VSCO-inspired styles.
   White canvas, black sidebar, masonry grid.
   ------------------------------------------------------------ */

:root {
  --bg: #ffffff;
  --fg: #111111;
  --muted: #8a8a8a;
  --rule: #ececec;
  --topbar-bg: #000000;
  --topbar-fg: #ffffff;
  --topbar-height: 44px;
  --footer-height: 28px;            /* fixed bottom bar */
  /* Gap and side padding scale with the viewport so photo proportions stay
     consistent whether the window is fullscreen or split. clamp(min, fluid, max). */
  --gap: clamp(4px, 0.5vw, 8px);              /* vertical gap between stacked tiles */
  --col-gap: clamp(12px, 1.5vw, 24px);        /* horizontal gap between columns */
  --grid-side-pad: clamp(96px, 18vw, 360px);  /* whitespace at left/right of page */
  --max-width: none;                          /* let the grid fill available width */
  --font-sans: 'DM Sans', -apple-system, BlinkMacSystemFont, "Helvetica Neue",
               Helvetica, Arial, "Segoe UI", Roboto, sans-serif;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-sans);
  -webkit-font-smoothing: antialiased;
}

/* ---------- Top navigation bar ---------- */

.topbar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: var(--topbar-height);
  background: var(--topbar-bg);
  color: var(--topbar-fg);
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 clamp(20px, 4vw, 48px);
}

.topbar-brand {
  /* Reset native button styling so it reads as plain text — then make it
     clickable (returns to the top of the photo view; wired in script.js). */
  background: none;
  border: 0;
  padding: 0;
  font-family: var(--font-sans);
  font-size: 15px;
  font-weight: 500;
  letter-spacing: 0.04em;
  color: #ffffff;
  white-space: nowrap;
  cursor: pointer;
}

.topbar-nav {
  display: flex;
  align-items: center;
  gap: 10px;
}

/* Top-right nav buttons: plain white text on the black bar, no outline.
   Enlarged a little for an obvious affordance; they swell on hover. */
.nav-link {
  background: none;
  border: 0;
  padding: 6px 12px;
  margin: 0;
  font-family: var(--font-sans);
  font-size: 15px;
  letter-spacing: 0.04em;
  text-align: center;
  cursor: pointer;
  color: rgba(255, 255, 255, 0.62);
  transition: color 200ms ease;
}

.nav-link span { pointer-events: none; }

/* Hover: brighten only (no swell). Active (current view): full white. */
.nav-link:hover { color: #ffffff; }
.nav-link.is-active { color: #ffffff; }

/* ---------- Main page area (push down below the fixed topbar) ---------- */

.page {
  padding-top: var(--topbar-height);
  /* Reserve space so content can scroll clear of the fixed footer. */
  padding-bottom: var(--footer-height);
}

/* ---------- View switching (pics / about) ---------- */

.view { display: none; }
.view.is-active { display: block; animation: viewReveal 0.5s ease-out backwards; }

/* When a view (Photos / About) becomes active, reveal its contents with a
   seamless top-to-bottom fade instead of snapping in. `backwards` fill applies
   the start frame at t=0 but leaves no clip-path lingering afterwards (so tile
   hover effects aren't clipped). Reduced-motion users get it near-instant via
   the global prefers-reduced-motion rule. */
@keyframes viewReveal {
  from { opacity: 0; clip-path: inset(0 0 100% 0); }
  to   { opacity: 1; clip-path: inset(0 0 0 0); }
}

/* ---------- About panel ---------- */

.about-panel {
  /* No max-width — the panel now uses the same horizontal footprint as the
     photo grid (identical var(--grid-side-pad) padding on each side).
     The old max-width: 640px was smaller than 2 × --grid-side-pad on wide
     screens, squashing the content to near-zero width. */
  margin: 8px 0 64px;
  padding: 0 var(--grid-side-pad);
  color: var(--fg);
  line-height: 1.6;
  font-size: 15px;
}

.about-title {
  font-size: 15px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: #3d3d3d;
  text-align: center;
  margin: 32px 0 28px;
  font-weight: 500;
}

.about-block { margin: 0; }
.about-block:last-child { margin-bottom: 0; }

/* Contact section: centred label + content (blurb and email). */
.about-block--contact { text-align: center; }

/* Thin section-divider rule. Default inset matches the grid/hero side padding;
   inside the about panel it spans the (already padded) content width. */
.section-divider {
  border: 0;
  border-top: 1px solid #dddddd;   /* fine line */
  height: 0;
  /* hero rule overhangs the hero photo's edges a little; raised 30px (top -4 vs 26) */
  margin: -4px calc(var(--grid-side-pad) - 32px) 26px;
}
.about-panel .section-divider {
  width: 60%;            /* 40% shorter, centred */
  margin: 28px auto;
}
/* GEAR/CONTACT rules match the hero rule's length (overhang past the content edges). */
.about-panel .section-divider--full {
  width: auto;
  margin-left: -32px;
  margin-right: -32px;
}

.about-subhead {
  font-size: 12.5px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: #565656;
  font-weight: 500;
  margin: 0 0 12px;
  text-align: center;
}

.about-text { margin: 0 0 12px; }
.about-text:last-child { margin-bottom: 0; }

.about-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.about-list li {
  padding: 6px 0;
  border-bottom: 1px solid var(--rule);
  font-size: 14px;
}

.about-list li:last-child { border-bottom: 0; }

/* Gear grid: three side-by-side device columns, each with its lenses underneath.
   Collapses to a single column on narrow viewports (see mobile media query). */
.gear-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
  margin-bottom: 24px;
}

.gear-col { min-width: 0; text-align: center; }
/* Titles stay aligned across columns (top of the cell); only the videos are
   nudged (below) so the clips sit at matched heights. */

.gear-device {
  font-family: var(--font-sans);
  font-size: 16px;
  font-weight: 400;
  letter-spacing: 0.01em;
  color: var(--fg);
  margin: 0 0 2px;       /* sit close above its video */
}

/* Looping gear demo video (e.g. the spinning lens) under a device heading.
   The clip's backdrop is whitened in the file itself; this feathers the four
   edges so the frame melts fully into the white page with no visible rectangle. */
.gear-video {
  display: block;
  width: 100%;
  height: auto;
  margin: 4px auto 0;    /* centred, tight under its title */
  -webkit-mask-image:
    linear-gradient(to right,  transparent 0, #000 3%, #000 97%, transparent 100%),
    linear-gradient(to bottom, transparent 0, #000 3%, #000 97%, transparent 100%);
  -webkit-mask-composite: source-in;
  mask-image:
    linear-gradient(to right,  transparent 0, #000 3%, #000 97%, transparent 100%),
    linear-gradient(to bottom, transparent 0, #000 3%, #000 97%, transparent 100%);
  mask-composite: intersect;
}

/* The square camera-body clips display smaller (centred) so their height
   roughly matches the widescreen lens clip, each nudged down slightly. */
.gear-video--r10 { width: 55%; transform: translateY(5px); }
.gear-video--scl { width: 60%; transform: translateY(20px); }

/* Lower the single-line camera-body titles so they sit level with the middle
   of the Sigma entry's two-line title. */
.gear-col--r10 .gear-device,
.gear-col--scl .gear-device { transform: translateY(13px); }

.about-link {
  color: var(--fg);
  text-decoration: none;
  border-bottom: 1px solid var(--rule);
  transition: border-color 120ms ease;
}

.about-link:hover { border-bottom-color: var(--fg); }

/* ---------- Site footer (copyright) ---------- */

.site-footer {
  /* Pinned to the bottom of the window, mirroring the fixed topbar. */
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 50;
  min-height: var(--footer-height);
  display: flex;
  align-items: center;
  justify-content: flex-end;
  padding: 6px clamp(18px, 5vw, 64px);
  background: var(--topbar-bg);
  text-align: center;
  color: rgba(255, 255, 255, 0.7);
  font-size: 11px;
  letter-spacing: 0.06em;
}

/* Version tag pinned to the left edge; the copyright stays centred. */
.footer-version {
  position: absolute;
  left: clamp(18px, 5vw, 64px);
  top: 50%;
  transform: translateY(-50%);
  color: rgba(255, 255, 255, 0.5);
  font-size: 11px;
  letter-spacing: 0.06em;
}


/* ---------- Hero slideshow ---------- */

.hero-section {
  /* Same horizontal margins as the collage grid below. */
  margin: 22px auto 0;
  padding: 0 var(--grid-side-pad);
}

/* Crossfade container.
   The first .hero-img stays in normal flow to give the container its height;
   all subsequent .hero-img elements are stacked absolutely on top of it. */
.hero-slideshow {
  position: relative;
  line-height: 0;       /* collapse any inline whitespace between images */
  overflow: hidden;
  cursor: zoom-in;      /* click to open the hero in the lightbox */
}

.hero-img {
  display: block;
  width: 100%;
  height: auto;
  opacity: 0;
  transition: opacity 700ms ease;
}

/* 2nd+ images out of flow — stacked on top of the first. */
.hero-img ~ .hero-img {
  position: absolute;
  top: 0;
  left: 0;
}

.hero-img.is-active {
  opacity: 1;
  z-index: 1;
}

/* Dot row */
.hero-dots {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 10px;
  padding: 14px 0 6px;
}

.hero-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  border: 1.5px solid var(--muted);
  background: transparent;
  cursor: pointer;
  padding: 0;
  transition: transform 200ms ease, background 200ms ease, border-color 200ms ease;
}

.hero-dot:hover {
  transform: scale(1.5);
  border-color: var(--fg);
}

.hero-dot.is-active {
  background: var(--fg);
  border-color: var(--fg);
  transform: scale(1.2);
}

/* EXIF caption below the dots */
.hero-exif {
  padding: 2px 0 20px;
  font-family: var(--font-sans);
  font-size: clamp(9px, 0.6vw + 3px, 12px);
  color: var(--muted);
  letter-spacing: 0.04em;
  text-align: center;
  transition: opacity 250ms ease;
}

/* ---------- Masonry grid ----------
   CSS Grid masonry simulation: grid-auto-rows is set to a fine unit (4px)
   and each tile's grid-row-end is computed by script.js from its aspect
   ratio, so tiles pack tightly without explicit column divs.
   row-gap is 0 — the tile spacing is baked into each tile's row span.
   --cols is set inline by script.js to match the breakpoint column count. */

.grid {
  max-width: var(--max-width);
  margin: 24px auto 64px;
  padding: 0 var(--grid-side-pad);
  display: grid;
  grid-template-columns: repeat(var(--cols, 4), 1fr);
  grid-auto-rows: 4px;
  grid-auto-flow: row dense;
  row-gap: 0;
  column-gap: var(--col-gap);
}

.tile {
  display: flex;
  flex-direction: column;
  margin: 0;          /* override the browser's default figure margin */
  cursor: zoom-in;
  align-self: start;  /* top-align: tile tops at shared row boundaries are flush;
                         photos display at natural size with spacing below */
}

.tile img {
  width: 100%;
  height: auto;              /* natural aspect ratio — no cropping */
  flex: 0 0 auto;            /* don't grow/shrink; photo displays at its true size */
  display: block;
  background: #f4f4f4;       /* placeholder colour while loading */
  transition: opacity 180ms ease, transform 220ms ease;
  transform-origin: center;
}

.tile:hover img {
  opacity: 0.88;
  transform: scale(1.012);
}

/* Video tiles in the masonry grid.
   Displayed at 3:2 to match the half-photo tiles — the footage is squished
   vertically (object-fit: fill) rather than cropped, so no bars are removed. */

.video-crop-wrapper {
  position: relative;
  aspect-ratio: 3 / 2;
  width: 100%;
  background: #111;
  overflow: hidden;
  /* Clip the horizontal edges to eliminate the subpixel rendering artefact
     on the right edge of the video's GPU texture.  Right side is clipped more
     aggressively (6 px) because the artefact sits just inside 2 px. */
  clip-path: inset(0 6px 0 2px);
}

.tile--video .video-crop-wrapper video {
  /* Fill the wrapper height so the video extends wider than the wrapper.
     scaleX(1.28) expands it further so the pillarbox bars (sides of the
     16:9 frame) overflow and are clipped.  Scale = target / content =
     1.5 / 1.24 ≈ 1.21.  Net result: bars removed, content fills 3:2.
     will-change: transform promotes the element to its own GPU compositor
     layer, eliminating the 1px subpixel rendering artifact at the edge. */
  position: absolute;
  height: 100%;
  width: auto;
  left: 50%;
  transform: translateX(-50%) scaleX(1.28);
  transform-origin: center center;
  will-change: transform;
  transition: opacity 180ms ease, transform 220ms ease;
}

.tile--video:hover .video-crop-wrapper video {
  opacity: 0.88;
  transform: translateX(-50%) scaleX(1.28) scale(1.012);
}

.tile-caption {
  margin-top: 4px;
  margin-bottom: 2px;
  font-family: var(--font-sans);
  /* Scales with viewport so captions stay proportional to photo size.
     At ~1300px+ it caps at 10.5px; at ~700px it floors at 8px. */
  font-size: clamp(8px, 0.55vw + 3.5px, 10.5px);
  color: var(--muted);
  letter-spacing: 0.03em;
  text-align: center;
  line-height: 1.25;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: transform 220ms ease;   /* slides down to match image swell */
}

/* When the tile is hovered the photo scales up ~1.2%, growing ~3 px at its
   bottom edge.  Nudging the caption down by the same amount keeps the gap
   between image and text visually constant. */
.tile:hover .tile-caption {
  transform: translateY(3px);
}

/* Video tiles have no caption text — add bottom margin to keep the vertical
   rhythm consistent with photo tiles (which have ~14px of caption text). */
.tile--video .tile-caption {
  margin-bottom: 14px;
}

/* ---------- Lightbox ---------- */

.lightbox {
  position: fixed;
  inset: 0;
  background: rgba(15, 15, 15, 0.94);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 100;
  padding: 32px;
  cursor: zoom-out;
}

.lightbox.is-open { display: flex; }

.lightbox-figure {
  margin: 0;
  max-width: 100%;
  max-height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  cursor: default;
}

.lightbox-figure img {
  max-width: 100%;
  max-height: calc(100vh - 120px);
  object-fit: contain;
  display: block;
}

/* Video wrapper in the lightbox — matches the 3:2 grid tiles. */
.lightbox-video-wrap {
  position: relative;
  display: none;
  overflow: hidden;
  /* Width: whichever is smaller — full available width, or the width that
     would result from using the full available height at 3:2.
     64px accounts for lightbox padding (32px × 2). */
  width: min(calc(100vw - 64px), calc((100vh - 120px) * 1.5));
  aspect-ratio: 3 / 2;
}

.lightbox-video-wrap.is-active { display: block; }

.lightbox-video-wrap video {
  /* Same crop-and-fill as the grid tile. */
  position: absolute;
  height: 100%;
  width: auto;
  left: 50%;
  transform: translateX(-50%) scaleX(1.28);
  transform-origin: center center;
  will-change: transform;
  display: block;
}

.lightbox-caption {
  margin-top: 14px;
  color: #d9d9d9;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
}

.lightbox-title {
  font-size: 14px;
  letter-spacing: 0.02em;
  color: #ffffff;
}

.lightbox-title:empty { display: none; }   /* hide gap when no title */

.lightbox-exif {
  font-size: 12px;
  letter-spacing: 0.04em;
  color: #d9d9d9;
}

.lightbox-close {
  position: absolute;
  z-index: 2;            /* stay clickable above the video wrapper */
  top: 16px;
  right: 20px;
  background: none;
  border: 0;
  color: #ffffff;
  font-size: 32px;
  line-height: 1;
  cursor: pointer;
  opacity: 0.7;
  transition: opacity 120ms ease;
}

.lightbox-close:hover { opacity: 1; }

/* Prev / next chevrons on the lightbox edges. */
.lightbox-nav {
  position: absolute;
  z-index: 2;            /* paint above the video wrapper so clicks land on a video too */
  top: 50%;
  transform: translateY(-50%);
  background: none;
  border: 0;
  color: #ffffff;
  font-size: 56px;
  line-height: 1;
  padding: 12px 18px;
  cursor: pointer;
  opacity: 0.5;
  transition: opacity 120ms ease;
  font-family: inherit;
  user-select: none;
}

.lightbox-nav:hover { opacity: 1; }
.lightbox-nav:disabled,
.lightbox-nav.is-hidden { opacity: 0; pointer-events: none; }

.lightbox-nav--prev { left: 16px; }
.lightbox-nav--next { right: 16px; }

/* ---------- Mobile: compact topbar ---------- */

@media (max-width: 700px) {
  /* Phones get a dedicated 2-column layout (built in renderGridMobile). Wide
     side margins keep the grid from filling the screen edge-to-edge. */
  :root {
    --topbar-height: 37px;
    --footer-height: 37px;
    --grid-side-pad: 35px;
  }

  /* The hero ignores the grid margins and runs nearly full-width. */
  .hero-section { padding: 0 16px; }

  .topbar { padding: 0 16px; }
  .topbar-brand { font-size: 13px; }
  .nav-link { padding: 5px 9px; font-size: 13.5px; }

  /* Collapse the three-column gear grid to a single stack on narrow screens. */
  .gear-grid {
    grid-template-columns: 1fr;
    gap: 24px;
    margin-bottom: 14px;   /* tighter gap to the divider below the gear stack */
  }

  /* Gear clips in the stack; drop the desktop nudges. The square camera-body
     clips are narrower than the widescreen Sigma so they don't tower over it. */
  .gear-video { width: 72%; transform: none; }
  .gear-video--r10,
  .gear-video--scl { width: 52%; transform: none; }
  .gear-col--r10 .gear-device,
  .gear-col--scl .gear-device { transform: none; }

  /* Stack-spacing tweaks: more room above the R10 entry, and tighten the R10
     label-to-clip gap. The R10 clip has ~33% empty white space on top (camera
     sits low in frame), so we clip that top region away and pull the clip up by
     the same proportion — the camera rises toward its label, while the cropped
     (transparent) top no longer covers the label. Proportional units so it
     holds as the clip scales with screen width. */
  .gear-col--r10 { margin-top: 5px; }
  .gear-video--r10 { margin-top: -13%; clip-path: inset(30% 0 0 0); }
  .gear-video--scl { margin-top: -1px; }   /* nudge the SCL clip up toward its title */

  /* GEAR / CONTACT labels: match the gear-title size and sit closer to the
     divider above them. */
  .about-subhead { font-size: 16px; margin-top: -8px; }

  /* Contact blurb reads left-aligned; the title and the email line below it
     stay centered. */
  .about-block--contact .about-text:first-of-type { text-align: left; }

  /* About: show the Sigma lens first in the single-column stack, then the R10. */
  .gear-col--sigma { order: 1; }
  .gear-col--r10   { order: 2; }
  .gear-col--scl   { order: 3; }

  /* Dividers span the content width with no edge overhang (avoid h-overflow). */
  .section-divider,
  .about-panel .section-divider--full { margin-left: 0; margin-right: 0; }

  /* Two columns; photos keep their native 3:2 / 2:3 aspect (no cropping), so
     tiles size to their image. minmax(0,1fr) stops a wide tile from stretching
     a column past the screen edge. */
  .grid { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-auto-rows: 1px; margin-bottom: 24px; }
  .tile { min-width: 0; }
  /* margin-top must match CAP_OFFSET in renderGridMobile. */
  .tile-caption { font-size: 6px; margin-top: 5px; margin-bottom: 0; }

  /* Grid videos sit in the 3:2 landscape slots. The source is 16:9 with ~15%
     black bars each side (5:4 content); scale it up so the bars overflow and
     clip, and bottom-anchor it so the corner timestamp stays in view. */
  .tile--video .video-crop-wrapper {
    aspect-ratio: 3 / 2;
    clip-path: none;
  }
  .tile--video .video-crop-wrapper video {
    position: absolute;
    bottom: 0;
    left: 50%;
    top: auto;
    width: 143%;
    height: auto;
    transform: translateX(-50%);
  }
  /* Keep the mobile framing on tap/hover (the desktop hover adds scaleX). */
  .tile--video:hover .video-crop-wrapper video { transform: translateX(-50%); }
}

/* ---------- Reduced motion ----------
   For visitors who've enabled the OS "reduce motion" accessibility setting:
   collapse transition/animation timing and drop the hover-zoom. The video
   crop transform (translateX(-50%) scaleX(1.28)) is preserved — only the
   extra hover scale is removed — so footage still fills its 3:2 frame.
   (The hero auto-advance is also paused in script.js.) */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
  }
  .tile:hover img { transform: none; }
  .tile:hover .tile-caption { transform: none; }
  .tile--video:hover .video-crop-wrapper video {
    transform: translateX(-50%) scaleX(1.28);
  }
}
