/* ============================================================
   Alexi Patsianis Photography
   ============================================================ */

:root {
  /* Type ------------------------------------------------------ */
  --font-sans:  'Heebo', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  /* Swap this one line to change the headline face site-wide.   */
  --font-serif: 'Times New Roman', Times, serif;

  --fs-base: 1.0625rem;   /* 17px */
  --fs-fine: 0.79rem;     /* the small-caps-ish list + note text */

  /* Colour ---------------------------------------------------- */
  --ink:        #545454;
  --ink-strong: #000000;
  --paper:      #ffffff;
  --line:       #545454;
  --line-soft:  #d6d6d6;

  /* Rhythm ---------------------------------------------------- */
  --gap:        0.7rem;          /* 11px — vertical rhythm in the sidebar */
  --logo-h:     250px;           /* the full lockup, wreath and all */

  /* Gallery. Photos sit close together, and the target row height is a
     share of the gallery width rather than a fixed figure, so rows hold
     the same few large photos whatever the screen size. --row-h is the
     floor, and the ratio is switched off on the stacked mobile layout. */
  --gallery-gap: 0.625rem;       /* 10px */
  --row-h:       300px;
  --row-ratio:   0.5;
  --pad-y:      2.5rem;          /* trimmed so the sidebar fits one screen */
  --pad-x:      3.25rem;
  --col-gap:    4.33rem;         /* 69px */
  --sidebar-w:  clamp(230px, 23vw, 300px);
  --max-w:      none;            /* fluid gutters, as the original site had */

  --ease: cubic-bezier(.4, 0, .2, 1);
}

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

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

body {
  margin: 0;
  min-width: 320px;
  overflow-x: hidden;
  background-color: var(--paper);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: var(--fs-base);
  font-weight: 400;
  line-height: 1.45;
  letter-spacing: -0.025em;
  word-wrap: break-word;
  -webkit-font-smoothing: antialiased;
}

/* The faint paper grain that sits over the whole page. */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 0;
  width: 100vw;
  height: 100vh;
  pointer-events: none;
  background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20viewBox%3D%220%200%20512%20512%22%20width%3D%22512%22%20height%3D%22512%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cfilter%20id%3D%22n%22%3E%3CfeTurbulence%20type%3D%22fractalNoise%22%20baseFrequency%3D%220.875%22%2F%3E%3CfeColorMatrix%20type%3D%22matrix%22%20values%3D%220.965%200%200%200%200%200%200.965%200%200%200%200%200%200.965%200%200%200%200%200%200.41%200%22%2F%3E%3C%2Ffilter%3E%3Crect%20filter%3D%22url(%23n)%22%20width%3D%22512%22%20height%3D%22512%22%20fill%3D%22transparent%22%2F%3E%3C%2Fsvg%3E");
  background-size: 512px;
  background-repeat: repeat;
}

img, svg { display: block; max-width: 100%; }
img { height: auto; }

a { color: inherit; }

/* Visible focus for keyboard users only. */
:focus-visible {
  outline: 2px solid var(--ink-strong);
  outline-offset: 3px;
}

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

.skip-link {
  position: absolute;
  left: -9999px;
  z-index: 100;
  padding: 0.75rem 1.25rem;
  background: var(--ink-strong);
  color: #fff;
  font-size: var(--fs-base);
  text-decoration: none;
}
.skip-link:focus {
  left: 1rem;
  top: 1rem;
}

/* ---------- Page frame -------------------------------------- */
.page {
  position: relative;
  z-index: 1;
  padding: var(--pad-y) var(--pad-x);
}

.layout {
  display: grid;
  grid-template-columns: var(--sidebar-w) minmax(0, 1fr);
  gap: var(--col-gap);
  max-width: var(--max-w);
  margin-inline: auto;
}

/* ============================================================
   Sidebar
   ============================================================ */
.sidebar > * { margin-block: var(--gap); }
.sidebar > :first-child { margin-top: 0; }
.sidebar > :last-child { margin-bottom: 0; }

.logo {
  width: auto;
  max-height: var(--logo-h);
  height: auto;
  margin-bottom: calc(var(--gap) * 0.9);
}

.tagline {
  margin-block: var(--gap);
  font-family: var(--font-sans);
  font-size: var(--fs-base);
  font-weight: 400;
  line-height: 1.25;
  letter-spacing: -0.05em;
  color: var(--ink);
}

.intro {
  font-size: 1rem;
  line-height: 1.32;
}
.intro .para { display: block; }
.intro .para + .para { margin-top: 0.8em; }
.intro em { font-style: italic; }
.intro .fine { font-size: 0.78em; }
.intro s { text-decoration-thickness: 1px; }

/* The "What I Do" list, with its little chevron markers. */
.do-list {
  margin-block: var(--gap);
  padding: 0;
  list-style: none;
  font-size: var(--fs-fine);
  font-weight: 300;
  line-height: 1.38;
  letter-spacing: 0;
}
.do-list li {
  position: relative;
  padding-left: 1.15em;
}
.do-list li + li { margin-top: 0.5em; }
.do-list li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.4em;
  width: 0.42em;
  height: 0.72em;
  background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2040%2040%22%3E%3Cpath%20d%3D%22M32%2C20v.5c-.2.3-.3.5-.6.8l-.2.2-13.4%2C13.8c-.4.3-.8.5-1.3.5s-1-.2-1.3-.5l-2.6-2.5c-.7-.7-.7-1.9%2C0-2.6l9.9-10.1-9.9-10.1c-.7-.7-.7-1.9%2C0-2.6l2.6-2.5c.7-.7%2C1.9-.7%2C2.6%2C0h0s13.5%2C13.9%2C13.5%2C13.9c.4.3.7.8.7%2C1.3Z%22%20fill%3D%22%23545454%22%2F%3E%3C%2Fsvg%3E");
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
}

.rule {
  height: 1px;
  margin-block: calc(var(--gap) * 1.1);
  border: 0;
  background-color: var(--line);
}

.booking {
  margin: 0.7rem 0 0;
  font-family: var(--font-serif);
  font-size: 0.78rem;
  font-weight: 400;
  line-height: 1.4;
  text-wrap: balance;
  letter-spacing: 0.01em;
  text-transform: uppercase;
  text-align: left;
  color: var(--ink);
}
.booking-sub {
  display: block;
  margin-top: 0.15em;
  font-size: 0.92em;
  opacity: 0.75;
}

.note {
  font-size: var(--fs-base);
  font-weight: 300;
  line-height: 1.5;
}

/* ============================================================
   Contact: trigger button and dialog
   ============================================================ */
/* Hover border gradient, after Aceternity UI's React component. A lit spot
   travels around the rim — top, right, bottom, left, one second apart — and
   becomes a blue flare on hover.

   Registering the gradient's geometry as typed custom properties is what makes
   it move: a plain transition on background-image snaps between gradients,
   but registered <percentage> and <color> properties interpolate, which is the
   job Motion does in the original. */
@property --hbg-x    { syntax: "<percentage>"; initial-value: 50%;     inherits: true; }
@property --hbg-y    { syntax: "<percentage>"; initial-value: 0%;      inherits: true; }
@property --hbg-w    { syntax: "<percentage>"; initial-value: 20.7%;   inherits: true; }
@property --hbg-h    { syntax: "<percentage>"; initial-value: 50%;     inherits: true; }
@property --hbg-tint { syntax: "<color>";      initial-value: #ffffff; inherits: true; }

.contact-open {
  position: relative;
  display: block;
  width: 100%;
  padding: 1px;
  border: 1px solid rgba(0, 0, 0, 0.12);
  border-radius: 999px;
  background-color: rgba(0, 0, 0, 0.2);
  cursor: pointer;
  overflow: visible;
  -webkit-appearance: none;
  appearance: none;
  transition:
    background-color 0.5s var(--ease),
    --hbg-x 1s linear, --hbg-y 1s linear,
    --hbg-w 1s linear, --hbg-h 1s linear,
    --hbg-tint 1s linear;
}
.contact-open:hover { background-color: rgba(0, 0, 0, 0.1); }
.contact-open:active { transform: translateY(1px); }

/* A slow breath, just enough to catch the eye. Animating the standalone
   `scale` property rather than `transform` leaves the :active nudge above
   free to apply at the same time. Hovering hands the attention over to the
   flare, so the breathing stops. */
@keyframes contact-breathe {
  0%, 100% { scale: 1; }
  50%      { scale: 1.015; }
}

.contact-open {
  animation: contact-breathe 3.2s ease-in-out infinite;
}
.contact-open:hover,
.contact-open:focus-visible {
  animation-play-state: paused;
}

/* The travelling light. */
.contact-open::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: inherit;
  overflow: hidden;
  filter: blur(2px);
  background: radial-gradient(
    var(--hbg-w) var(--hbg-h) at var(--hbg-x) var(--hbg-y),
    var(--hbg-tint) 0%,
    rgba(255, 255, 255, 0) 100%
  );
}

/* Masks the glow back to a rim, so it reads as a lit border. */
.contact-open::after {
  content: '';
  position: absolute;
  inset: 2px;
  z-index: 1;
  border-radius: 999px;
  background-color: var(--ink-strong);
}

.contact-open-face {
  position: relative;
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 3.4rem;
  padding: 0 1.5rem;
  border-radius: inherit;
  background-color: var(--ink-strong);
  color: #fff;
  font-family: var(--font-sans);
  font-size: 1.05rem;
  font-weight: 400;
  letter-spacing: 0.005em;
}

.contact-dialog {
  width: min(28rem, calc(100vw - 2rem));
  max-height: calc(100dvh - 2rem);
  padding: 0;
  border: 0;
  border-radius: 0.4rem;
  background-color: var(--paper);
  color: var(--ink);
  box-shadow: 0 1.5rem 4rem rgba(0, 0, 0, 0.22);
  overflow: visible;
}

.contact-dialog::backdrop {
  /* Lightened a little from the flat scrim it replaced: at full strength the
     darkness hides the blur doing its work. */
  background-color: rgba(28, 28, 28, 0.42);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}

.contact-dialog-panel {
  position: relative;
  max-height: calc(100dvh - 2rem);
  overflow-y: auto;
  padding: 2.25rem 2rem 2rem;
}

.contact-dialog-title {
  margin: 0 0 0.9rem;
  font-family: var(--font-serif);
  font-size: 1.35rem;
  font-weight: 400;
  line-height: 1.2;
  letter-spacing: -0.02em;
  text-transform: uppercase;
}

.contact-dialog .note {
  margin: 0 0 1.4rem;
  font-size: var(--fs-fine);
  line-height: 1.55;
}

.contact-dialog-close {
  position: absolute;
  top: 0.6rem;
  right: 0.6rem;
  display: grid;
  place-items: center;
  width: 2.25rem;
  height: 2.25rem;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background-color: transparent;
  color: var(--ink-strong);
  cursor: pointer;
  transition: background-color 0.2s var(--ease);
}
.contact-dialog-close:hover { background-color: rgba(0, 0, 0, 0.07); }
.contact-dialog-close svg { width: 1.1rem; height: 1.1rem; }

/* Without JavaScript the dialog never opens, so render it inline in the
   sidebar instead and drop the button that would do nothing. */
.no-js .contact-open { display: none; }
.no-js .contact-dialog {
  display: block;
  width: auto;
  max-height: none;
  margin: var(--gap) 0;
  box-shadow: none;
  border-radius: 0;
}
.no-js .contact-dialog-panel { max-height: none; overflow: visible; padding: 0; }
.no-js .contact-dialog-close { display: none; }

/* ============================================================
   Contact form
   ============================================================ */
.contact-form {
  display: grid;
  gap: 0.75rem;
  margin-block: var(--gap);
  font-weight: 300;
}

.contact-form label { /* placeholders carry the label visually */
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.contact-form input[type="text"],
.contact-form input[type="email"],
.contact-form textarea {
  display: block;
  width: 100%;
  padding: 0 0.9rem;
  border: 1px solid var(--line);
  border-radius: 0.26rem;
  background-color: transparent;
  color: var(--ink);
  font: inherit;
  letter-spacing: -0.025em;
  transition: border-color 0.2s var(--ease), box-shadow 0.2s var(--ease);
}

.contact-form input[type="text"],
.contact-form input[type="email"] {
  height: 2.65rem;
  line-height: 2.55rem;
}

.contact-form textarea {
  padding-block: 0.7rem;
  min-height: 10.5rem;
  line-height: 1.45;
  resize: vertical;
}

.contact-form ::placeholder {
  color: var(--ink);
  opacity: 0.7;
}

.contact-form input:hover,
.contact-form textarea:hover {
  border-color: var(--ink-strong);
}

.contact-form input:focus,
.contact-form textarea:focus {
  outline: 0;
  border-color: var(--ink-strong);
  box-shadow: 0 0 0 1px var(--ink-strong);
}

/* Only scold a field once the person has actually left it wrong. */
.contact-form input.is-invalid,
.contact-form textarea.is-invalid {
  border-color: #b3261e;
  box-shadow: 0 0 0 1px #b3261e;
}

/* Submit sits at the trailing edge, where a dialog's confirming action is
   conventionally found. */
.contact-form .actions {
  display: flex;
  justify-content: flex-end;
  margin-top: 0.4rem;
}

.contact-form button {
  min-height: 2.65rem;
  padding: 0 1.4rem;
  border: 0;
  border-radius: 0.26rem;
  background-color: var(--ink-strong);
  color: #fff;
  font: inherit;
  font-weight: 300;
  letter-spacing: -0.025em;
  cursor: pointer;
  transition: opacity 0.2s var(--ease), transform 0.2s var(--ease);
}
.contact-form button:hover { opacity: 0.82; }
.contact-form button:active { transform: translateY(1px); }
.contact-form button[disabled] { opacity: 0.5; cursor: progress; }

.form-status {
  margin: 0;
  font-size: var(--fs-fine);
  line-height: 1.5;
  min-height: 1.2em;
}
.form-status.is-ok    { color: var(--ink-strong); }
.form-status.is-error { color: #b3261e; }

.hp {
  position: absolute;
  left: -9999px;
  opacity: 0;
}

/* ---------- Socials + colophon ------------------------------ */
.socials {
  display: flex;
  gap: 0.6rem;
  margin-block: calc(var(--gap) * 1.05);
  padding: 0;
  list-style: none;
}
.socials a {
  display: grid;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  margin-left: -0.6rem;   /* optically align the first glyph to the edge */
  color: var(--ink-strong);
  transition: opacity 0.2s var(--ease);
}
.socials a:hover { opacity: 0.6; }
.socials svg {
  width: 1.3rem;
  height: 1.3rem;
  fill: currentColor;
}

.colophon {
  margin-top: 0.55rem;
  font-size: 0.68rem;
  font-weight: 300;
  line-height: 1.5;
  color: var(--ink);
  opacity: 0.65;
  letter-spacing: 0;
}

/* ============================================================
   Gallery — justified rows, no cropping

   Each item carries --r (its aspect ratio). main.js measures the
   container and sets an exact width per item so every row fills the
   width at a shared height. The rules below are the no-JS fallback:
   flex-basis r × row-height with flex-grow r gets close on its own,
   and max-width stops a sparse row ballooning.
   ============================================================ */
/* The gallery is pinned to the height of the sidebar and scrolls inside
   itself, so the page stays about one sidebar tall instead of running on for
   thousands of pixels. The scroller is absolutely positioned, which keeps its
   32 photos out of the grid row height calculation — the row is then sized by
   the sidebar alone. */
.gallery-wrap {
  position: relative;
}

.gallery-scroll {
  position: absolute;
  inset: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  /* Reserve the scrollbar track so its arrival cannot change the width that
     the justify pass measured. */
  scrollbar-gutter: stable;
}

/* The last row fades into the paper and a "Scroll" cue sits over it, so the
   gallery reads as continuing rather than ending at the fold. Both belong to
   the scroll box, so neither exists on the stacked layout, where the gallery
   runs down the page normally. Both retire once there is nothing left below. */
.gallery-hint { display: none; }

@media (min-width: 981px) {
  .gallery-wrap::after {
    content: '';
    position: absolute;
    right: 0;
    bottom: 0;
    left: 0;
    height: 7rem;
    z-index: 2;
    pointer-events: none;
    background: linear-gradient(
      to top,
      var(--paper) 10%,
      rgba(255, 255, 255, 0) 100%
    );
    transition: opacity 0.4s var(--ease);
  }

  .gallery-hint {
    position: absolute;
    bottom: 1.15rem;
    left: 50%;
    transform: translateX(-50%);
    z-index: 3;
    display: flex;
    align-items: center;
    gap: 0.4rem;
    margin: 0;
    pointer-events: none;
    font-size: 0.66rem;
    font-weight: 300;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: var(--ink);
    opacity: 0.8;
    transition: opacity 0.4s var(--ease);
  }

  .gallery-hint svg {
    width: 0.72rem;
    height: 0.72rem;
    animation: hint-nudge 2.2s ease-in-out infinite;
  }

  /* Said once. After the first scroll the word has done its job. */
  .gallery-wrap.is-scrolled .gallery-hint { opacity: 0; }

  /* At the end there is nothing more to tease, so the fade lifts. */
  .gallery-wrap.is-at-end::after { opacity: 0; }
}

@keyframes hint-nudge {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(3px); }
}

.gallery-scroll:focus-visible {
  outline: 2px solid var(--ink-strong);
  outline-offset: 2px;
}

.gallery {
  display: flex;
  flex-wrap: wrap;
  gap: var(--gallery-gap);
  margin: 0;
  padding: 0;
  list-style: none;
}

.gallery li {
  flex: var(--r) 1 calc(var(--r) * var(--row-h));
  max-width: calc(var(--r) * var(--row-h) * 1.7);
  min-width: 0;
}

/* Once main.js has measured, widths are exact — stop flex improvising. */
.gallery.is-justified li {
  flex: 0 0 auto;
  max-width: none;
}

.gallery a {
  display: block;
  position: relative;
  aspect-ratio: var(--r);
  overflow: hidden;
  background-color: #efefed;
  cursor: zoom-in;
}

.gallery img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.55s var(--ease), opacity 0.4s var(--ease);
}

.gallery a:hover img,
.gallery a:focus-visible img { transform: scale(1.035); }

/* ============================================================
   Lightbox
   ============================================================ */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: grid;
  /* Explicit 1fr track so the stage gets a definite height to shrink into;
     with an auto row the image would size to its natural width and overflow. */
  grid-template: 1fr / 1fr;
  padding: clamp(1rem, 4vw, 3.5rem);
  background-color: var(--paper);
  opacity: 0;
  transition: opacity 0.28s var(--ease);
}
.lightbox[hidden] { display: none; }
.lightbox.is-open { opacity: 1; }

.lightbox-stage {
  position: relative;
  min-width: 0;
  min-height: 0;
}

/* inset:0 + margin:auto centres the photo and, unlike a nested grid, gives
   max-width/max-height a definite box to be a percentage of. */
.lightbox-img {
  position: absolute;
  inset: 0;
  margin: auto;
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  opacity: 0;
  transition: opacity 0.25s var(--ease);
}
.lightbox-img.is-ready { opacity: 1; }

.lightbox-count {
  position: absolute;
  left: 50%;
  bottom: 1.1rem;
  transform: translateX(-50%);
  margin: 0;
  font-size: 0.78rem;
  font-weight: 300;
  letter-spacing: 0.08em;
  color: var(--ink);
}

.lightbox-btn {
  position: absolute;
  display: grid;
  place-items: center;
  width: 3rem;
  height: 3rem;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background-color: transparent;
  color: var(--ink-strong);
  cursor: pointer;
  transition: opacity 0.2s var(--ease), background-color 0.2s var(--ease);
}
.lightbox-btn:hover { background-color: rgba(0, 0, 0, 0.06); }
.lightbox-btn svg { width: 1.5rem; height: 1.5rem; }
.lightbox-btn[disabled] { opacity: 0.25; cursor: default; background: none; }

.lightbox-prev  { left: 0.75rem;  top: 50%; transform: translateY(-50%); }
.lightbox-next  { right: 0.75rem; top: 50%; transform: translateY(-50%); }
.lightbox-close { right: 0.75rem; top: 0.75rem; }

body.is-locked { overflow: hidden; }

/* ============================================================
   Desktop: the whole page is exactly one screen

   The frame is locked to the viewport and never scrolls. The gallery
   scrolls inside itself, and the sidebar gets its own overflow as a
   safety valve for short screens — it is sized to fit without it.
   Skipped when JavaScript is off, where the contact dialog renders
   inline below the layout and the page needs to scroll to reach it.
   ============================================================ */
@media (min-width: 981px) {
  html:not(.no-js), html:not(.no-js) body { height: 100%; }

  html:not(.no-js) .page {
    height: 100dvh;
    overflow: hidden;
  }

  html:not(.no-js) .layout { height: 100%; }

  html:not(.no-js) .sidebar {
    overflow-y: auto;
    scrollbar-gutter: stable;
    padding-right: 0.25rem;
  }

  /* Shorter laptop screens (1366x768 and friends): step the whole sidebar
     down a notch so it still fits without an inner scrollbar. */
  @media (max-height: 850px) {
    :root {
      --pad-y:   1.5rem;
      --gap:     0.5rem;
      --logo-h:  235px;
      --fs-base: 0.92rem;
      --fs-fine: 0.7rem;
    }
    .intro { font-size: 0.92rem; }
    .contact-open-face { min-height: 3rem; font-size: 1rem; }
  }
}

/* ============================================================
   Responsive
   ============================================================ */
@media (max-width: 1280px) {
  :root { --row-h: 260px; }
}

@media (max-width: 980px) {
  :root {
    --pad-y: 3.5rem;
    --pad-x: 2rem;
    --col-gap: 3rem;
    --row-h: 230px;
    /* Stacked, rows go back to a fixed height: scaling them with the full
       screen width would make the photos big and the page very long. */
    --row-ratio: 0;
  }
  .layout {
    grid-template-columns: minmax(0, 1fr);
    max-width: 640px;
  }
  /* Stacked, there is no sidebar to match, and a scroll box inside a scrolling
     page is miserable on a phone. Let the gallery run down the page instead. */
  .gallery-wrap { position: static; }
  .gallery-scroll {
    position: static;
    overflow: visible;
    scrollbar-gutter: auto;
  }
  .logo {
    max-width: 320px;
    margin-inline: auto;
  }
  .gallery li { max-width: calc(var(--r) * var(--row-h) * 2.4); }
}

@media (max-width: 560px) {
  :root {
    --pad-y: 2.75rem;
    --pad-x: 1.5rem;
    --row-h: 180px;
    --gap: 0.8rem;
    --gallery-gap: 0.375rem;
  }
  .lightbox-prev  { left: 0.25rem; }
  .lightbox-next  { right: 0.25rem; }
  .lightbox-close { right: 0.25rem; }
}

/* ---------- Motion preferences ------------------------------ */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  .gallery a:hover img { transform: none; }
}
