/* Jan Timmer Photography — minimal, photo-first design system.
   No framework, no build step: edit this file directly and refresh. */

:root {
  --color-bg: #fafafa;
  --color-bg-rgb: 250, 250, 250;
  --color-fg: #161616;
  --color-muted: #6b6b6b;
  --color-line: #e2e2e2;
  --color-accent: #161616;
  --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  --max-width: 1180px;
  --space: 1.5rem;
  --header-height: 4.5rem;
  color-scheme: light;
}

/* Dark mode follows the OS by default (prefers-color-scheme). The header
   theme-toggle (see .theme-toggle below and static/js/theme-toggle.js)
   overrides that choice by setting data-theme="light"/"dark" on <html>,
   persisted in localStorage — :not([data-theme="light"]) below is what
   lets an explicit "light" pick win over a dark OS setting. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --color-bg: #131313;
    --color-bg-rgb: 19, 19, 19;
    --color-fg: #f2f2f2;
    --color-muted: #9a9a9a;
    --color-line: #2b2b2b;
    --color-accent: #f2f2f2;
    color-scheme: dark;
  }
}
:root[data-theme="dark"] {
  --color-bg: #131313;
  --color-bg-rgb: 19, 19, 19;
  --color-fg: #f2f2f2;
  --color-muted: #9a9a9a;
  --color-line: #2b2b2b;
  --color-accent: #f2f2f2;
  color-scheme: dark;
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; overflow-x: hidden; }
body {
  margin: 0;
  background: var(--color-bg);
  color: var(--color-fg);
  font-family: var(--font-sans);
  line-height: 1.55;
  overflow-x: hidden;
}
img { max-width: 100%; display: block; }
a {
  color: inherit;
  font-weight: 600;
}

/* Transparent at the top of the page, gradually fading to a solid
   background + soft shadow as you scroll (see nav.js, which sets
   --header-bg-opacity from 0 to 1 as you scroll through the first half
   of the viewport height). No border — the shadow does the separating. */
.site-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 20;
  background: rgba(var(--color-bg-rgb), var(--header-bg-opacity, 0));
  box-shadow: 0 4px 20px rgba(0, 0, 0, calc(0.12 * var(--header-bg-opacity, 0)));
}
.site-header__inner {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: var(--space);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}
.site-brand {
  font-weight: 600;
  font-size: 1.1rem;
  text-decoration: none;
}
.site-nav { display: flex; gap: 1.25rem; flex-wrap: wrap; }
.site-nav a { text-decoration: none; color: var(--color-muted); font-size: 0.95rem; }
.site-nav a:hover, .site-nav a.is-active { color: var(--color-fg); }

.cart-link { display: inline-flex; align-items: center; gap: 0.4rem; }
.cart-count[hidden] { display: none; }
.cart-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.2rem;
  height: 1.2rem;
  padding: 0 0.3rem;
  border-radius: 999px;
  background: var(--color-accent);
  color: var(--color-bg);
  font-size: 0.75rem;
  line-height: 1;
}

.nav-toggle {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 32px;
  height: 32px;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
}
.nav-toggle__bar {
  display: block;
  width: 100%;
  height: 2px;
  background: currentColor;
  border-radius: 2px;
}

/* Light/dark toggle, next to the Cart link (see layouts/partials/header.html
   and static/js/theme-toggle.js). Icon swap is pure CSS, keyed off the same
   data-theme attribute the color variables above respond to: shows the
   moon (switch to dark) normally, the sun (switch to light) once dark mode
   is active — whether that's from an explicit choice or the OS default. */
.theme-toggle {
  display: inline-flex;
  align-items: center;
  padding: 0;
  border: none;
  background: none;
  color: var(--color-muted);
  cursor: pointer;
}
.theme-toggle:hover { color: var(--color-fg); }
.theme-toggle svg { display: block; width: 18px; height: 18px; }
.theme-toggle .icon-sun { display: none; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .theme-toggle .icon-sun { display: block; }
  :root:not([data-theme="light"]) .theme-toggle .icon-moon { display: none; }
}
:root[data-theme="dark"] .theme-toggle .icon-sun { display: block; }
:root[data-theme="dark"] .theme-toggle .icon-moon { display: none; }

/* White nav text over the homepage hero photo (see layouts/index.html and
   layouts/partials/header.html — applied only when a hero.* resource exists
   on the home page), since it sits on a photo rather than the page
   background. Faded to the normal themed color using the same
   --header-bg-opacity progress that fades the header background (see
   above), so the text change is exactly as gradual as the background —
   never a sudden swap, never white-on-white. In dark mode the themed
   color is already near-white, so it's pinned to pure white throughout
   instead (below) rather than dipping through the muted gray. */
.site-header--overlay {
  --header-overlay-fg: color-mix(in srgb, #fff calc((1 - var(--header-bg-opacity, 0)) * 100%), var(--color-fg) calc(var(--header-bg-opacity, 0) * 100%));
  --header-overlay-muted: color-mix(in srgb, #fff calc((1 - var(--header-bg-opacity, 0)) * 100%), var(--color-muted) calc(var(--header-bg-opacity, 0) * 100%));
}
.site-header--overlay .site-brand {
  color: var(--header-overlay-fg);
}
.site-header--overlay .site-nav a,
.site-header--overlay .theme-toggle {
  color: var(--header-overlay-muted);
  opacity: 0.85;
}
.site-header--overlay .site-nav a:hover,
.site-header--overlay .site-nav a.is-active,
.site-header--overlay .theme-toggle:hover {
  color: var(--header-overlay-fg);
  opacity: 1;
}
.site-header--overlay .nav-toggle__bar { background: var(--header-overlay-fg); }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .site-header--overlay {
    --header-overlay-fg: #fff;
    --header-overlay-muted: #fff;
  }
}
:root[data-theme="dark"] .site-header--overlay {
  --header-overlay-fg: #fff;
  --header-overlay-muted: #fff;
}

@media (max-width: 640px) {
  .nav-toggle { display: flex; }
  .site-nav {
    display: none;
    flex-basis: 100%;
    flex-direction: column;
    gap: 0;
    order: 3;
  }
  .site-nav.is-open { display: flex; }
  .site-nav a,
  .site-nav .theme-toggle {
    padding: 0.7rem 0;
    border-top: 1px solid var(--color-line);
  }
  /* The header itself fades in from transparent as you scroll (see
     --header-bg-opacity in nav.js), so near the top of any page the open
     mobile menu would otherwise have nothing behind it but the page
     content. Give it its own solid backdrop unconditionally rather than
     relying on the header's scroll-based one. */
  .site-nav.is-open {
    background: var(--color-bg);
    margin: 0 calc(-1 * var(--space));
    flex-basis: calc(100% + 2 * var(--space));
    padding: 0 var(--space) 0.5rem;
  }
  /* Over the hero photo, fade the open menu's own backdrop in step with
     --header-bg-opacity too — using the theme's own background color
     (so light mode eases in through white, dark mode through black)
     rather than a fixed dark scrim that would otherwise jump straight
     to solid the instant the header behind it finishes fading in. Floored
     at 92% (not fully opaque, so the transition to fully solid on scroll
     is never a sudden jump) — a lower floor left a bright, busy photo
     showing through enough to make the menu text hard to read, especially
     in light mode. */
  .site-header--overlay .site-nav.is-open {
    background: rgba(var(--color-bg-rgb), calc(0.92 + 0.08 * var(--header-bg-opacity, 0)));
  }
  /* The dropdown is its own opaque-ish panel rather than text floating
     directly over the photo, so — unlike the single-row bar above it —
     its text and separators can just use their normal themed colors right
     away instead of fading in with the rest of the header. */
  .site-header--overlay .site-nav.is-open a,
  .site-header--overlay .site-nav.is-open .theme-toggle {
    color: var(--color-muted);
    opacity: 1;
  }
  .site-header--overlay .site-nav.is-open a:hover,
  .site-header--overlay .site-nav.is-open a.is-active,
  .site-header--overlay .site-nav.is-open .theme-toggle:hover {
    color: var(--color-fg);
  }
  .site-header--overlay .site-nav.is-open a { border-top-color: var(--color-line); }

  /* ...except in dark mode, which (per the header text above) stays pure
     white throughout rather than dipping into the muted gray. */
  @media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .site-header--overlay .site-nav.is-open a,
    :root:not([data-theme="light"]) .site-header--overlay .site-nav.is-open .theme-toggle {
      color: #fff;
    }
  }
  :root[data-theme="dark"] .site-header--overlay .site-nav.is-open a,
  :root[data-theme="dark"] .site-header--overlay .site-nav.is-open .theme-toggle {
    color: #fff;
  }
}

.site-main {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: var(--header-height) var(--space) 4rem;
  min-height: 60vh;
}

.hero { padding: 2rem 0 1rem; max-width: 60ch; }
.hero h1 { font-size: 1.9rem; margin-bottom: 0.5rem; }

/* Full-bleed homepage hero, used when content/hero.{jpg,png,webp} exists
   (see layouts/index.html). Breaks out of .site-main's max-width via the
   50vw trick rather than a wrapper, so it stays a drop-in that needs no
   change to baseof.html. */
.hero-photo {
  position: relative;
  width: 100vw;
  margin-left: calc(50% - 50vw);
  margin-right: calc(50% - 50vw);
  margin-top: calc(-1 * var(--header-height));
  height: 100vh;
  height: 100svh;
  overflow: hidden;
  display: flex;
  align-items: flex-end;
}
.hero-photo__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.hero-photo__img[hidden] { display: none; }
.hero-photo__overlay {
  position: relative;
  width: 100%;
  padding: 3rem var(--space) 2.5rem;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0) 70%);
}
.hero-photo__content {
  max-width: 60ch;
  margin: 0 auto;
  color: #fff;
}
.hero-photo__content h1 { font-size: 1.9rem; margin-bottom: 0.5rem; color: #fff; text-shadow: 0 1px 6px rgba(0, 0, 0, 0.6); }
.hero-photo__content p { color: #f2f2f2; text-shadow: 0 1px 6px rgba(0, 0, 0, 0.6); }
.hero-photo__content a {
  display: inline-block;
  margin-top: 0.75rem;
  background: var(--color-accent);
  color: var(--color-bg);
  text-decoration: none;
  padding: 0.65rem 1.4rem;
  border-radius: 4px;
  font-size: 0.95rem;
}
.hero-photo__content a:hover { opacity: 0.85; }

.home-section { margin-top: 3rem; }
.home-section h2 { font-size: 1.3rem; margin-bottom: 1rem; }

.portfolio-categories {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
  margin: 1rem 0 2rem;
}
.portfolio-categories a {
  color: var(--color-muted);
  text-decoration: none;
  font-size: 0.95rem;
}
.portfolio-categories a:hover {
  color: var(--color-fg);
  text-decoration: underline;
}

.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 1.25rem;
  margin: 1.5rem 0;
}
.card {
  text-decoration: none;
  color: inherit;
  display: block;
}
.card:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 4px;
  border-radius: 6px;
}
.card__media {
  display: block;
  overflow: hidden;
  border-radius: 4px;
  background: var(--color-line);
}
.card__media img {
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  display: block;
  transition: transform 0.35s ease;
}
.card:hover .card__media img { transform: scale(1.04); }
.card--square .card__media img { aspect-ratio: 1 / 1; }
.card__title {
  display: block;
  margin-top: 0.5rem;
  font-size: 0.95rem;
}
.card__price {
  display: block;
  color: var(--color-muted);
  font-size: 0.85rem;
}

/* Masonry-style: photos keep their native aspect ratio (no forced crop) so
   portrait and landscape shots sit together cleanly. This plain
   CSS-columns layout is the no-JS fallback and the only layout for a
   gallery-grid.html call that doesn't pass "shuffle" (e.g. a product's
   mockup photos) — full portfolio galleries upgrade to the mosaic layout
   below via static/js/mosaic.js. */
.gallery-grid {
  columns: 3 280px;
  column-gap: 0.75rem;
  margin: 1.5rem 0;
}
.gallery-grid__item {
  display: block;
  break-inside: avoid;
  margin: 0 0 0.75rem;
}
/* Mosaic layout: mosaic.js absolutely-positions every item itself (left/
   top/width in px, computed from the column count and each photo's own
   aspect ratio) rather than using CSS Grid's row-track system. Grid rows
   are a GLOBAL resource shared across every column - when different
   columns' items span different, non-row-aligned ranges (exactly what a
   masonry of varying-height photos does), the browser can silently grow a
   shared row to fit whatever any column needs at that row index, shifting
   every other column's items down by however much that grew, in ways this
   layout's own bookkeeping has no way to see or account for. That showed
   up as inexplicable ~10px gaps between items that should have been flush
   - not a rounding error, a structural mismatch between what JS *thinks*
   each row costs (a fixed 1px) and what the grid actually rendered it as.
   Absolute positioning has no shared tracks to distort: every item's own
   top/left is exactly the pixel value JS computed, full stop. Landscape
   photos still span 2 columns (double width) so they render at roughly
   the same visual area as a portrait instead of shrinking to a sliver -
   no cropping beyond the small flex SQUEEZE_TOLERANCE allows, see
   mosaic.js. Column count (1/2/3 by viewport) needs a JS-set --mosaic-cols
   only for legacy/no-JS reference elsewhere; the positioning itself no
   longer reads it. */
.gallery-grid--mosaic {
  position: relative;
}
.gallery-grid--mosaic .gallery-grid__item {
  position: absolute;
  top: 0;
  left: 0;
  margin: 0;
}
/* Decorative filler for gaps in the mosaic - a soft tint of the theme's
   own text colour (light on dark, dark on light), as a gradient rather
   than a flat fill for a little depth. No hue of its own, so it never
   clashes with whatever photos happen to sit next to it. */
.gallery-grid__tile {
  position: absolute;
  border-radius: 4px;
  background: linear-gradient(135deg,
    color-mix(in srgb, var(--color-fg) 10%, transparent),
    color-mix(in srgb, var(--color-fg) 2%, transparent));
  pointer-events: none;
}
.gallery-grid__link {
  display: block;
  overflow: hidden;
  border-radius: 4px;
}
.gallery-grid__link:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}
.gallery-grid__link img {
  width: 100%;
  height: auto;
  display: block;
  cursor: zoom-in;
  transition: transform 0.35s ease;
}
.gallery-grid__caption {
  font-size: 0.85rem;
  color: var(--color-muted);
  margin-top: 0.35rem;
}
.gallery-grid__link:hover img { transform: scale(1.04); }

.print-page { max-width: var(--max-width); }
.print-layout {
  display: grid;
  gap: 1rem 3rem;
}
@media (min-width: 860px) {
  .print-layout {
    grid-template-columns: 1.3fr 1fr;
    align-items: start;
  }
  .print-details { position: sticky; top: 1.5rem; }
}
.print-details h1 { margin-bottom: 0.25rem; }
.print-specs { display: grid; grid-template-columns: auto 1fr; gap: 0.25rem 1rem; margin: 1.5rem 0; }
.print-specs dt { color: var(--color-muted); }
.print-specs dd { margin: 0; }

.print-photo-wrap { display: block; border-radius: 6px; overflow: hidden; }
.print-photo { width: 100%; height: auto; display: block; cursor: zoom-in; }

.print-mockups { margin-top: 2.5rem; }
.print-mockups__heading { font-size: 1.1rem; margin-bottom: 0.75rem; }

.print-configurator {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  max-width: 360px;
  margin-top: 1.5rem;
  padding: 1.1rem 1.25rem;
  border: 1px solid var(--color-line);
  border-radius: 6px;
}
.form-field--checkbox label {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  color: var(--color-fg);
  font-size: 0.95rem;
}
.print-configurator__price {
  margin: 0;
  font-size: 1.1rem;
}
.print-configurator__status {
  margin: 0;
  font-size: 0.85rem;
  color: var(--color-muted);
}
.print-configurator__actions { display: flex; gap: 0.75rem; flex-wrap: wrap; }
.btn-buy-print {
  display: inline-block;
  background: var(--color-accent);
  color: var(--color-bg);
  text-decoration: none;
  padding: 0.55rem 1.1rem;
  border-radius: 4px;
  font-size: 0.9rem;
  white-space: nowrap;
}
.btn-buy-print:hover { opacity: 0.85; }

.btn-primary {
  display: inline-block;
  background: var(--color-accent);
  color: var(--color-bg);
  border: none;
  padding: 0.65rem 1.4rem;
  border-radius: 4px;
  font-size: 0.95rem;
  font-family: inherit;
  cursor: pointer;
}
.btn-primary:hover { opacity: 0.85; }

.btn-secondary {
  display: inline-block;
  background: none;
  color: var(--color-fg);
  border: 1px solid var(--color-line);
  padding: 0.65rem 1.4rem;
  border-radius: 4px;
  font-size: 0.95rem;
  font-family: inherit;
  cursor: pointer;
}
.btn-secondary:hover { border-color: var(--color-fg); }

.cart-empty { color: var(--color-muted); }

.cart-lines { display: flex; flex-direction: column; gap: 0.75rem; margin: 1.5rem 0; max-width: 640px; }
.cart-line {
  display: grid;
  grid-template-columns: 1fr auto auto auto;
  align-items: center;
  gap: 1rem;
  padding: 0.85rem 1rem;
  border: 1px solid var(--color-line);
  border-radius: 6px;
}
.cart-line__description { font-size: 0.95rem; }
.cart-line__quantity { width: 3.5rem; font: inherit; color: var(--color-fg); background: var(--color-bg); border: 1px solid var(--color-line); border-radius: 4px; padding: 0.35rem; }
.cart-line__price { font-variant-numeric: tabular-nums; white-space: nowrap; }
.cart-line__remove {
  background: none;
  border: none;
  color: var(--color-muted);
  cursor: pointer;
  font-size: 0.85rem;
  text-decoration: underline;
  padding: 0;
  font-family: inherit;
}
.cart-line__remove:hover { color: var(--color-fg); }

@media (max-width: 640px) {
  .cart-line { grid-template-columns: 1fr auto; grid-template-areas: "description description" "quantity price" "remove remove"; }
  .cart-line__description { grid-area: description; }
  .cart-line__quantity { grid-area: quantity; }
  .cart-line__price { grid-area: price; text-align: right; }
  .cart-line__remove { grid-area: remove; justify-self: start; }
}

.cart-summary { max-width: 360px; display: flex; flex-direction: column; gap: 0.75rem; }
.cart-summary__subtotal { margin: 0; font-size: 1.1rem; }

.trust-bar {
  margin-top: 1.25rem;
  padding: 1rem 1.1rem;
  border: 1px solid var(--color-line);
  border-radius: 6px;
}
.trust-bar__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  font-size: 0.85rem;
}
.trust-bar__item {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}
.trust-bar__item svg {
  flex: none;
  width: 1.1rem;
  height: 1.1rem;
  color: var(--color-muted);
}
.trust-bar__returns {
  margin: 0.75rem 0 0;
  padding-top: 0.75rem;
  border-top: 1px solid var(--color-line);
  font-size: 0.85rem;
}

.print-disclosure {
  margin-top: 1rem;
  font-size: 0.85rem;
  color: var(--color-muted);
}

.page { max-width: 70ch; }
.page h1 { margin-bottom: 1rem; }

.contact-form {
  display: flex;
  flex-direction: column;
  gap: 1.1rem;
  max-width: 480px;
  margin-top: 2rem;
}
.form-field { display: flex; flex-direction: column; gap: 0.35rem; }
.form-field[hidden] { display: none; }
.form-field label { font-size: 0.9rem; color: var(--color-muted); }
.form-field input,
.form-field select,
.form-field textarea {
  font: inherit;
  font-size: 0.95rem;
  color: var(--color-fg);
  background: var(--color-bg);
  border: 1px solid var(--color-line);
  border-radius: 4px;
  padding: 0.6rem 0.75rem;
  resize: vertical;
}
.form-field input:focus,
.form-field select:focus,
.form-field textarea:focus {
  outline: 2px solid var(--color-accent);
  outline-offset: 1px;
}
.page blockquote {
  margin: 1.5rem 0;
  padding: 1rem 1.25rem;
  border-left: 3px solid var(--color-accent);
  background: var(--color-line);
  border-radius: 0 4px 4px 0;
  font-size: 0.95rem;
}
.page blockquote p { margin: 0; }
.page blockquote p + p { margin-top: 0.75rem; }

/* Shared by any page/text bundle that pairs a photo with body copy
   (About, Services) via a "photo.*" resource in its content bundle —
   see layouts/about/list.html and layouts/services/list.html. */
.page--wide { max-width: var(--max-width); }
.split-layout {
  display: grid;
  gap: 2rem;
}
@media (min-width: 700px) {
  .split-layout {
    grid-template-columns: 1fr minmax(220px, 340px);
    align-items: start;
  }
}
.split-photo {
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  display: block;
  border-radius: 6px;
}
.split-bio { max-width: 60ch; }

.site-footer {
  max-width: var(--max-width);
  margin: 3rem auto 0;
  padding: var(--space);
  border-top: 1px solid var(--color-line);
  color: var(--color-muted);
  font-size: 0.85rem;
}
.site-footer a { color: var(--color-muted); }
.site-footer a:hover { color: var(--color-fg); }

/* Lightbox (see js/lightbox.js) */
.lightbox-overlay {
  position: fixed; inset: 0;
  background: rgba(0,0,0,0.92);
  display: flex; align-items: center; justify-content: center;
  z-index: 1000;
  padding: 2rem;
}
.lightbox-overlay[hidden] { display: none; }
.lightbox-figure {
  display: flex;
  flex-direction: column;
  align-items: center;
  max-width: 100%;
  max-height: 90vh;
}
.lightbox-figure img { max-width: 100%; max-height: 90vh; object-fit: contain; }
.lightbox-caption {
  color: #f2f2f2;
  font-size: 0.9rem;
  text-align: center;
  margin: 0.75rem 0 0;
}
.lightbox-caption[hidden] { display: none; }
.lightbox-close {
  position: absolute; top: 1rem; right: 1.5rem;
  color: #fff; font-size: 2rem; background: none; border: none; cursor: pointer;
  line-height: 1;
}
.lightbox-nav {
  position: absolute; top: 50%; transform: translateY(-50%);
  color: #fff; font-size: 2.5rem; background: none; border: none; cursor: pointer;
  padding: 0.5rem 1rem;
}
.lightbox-prev { left: 0.5rem; }
.lightbox-next { right: 0.5rem; }
