@charset "utf-8";
/* ============================================================================
   Fernando A. Torres — site stylesheet
   One file. Replaces 13 divergent inline <style> forks.
   Cascade layers do all the specificity work. !important appears only where
   it is the correct tool: the reduced-motion override, and the two utilities
   that must win unconditionally (.visually-hidden, [hidden]). No layout rule
   uses it — the previous build needed five to fight its own inline styles.
   ========================================================================== */

@layer reset, tokens, base, layout, components, utilities;

/* ---------------------------------------------------------------- reset -- */
@layer reset {
  *, *::before, *::after { box-sizing: border-box; }
  * { margin: 0; padding: 0; }
  html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
  body { -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; }
  img, picture, svg, video { display: block; max-width: 100%; }
  button, input, select, textarea { font: inherit; color: inherit; }
  button { background: none; border: 0; cursor: pointer; }
  ul[class], ol[class] { list-style: none; }
  :target { scroll-margin-block-start: 5rem; }
}

/* --------------------------------------------------------------- tokens -- */
@layer tokens {
  :root {
    /* Palette — carried over from the original build, formalised.
       Washi cream ground, sumi ink text, indigo headings, vermilion accent. */
    --ink:        #221E19;
    --ink-soft:   #6C6456;
    --cream:      #F3ECDE;
    --card:       #FAF6EC;
    --card-warm:  #FDF8EC;
    --line:       #DCD1BC;
    --line-soft:  #E6DCC2;
    --indigo:     #2A3D5C;
    --verm:       #BE4327;
    --gold:       #9C7C36;
    --sepia:      #7A5A2E;
    --sepia-deep: #5A3D1A;

    --surface-scrim: rgb(243 236 222 / 0.82);
    --shadow-sm: 0 2px 6px rgb(34 30 25 / 0.08);
    --shadow-md: 0 8px 18px rgb(34 30 25 / 0.16);
    --shadow-lg: 0 16px 30px rgb(34 30 25 / 0.24);
    --shadow-xl: 0 22px 55px rgb(34 30 25 / 0.28);

    /* Type families */
    --font-display: "Cormorant Garamond", Georgia, "Times New Roman", serif;
    --font-body:    "EB Garamond", Georgia, "Times New Roman", serif;
    --font-hand:    "Special Elite", "Courier New", monospace;
    --font-ui:      ui-sans-serif, -apple-system, "Segoe UI", Roboto, sans-serif;

    /* Fluid type scale — clamp() between 320px and 1440px viewports.
       Replaces min(Nvw, Xpx), which collapsed to ~5px on small phones. */
    --text-2xs: clamp(0.6875rem, 0.664rem + 0.116vw, 0.75rem);
    --text-xs:  clamp(0.75rem,  0.723rem + 0.134vw, 0.8125rem);
    --text-sm:  clamp(0.875rem, 0.848rem + 0.134vw, 0.9375rem);
    --text-base:clamp(1rem,     0.964rem + 0.179vw, 1.0625rem);
    --text-md:  clamp(1.0625rem,1.009rem + 0.268vw, 1.1875rem);
    --text-lg:  clamp(1.1875rem,1.089rem + 0.491vw, 1.4375rem);
    --text-xl:  clamp(1.375rem, 1.196rem + 0.893vw, 1.875rem);
    --text-2xl: clamp(1.625rem, 1.339rem + 1.429vw, 2.375rem);
    --text-3xl: clamp(1.875rem, 1.429rem + 2.232vw, 3rem);

    /* Spacing rhythm — 4px base, fluid at the top end */
    --sp-1: 0.25rem;  --sp-2: 0.5rem;   --sp-3: 0.75rem;  --sp-4: 1rem;
    --sp-5: 1.25rem;  --sp-6: 1.5rem;   --sp-8: 2rem;     --sp-10: 2.5rem;
    --sp-12: 3rem;    --sp-16: 4rem;
    --sp-section: clamp(2.5rem, 1.5rem + 5vw, 5rem);

    /* Measure + shell widths.
       --wrap holds prose; --wrap-wide lets galleries breathe on ultra-wide. */
    --wrap:      69.5rem;   /* 1112px */
    --wrap-wide: 90rem;     /* 1440px */
    --wrap-prose: 68ch;
    --gutter: clamp(1rem, 0.5rem + 2.5vw, 1.75rem);

    --radius-sm: 2px;
    --radius-md: 3px;

    /* ---- motion ------------------------------------------------------
       Springs are real damped-oscillator curves sampled into linear(),
       so overshoot and settle happen on the compositor with no JS. */
    --ease:            cubic-bezier(0.22, 0.61, 0.36, 1);   /* general out */
    --ease-out-quart:  cubic-bezier(0.165, 0.84, 0.44, 1);  /* exits */
    --ease-in-out:     cubic-bezier(0.65, 0, 0.35, 1);      /* reversible */
    --ease-lift:       cubic-bezier(0.34, 1.2, 0.36, 1);    /* small hovers */

    --ease-spring: linear(0.0000 0.0%, 0.1170 4.2%, 0.3711 8.3%, 0.6482 12.5%,
      0.8791 16.7%, 1.0347 20.8%, 1.1148 25.0%, 1.1354 29.2%, 1.1181 33.3%,
      1.0831 37.5%, 1.0457 41.7%, 1.0150 45.8%, 0.9945 50.0%, 0.9841 54.2%,
      0.9817 58.3%, 0.9842 62.5%, 0.9890 66.7%, 0.9941 70.8%, 0.9982 75.0%,
      1.0009 79.2%, 1.0022 83.3%, 1.0025 87.5%, 1.0021 91.7%, 1.0014 95.8%, 1 100%);

    --ease-spring-snap: linear(0.0000 0.0%, 0.1580 5.0%, 0.4756 10.0%,
      0.7833 15.0%, 0.9989 20.0%, 1.1072 25.0%, 1.1312 30.0%, 1.1060 35.0%,
      1.0634 40.0%, 1.0241 45.0%, 0.9976 50.0%, 0.9849 55.0%, 0.9829 60.0%,
      0.9867 65.0%, 0.9924 70.0%, 0.9974 75.0%, 1.0006 80.0%, 1.0021 85.0%,
      1.0022 90.0%, 1.0017 95.0%, 1 100%);

    --ease-spring-soft: linear(0.0000 0.0%, 0.0643 5.6%, 0.2008 11.1%,
      0.3553 16.7%, 0.5005 22.2%, 0.6249 27.8%, 0.7254 33.3%, 0.8033 38.9%,
      0.8619 44.4%, 0.9049 50.0%, 0.9357 55.6%, 0.9573 61.1%, 0.9722 66.7%,
      0.9823 72.2%, 0.9890 77.8%, 0.9934 83.3%, 0.9961 88.9%, 1 100%);

    /* Duration scales with travel distance — a 4px nudge and a full-screen
       dialog should not share a number. */
    --dur-instant: 90ms;
    --dur-fast:    160ms;
    --dur:         260ms;
    --dur-slow:    520ms;
    --dur-reveal:  760ms;
    --dur-spring:  620ms;

    /* Magnetic pointer offset, written by site.js, consumed by transforms. */
    --mx: 0px;
    --my: 0px;
    --lift: 0px;

    --tap: 44px; /* minimum touch target */
  }

  /* Honour reduced-motion globally rather than per-component. */
  @media (prefers-reduced-motion: reduce) {
    :root {
      --dur-instant: 1ms; --dur-fast: 1ms; --dur: 1ms; --dur-slow: 1ms;
      --dur-reveal: 1ms; --dur-spring: 1ms;
      --ease-spring: linear; --ease-spring-snap: linear;
      --ease-spring-soft: linear; --ease-lift: linear;
    }
    *, *::before, *::after {
      animation-duration: 1ms !important;
      animation-iteration-count: 1 !important;
      transition-duration: 1ms !important;
      scroll-behavior: auto !important;
      animation-timeline: none !important;
    }
  }
}

/* ----------------------------------------------------------------- base -- */
@layer base {
  html { scroll-behavior: smooth; }

  body {
    background-color: var(--cream);
    background-image: url("../img/washi-light.jpg");
    background-repeat: repeat;
    color: var(--ink);
    font-family: var(--font-body);
    font-size: var(--text-base);
    line-height: 1.65;
    min-height: 100svh;
    display: flex;
    flex-direction: column;
    overflow-wrap: break-word;
  }

  /* background-attachment:fixed forces a full-surface repaint on every scroll
     frame in iOS Safari. A fixed pseudo-layer is composited instead. */
  body::before {
    content: "";
    position: fixed;
    inset: 0;
    z-index: -1;
    background-image: url("../img/washi-light.jpg");
    background-repeat: repeat;
    pointer-events: none;
  }
  @supports (background-attachment: fixed) {
    @media (hover: hover) and (pointer: fine) {
      body { background-attachment: fixed; }
      body::before { display: none; }
    }
  }

  h1, h2, h3, h4 {
    font-family: var(--font-display);
    font-weight: 600;
    line-height: 1.15;
    color: var(--indigo);
    text-wrap: balance;
  }
  h1 { font-size: var(--text-3xl); }
  h2 { font-size: var(--text-2xl); }
  h3 { font-size: var(--text-xl); }

  p { text-wrap: pretty; }
  p + p { margin-block-start: var(--sp-4); }

  a { color: inherit; text-decoration: none; }
  a:hover { color: var(--verm); }

  /* Prose links stay underlined — colour alone is not an accessible affordance. */
  .prose a, main p > a {
    text-decoration: underline;
    text-decoration-thickness: 1px;
    text-underline-offset: 0.18em;
    text-decoration-color: color-mix(in srgb, var(--verm) 45%, transparent);
  }
  .prose a:hover, main p > a:hover { text-decoration-color: var(--verm); }

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

  ::selection { background: color-mix(in srgb, var(--gold) 30%, transparent); }
}

/* --------------------------------------------------------------- layout -- */
@layer layout {
  .wrap {
    width: 100%;
    max-width: var(--wrap);
    margin-inline: auto;
    padding-inline: var(--gutter);
  }
  .wrap--wide  { max-width: var(--wrap-wide); }
  .wrap--prose { max-width: var(--wrap-prose); }

  main { flex: 1 0 auto; display: block; }

  .skip-link {
    position: absolute;
    left: var(--sp-4);
    top: -4rem;
    z-index: 200;
    background: var(--card);
    color: var(--indigo);
    font-family: var(--font-ui);
    font-size: var(--text-sm);
    padding: var(--sp-3) var(--sp-5);
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-md);
    transition: top var(--dur) var(--ease);
  }
  .skip-link:focus { top: var(--sp-4); }

  /* ---- header ---- */
  .site-header {
    position: sticky;
    top: 0;
    z-index: 50;
    padding-block: var(--sp-3);
    background-color: var(--cream);
    background-image:
      linear-gradient(var(--surface-scrim), var(--surface-scrim)),
      url("../img/washi-header.jpg");
    background-size: cover;
    border-block-end: 1px solid var(--line);
    -webkit-backdrop-filter: saturate(1.1) blur(6px);
    backdrop-filter: saturate(1.1) blur(6px);
  }
  .site-header > .wrap {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-4);
  }

  /* Scroll-linked hairline + header condensation. Both run on native scroll
     timelines, so they never touch the main thread and never read layout. */
  .site-header::after {
    content: "";
    position: absolute;
    inset-block-end: -1px;
    inset-inline-start: 0;
    height: 2px;
    width: 100%;
    transform-origin: 0 50%;
    transform: scaleX(0);
    background: linear-gradient(90deg, var(--verm), var(--gold) 60%, transparent);
  }
  @supports (animation-timeline: scroll()) {
    @media (prefers-reduced-motion: no-preference) {
      .site-header::after {
        animation: scroll-rule linear both;
        animation-timeline: scroll(root block);
      }
      /* Condenses over the first 140px of scroll, then holds. */
      .site-header {
        animation: header-condense linear both;
        animation-timeline: scroll(root block);
        animation-range: 0 140px;
      }
      .wordmark {
        animation: wordmark-tighten linear both;
        animation-timeline: scroll(root block);
        animation-range: 0 140px;
      }
    }
  }
  @keyframes scroll-rule { to { transform: scaleX(1); } }
  @keyframes header-condense {
    to {
      padding-block: var(--sp-2);
      box-shadow: 0 6px 20px rgb(34 30 25 / 0.10);
      background-color: color-mix(in srgb, var(--cream) 92%, white);
    }
  }
  @keyframes wordmark-tighten {
    to { letter-spacing: 0.11em; }
  }

  .wordmark {
    font-family: var(--font-display);
    font-size: var(--text-md);
    font-weight: 600;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--ink);
    white-space: nowrap;
  }
  .wordmark:hover { color: var(--verm); }

  /* ---- nav ---- */
  .site-nav ul {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    align-items: center;
    gap: var(--sp-1) var(--sp-5);
    font-family: var(--font-ui);
    font-size: var(--text-xs);
    letter-spacing: 0.02em;
    color: var(--ink-soft);
  }
  .site-nav a {
    display: inline-flex;
    align-items: center;
    min-height: var(--tap);
    position: relative;
  }
  .site-nav a::after {
    content: "";
    position: absolute;
    inset-inline: 0;
    inset-block-end: 0.6rem;
    height: 1px;
    background: var(--verm);
    transform: scaleX(0);
    transform-origin: 0 50%;
    transition: transform var(--dur) var(--ease);
  }
  .site-nav a:hover::after,
  .site-nav a:focus-visible::after { transform: scaleX(1); }
  .site-nav a[aria-current="page"] { color: var(--indigo); }
  .site-nav a[aria-current="page"]::after { transform: scaleX(1); background: var(--gold); }

  /* Below 900px the nav becomes a horizontally scrollable rail rather than
     wrapping to three rows and shoving the wordmark off-centre. */
  @media (max-width: 56.25rem) {
    .site-header > .wrap { flex-wrap: wrap; row-gap: var(--sp-1); }
    .site-nav {
      order: 3;
      width: 100%;
      overflow-x: auto;
      overscroll-behavior-inline: contain;
      scrollbar-width: none;
      -webkit-mask-image: linear-gradient(90deg, transparent, #000 var(--sp-4),
                          #000 calc(100% - var(--sp-8)), transparent);
      mask-image: linear-gradient(90deg, transparent, #000 var(--sp-4),
                  #000 calc(100% - var(--sp-8)), transparent);
    }
    .site-nav::-webkit-scrollbar { display: none; }
    .site-nav ul { flex-wrap: nowrap; justify-content: flex-start; }
    .site-nav a { white-space: nowrap; }
  }

  /* ---- breadcrumb ---- */
  .breadcrumb {
    font-family: var(--font-ui);
    font-size: var(--text-xs);
    color: var(--ink-soft);
    padding-block-start: var(--sp-5);
  }
  .breadcrumb ol { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); }
  .breadcrumb li { display: flex; align-items: center; gap: var(--sp-2); }
  .breadcrumb li + li::before { content: "/"; color: var(--line); }
  .breadcrumb [aria-current="page"] { color: var(--ink); }

  /* ---- footer ---- */
  .site-footer {
    flex-shrink: 0;
    margin-block-start: var(--sp-section);
    border-block-start: 1px solid var(--line);
    padding-block: var(--sp-8);
    text-align: center;
  }
  .footer-links {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--sp-2) var(--sp-6);
    font-family: var(--font-ui);
    font-size: var(--text-xs);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ink-soft);
    margin-block-end: var(--sp-5);
  }
  .footer-links a { display: inline-flex; align-items: center; min-height: var(--tap); }
  .colophon {
    font-family: var(--font-ui);
    font-size: var(--text-2xs);
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--ink-soft);
  }
}

/* ----------------------------------------------------------- components -- */
@layer components {

  .eyebrow {
    font-family: var(--font-ui);
    font-size: var(--text-2xs);
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--ink-soft);
  }

  /* ---- lead postcard -------------------------------------------------- */
  /* Container query, not viewport query: the card responds to the space it
     is actually given, so it survives being dropped into any column. */
  .postcard-shell {
    container-type: inline-size;
    container-name: postcard;
    padding-block: var(--sp-6) var(--sp-2);
  }

  .postcard {
    position: relative;
    max-width: 56.25rem;
    margin-inline: auto;
    margin-block-end: var(--sp-4);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-xl);
  }
  .postcard__base {
    width: 100%;
    height: auto;
    border-radius: var(--radius-md);
  }
  /* Position comes in as custom properties from the generator, so the stacked
     layout below can simply redefine them. The original overrode four inline
     styles with !important; none is needed now. */
  .postcard__photo {
    position: absolute;
    left: var(--pc-l, 0);
    top: var(--pc-t, 0);
    width: var(--pc-w, 100%);
    height: var(--pc-h, auto);
    object-fit: cover;
    border-radius: 1px;
    filter: sepia(0.1) saturate(1.05);
  }
  /* No parallax between the photograph and the card. A print mounted on a
     postcard cannot slide relative to the card it is stuck to, and faking it
     is what makes a paper object read as a web surface. The whole card
     settles instead — one object, moving as one. */
  @supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {
      .postcard {
        animation: postcard-settle var(--dur-reveal) var(--ease-spring-soft) both;
        animation-timeline: view();
        animation-range: entry 0% cover 18%;
      }
    }
  }
  @keyframes postcard-settle {
    from { opacity: 0; transform: translate3d(0, 24px, 0) rotate(-0.4deg); }
    to   { opacity: 1; transform: none; }
  }
  .postcard__msg {
    position: absolute;
    right: 4%;
    top: 13%;
    width: 34%;
    color: #2B2417;
  }
  .postcard__place {
    font-family: var(--font-display);
    font-weight: 600;
    color: var(--indigo);
    line-height: 1;
    font-size: clamp(1.125rem, 0.6rem + 2.6cqi, 1.875rem);
  }
  .postcard__region {
    font-family: var(--font-hand);
    color: var(--sepia);
    margin-block: var(--sp-1) var(--sp-2);
    font-size: clamp(0.6875rem, 0.6rem + 0.55cqi, 0.75rem);
  }
  .postcard__text {
    font-size: clamp(0.8125rem, 0.72rem + 0.75cqi, 0.875rem);
    line-height: 1.5;
  }
  .postcard__text p + p { margin-block-start: var(--sp-2); }
  .postcard__sign {
    font-family: var(--font-display);
    font-style: italic;
    color: var(--sepia-deep);
    margin-block-start: var(--sp-2);
    font-size: clamp(0.9375rem, 0.8rem + 0.9cqi, 1.0625rem);
  }

  /* Stacked mode. Overlaying type on a postcard cannot stay legible below
     ~48rem, so the card becomes a real card: photo on top, message beneath. */
  @container postcard (max-width: 47.5rem) {
    .postcard {
      background: var(--card-warm);
      border: 1px solid var(--line-soft);
      padding: var(--sp-3);
      box-shadow: var(--shadow-lg);
      --pc-l: auto;
      --pc-t: auto;
      --pc-w: 100%;
      --pc-h: clamp(11rem, 45cqi, 15rem);
    }
    .postcard__base { display: none; }
    .postcard__photo { position: relative; }
    .postcard__msg {
      position: static;
      width: auto;
      padding: var(--sp-4) var(--sp-3) var(--sp-2);
    }
    /* Type returns to the document scale — no viewport lock. */
    .postcard__place  { font-size: var(--text-xl); }
    .postcard__region { font-size: var(--text-xs); }
    .postcard__text   { font-size: var(--text-base); line-height: 1.6; }
    .postcard__sign   { font-size: var(--text-md); }
  }
  /* Viewport fallback for engines without container queries. */
  @supports not (container-type: inline-size) {
    @media (max-width: 47.5rem) {
      .postcard { background: var(--card-warm); border: 1px solid var(--line-soft);
                  padding: var(--sp-3); --pc-l: auto; --pc-t: auto;
                  --pc-w: 100%; --pc-h: 13rem; }
      .postcard__base { display: none; }
      .postcard__photo { position: relative; }
      .postcard__msg { position: static; width: auto; padding: var(--sp-4) var(--sp-3) var(--sp-2); }
      .postcard__place { font-size: var(--text-xl); }
      .postcard__text  { font-size: var(--text-base); }
      .postcard__sign  { font-size: var(--text-md); }
    }
  }

  /* ---- section heading ------------------------------------------------ */
  .section-head {
    text-align: center;
    margin-block: var(--sp-10) var(--sp-1);
    display: grid;
    gap: var(--sp-2);
    justify-items: center;
  }
  .section-head h2 { font-size: var(--text-2xl); }
  .section-head p { color: var(--ink-soft); font-style: italic; max-width: 46ch; }
  /* A hairline that continues the header rule's language rather than
     introducing a third divider style. */
  .section-head::after {
    content: "";
    width: 3.5rem;
    height: 1px;
    background: linear-gradient(90deg, transparent, var(--line) 20%, var(--line) 80%, transparent);
  }

  /* ---- gallery -------------------------------------------------------- */
  .gallery {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(16.25rem, 100%), 1fr));
    gap: clamp(0.875rem, 0.5rem + 1.4vw, 1.5rem);
    margin-block: var(--sp-6) var(--sp-3);
    /* Skip layout/paint for off-screen rows on long galleries. */
    content-visibility: auto;
    contain-intrinsic-size: auto 900px;
  }

  .tile {
    position: relative;
    isolation: isolate;
    display: block;
    width: 100%;
    text-align: left;
    background: var(--card-warm);
    border: 1px solid var(--line-soft);
    border-radius: var(--radius-sm);
    padding: var(--sp-2) var(--sp-2) var(--sp-3);
    box-shadow: var(--shadow-md);
    color: inherit;
    /* translate3d promotes the tile to its own layer. Rotation lives in the
       same transform so hover unwinds it in a single interpolation. */
    transform:
      translate3d(var(--mx, 0px), calc(var(--my, 0px) + var(--lift, 0px)), 0)
      rotate(var(--tilt, 0deg));
    transition:
      transform var(--dur-spring) var(--ease-spring),
      border-color var(--dur) var(--ease);
  }
  /* Scattered-snapshot tilt. nth-child on a grid is stable and cheap. */
  .tile:nth-child(3n)   { --tilt: -1.2deg; }
  .tile:nth-child(3n+1) { --tilt: 1deg; }
  .tile:nth-child(4n)   { --tilt: 0.6deg; }

  /* The lifted shadow sits on a pseudo-element and crossfades by opacity.
     Transitioning box-shadow itself repaints the element every frame. */
  .tile::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    border-radius: inherit;
    box-shadow: var(--shadow-lg);
    opacity: 0;
    transition: opacity var(--dur) var(--ease);
    pointer-events: none;
  }
  .tile:hover, .tile:focus-visible {
    --lift: -6px;
    --tilt: 0deg;
    border-color: var(--line);
  }
  .tile:hover::after, .tile:focus-visible::after { opacity: 1; }
  .tile:active { --lift: -2px; transition-duration: var(--dur-instant); }

  /* Frame clips the photograph so it can scale inside a fixed box. */
  .tile__frame {
    display: block;
    overflow: hidden;
    border-radius: 1px;
    background: color-mix(in srgb, var(--line) 40%, transparent);
  }
  /* The photograph does not zoom inside its frame. A snapshot fixed to an
     album page has no such degree of freedom — that gesture belongs to video
     thumbnails. The card lifts and squares up; the print rides along. */
  .tile img {
    width: 100%;
    aspect-ratio: 4 / 3;
    height: auto;
    object-fit: cover;
    filter: sepia(0.06) saturate(1.04);
  }

  .tile figcaption,
  .tile .tile__cap {
    display: grid;
    gap: 1px;
    padding: var(--sp-2) var(--sp-1) 0;
  }
  .tile__title {
    font-family: var(--font-display);
    font-size: var(--text-lg);
    font-weight: 600;
    color: var(--indigo);
    line-height: 1.2;
  }
  .tile__sub {
    font-family: var(--font-hand);
    font-size: var(--text-2xs);
    color: var(--sepia);
    line-height: 1.4;
  }

  /* ---- scroll-driven reveal ------------------------------------------- */
  /* Native view() timeline: the animation is driven by the tile's own
     position in the scrollport, runs off the main thread, and staggers
     for free because each tile crosses the threshold at a different time.
     No IntersectionObserver, no JS-set delays, no layout reads. */
  @supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {
      .tile {
        animation: tile-in var(--dur-reveal) var(--ease-spring-soft) both;
        animation-timeline: view();
        animation-range: entry 0% cover 22%;
      }
    }
  }
  @keyframes tile-in {
    from { opacity: 0; transform: translate3d(0, 20px, 0) rotate(0deg) scale(0.985); }
    to   { opacity: 1;
           transform: translate3d(var(--mx,0px), calc(var(--my,0px) + var(--lift,0px)), 0)
                      rotate(var(--tilt, 0deg)) scale(1); }
  }

  /* JS fallback for engines without scroll-driven animations. site.js only
     sets [data-reveal] when it has to, so nothing is ever hidden without a
     working way to reveal it. */
  [data-reveal] .tile { opacity: 0; transform: translate3d(0, 20px, 0) rotate(0deg); }
  [data-reveal] .tile.is-in {
    opacity: 1;
    transform: translate3d(0, 0, 0) rotate(var(--tilt, 0deg));
    transition:
      opacity var(--dur-reveal) var(--ease-out-quart) var(--delay, 0ms),
      transform var(--dur-reveal) var(--ease-spring-soft) var(--delay, 0ms);
  }
  @media (prefers-reduced-motion: reduce) {
    [data-reveal] .tile { opacity: 1; transform: rotate(var(--tilt, 0deg)); }
  }

  /* ---- lightbox ------------------------------------------------------- */
  /* Native <dialog>: focus trap, inert background, Esc handling and
     top-layer stacking come from the platform instead of being hand-rolled. */
  .lightbox {
    width: min(75rem, 100vw);
    max-width: 100vw;
    max-height: 100svh;
    padding: clamp(1rem, 0.5rem + 2vw, 2rem);
    border: 0;
    background: transparent;
    color: var(--cream);
    overflow: hidden;
  }
  .lightbox::backdrop {
    background: rgb(28 24 18 / 0.92);
    -webkit-backdrop-filter: blur(2px);
    backdrop-filter: blur(2px);
  }
  .lightbox[open] { display: grid; place-content: center; }

  @media (prefers-reduced-motion: no-preference) {
    .lightbox[open] { animation: lb-in var(--dur-spring) var(--ease-spring-soft) both; }
    .lightbox[open]::backdrop { animation: fade-in var(--dur) var(--ease) both; }
    .lightbox.is-closing { animation: lb-out var(--dur-fast) var(--ease-out-quart) both; }
    .lightbox.is-closing::backdrop { animation: fade-out var(--dur-fast) var(--ease) both; }
  }
  @keyframes lb-in  { from { opacity: 0; transform: translate3d(0, 12px, 0) scale(0.965); } }
  @keyframes lb-out { to   { opacity: 0; transform: translate3d(0, 6px, 0) scale(0.985); } }
  @keyframes fade-in  { from { opacity: 0; } }
  @keyframes fade-out { to   { opacity: 0; } }

  /* Shared-element morph: the thumbnail and the opened photograph carry the
     same view-transition-name, so the browser tweens between them instead of
     cross-fading two unrelated boxes. site.js assigns the name on the tile
     being opened and clears it afterwards. */
  .lightbox__img { view-transition-name: var(--vt, none); }

  /* Swapping the photograph inside an open lightbox slides rather than cuts. */
  .lightbox__img.is-swapping {
    animation: img-swap var(--dur) var(--ease-out-quart) both;
  }
  @keyframes img-swap {
    from { opacity: 0; transform: translate3d(var(--swap-from, 18px), 0, 0); }
  }

  .lightbox__figure { display: grid; gap: var(--sp-4); justify-items: center; margin: 0; }
  .lightbox__img {
    max-width: 100%;
    max-height: 74svh;
    width: auto;
    height: auto;
    object-fit: contain;
    border: clamp(5px, 1.4vw, 10px) solid var(--card-warm);
    border-radius: var(--radius-sm);
    box-shadow: 0 20px 60px rgb(0 0 0 / 0.6);
  }
  .lightbox__cap { text-align: center; display: grid; gap: var(--sp-1); }
  .lightbox__cap-title {
    font-family: var(--font-display);
    font-size: var(--text-xl);
    font-weight: 600;
    color: var(--cream);
  }
  .lightbox__cap-sub {
    font-family: var(--font-hand);
    font-size: var(--text-sm);
    color: #CBB99A;
  }
  .lightbox__close {
    position: fixed;
    inset-block-start: var(--sp-3);
    inset-inline-end: var(--sp-4);
    width: var(--tap);
    height: var(--tap);
    display: grid;
    place-items: center;
    font-family: var(--font-display);
    font-size: var(--text-2xl);
    line-height: 1;
    color: var(--cream);
    border-radius: 50%;
    transition: background var(--dur-fast) var(--ease);
  }
  .lightbox__close:hover { background: rgb(255 255 255 / 0.12); }

  .lightbox__nav {
    position: fixed;
    inset-block-start: 50%;
    translate: 0 -50%;
    width: var(--tap);
    height: var(--tap);
    display: grid;
    place-items: center;
    font-size: var(--text-2xl);
    color: var(--cream);
    border-radius: 50%;
    transition: background var(--dur-fast) var(--ease);
  }
  .lightbox__nav:hover { background: rgb(255 255 255 / 0.12); }
  .lightbox__nav[data-dir="prev"] { inset-inline-start: var(--sp-2); }
  .lightbox__nav[data-dir="next"] { inset-inline-end: var(--sp-2); }
  .lightbox__nav[hidden] { display: none; }

  @media (max-width: 30rem) {
    .lightbox__nav { inset-block-start: auto; inset-block-end: var(--sp-4); translate: 0; }
  }
}

/* ------------------------------------------------------------ utilities -- */

  /* Static fallback for the showpiece globe. Shown only when the WebGL module
     could not run, so the hero is never an empty rectangle. */
  .globe-stage[data-globe-failed] canvas { display: none; }
  .globe-stage[data-globe-failed]::after {
    content: "";
    display: block;
    width: min(60vmin, 26rem);
    aspect-ratio: 1;
    margin-inline: auto;
    border-radius: 50%;
    background: url("../img/globe-fallback.jpg") center / 200% 100% no-repeat;
    box-shadow:
      inset -2rem -1.4rem 4rem rgb(0 0 0 / 0.75),
      inset 0.6rem 0.4rem 2.5rem rgb(255 224 176 / 0.12),
      0 0 4rem rgb(232 154 74 / 0.22);
  }

@layer utilities {
  .visually-hidden {
    position: absolute !important;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }
  .stack > * + * { margin-block-start: var(--flow, var(--sp-4)); }
  .center { margin-inline: auto; }
  [hidden] { display: none !important; }
}

/* ------------------------------------------------------------- printing -- */
@media print {
  .site-header, .site-nav, .site-footer, .lightbox, .skip-link { display: none; }
  body { background: #fff; color: #000; }
  .tile { break-inside: avoid; box-shadow: none; transform: none; border-color: #999; }
}
