/* ─────────────────────────────────────────────────────────────────
   gaxix
   ─────────────────────────────────────────────────────────────────

   The same design language as taurasg.com, in its own colour.

   That language changed, and this page was left speaking the old one:
   cut corners, a bordered button, a near-black ground, one accent used
   as a highlight. The rules now are the portfolio's rules, and they are
   worth stating because they are almost all negative:

     NO CONTAINERS. Not cut, not rounded, not square. Nothing is drawn
     around anything. Structure comes from type scale, spacing and
     position — a label, a hairline, and the thing itself.

     A CONTROL IS A RULE AND A WORD, and the pair steps into the screen
     when you point at it. No fill, no border, no plate lighting up.

     THE GROUND IS A MEDIUM VALUE, not black and not white. The
     portfolio's is a warm stone grey; this one is indigo. A medium
     ground is what lets a page have things that are lighter than it as
     well as darker — which is the whole reason the portfolio's rooms
     read as rooms rather than as diagrams.

   What is still gaxix's own is the colour and the restraint: one mark,
   one link, and a clock. No build step, no dependencies, no framework
   and no webfont — a small WebGL2 distance-field renderer stands up the
   generated outlines as one continuous solid. See assets/mark.js.
   ───────────────────────────────────────────────────────────────── */

:root {
  /*
   * Indigo, and a whole ramp of it rather than one value.
   *
   * The ground is the middle of the ramp — `--indigo-5` — which is dark
   * enough to carry pale type and far enough from black that the two
   * hairline weights above it are visibly different from each other.
   * Near-black cannot do that: every line on it is either invisible or
   * white, and a page with one contrast is a page with no hierarchy.
   *
   * `oklch` because the steps have to be perceptually even. The same
   * ramp in sRGB gets muddy in the middle and the two dim greys end up
   * the same grey.
   */
  --indigo-0: oklch(0.96 0.014 285);
  --indigo-1: oklch(0.88 0.028 285);
  /* 4.7:1 on the ground — solved, not eyeballed. `--faint` sets the two
     readouts in the bars, both of which are small type at label size; at the
     step this used to be they were 3.45:1. */
  --indigo-3: oklch(0.74 0.055 284);
  --indigo-5: oklch(0.365 0.078 283); /* the ground */
  --indigo-7: oklch(0.24 0.062 282);
  --indigo-9: oklch(0.145 0.042 281);

  --bg: var(--indigo-5);
  /* 9.7:1, 7.5:1 and 4.7:1 on the ground respectively. The last one is the
     floor: everything on this page is small type, so nothing may sit below
     4.5:1, which is what the old cobalt-on-near-black palette could not say. */
  --fg: var(--indigo-0);
  --fg-dim: var(--indigo-1);
  --faint: var(--indigo-3);

  --hair: color-mix(in oklab, var(--indigo-0) 22%, transparent);
  --hair-strong: color-mix(in oklab, var(--indigo-0) 48%, transparent);

  --gutter: clamp(1.1rem, 0.6rem + 2.4vw, 3rem);
  --ease: cubic-bezier(0.16, 1, 0.3, 1);
  --dur: 0.32s;

  color-scheme: dark;
}

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

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  background: var(--bg);
}

body {
  min-height: 100svh;
  display: grid;
  grid-template-rows: auto 1fr auto;
  background: var(--bg);
  color: var(--fg-dim);
  /*
   * A system stack, and deliberately.
   *
   * The portfolio sets everything in ZT Nature, which is a licensed face
   * that lives in that repository under that licence. Copying it here to
   * make two sites match would be putting somebody else's font on a
   * second domain because it looked right, which is not a thing a
   * licence check is likely to agree with. The one place the face
   * actually matters is the mark, and the mark is already OUTLINES —
   * `assets/gaxix-wordmark.svg` is the letters as paths, so it is the
   * real thing rather than a substitute.
   *
   * Everything else on this page is a label, and labels are monospace on
   * both sites anyway.
   */
  font-family: ui-monospace, "SF Mono", "Cascadia Code", "Roboto Mono", monospace;
  font-size: clamp(0.7rem, 0.66rem + 0.2vw, 0.78rem);
  line-height: 1.5;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
}

/* ── The room ────────────────────────────────────────────────────
   Not an object and not a container: two washes of the ramp, one
   above and one below, so the page has a top and a bottom without
   anything being drawn. The portfolio gets this from a lit room
   behind a canvas; here it is the ground itself being uneven, which
   is the same idea at a hundredth of the cost. */

body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(120% 80% at 50% -10%, color-mix(in oklab, var(--indigo-3) 46%, transparent), transparent 62%),
    radial-gradient(100% 70% at 50% 110%, color-mix(in oklab, var(--indigo-9) 70%, transparent), transparent 66%);
  transition: opacity 500ms var(--ease);
}

body[data-solid]::before { opacity: 0; }

/* ── Bars ────────────────────────────────────────────────────────── */

.bar {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: var(--gutter);
  opacity: 0;
  animation: cornersIn 720ms var(--ease) 1080ms both;
}

.tag {
  font-size: inherit;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--fg-dim);
}

.tag--dim {
  color: var(--faint);
  font-variant-numeric: tabular-nums;
}

.bar--foot .tag {
  min-width: 0;
  text-align: right;
  letter-spacing: 0.16em;
}

/* ── Stage ───────────────────────────────────────────────────────── */

.stage {
  position: relative;
  z-index: 2;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: clamp(1.6rem, 4vw, 3rem);
  padding: var(--gutter);
  text-align: center;
}

.mark {
  inline-size: min(78vw, 40rem);
  margin: 0;
  line-height: 0;
  color: var(--fg);
  cursor: grab;
  touch-action: pan-y;
}

/*
 * The flat mark, which is what a reader gets until the solid one is built —
 * and what they keep if it never is, or if they have asked for less movement.
 * `data-solid` is set by assets/mark.js once the letters are standing.
 */
.wordmark {
  inline-size: 100%;
  block-size: auto;
  display: block;
  opacity: 0;
  animation: logoIn 900ms var(--ease) 120ms forwards;
}

.mark[data-solid] .wordmark { visibility: hidden; }

/* ── The mark, as a solid ────────────────────────────────────────
   A full-window ray-marched room. The logo is an extruded signed distance
   field, so its sharp face, side and back share one geometric surface. A
   camera-positioned light keeps the visible face white at every rotation; the
   azimuthally uniform room moves with the orbit without exposing a hotspot. */
.scene3d {
  position: fixed;
  inset: 0;
  z-index: 1;
  display: block;
  inline-size: 100%;
  block-size: 100%;
  pointer-events: none;
  opacity: 0;
  animation: logoIn 900ms var(--ease) 120ms forwards;
}

body.turning,
body.turning .mark {
  cursor: grabbing;
  user-select: none;
}

/* ── Failure document ────────────────────────────────────────────
   Kept deliberately free of WebGL: if the server cannot supply an asset, the
   explanation must not depend on the most expensive asset on the page. */
.errorPage {
  grid-template-rows: 1fr;
}

.errorStage {
  position: relative;
  z-index: 2;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 1rem;
  min-height: 100svh;
  padding: var(--gutter);
  text-align: center;
}

.errorIcon {
  inline-size: min(8rem, 28vw);
  block-size: min(8rem, 28vw);
  margin-block-end: 0.75rem;
  filter: invert(1);
  opacity: 0.9;
}

.errorTitle {
  max-inline-size: 18ch;
  color: var(--fg);
  font-family: inherit;
  font-size: clamp(1.45rem, 1.1rem + 1.8vw, 2.6rem);
  font-weight: 500;
  line-height: 1.1;
  letter-spacing: -0.035em;
}

.errorCopy {
  max-inline-size: 46ch;
  margin-block-end: 0.85rem;
  color: var(--fg-dim);
}

@keyframes logoIn {
  to { opacity: 1; }
}

@keyframes cornersIn {
  from {
    opacity: 0;
    transform: translateY(0.6rem);
  }
  to { opacity: 1; transform: none; }
}

/* ── Link ────────────────────────────────────────────────────────
   A rule and a word, and the pair steps into the screen when you
   point at it. That is every control on taurasg.com — the contact
   form's send, the CV's save, the error pages' way out — and it is
   what replaced the bordered, chamfered button that used to be here.

   The cut corners are gone. They were the detail that made this page
   a sibling of the portfolio's old shapes, and the portfolio does not
   have shapes any more. */

.link {
  display: inline-flex;
  align-items: center;
  gap: 0.7rem;
  color: var(--fg);
  text-decoration: none;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  transition: transform var(--dur) var(--ease);
}

.link::before {
  content: "";
  display: block;
  flex: none;
  inline-size: 1.6em;
  block-size: 1px;
  background: currentColor;
  transition: inline-size var(--dur) var(--ease);
}

.link:hover,
.link:focus-visible {
  transform: translateX(0.34em);
}

.link:hover::before,
.link:focus-visible::before {
  inline-size: 2.6em;
}

.link:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 4px;
}

.arrow {
  inline-size: 0.85rem;
  block-size: 0.85rem;
  transition: transform var(--dur) var(--ease);
}

.link:hover .arrow,
.link:focus-visible .arrow {
  transform: translate(2px, -2px);
}

@media (max-width: 30rem) {
  .bar {
    justify-content: center;
    text-align: center;
  }

  .bar--foot {
    flex-direction: column-reverse;
    gap: 1.2rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation: none !important;
    transition-duration: 1ms !important;
  }

  /* The solid mark is never built under this preference, so the flat mark and
     all four corner labels are present without an intermediate hidden state. */
  .bar,
  .wordmark { opacity: 1; }
}
