/* The front door: one centred screen, the name, and five ways out.
   Everything here is centred on the optical axis of the page, because the
   wave behind it radiates from a point and an off-centre column fights that. */

.stage {
  position: relative;
  /* Keep the document boundary below the front door fixed while mobile
     browser chrome expands and retracts. A dynamic viewport height makes the
     whole resumé jump because this section sits above it. Firefox on iOS also
     changes `svh`, so site.js freezes this fallback into a pixel value there. */
  min-height: var(--stable-stage-height, 100svh);
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr) auto minmax(0, 1fr) auto;
  align-content: stretch;
  justify-items: center;
  padding: var(--rhythm) var(--page-pad);
  overflow: hidden;
  touch-action: pan-y pinch-zoom;
}

.stage[data-dragging] {
  cursor: grabbing;
  user-select: none;
}

/* The field sits under the content. The name is opaque ink on top of it: the
   letters are a scatterer in the simulation, so the wave has to be visibly
   behind them rather than washing over them. */
.stage__canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
  pointer-events: none;
}

/* Deployment fingerprint: deliberately quieter than the field-control tab and
   tucked against the opposite corner from the site menu. Cloudflare supplies
   the exact SHA at runtime; local static serving leaves the label absent. */
.stage__revision {
  position: absolute;
  top: clamp(0.85rem, 1.8vw, 1.3rem);
  left: clamp(0.85rem, 1.8vw, 1.3rem);
  z-index: 3;
  display: flex;
  gap: 0.45em;
  color: var(--ink-ghost);
  font-size: var(--step--2);
  line-height: 1;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  pointer-events: none;
}

.stage__revision[hidden] {
  display: none;
}

.stage__revision::before {
  content: '[';
}

.stage__revision::after {
  content: ']';
}

.stage__revision code {
  font: inherit;
  letter-spacing: inherit;
}

.stage__content {
  position: relative;
  z-index: 2;
  grid-row: 2;
  align-self: center;
  display: grid;
  gap: clamp(1rem, 0.5rem + 1.6vw, 1.9rem);
  justify-items: center;
  text-align: center;
  max-width: 68rem;
  min-width: 0;
  width: 100%;
}

/* ---- name ------------------------------------------------------------- */

.name {
  display: flow-root;
  font-size: var(--step-name);
  line-height: 1;
  font-weight: 600;
  /* A fixed pitch already sets the columns; negative tracking on top of that
     collides the glyphs into their own advance boxes. Let it breathe instead. */
  letter-spacing: 0.01em;
  margin-block: clamp(0.2rem, 0.4vw, 0.6rem);
}

.name::after {
  content: '';
  display: block;
  width: 45%;
  height: clamp(2px, 0.055em, 4px);
  margin: 0.28em auto 0;
  background: var(--accent);
}

.name__text {
  --reveal-x: 50%;
  --reveal-y: -65%;
  --reveal-radius: 0px;
  --reveal-feather: 0px;

  display: block;
  margin-block: -0.25em;
  padding-block: 0.25em;
  white-space: nowrap;
  color: var(--ink);
}

.name__traveller {
  display: none;
}

.name[data-location] {
  max-width: min(100%, 32ch);
  /* Keep the personalised glyphs resolved by the fixed 320-cell acoustic
     grid. The cell is the stage width over 320, so a title capped in rem
     shrinks in cells as the window widens: at the old 2.5rem cap a 1440px
     stage left the cap height at 0.73 of the 11-cell carrier wavelength and
     the strokes under one cell, so the burst passed through the letters with
     no reflected front — only a near field that lingered around them. The
     greeting is four times longer than the Welcome fallback and cannot match
     its 4.5rem, so this stays the compromise: legible scattering to about
     1600px, degrading gently past it. */
  font-size: clamp(1.35rem, 0.9rem + 3vw, 3.5rem);
  line-height: 1.2;
}

.name[data-location] .name__text {
  white-space: normal;
  text-wrap: balance;
}

.name[data-location] .name__fallback {
  display: none;
}

.name[data-location] .name__traveller {
  display: block;
}

.name[data-location] [data-traveller-place] {
  color: var(--accent);
}

/* The opening pulse reveals the native glyphs, so the final title remains
   real text rather than swapping from a canvas copy. Without JS the stage
   never gets `data-sim-ready` and the name is visible from first paint. */
.stage[data-sim-ready] .name__text {
  -webkit-mask-image: radial-gradient(
    circle at var(--reveal-x) var(--reveal-y),
    #000 calc(var(--reveal-radius) - var(--reveal-feather)),
    transparent var(--reveal-radius)
  );
  mask-image: radial-gradient(
    circle at var(--reveal-x) var(--reveal-y),
    #000 calc(var(--reveal-radius) - var(--reveal-feather)),
    transparent var(--reveal-radius)
  );
}

.stage[data-sim-ready][data-reveal-complete] .name__text {
  -webkit-mask-image: none;
  mask-image: none;
}

.name__descriptor {
  display: grid;
  justify-items: center;
  gap: var(--introduction-gap);
  width: 100%;
  max-width: 100%;
  color: var(--ink-dim);
  font-size: var(--step--1);
  line-height: 1.55;
  text-wrap: balance;
}

.stage__introduction {
  --introduction-gap: clamp(0.45rem, 0.3rem + 0.4vw, 0.7rem);

  display: grid;
  justify-items: center;
  gap: var(--introduction-gap);
}

.name__descriptor-lead,
.hint__moving {
  font-size: var(--step-0);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: 0.055em;
}

.name__descriptor-lead {
  display: block;
  color: var(--ink);
}

.name__descriptor-copy {
  display: block;
}

/* ---- field controls --------------------------------------------------- */

.field-console {
  position: relative;
  z-index: 3;
  grid-row: 4;
  align-self: end;
  display: grid;
  justify-items: center;
  transform: translateY(calc(var(--rhythm) - 0.75rem));
}

.field-console__tab {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.32rem 0.65rem;
  list-style: none;
  border: 1px solid var(--rule);
  background: color-mix(in srgb, var(--ground) 58%, transparent);
  color: var(--ink-dim);
  font-size: var(--step--2);
  line-height: 1.2;
  letter-spacing: 0.08em;
  cursor: pointer;
  transition:
    border-color 0.2s var(--ease),
    color 0.2s var(--ease);
}

.field-console__tab::-webkit-details-marker {
  display: none;
}

.field-console__tab::after {
  content: '+';
  color: var(--accent);
}

.field-console[open] .field-console__tab::after {
  content: '−';
}

.field-console__tab:hover,
.field-console__tab:focus-visible,
.field-console[open] .field-console__tab {
  border-color: var(--ink-dim);
  color: var(--ink);
}

.field-console__tab:focus-visible {
  outline: 1px solid var(--ink);
  outline-offset: 3px;
}

.field-controls {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 0.45rem);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(1rem, 2.5vw, 2.25rem);
  width: max-content;
  max-width: calc(100vw - 2 * var(--page-pad));
  padding: 0.45rem clamp(0.7rem, 1.5vw, 1.1rem);
  border: 1px solid var(--rule);
  background: color-mix(in srgb, var(--ground) 42%, transparent);
  font-size: var(--step--2);
  line-height: 1.2;
  text-align: left;
  visibility: hidden;
  opacity: 0;
  pointer-events: none;
  transform: translate(-50%, 0.35rem);
  transition:
    opacity 0.2s var(--ease),
    transform 0.2s var(--ease),
    visibility 0s linear 0.2s;
}

.field-console[open] .field-controls {
  visibility: visible;
  opacity: 1;
  pointer-events: auto;
  transform: translate(-50%, 0);
  transition-delay: 0s;
}

.field-control {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
  letter-spacing: 0.06em;
  color: var(--ink-dim);
}

.field-control legend {
  float: left;
  margin: 0;
  padding: 0;
  white-space: nowrap;
}

.field-control__options {
  display: flex;
  flex: 0 0 3.45rem;
  align-items: center;
  justify-content: space-between;
}

.field-control__options label {
  position: relative;
  display: grid;
  place-items: center;
  width: 1rem;
  height: 1rem;
  cursor: pointer;
}

.field-control__options label::before {
  content: '';
  width: 0.42rem;
  height: 0.42rem;
  border-radius: 50%;
  background: var(--ink);
}

.field-control__options label[data-level='low']::before {
  opacity: 0.22;
}

.field-control__options label[data-level='medium']::before {
  opacity: 0.58;
}

.field-control__options input:checked + label::after {
  content: '';
  position: absolute;
  inset: 0;
  border: 1px solid var(--accent);
  border-radius: 50%;
}

.field-control__options input:focus-visible + label {
  outline: 1px solid var(--ink);
  outline-offset: 2px;
  border-radius: 50%;
}

.field-control__options input:disabled + label {
  cursor: not-allowed;
  opacity: 0.35;
}

.field-control__options--modes {
  flex-basis: auto;
  gap: 0.4rem;
}

.field-control__options--modes label {
  width: auto;
  height: auto;
  padding: 0.08rem 0.22rem;
  color: var(--ink-ghost);
}

.field-control__options--modes label::before {
  content: none;
}

.field-control__options--modes input:checked + label {
  color: var(--ink);
}

.field-control__options--modes input:checked + label::after {
  inset: -0.12rem;
  border-radius: 0;
}

.field-control__options--modes input:focus-visible + label {
  border-radius: 0;
}

/* ---- navigation -------------------------------------------------------- */

.nav {
  width: 100%;
  margin-top: clamp(0.5rem, 1.4vw, 1.6rem);
}

.nav__rows {
  display: flex;
  justify-content: center;
  width: max-content;
  max-width: 100%;
  margin-inline: auto;
  border-top: 1px solid var(--rule);
}

.nav__row {
  display: flex;
}

.nav__link {
  position: relative;
  display: grid;
  place-items: center;
  justify-items: center;
  padding: clamp(0.9rem, 1.4vw, 1.35rem) 0.75rem;
  text-decoration: none;
  border-bottom: 1px solid var(--rule);
  transition: color 0.3s var(--ease);
}

/* The well remains at the field's waterline while the visible surface rises
   out of it. Its box is also the geometry rasterised into the acoustic medium
   while the link is active. */
.nav__lift {
  position: relative;
  width: max-content;
  max-width: 100%;
  min-width: min(100%, 9rem);
  min-height: 3.35rem;
  border-radius: 0.72rem;
}

.nav__well {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: transparent;
  box-shadow: inset 0 0 0 1px transparent;
  transition: box-shadow 0.28s var(--ease);
}

.nav__surface {
  position: relative;
  z-index: 1;
  display: grid;
  align-content: center;
  justify-items: center;
  min-width: 100%;
  min-height: 3.35rem;
  padding: 0.45rem 0.72rem;
  border-radius: inherit;
  background: transparent;
  /* The surface is opaque only while raised, and its colour is never
     interpolated — a half-transparent ground washes the ink out. On the way
     down it is instead held at full strength for the whole descent and then
     dropped in one zero-duration step, so the well's lip is never read through
     the tile that is supposed to be covering it. */
  transition:
    background-color 0s linear 0.38s,
    transform 0.38s var(--ease-out),
    box-shadow 0.38s var(--ease-out);
}

.nav__link[data-isonified] .nav__well {
  box-shadow:
    inset 0 0 0 1px rgba(var(--ink-rgb), 0.14),
    inset 0 0.35rem 0.8rem rgba(0, 0, 0, 0.34);
}

.nav__link[data-isonified] .nav__surface {
  background: var(--ground);
  transform: translateY(-0.48rem);
  box-shadow: 0 0.78rem 1.1rem rgba(0, 0, 0, 0.38);
  /* A leaf pulling free of the water: almost still at first, then accelerating
     into an abrupt stop. The transition end is the acoustic impulse trigger. */
  transition-timing-function:
    linear,
    cubic-bezier(0.55, 0, 0.9, 0.35),
    var(--ease-out);
  transition-duration: 0s, 0.316s, 0.38s;
  transition-delay: 0s;
}

.nav__label {
  font-size: var(--step-2);
  line-height: 1.1;
}

.nav__link:hover,
.nav__link:focus-visible {
  color: var(--accent);
}

.nav__link:focus-visible {
  outline: 0;
}

.nav__link:focus-visible .nav__surface {
  outline: 1px solid var(--accent);
  outline-offset: 3px;
}

/* ---- hint -------------------------------------------------------------- */

.hint {
  position: relative;
  z-index: 2;
  max-width: 56ch;
  font-size: var(--step--1);
  line-height: 1.55;
  color: var(--ink-ghost);
}

.hint[data-dismissed] {
  pointer-events: none;
}

.hint__moving {
  position: relative;
  display: inline-block;
  padding-inline: clamp(0.8rem, 2vw, 1.15rem);
  color: var(--accent);
  text-shadow: 0 0 0.75rem rgba(var(--accent-rgb), 0.42);
  cursor: crosshair;
  transition:
    color 0.25s var(--ease),
    text-shadow 0.25s var(--ease),
    transform 0.25s var(--ease-out);
}

.hint__moving::before {
  content: '';
  position: absolute;
  inset: -0.55rem 0;
}

.hint__moving:hover,
.hint__moving:focus-visible {
  color: var(--ink);
  text-shadow:
    0 0 0.65rem rgba(var(--accent-rgb), 0.78),
    0 0 1.5rem rgba(var(--accent-rgb), 0.42);
  transform: translateY(-1px);
}

.hint__copy {
  display: inline-block;
  white-space: nowrap;
}

.hint__glyph {
  --drift-x: -0.12em;
  --drift-y: -0.48em;
  --drift-rotate: -4deg;

  display: inline-block;
}

.hint__glyph:nth-child(3n + 1) {
  --drift-x: 0.16em;
  --drift-y: -0.3em;
  --drift-rotate: 5deg;
}

.hint__glyph:nth-child(3n + 2) {
  --drift-x: 0.04em;
  --drift-y: 0.32em;
  --drift-rotate: 2deg;
}

.hint[data-dismissed] .hint__moving {
  animation: hint-afterglow 2.35s linear forwards;
}

.hint[data-dismissed] .hint__glyph {
  animation: hint-glyph-dissolve 1.2s var(--ease-out) forwards;
  animation-delay: calc(var(--glyph-index) * 32ms);
  will-change: opacity, filter, transform;
}

@keyframes hint-glyph-dissolve {
  0%,
  18% {
    opacity: 1;
    filter: blur(0);
    transform: translate3d(0, 0, 0) rotate(0);
  }

  42% {
    opacity: 0.9;
    color: var(--ink);
    filter: blur(0.02rem);
    text-shadow: 0 0 0.9rem rgba(var(--accent-rgb), 0.85);
  }

  100% {
    opacity: 0;
    filter: blur(0.3rem);
    transform: translate3d(var(--drift-x), var(--drift-y), 0)
      rotate(var(--drift-rotate));
  }
}

@keyframes hint-afterglow {
  0%,
  28% {
    opacity: 1;
  }

  42% {
    color: var(--ink);
    filter: drop-shadow(0 0 1.1rem rgba(var(--accent-rgb), 0.72));
    text-shadow:
      0 0 0.8rem rgba(var(--accent-rgb), 0.95),
      0 0 1.8rem rgba(var(--accent-rgb), 0.5);
  }

  74% {
    filter: drop-shadow(0 0 1.8rem rgba(var(--accent-rgb), 0.14));
    text-shadow: 0 0 2.2rem rgba(var(--accent-rgb), 0.22);
  }

  100% {
    visibility: hidden;
    opacity: 0;
    filter: blur(0.35rem) drop-shadow(0 0 2rem rgba(var(--accent-rgb), 0));
    text-shadow: 0 0 2.4rem rgba(var(--accent-rgb), 0);
    transform: scale(1.035);
  }
}

/* Reduced motion gets the medium and nothing that moves, so the instruction
   to click would be a lie. Swapped in CSS rather than JS, because it is
   content and it belongs in the markup. */
.hint__still {
  display: none;
}

@media (prefers-reduced-motion: reduce) {
  .field-console {
    display: none;
  }

  .hint__moving {
    display: none;
  }

  .hint__still {
    display: inline;
  }
}

/* On wrapped layouts, size and centre each row independently. The outer edges
   of About Me and Publications therefore balance around the page centre even
   though the labels — and consequently their boxes — have different widths. */
@media (max-width: 62rem) {
  .nav__rows {
    display: grid;
    justify-items: center;
    border-top: 0;
  }

  .nav__row--primary {
    border-top: 1px solid var(--rule);
  }
}

@media (max-width: 40rem) {
  .nav__rows,
  .nav__row--primary {
    width: 100%;
  }

  .nav__row--primary {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  .nav__row--secondary {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    width: 66.6667%;
  }

  .nav__link {
    padding-inline: 0;
  }

  .nav__surface {
    padding-inline: 0.1rem;
  }

  .nav__label {
    font-size: var(--step--1);
  }

  .field-controls {
    display: grid;
    grid-template-columns: repeat(2, auto);
    column-gap: 1.25rem;
    row-gap: 0.45rem;
  }
}

/* Printing means printing the resumé. The front door collapses to its name. */
@media print {
  .stage {
    min-height: 0;
    padding-block: 0 1rem;
  }

  .stage__canvas,
  .stage__revision,
  .field-console,
  .hint,
  .nav {
    display: none !important;
  }

  .name[data-location] {
    max-width: none;
    font-size: var(--step-name);
    line-height: 1;
  }

  .name[data-location] .name__text {
    white-space: nowrap;
  }

  .name[data-location] .name__fallback {
    display: inline;
  }

  .name[data-location] .name__traveller {
    display: none;
  }
}
