/* ==========================================================================
   A gallery behind glass.
   Everything here assumes the work is the only bright thing in the room.
   ========================================================================== */

:root {
  --ink:        #05070a;
  --ground:     #0a0d13;
  --ground-lit: #11161f;
  --mist:       #1b222c;

  --text:       #c2ccd9;
  --text-dim:   #7a8697;
  --text-faint: #4a5464;

  --edge:       rgba(163, 184, 210, 0.10);
  --edge-lit:   rgba(190, 210, 235, 0.20);

  /* The one warm thing on the page. Used sparingly, and never for a border. */
  --sorrow:     #a3675a;
  --sorrow-dim: #6d443a;

  --serif: 'Cormorant Garamond', Cormorant, Garamond, 'Hoefler Text', 'Times New Roman', serif;
  --sans:  ui-sans-serif, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;

  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --slow: 1.6s;
}

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

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

body {
  margin: 0;
  min-height: 100vh;
  background:
    radial-gradient(120% 80% at 50% -10%, var(--ground-lit) 0%, transparent 60%),
    radial-gradient(90% 60% at 50% 108%, #0d1119 0%, transparent 55%),
    var(--ground);
  background-attachment: fixed;
  color: var(--text);
  font-family: var(--serif);
  font-weight: 300;
  font-size: clamp(1rem, 0.94rem + 0.3vw, 1.1rem);
  line-height: 1.7;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
}

::selection { background: var(--sorrow-dim); color: #f2f5f8; }

img { max-width: 100%; display: block; }

a { color: var(--text-dim); text-decoration: none; border-bottom: 1px solid transparent; transition: color .5s var(--ease), border-color .5s var(--ease); }
a:hover { color: var(--sorrow); border-bottom-color: var(--sorrow-dim); }

:focus-visible { outline: 1px solid var(--sorrow); outline-offset: 4px; }

/* Film grain over the whole page. Kills the flatness of large dark fields. */
.grain {
  position: fixed;
  inset: -50%;
  pointer-events: none;
  z-index: 60;
  opacity: 0.028;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* --------------------------------------------------------------- the veil */

.veil {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: grid;
  place-items: center;
  background: var(--ink);
  transition: opacity 1.9s var(--ease), visibility 1.9s;
}
.veil.is-lifted { opacity: 0; visibility: hidden; pointer-events: none; }

.veil__inner { text-align: center; padding: 2rem; }

.veil__mark {
  width: clamp(88px, 15vw, 132px);
  height: auto;
  margin: 0 auto clamp(1.75rem, 4vw, 2.75rem);
  animation: breathe 5.5s var(--ease) infinite;
}

.veil__name {
  margin: 0;
  font-size: clamp(1.9rem, 7vw, 4.4rem);
  font-weight: 300;
  letter-spacing: 0.42em;
  text-indent: 0.42em;
  color: transparent;
  /* Etched, not printed: the letters are a highlight on the glass surface. */
  background: linear-gradient(180deg, #dce6f2 0%, #7e8b9d 55%, #444f5f 100%);
  -webkit-background-clip: text;
  background-clip: text;
  filter: drop-shadow(0 1px 0 rgba(0,0,0,.6));
  animation: breathe 5.5s var(--ease) infinite;
}

.veil__hint {
  margin: 2.6rem 0 0;
  font-family: var(--sans);
  font-size: 0.62rem;
  letter-spacing: 0.34em;
  text-transform: uppercase;
  color: var(--text-faint);
  animation: fadePulse 5.5s var(--ease) infinite;
}

@keyframes breathe {
  0%, 100% { opacity: 0.30; filter: blur(2.4px); }
  50%      { opacity: 1;    filter: blur(0); }
}
@keyframes fadePulse {
  0%, 100% { opacity: 0.15; }
  50%      { opacity: 0.55; }
}

/* ---------------------------------------------------------------- masthead */

.masthead {
  position: relative;
  z-index: 20;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 2rem;
  padding: clamp(2.5rem, 6vw, 5rem) clamp(1.25rem, 5vw, 5rem) 0;
  max-width: 1500px;
  margin: 0 auto;
}

.masthead__bar {
  display: flex;
  align-items: center;
  gap: clamp(0.75rem, 2vw, 1.35rem);
}
.masthead__mark {
  width: clamp(52px, 7vw, 74px);
  height: auto;
  flex-shrink: 0;
  opacity: 0.92;
}

.masthead__name {
  margin: 0;
  font-size: clamp(0.82rem, 1.6vw, 1rem);
  letter-spacing: 0.44em;
  text-indent: 0.44em;
  text-transform: uppercase;
  color: var(--text);
}
.masthead__tagline {
  margin: 0.7rem 0 0;
  font-size: 0.9rem;
  font-style: italic;
  color: var(--text-faint);
}

.glass-toggle {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  position: relative;
  justify-content: center;
  gap: 0.6rem;
  padding: 0.62rem 1.05rem;
  /* Wide enough for the longer of the two labels, so swapping them cannot
     resize the button underneath the cursor. */
  min-width: 12.4rem;
  background: transparent;
  border: 1px solid var(--edge-lit);
  border-radius: 2rem;
  color: #b9c6d6;
  font-family: var(--sans);
  font-size: 0.7rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  cursor: pointer;
  transition: color .6s var(--ease), border-color .6s var(--ease),
              background .6s var(--ease), box-shadow .6s var(--ease);
}
.glass-toggle:hover {
  color: #dbe3ee;
  border-color: var(--edge-lit);
  background: rgba(163,184,210,0.06);
}
/* The glow is tied to there actually being fog to clear — not to a flag
   remembering whether the visitor has clicked before. The old version stopped
   for good on first use, which meant anyone returning never saw it again and
   the control looked inert. Condition, not history. */
.glass-toggle__halo {
  position: absolute;
  inset: -1px;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  border: 1px solid rgba(220, 236, 255, 0.72);
  box-shadow: 0 0 26px -3px rgba(200, 218, 240, 0.55),
              inset 0 0 14px -6px rgba(210, 228, 250, 0.5);
  transition: opacity .7s var(--ease);
}
.glass-on .glass-toggle .glass-toggle__halo {
  animation: haloBreath 4.6s ease-in-out infinite;
}
.glass-on .glass-toggle .glass-toggle__dot {
  animation: dotBreath 4.6s ease-in-out infinite;
}

@keyframes haloBreath {
  0%, 100% { opacity: 0; }
  50%      { opacity: 0.95; }
}
@keyframes dotBreath {
  0%, 100% { background: var(--text-faint); box-shadow: 0 0 0 0 rgba(226, 236, 247, 0); }
  50%      { background: #e8f0fa;           box-shadow: 0 0 11px 1px rgba(226, 236, 247, 0.5); }
}

.glass-toggle__dot {
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--text-faint);
  box-shadow: 0 0 0 0 rgba(163,103,90,.5);
  transition: background .6s var(--ease), box-shadow .6s var(--ease);
}
.glass-toggle__label {
  transition: opacity .22s var(--ease);
}
.glass-toggle[aria-pressed="true"] .glass-toggle__dot {
  background: var(--sorrow);
  box-shadow: 0 0 12px 1px rgba(163,103,90,.45);
}

/* -------------------------------------------------------------- the rail */

.rail {
  position: fixed;
  top: 50%;
  right: clamp(0.6rem, 2vw, 2rem);
  transform: translateY(-50%);
  z-index: 20;
  pointer-events: none;
}
.rail__track {
  position: relative;
  width: 1px;
  height: min(42vh, 340px);
  background: var(--edge);
  overflow: hidden;
}
.rail__fill {
  position: absolute;
  inset: 0 0 auto 0;
  height: 0%;
  background: linear-gradient(180deg, transparent, var(--sorrow-dim) 40%, var(--sorrow));
  transition: height .35s linear;
}

/* -------------------------------------------------------------- the hang */

main { position: relative; z-index: 10; }

.hang {
  max-width: 1360px;
  margin: clamp(4rem, 10vw, 9rem) auto 0;
  padding: 0 clamp(1.25rem, 5vw, 5rem);
}

/* One piece to a row. These are small, quiet drawings — two of them side by
   side made them compete, and the eye settled on neither. Each now gets the
   full width, with its caption in the space opposite, and the side alternates
   so the scroll has a rhythm instead of a column. */
.piece {
  display: grid;
  grid-template-columns: 1.45fr 1fr;
  align-items: center;
  justify-items: center;
  gap: clamp(2rem, 5vw, 5.5rem);
  margin-bottom: clamp(7rem, 17vh, 15rem);
}
.piece:last-child { margin-bottom: 0; }

/* Both elements pull to the outside of the spread: the picture to the outer
   margin, the caption to the inner edge of its column so it sits beside the
   work rather than drifting in the middle of empty space. */
.piece__frame   { justify-self: start; }
.piece__caption { justify-self: start; }

.piece:nth-child(even) { grid-template-columns: 1fr 1.45fr; }
.piece:nth-child(even) .piece__frame { order: 2; justify-self: end; }
.piece:nth-child(even) .piece__caption { order: 1; justify-self: end; text-align: right; }

/* Sized by height, not width, so a whole piece and its caption sit inside the
   viewport at once — which is the point of showing one at a time. */
.piece__frame {
  position: relative;
  overflow: hidden;
  background: var(--ink);
  /* A definite height, not max-height: the image inside is absolutely
     positioned, so the frame has no content to size itself from, and
     aspect-ratio needs one real dimension to resolve the other against.
     With max-height the frame collapsed to zero width. */
  height: min(74vh, 720px);
  width: auto;
  max-width: 100%;
  box-shadow:
    0 1px 0 rgba(255,255,255,.045) inset,
    0 40px 90px -50px rgba(0,0,0,.95),
    0 2px 34px -14px rgba(0,0,0,.72);
}

/* ------------------------------------------------------- arriving on screen */

/* Visible by default. The reveal is opt-in via .js-reveal, which the script
   only adds once it knows it can put the pieces back — otherwise a blocked or
   broken glass.js would leave the wall permanently blank. */

/* The work is uncovered from the bottom up, the way a hand clears a misted
   pane. The transitions live on the arriving state only: put them on the
   hidden state and every piece below the fold visibly animates *away* a
   second after load. */
.js-reveal .piece__frame {
  clip-path: inset(0 0 100% 0);
  transform: scale(1.05);
  opacity: 0;
}
.js-reveal .piece.is-visible .piece__frame {
  clip-path: inset(0 0 0 0);
  transform: none;
  opacity: 1;
  transition:
    clip-path 1.5s var(--ease),
    transform 1.9s var(--ease),
    opacity   1s   var(--ease);
}

/* The words follow the picture rather than arriving with it. */
.js-reveal .piece__caption > * {
  opacity: 0;
  transform: translateY(16px);
}
.js-reveal .piece.is-visible .piece__caption > * {
  opacity: 1;
  transform: none;
  transition: opacity .95s var(--ease), transform .95s var(--ease);
}
.js-reveal .piece.is-visible .piece__numeral { transition-delay: .5s; }
.js-reveal .piece.is-visible .piece__title   { transition-delay: .64s; }
.js-reveal .piece.is-visible .piece__words   { transition-delay: .78s; }
.js-reveal .piece.is-visible .piece__year    { transition-delay: .92s; }

/* The condensation itself — a milky veil sitting on the pane, above the
   blurred work and below the wiped-clear canvas. Doing the fog as its own
   light layer rather than by brightening the artwork is what makes a wipe
   read as detail arriving instead of as a dark smear. */
.piece__frame::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 1;
  opacity: 0;
  transition: opacity 1.2s var(--ease);
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='f' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.55' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23f)' opacity='0.3'/%3E%3C/svg%3E"),
    radial-gradient(125% 95% at var(--fx, 28%) var(--fy, 18%), rgba(176,199,228,0.15) 0%, rgba(116,142,178,0.12) 55%, rgba(74,96,128,0.17) 100%);
  background-blend-mode: overlay, normal;
}
.glass-on .piece__frame::before { opacity: var(--fog, 1); }

/* A faint specular sweep across the surface. Sits above everything, because
   a reflection lands on the glass, not on the work behind it. */
.piece__frame::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 3;
  background: linear-gradient(114deg,
    rgba(255,255,255,.07) 0%, transparent 30%, transparent 74%, rgba(255,255,255,.035) 100%);
  mix-blend-mode: screen;
  opacity: 0;
  transition: opacity 1.2s var(--ease);
}
.glass-on .piece__frame::after { opacity: 1; }

.piece__image {
  width: 100%;
  height: 100%;
  object-fit: cover;
  position: absolute;
  inset: 0;
  transition: filter 1.1s var(--ease), transform 1.1s var(--ease);
}

/* Fogged state. Only applied once the glass engine is running, so that with
   JavaScript off the work is simply visible — which is the point of a gallery.
   The blur suggests the shape behind; the veil above supplies the milkiness. */
.glass-on .piece__image {
  filter: blur(13px) saturate(0.2) brightness(0.4) contrast(1.02);
  transform: scale(1.07);
}

.piece__glass {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 2;
  opacity: 0;
  pointer-events: none;
  transition: opacity .8s var(--ease);
}
.glass-on .piece__glass { opacity: 1; }

.piece__open {
  position: absolute;
  inset: 0;
  z-index: 4;
  border: 0;
  padding: 0;
  background: transparent;
  cursor: pointer;
}
.glass-on .piece__open {
  cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='40'%3E%3Ccircle cx='20' cy='20' r='13' fill='none' stroke='rgba(226,236,247,.5)' stroke-width='1'/%3E%3Ccircle cx='20' cy='20' r='1.4' fill='rgba(226,236,247,.6)'/%3E%3C/svg%3E") 20 20, crosshair;
}

/* The one instruction on the page. Sits above the fog but below the click
   target, so it never intercepts the wipe it is asking for. */
.piece__prompt {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: 3;
  display: none;
  flex-direction: column;
  align-items: center;
  gap: 0.9rem;
  pointer-events: none;
  font-family: var(--sans);
  font-size: 0.66rem;
  letter-spacing: 0.34em;
  text-transform: uppercase;
  color: rgba(238,244,251,0.82);
  text-shadow: 0 1px 12px rgba(0,0,0,0.65);
  white-space: nowrap;
  animation: promptBreathe 3.4s var(--ease) infinite;
  transition: opacity .9s var(--ease);
}
.glass-on .piece__prompt { display: flex; }
.piece__prompt.is-done { opacity: 0; animation: none; }

.piece__prompt-line {
  display: block;
  width: 54px;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(238,244,251,0.8), transparent);
  animation: promptSweep 3.4s var(--ease) infinite;
}

@keyframes promptBreathe {
  0%, 100% { opacity: 0.45; }
  50%      { opacity: 1; }
}
@keyframes promptSweep {
  0%, 100% { transform: translateX(-16px); }
  50%      { transform: translateX(16px); }
}

/* ------------------------------------------------------------- the caption */

.piece__caption {
  margin: 0;
  max-width: 34ch;
  opacity: 0.62;
  transition: opacity 1.4s var(--ease);
}
.piece.is-cleared .piece__caption,
.no-glass .piece__caption { opacity: 1; }

.piece__numeral {
  display: block;
  font-family: var(--sans);
  font-size: 0.58rem;
  letter-spacing: 0.34em;
  color: #5d6879;
  margin-bottom: 0.75rem;
}

.piece__title {
  margin: 0;
  font-size: clamp(1.35rem, 2.4vw, 1.9rem);
  font-weight: 400;
  letter-spacing: 0.035em;
  color: #d7dfea;
}

.piece__words {
  margin: 0.85rem 0 0;
  font-style: italic;
  font-size: clamp(1rem, 1.6vw, 1.15rem);
  line-height: 1.72;
  color: var(--text-dim);
  text-wrap: pretty;
}

.piece__year {
  margin: 0.9rem 0 0;
  font-family: var(--sans);
  font-size: 0.56rem;
  letter-spacing: 0.28em;
  color: var(--text-faint);
}

.empty {
  text-align: center;
  font-style: italic;
  color: var(--text-faint);
  padding: 8rem 1.5rem;
}

/* ------------------------------------------------------------- statement */

.statement {
  max-width: 46ch;
  margin: clamp(9rem, 22vw, 20rem) auto 0;
  padding: 0 clamp(1.25rem, 5vw, 5rem);
  font-size: clamp(1.02rem, 1.7vw, 1.2rem);
  font-style: italic;
  line-height: 1.85;
  color: var(--text-dim);
  text-wrap: pretty;
}
.statement__rule {
  width: 1px;
  height: clamp(3rem, 8vw, 6rem);
  margin: 0 auto clamp(2.5rem, 6vw, 4rem);
  background: linear-gradient(180deg, transparent, var(--edge-lit));
}
.statement p { margin: 0 0 1.4rem; }

/* -------------------------------------------------------------- colophon */

.colophon {
  max-width: 1500px;
  margin: clamp(8rem, 18vw, 16rem) auto 0;
  padding: clamp(3rem, 7vw, 5rem) clamp(1.25rem, 5vw, 5rem) clamp(4rem, 9vw, 7rem);
  text-align: center;
  font-family: var(--sans);
  font-size: 0.6rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--text-faint);
  border-top: 1px solid var(--edge);
}
.colophon p { margin: 0 0 1.1rem; }
.colophon__links a { letter-spacing: 0.2em; }
.colophon__tally {
  font-family: var(--serif);
  font-size: 0.95rem;
  font-style: italic;
  letter-spacing: 0.02em;
  text-transform: none;
  color: var(--text-faint);
  min-height: 1.4em;
  transition: color 1.2s var(--ease);
}
.colophon__tally.is-complete { color: var(--sorrow); }

/* ----------------------------------------------------------- the lightbox */

.plate {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: grid;
  place-items: center;
  padding: clamp(1rem, 4vw, 3rem);
  background: rgba(4, 6, 9, 0.94);
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
  opacity: 0;
  transition: opacity .7s var(--ease);
}
.plate[hidden] { display: none; }
.plate.is-open { opacity: 1; }

.plate__body {
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(1.25rem, 3vw, 2.25rem);
  max-height: 100%;
}

.plate__frame {
  position: relative;
  line-height: 0;
  box-shadow: 0 40px 120px -50px rgba(0,0,0,1);
}
.plate__image {
  max-height: min(66vh, 780px);
  max-width: 100%;
  width: auto;
  transition: filter 1.4s var(--ease);
}
.plate__glass {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.plate__caption { text-align: center; max-width: 48ch; }
.plate__numeral {
  display: block;
  font-family: var(--sans);
  font-size: 0.54rem;
  letter-spacing: 0.34em;
  color: var(--text-faint);
  margin-bottom: 0.7rem;
}
.plate__title {
  margin: 0;
  font-size: clamp(1.2rem, 2.4vw, 1.7rem);
  font-weight: 400;
  letter-spacing: 0.03em;
  color: #dbe3ee;
}
.plate__words {
  margin: 0.65rem 0 0;
  font-style: italic;
  color: var(--text-dim);
  text-wrap: pretty;
}

.plate__close,
.plate__step {
  position: absolute;
  background: transparent;
  border: 0;
  color: var(--text-faint);
  cursor: pointer;
  font-family: var(--serif);
  line-height: 1;
  transition: color .5s var(--ease);
  padding: 1rem;
}
.plate__close:hover,
.plate__step:hover { color: var(--text); }

.plate__close { top: clamp(.5rem, 2vw, 1.5rem); right: clamp(.5rem, 2vw, 1.5rem); font-size: 2rem; }
.plate__step  { top: 50%; transform: translateY(-50%); font-size: 2.6rem; }
.plate__step--prev { left: clamp(.25rem, 2vw, 1.5rem); }
.plate__step--next { right: clamp(.25rem, 2vw, 1.5rem); }

/* ------------------------------------------------------------- responsive */

@media (max-width: 900px) {
  .hang {
    margin-top: clamp(3.5rem, 14vw, 7rem);
  }
  .piece,
  .piece:nth-child(even) {
    grid-template-columns: 1fr;
    gap: 1.75rem;
    margin-bottom: clamp(5rem, 12vh, 9rem);
  }
  .piece:nth-child(even) .piece__frame { order: 1; }
  .piece:nth-child(even) .piece__caption { order: 2; text-align: left; }
  .piece__frame { height: min(62vh, 560px); }
  .piece__frame,
  .piece:nth-child(even) .piece__frame { justify-self: start; }
  .piece__caption,
  .piece:nth-child(even) .piece__caption { max-width: none; justify-self: start; }
  .rail { display: none; }
  .masthead { flex-direction: column; gap: 1.5rem; }
  .plate__step { font-size: 2rem; padding: .6rem; }
  .plate__image { max-height: 54vh; }
}

@media (max-width: 480px) {
  .veil__name { letter-spacing: 0.3em; text-indent: 0.3em; }
}

/* Wiping fog is a motion effect and a fine motor task. Opt out entirely. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  /* Must out-specify .glass-on .glass-toggle …, which is where the breathing
     is declared — a bare .glass-toggle selector loses to it. */
  .glass-on .glass-toggle:not(:hover) .glass-toggle__halo,
  .glass-on .glass-toggle .glass-toggle__dot { animation: none; }
  .glass-toggle__halo { opacity: 0; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
  .piece { opacity: 1; transform: none; }
}
