/* Vevris
   The canvas is the page. This file only does three things: hold the frame,
   place the copy layer where the particle field says it should go, and resolve
   that copy without ever using a bare opacity fade. */

:root {
  --bg: #050505;
  --ink: #ffffff;
  --ink-2: #a8a8a8;
  --accent: #b11226;
  /* The brand red lifted for use as text on black, where #b11226 reads muddy. */
  --accent-ink: #d4243c;
  --font: 'Satoshi', 'Satoshi-Variable', Inter, 'Helvetica Neue', Arial, sans-serif;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--ink);
}

html {
  overscroll-behavior-y: none;
  scrollbar-width: none;          /* the scroll cue is drawn in particles */
  -ms-overflow-style: none;
}
html::-webkit-scrollbar { display: none; }

/* Let the opening breathe: no scrolling until the logo has resolved. */
html.is-intro, html.is-intro body { overflow: hidden; height: 100%; }

/* Paged mode (touch): the page itself never scrolls — a swipe advances one
   chapter and the field is driven directly. The runway still exists to scale
   the timeline against; it is simply clipped. */
html.is-paged, html.is-paged body { overflow: hidden; height: 100%; }
html.is-paged #stage { touch-action: none; }

body {
  font-family: var(--font);
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

/* ---------------------------------------------------------------- stage */

#stage {
  position: fixed;
  inset: 0;
  z-index: 1;
}

#field {
  display: block;
  width: 100%;
  height: 100%;
}

#overlay {
  position: absolute;
  inset: 0;
  pointer-events: none;
  contain: layout style;
}

/* Positioned by JS from the same layout that generated the particle targets,
   so the text lands exactly inside the shape the particles just built. */
.ov {
  position: absolute;
  left: 0;
  top: 0;
  --r: 0;
  will-change: transform;
  pointer-events: none;
}

/* The resolve. A soft directional mask sweeps the copy in while it comes out of
   blur — construction, not appearance. */
.ovi {
  --m0: calc(var(--r) * 152% - 52%);
  --m1: calc(var(--r) * 152% - 4%);
  display: block;
  -webkit-mask-image: linear-gradient(97deg, #000 var(--m0), rgba(0, 0, 0, 0) var(--m1));
  mask-image: linear-gradient(97deg, #000 var(--m0), rgba(0, 0, 0, 0) var(--m1));
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
  filter: blur(calc((1 - var(--r)) * 4.5px));
  transform: scale(calc(0.986 + var(--r) * 0.014));
  transform-origin: 50% 50%;
}

/* ---------------------------------------------------------------- copy */

.copy {
  text-align: center;
  color: var(--ink-2);
  font-weight: 400;
  /* Tracking comes from JS so it matches the canvas exactly. Kerning and
     ligatures are off for the same reason — the sampler draws glyph by glyph. */
  font-kerning: none;
  font-variant-ligatures: none;
  font-feature-settings: 'kern' 0, 'liga' 0;
}

.copy .l { display: block; white-space: nowrap; }

/* Crimson, not the browser's blue. The flat brand red is too dark to read as
   small text on black, so links use a lifted version of it. */
#footer-mail a {
  color: var(--accent-ink);
  text-decoration: none;
  transition: color 0.5s cubic-bezier(0.16, 1, 0.3, 1);
}
#footer-mail a:hover { color: #ec4159; }

/* ---------------------------------------------------------------- button */

.btn { text-decoration: none; display: block; }

.btn-box {
  position: relative;
  display: block;
  overflow: hidden;
}

/* Grows out of a line rather than fading up — the particle outline is already
   sitting exactly where this edge lands. */
.btn-fill {
  position: absolute;
  inset: 0;
  background: var(--accent);
  transform: scaleY(calc(0.05 + var(--r) * 0.95));
  transform-origin: 50% 50%;
}

.btn-label {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--ink);
  font-weight: 500;
  letter-spacing: 0.02em;
  line-height: 1;
}

.btn:focus-visible .btn-box {
  outline: 1px solid rgba(255, 255, 255, 0.6);
  outline-offset: 6px;
}

/* ---------------------------------------------------------------- chrome
   Navigation is ordinary DOM, deliberately. It has to work before the opening
   finishes, on a failed font load, and with the canvas absent — so none of it
   is built from particles. */

#bar {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 6;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 22px 30px;
  pointer-events: none;
}
#bar > * { pointer-events: auto; }

/* Undoes the inline first-paint guard once the real stylesheet is in. */
#bar, #menu, #skip { visibility: visible; }
#bar[hidden], #menu[hidden], #skip[hidden] { display: none; }

/* On the homepage the bar arrives after the opening, so it eases in rather than
   popping — otherwise it would be the one thing on the page that simply
   appears. Scoped to [data-fade]: the document pages have no opening sequence
   and no script to reveal it, so their bar must be visible from the first paint. */
#bar[data-fade] { opacity: 0; transition: opacity 0.9s cubic-bezier(0.16, 1, 0.3, 1); }
#bar[data-fade].in { opacity: 1; }

/* The burger is the whole navigation, so it sits alone on the right. */
#burger {
  appearance: none;
  background: none;
  border: 0;
  padding: 8px;
  margin: -8px;
  cursor: pointer;
  display: grid;
  gap: 5px;
  width: 40px;
  justify-self: end;
}
#burger span {
  display: block;
  height: 1px;
  background: var(--ink);
  transition: transform 0.5s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.3s ease;
}
#burger[aria-expanded='true'] span:first-child { transform: translateY(3px) rotate(45deg); }
#burger[aria-expanded='true'] span:last-child { transform: translateY(-3px) rotate(-45deg); }

/* The mark IS the V: it sits flush against "evris" to spell the name once.
   The viewBox is cropped to the glyph's own bounds so its bottom edge is the
   baseline, which is what lets it sit on the text baseline via flex-end. */
.bar-logo {
  display: flex;
  align-items: flex-end;
  /* No gap here — flexbox clamps a negative gap to zero. The kern is applied as
     a negative margin on the word instead; see .bar-logo span. */
  gap: 0;
  text-decoration: none;
  color: var(--ink);
  font-size: 20px;
  line-height: 1;
}
.bar-logo svg {
  height: 0.98em;
  width: auto;
  fill: currentColor;
  display: block;
}
.bar-logo span {
  font-weight: 500;
  letter-spacing: 0.005em;
  line-height: 1;
  /* The V's ink recedes leftward as it descends, so setting the boxes apart
     leaves a large optical hole. Pulling the word back tucks the 'a' under the
     diagonal, which is what kerning a "Va" pair means. Chosen by eye from a
     rendered ladder rather than by formula: matching the geometric gap read far
     too loose, and matching the closest ink-to-ink distance read too tight. */
  margin-left: -0.1em;
}

/* ---------------------------------------------------------------- menu */

#menu {
  position: fixed;
  inset: 0;
  z-index: 5;
  background: rgba(5, 5, 5, 0.94);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  display: flex;
  align-items: center;
  /* Opens under its own button, on the right. A sheet that opens on the
     opposite side to the control that summoned it reads as unrelated to it. */
  justify-content: flex-end;
  padding: 0 clamp(30px, 9vw, 140px);
  opacity: 0;
  transition: opacity 0.5s cubic-bezier(0.16, 1, 0.3, 1);
}
#menu.open { opacity: 1; }

#menu-inner { display: flex; flex-direction: column; align-items: flex-end; }

/* One scale for every entry. The only deliberate difference is the app list,
   which is a step smaller and indented because it is subordinate to Apps —
   everything else shares a size, weight and rhythm. */
#menu button, #menu a, #menu .group {
  appearance: none;
  background: none;
  border: 0;
  color: var(--ink);
  font: inherit;
  font-size: clamp(25px, 3.9vw, 40px);
  font-weight: 500;
  letter-spacing: -0.012em;
  /* One line-height and one padding for every row, so the vertical rhythm is
     even from top to bottom — no group is spaced apart from any other. */
  line-height: 1.34;
  text-align: right;
  text-decoration: none;
  padding: 0;
  cursor: pointer;
  /* Each row rises as the sheet opens, staggered by --i. */
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 0.55s cubic-bezier(0.16, 1, 0.3, 1),
              transform 0.55s cubic-bezier(0.16, 1, 0.3, 1),
              color 0.4s ease;
  transition-delay: calc(var(--i, 0) * 42ms);
}
#menu.open button, #menu.open a, #menu.open .group { opacity: 1; transform: none; }
#menu button:hover, #menu a:hover { color: var(--ink-2); }

/* Apps names a group rather than linking anywhere, so it is not interactive. */
#menu .group { color: var(--ink-2); cursor: default; }

/* Indented from the right, since the list is right-aligned. Kept on the same
   line-height as everything else so the rhythm does not break. */
#menu .child {
  font-size: clamp(19px, 2.7vw, 27px);
  padding-right: 26px;
  color: var(--ink-2);
}
#menu .child:hover { color: var(--ink); }

/* The tag sits to the LEFT of the name, so every app name still ends flush on
   the same right edge as the rest of the list. */
#menu .soon {
  display: flex;
  align-items: center;
  justify-content: flex-end;
}
#menu .soon::after {
  content: 'Coming soon';
  order: -1;
  margin-right: 14px;
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--accent);
}

/* ---------------------------------------------------------------- skip */

#skip {
  position: fixed;
  right: 30px;
  bottom: 28px;
  z-index: 7;
  appearance: none;
  background: none;
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 999px;
  color: var(--ink-2);
  font: inherit;
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 10px 18px;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.7s cubic-bezier(0.16, 1, 0.3, 1),
              color 0.4s ease, border-color 0.4s ease;
}
#skip.show { opacity: 1; }
#skip:hover { color: var(--ink); border-color: rgba(255, 255, 255, 0.4); }
#skip span { margin-left: 8px; }

/* ---------------------------------------------------------------- footer */

.foot-nav {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.6em 2.1em;
  color: var(--ink-2);
  font-weight: 400;
  letter-spacing: 0.05em;
}
.foot-nav a,
.foot-nav button {
  appearance: none;
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  text-decoration: none;
  cursor: pointer;
  transition: color 0.45s cubic-bezier(0.16, 1, 0.3, 1);
}
.foot-nav a:hover,
.foot-nav button:hover { color: var(--ink); }

/* Real navigation, so it has to be reachable by keyboard too. */
.foot-nav a:focus-visible,
.foot-nav button:focus-visible {
  outline: 1px solid rgba(255, 255, 255, 0.5);
  outline-offset: 5px;
  color: var(--ink);
}

/* The hidden entrance. No cursor change, no outline, no hint of any kind —
   a visitor who does not already know is meant never to find out. */
#footer-well {
  cursor: default;
  -webkit-tap-highlight-color: transparent;
  user-select: none;
  -webkit-user-select: none;
}

/* The descent into the CMS. */
#blackout {
  position: fixed;
  inset: 0;
  background: #000;
  z-index: 20;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.85s cubic-bezier(0.4, 0, 0.6, 1);
}
#blackout.on { opacity: 1; }

/* ---------------------------------------------------------------- track */

/* The scroll runway. Every keyframe transition is measured against this. */
#track {
  position: relative;
  height: 630vh;
  pointer-events: none;
}

/* ---------------------------------------------------------------- a11y */

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Reduced motion: the opening is skipped and the field is placed rather than
   animated, so the copy should simply be present — no wipe, no blur, no scale. */
@media (prefers-reduced-motion: reduce) {
  .ovi {
    filter: none;
    transform: none;
    -webkit-mask-image: none;
    mask-image: none;
  }
  .btn-fill { transform: none; }
}
