/* ==========================================================================
   SONGLAP - A photo gallery
   main.css — shared styles for every page (tokens, base, header, cursor,
   hero, galleries, footer, buttons). Loaded on both index.html and
   contact/index.html. Page-specific overrides live in contact.css.
   Hand-written CSS — no framework, per the project's "static architecture
   only" requirement.
   ========================================================================== */

/* ------------------------------ Design tokens ---------------------------- */
:root {
  /* Achromatic Gallery palette (dark, the site's single theme) */
  --surface: #12131a;
  --surface-dim: #12131a;
  --surface-bright: #383941;
  --surface-container-lowest: #0d0e15;
  --surface-container-low: #1a1b22;
  --surface-container: #1e1f26;
  --surface-container-high: #292931;
  --surface-container-highest: #33343c;
  --on-surface: #e3e1ec;
  --on-surface-variant: #c7c6ca;
  --outline: #919094;
  --outline-variant: #46464a;
  --primary: #c8c6c7;
  --on-primary: #313031;
  --primary-container: #0b0b0c;
  --on-primary-container: #7b797a;
  /* Same family as on-primary-container, nudged for AA contrast at the
     small sizes labels/eyebrows actually render at (source token is
     ~4:1 against surface-container-low; this is ~4.6:1). */
  --label-fg: #858384;
  --background: #12131a;
  --on-background: #e3e1ec;
  --text: #f5f5f5;

  --glass-bg: rgba(255, 255, 255, 0.06);
  --glass-border: rgba(255, 255, 255, 0.12);
  --glass-bg-strong: rgba(255, 255, 255, 0.045);

  /* Type */
  --font-display: "EB Garamond", "Iowan Old Style", Georgia, serif;
  --font-body: "Hanken Grotesk", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-caption: Papyrus, "Papyrus Condensed", fantasy;

  /* Radii */
  --radius-sm: 0.125rem;
  --radius-default: 0.25rem;
  --radius-md: 0.375rem;
  --radius-lg: 0.5rem;
  --radius-xl: 0.75rem;
  --radius-glass: 1rem;
  --radius-full: 9999px;

  /* Spacing */
  --safe-margin: 1.5rem;
  --gutter-gallery: 0px;
  --section-gap: clamp(4rem, 9vw, 8rem);
  --element-gap: 1rem;
  --content-max: 1440px;

  /* Motion */
  --ease-settle: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
}

@media (min-width: 640px) {
  :root {
    --safe-margin: 4rem;
  }
}

/* ------------------------------- Reset ----------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

body {
  margin: 0;
  background-color: var(--background);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 1rem;
  line-height: 1.6;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
}

img { max-width: 100%; display: block; }
a { color: inherit; text-decoration: none; }
ul { list-style: none; margin: 0; padding: 0; }
h1, h2, h3, p { margin: 0; }
button { font: inherit; color: inherit; background: none; border: none; cursor: pointer; }
svg { display: block; }

/* Native cursor stays put until JS confirms a fine pointer is present, so
   touch/trackpad-only visitors never lose their cursor. */
body.has-custom-cursor { cursor: none; }
body.has-custom-cursor a,
body.has-custom-cursor button { cursor: none; }

::selection { background: var(--primary); color: var(--on-primary); }

:focus-visible {
  outline: 1.5px solid var(--on-surface);
  outline-offset: 3px;
  border-radius: var(--radius-sm);
}

/* ------------------------------ Typography -------------------------------- */
.eyebrow {
  font-family: var(--font-body);
  font-size: 1.0806rem; /* enlarged 1.33x from the original 0.8125rem */
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--label-fg);
}

.headline-display {
  font-family: var(--font-display);
  font-size: clamp(2.5rem, 6vw, 5rem);
  font-weight: 400;
  line-height: 1.1;
  letter-spacing: -0.02em;
  color: var(--on-surface);
}

.headline-lg {
  font-family: var(--font-display);
  font-size: clamp(2rem, 4vw, 3.5rem);
  font-weight: 400;
  line-height: 1.2;
  color: var(--on-surface);
}

.headline-md {
  font-family: var(--font-display);
  font-size: 2rem;
  font-weight: 400;
  line-height: 1.3;
  color: var(--on-surface);
}

.body-lg {
  font-family: var(--font-body);
  font-size: 1.35rem;
  line-height: 1.6;
  color: var(--on-surface-variant);
}

.body-md {
  font-family: var(--font-body);
  font-size: 1rem;
  line-height: 1.6;
  color: var(--on-surface-variant);
}

.nav-link {
  font-family: var(--font-body);
  font-size: 0.9375rem;
  font-weight: 500;
  line-height: 1;
  letter-spacing: 0.05em;
}

/* -------------------------------- Layout ---------------------------------- */
.wrap {
  max-width: var(--content-max);
  margin-inline: auto;
  padding-inline: var(--safe-margin);
}

.glass {
  background: var(--glass-bg);
  backdrop-filter: blur(16px) saturate(140%);
  -webkit-backdrop-filter: blur(16px) saturate(140%);
  border: 1px solid var(--glass-border);
  border-radius: var(--radius-glass);
}

/* ---------------------------- Custom cursor -------------------------------
   Single element, three states. Position is driven from a raw rAF loop
   (see main.js) so tracking never feels laggy; only the morph (size/
   border) transitions are eased.

   Bug fix: the previous "big ring" hover state kept `mix-blend-mode:
   difference` while switching to a border colour. Differencing against
   black leaves the backdrop completely unchanged, so a black border
   under that blend mode is invisible by construction — that's why the
   ring used to disappear over photos. The gallery-hover state below
   drops the blend mode and uses a genuinely opaque black stroke plus a
   soft light halo instead, so it reads clearly over both bright and
   dark image areas.
   ---------------------------------------------------------------------- */
#cursor {
  position: fixed;
  top: 0;
  left: 0;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background-color: var(--text);
  border: 0px solid transparent;
  mix-blend-mode: difference;
  pointer-events: none;
  z-index: 9999;
  transform: translate(-50%, -50%);
  transition:
    width 0.28s var(--ease-settle),
    height 0.28s var(--ease-settle),
    border-width 0.28s var(--ease-settle),
    background-color 0.2s ease,
    box-shadow 0.2s ease,
    opacity 0.2s ease;
  will-change: transform;
}

#cursor.cursor--link {
  width: 46px;
  height: 46px;
  background-color: transparent;
  border: 1.5px solid var(--text);
  mix-blend-mode: difference;
}

#cursor.cursor--gallery {
  width: 84px;
  height: 84px;
  background-color: transparent;
  border: 2.5px solid #000;
  mix-blend-mode: normal;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.55), 0 2px 20px rgba(0, 0, 0, 0.35);
}

#cursor.cursor--hidden { opacity: 0; }

@media (prefers-reduced-motion: reduce) {
  #cursor { transition: none; }
}

/* ------------------------------- Header ------------------------------------ */
.site-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 500;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  padding: 1.1rem var(--safe-margin);
  background: transparent;
  border-bottom: 1px solid transparent;
  transition: border-color 0.5s var(--ease-settle);
}

/* The scrolled-state blur/shade lives on ::before rather than on
   .site-header itself, so it can be masked into a left/right-only
   gradient without also fading the header's actual content (logo,
   buttons) — masking the header directly would do that too, since a
   CSS mask affects everything an element renders, not just its own
   background. */
.site-header::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: rgba(18, 19, 26, 0.72);
  backdrop-filter: blur(16px) saturate(140%);
  -webkit-backdrop-filter: blur(16px) saturate(140%);
  opacity: 0;
  transition: opacity 0.5s var(--ease-settle);
}

.site-header.is-scrolled {
  border-bottom-color: var(--glass-border);
}

.site-header.is-scrolled::before {
  opacity: 1;
}

/* Mobile: no gradient — the blur/shade stays uniform edge-to-edge (no
   mask-image applied at all, i.e. the default ::before above). */

/* Tablet: 1 : 1.5 : 1 ratio (edges each 1/3.5 ≈ 28.57% of the width) */
@media (min-width: 640px) {
  .site-header::before {
    mask-image: linear-gradient(to right, black 0%, transparent 28.5714%, transparent 71.4286%, black 100%);
    -webkit-mask-image: linear-gradient(to right, black 0%, transparent 28.5714%, transparent 71.4286%, black 100%);
  }
}

/* Desktop: 1 : 3 : 1 ratio (edges each 1/5 = 20% of the width) */
@media (min-width: 1024px) {
  .site-header::before {
    mask-image: linear-gradient(to right, black 0%, transparent 20%, transparent 80%, black 100%);
    -webkit-mask-image: linear-gradient(to right, black 0%, transparent 20%, transparent 80%, black 100%);
  }
}

.brand {
  display: flex;
  align-items: center;
  gap: 0.65rem;
}

.header-actions {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.5rem;
}

.brand img {
  height: 120px;
  width: auto;
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  font-family: var(--font-body);
  font-size: 0.9375rem;
  font-weight: 500;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  white-space: nowrap;
  padding: 0.65rem 1.5rem;
  border-radius: var(--radius-full);
  border: 1px solid var(--glass-border);
  color: var(--on-surface);
  transition: background-color 0.3s ease, transform 0.4s var(--ease-spring), border-color 0.3s ease;
}

.btn:hover { background: var(--glass-bg); border-color: var(--outline); }

.btn--solid {
  background: var(--on-surface);
  color: var(--primary-container);
  border-color: var(--on-surface);
}

.btn--solid:hover {
  background: var(--primary);
  border-color: var(--primary);
}

/* -------------------------------- Hero -------------------------------------
   Full-viewport slideshow, shared markup pattern reused (with a muted
   modifier) for the contact page background. */
.hero {
  position: relative;
  height: 100svh;
  min-height: 560px;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  scroll-margin-top: 5rem; /* keeps the fixed header from covering the target on anchor jumps */
}

.slideshow {
  position: absolute;
  inset: 0;
  z-index: 0;
}

.slideshow__slide {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 2s ease-in-out;
}

.slideshow__slide.is-active { opacity: 1; }

.hero__scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: linear-gradient(to bottom, rgba(0, 0, 0, 0.18), rgba(0, 0, 0, 0.5));
}

.hero__content {
  position: relative;
  z-index: 2;
  max-width: 46rem;
  width: min(100%, 46rem);
  margin-inline: 1.5rem;
  padding: clamp(2rem, 5vw, 3.5rem);
  text-align: center;
}

.hero__content .eyebrow { display: block; margin-bottom: 1rem; }
.hero__content .headline-display { margin-bottom: 1rem; }

/* Homepage-only hero placement: panel sits just under the fixed header
   instead of centered, with zero .glass treatment left (no background
   fill, no backdrop blur/saturate, no border) — plain text directly
   over the raw photo, sized up 2pt with looser letter-spacing (body is
   uppercase too, wider-tracked than the other two). Line spacing (the
   margin-bottom between eyebrow/headline/body) is left at the original
   1rem, inherited from the shared rules below. Scoped to #hero-top
   (unique to index.html) so the community and contact pages — which
   reuse this same .hero/.hero__content/.glass/.eyebrow/
   .headline-display/.body-lg markup — are left exactly as they were. */
#hero-top { align-items: flex-start; }

#hero-top .hero__content {
  /* Header bottom edge is ~156.19px (120px logo + 1.1rem vertical
     padding, same at every breakpoint); this sits right at that edge —
     as high as it can go without sliding under the header. */
  margin-top: 9.8rem;
  background: transparent;
  backdrop-filter: none; /* saturate(140%) was still boosting colour behind the box, reading as a faint background */
  -webkit-backdrop-filter: none;
  border: none;
}

#hero-top .hero__content .eyebrow {
  font-size: calc(1.0806rem + 2pt);
  color: var(--on-surface-variant); /* was var(--label-fg); matches "Lights of every moment" rather than pure white */
  letter-spacing: 0.58em; /* was 0.1em, then 0.2em, then 0.3em */
}

#hero-top .hero__content .headline-display {
  font-size: calc((clamp(2.5rem, 6vw, 5rem) + 2pt) * 1.15); /* 1.15x the prior size */
  letter-spacing: 0.19em; /* was -0.02em, then 0.04em, then 0.1em */
}

#hero-top .hero__content .body-lg {
  font-size: calc(1.35rem + 2pt);
  text-transform: uppercase;
  letter-spacing: 0.29em; /* was normal, then 0.04em, then 0.15em */
}

.hero__scroll-cue {
  position: absolute;
  bottom: 2.5rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  opacity: 0.7;
  animation: bob 2.2s ease-in-out infinite;
}

.hero__scroll-cue svg { width: 14px; height: 22px; }

@keyframes bob {
  0%, 100% { transform: translate(-50%, 0); }
  50% { transform: translate(-50%, 8px); }
}

@media (prefers-reduced-motion: reduce) {
  .hero__scroll-cue { animation: none; }
  .slideshow__slide { transition: opacity 0.4s linear; }
}

/* ---------------------------- Side navigation ------------------------------
   Homepage only, tablet/desktop only (see breakpoint below). Icon rail,
   fixed to the right edge, vertically centered. */
.side-nav {
  display: none;
}

@media (min-width: 1150px) {
  .side-nav {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    position: fixed;
    right: 1.6rem;
    top: 50%;
    transform: translateY(-50%);
    z-index: 400;
    padding: 0.6rem;
    border-radius: var(--radius-full);
    background: var(--glass-bg-strong);
    backdrop-filter: blur(14px) saturate(140%);
    -webkit-backdrop-filter: blur(14px) saturate(140%);
    border: 1px solid var(--glass-border);
  }
}

.side-nav__item {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
}

.side-nav__link {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  color: var(--on-surface-variant);
  transition: color 0.3s ease, background-color 0.3s ease;
}

.side-nav__link svg { width: 15px; height: 15px; }

.side-nav__link:hover,
.side-nav__item.is-active .side-nav__link {
  color: var(--on-surface);
  background: rgba(255, 255, 255, 0.08);
}

.side-nav__label {
  position: absolute;
  right: calc(100% + 0.75rem);
  top: 50%;
  transform: translateY(-50%) translateX(6px);
  white-space: nowrap;
  font-family: var(--font-body);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--on-surface);
  background: rgba(18, 19, 26, 0.85);
  border: 1px solid var(--glass-border);
  padding: 0.4rem 0.75rem;
  border-radius: var(--radius-default);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s var(--ease-settle), transform 0.25s var(--ease-settle);
}

.side-nav__item:hover .side-nav__label {
  opacity: 1;
  transform: translateY(-50%) translateX(0);
}

@media (prefers-reduced-motion: reduce) {
  .side-nav__label { transition: opacity 0.15s linear; }
}

/* -------------------------------- Main -------------------------------------- */
main { position: relative; z-index: 1; background: var(--background); }

/* --------------------------- Gallery sections --------------------------------
   Lighter, warm-neutral gray — reads as a gallery wall rather than a
   continuation of the near-black page background. Text/line colours
   are re-declared as local custom properties scoped to this class so
   everything inside (heading, count, rule, captions-on-scrim) resolves
   correctly against a light background without touching the dark-theme
   tokens used everywhere else on the site. */
.gallery-section {
  /* Dark enough now that dark text (the previous #A5A9AE scheme) would
     fail contrast badly — flipped to light text, matching the rest of
     the site's dark-theme palette instead of the earlier light-gray
     inversion. */
  --gs-bg: #5F6266;
  --gs-fg: #e3e1ec;
  --gs-fg-muted: #e0dfe2;
  --gs-rule: #8f9195;
  --gs-media-bg: #54575b;
  padding-block: var(--section-gap);
  background: var(--gs-bg);
  border-top: 1px solid var(--outline-variant);
  border-bottom: 1px solid var(--outline-variant);
  margin-top: -1px; /* collapse doubled borders between adjacent sections */
  scroll-margin-top: 5rem; /* keeps the fixed header from covering the heading on anchor jumps */
  overflow: hidden; /* contains the reveal wrapper's blur/scale during its transition */
}

.gallery-section__inner {
  max-width: var(--content-max);
  margin-inline: auto;
  padding-inline: var(--safe-margin);
}

.gallery-section__head {
  display: flex;
  align-items: baseline;
  gap: 1.25rem;
  margin-bottom: 2rem;
}

.gallery-section__count {
  font-family: var(--font-body);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  color: var(--gs-fg-muted);
  white-space: nowrap;
}

.gallery-section__title {
  font-family: var(--font-display);
  font-size: clamp(2rem, 4vw, 3.5rem);
  font-weight: 400;
  line-height: 1.2;
  color: var(--gs-fg);
}

.gallery-section__rule {
  flex: 1;
  height: 1px;
  background: var(--gs-rule);
  align-self: center;
}

/* Masonry grid — position:relative container; items are placed with
   exact pixel coordinates computed in JS (see initMasonryGrids in
   main.js). This replaces an earlier CSS-Grid-based approach that used
   grid-auto-flow:dense with JS-computed row-spans: "dense" backfills
   gaps by scanning row-by-row, which is NOT the same as true shortest-
   column placement, and with images this varied in aspect ratio (and
   some spanning two columns) it was visibly leaving gaps. True masonry
   — placing each item into whichever column (or column-pair, for a
   "wide" item) is currently shortest — is what actually guarantees a
   tight pack, which is why every real masonry implementation works
   this way rather than relying on CSS auto-placement. Falls back to a
   plain stacked block list before JS runs (see .gallery-item below). */
.masonry-grid {
  position: relative;
}

.gallery-item {
  display: block;
  overflow: hidden;
  background: var(--gs-media-bg);
  /* <figure> ships a browser-default margin (40px inline, 1em block-start)
     that the reset above never zeroed out. It was invisible in normal flow
     but once JS switches items to position:absolute with exact computed
     coordinates, that leftover margin still renders — shifting every photo
     40px right of where it was placed (an empty 40px gap on the grid's
     left edge, the same 40px silently eaten from the right-side margin,
     plus a 16px vertical overlap at the bottom of every gallery). Reset to
     0 on every side except bottom, which stays the intentional no-JS
     fallback gutter for the plain stacked list. */
  margin: 0 0 var(--gutter-gallery);
}

.masonry-grid.is-laid-out .gallery-item {
  position: absolute;
  top: 0;
  left: 0;
  margin-bottom: 0;
  will-change: transform;
}

.gallery-item img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: filter 0.4s var(--ease-settle), transform 0.5s var(--ease-settle);
}

/* Caption stays hidden until hover/focus/tap — the image itself is the
   gallery; the title is a detail you find, not a label you read past.
   This is the figure's <figcaption> directly (no extra wrapper), which
   also keeps it valid as the figure's only other child. Papyrus isn't
   a licensed web font, so it's referenced directly rather than loaded
   — it renders on systems that have it installed (macOS, some Windows
   Office installs) and falls back to the browser's generic "fantasy"
   font elsewhere. */
.gallery-item__overlay {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: flex-end;
  padding: 1.1rem;
  background: linear-gradient(to top, rgba(10, 10, 12, 0.72) 0%, rgba(10, 10, 12, 0.28) 40%, transparent 70%);
  opacity: 0;
  transition: opacity 0.4s var(--ease-settle);
  font-family: var(--font-caption);
  font-style: normal;
  font-size: 1.85rem;
  line-height: 1.2;
  color: #f5f5f5;
}

.gallery-item:hover img,
.gallery-item:focus-within img,
.gallery-item.is-active img {
  filter: brightness(0.9);
  transform: scale(1.08);
}

.gallery-item:hover .gallery-item__overlay,
.gallery-item:focus-within .gallery-item__overlay,
.gallery-item.is-active .gallery-item__overlay {
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .gallery-item img,
  .gallery-item__overlay { transition: none; }
  .gallery-item:hover img,
  .gallery-item:focus-within img,
  .gallery-item.is-active img { transform: none; }
}

/* --------------------------- Section reveal ------------------------------
   Each gallery section reveals as ONE block (heading + full grid together),
   not per-image — the grid itself never animates on scroll once visible.

   This is scroll-position-driven, not timer-driven: main.js computes how
   far each section has scrolled through a defined zone and sets opacity/
   filter/transform directly from that fraction on every scroll frame, so
   the effect tracks your actual scroll position rather than running on a
   fixed clock once triggered — scroll slowly and it reveals slowly,
   reverse direction and it reverses in step, stop scrolling and it holds
   exactly where it is. No CSS transition is involved in that motion (a
   transition would fight the per-frame values, always lagging a step
   behind actual scroll position), so there's nothing to configure here
   beyond a safe default: fully visible, in case JS hasn't run yet.

   The animated properties still live on .gallery-section__reveal, a
   child of .gallery-section, not on .gallery-section itself — mirroring
   an earlier fix where animating the same element an observer measures
   caused the reveal's own motion to retrigger it mid-transition. */
.gallery-section__reveal {
  will-change: opacity, filter, transform;
}

.reveal-simple {
  opacity: 0;
  transform: translateY(28px);
  transition: opacity 0.9s var(--ease-settle), transform 0.9s var(--ease-settle);
}

.reveal-simple.is-visible { opacity: 1; transform: translateY(0); }

@media (prefers-reduced-motion: reduce) {
  .gallery-section__reveal {
    opacity: 1 !important;
    filter: none !important;
    transform: none !important;
  }
  .reveal-simple {
    transform: none !important;
    transition: opacity 0.3s linear !important;
  }
}

/* -------------------------------- Footer -------------------------------------- */
.site-footer {
  width: 100%;
  background: var(--surface-container-low);
  padding-block: clamp(1.5rem, 3vw, 2.25rem);
  padding-inline: var(--safe-margin);
  border-top: 1px solid var(--outline-variant);
}

.site-footer__inner {
  max-width: var(--content-max);
  margin-inline: auto;
  display: flex;
  flex-direction: column;
  gap: 2rem;
  align-items: center;
  text-align: center;
}

@media (min-width: 768px) {
  .site-footer__inner {
    flex-direction: row;
    justify-content: space-between;
    text-align: left;
  }
}

.site-footer__brand {
  display: flex;
  align-items: center;
  gap: 0.65rem;
}

.site-footer__brand img {
  height: 34px;
  width: auto;
}

.site-footer__brand span {
  font-family: var(--font-display);
  font-size: 1.21875rem;
  font-weight: 500;
  letter-spacing: 0.01em;
  text-transform: uppercase;
  color: var(--on-surface);
}

.site-footer__meta {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.1rem;
}

@media (min-width: 768px) {
  .site-footer__meta { align-items: flex-end; }
}

.site-footer small {
  font-size: 0.8125rem;
  color: var(--on-surface-variant);
}

/* ------------------------------ Social icons ----------------------------------
   Rendered by JS from a single shared data source (see main.js) so the
   Instagram → Flickr → Behance → Facebook order only has to live in one
   place. */
.social-list {
  display: flex;
  align-items: center;
  gap: 1.1rem;
}

.social-list__link {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  border: 1px solid var(--glass-border);
  color: var(--on-surface-variant);
  transition: color 0.3s ease, border-color 0.3s ease, transform 0.35s var(--ease-spring), background-color 0.3s ease;
}

.social-list__link svg { width: 16px; height: 16px; }

.social-list__link:hover {
  color: var(--on-surface);
  border-color: var(--outline);
  background: var(--glass-bg);
  transform: translateY(-3px);
}

/* Expanded variant used in the contact page's social column (icon + label) */
.social-list--expanded {
  flex-direction: column;
  align-items: stretch;
  gap: 0.9rem;
}

.social-list--expanded .social-list__link {
  width: auto;
  height: auto;
  border: none;
  border-radius: 0;
  justify-content: flex-start;
  gap: 0.85rem;
  padding: 0;
  font-family: var(--font-body);
  font-size: 1.0625rem;
  font-weight: 500;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--on-surface);
}

.social-list--expanded .social-list__link svg { width: 18px; height: 18px; }

.social-list--expanded .social-list__link:hover {
  color: var(--primary);
  transform: translateX(4px);
  background: none;
}

/* -------------------------------- Utilities ------------------------------------ */
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

@media (min-width: 1024px) {
  .cross-view-transition-target { view-transition-name: page-shell; }
}
