:root {
  --ground: #000;
  /* Space-separated triplets so the glow can carry its own alpha via
     rgb(var(--c) / a). The artwork already has bloom baked in, so these
     shadows stay translucent -- opaque ones wash the tubes into haze. */
  --glow-not: 255 77 133;
  --glow-your: 34 232 240;
  --glow-business: 169 123 245;
  --glow-word: 223 246 250;
  --link-rest: #6E7A80;
}

* { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  background: var(--ground);
  display: grid;
  place-items: center;
  min-height: 100dvh;
}

/* One scale knob drives the whole lockup, so every part stays in the
   proportions the artwork was drawn in. --unit is the wordmark's width
   because the wordmark is the widest element; deriving the narrower icon
   row from it is what keeps the lockup inside the viewport on mobile. */
.sign {
  --unit: clamp(288px, 86vw, 860px);
  /* The wordmark spans wider than the icon row in the source artwork,
     1209px against 1024px. */
  --row: calc(var(--unit) / 1.18);
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 6vmin 4vw;
}

/* The icon crops sit 4px and 21px apart in the source, so edge-to-edge in a
   flex row reproduces the reference spacing without any gap. Each crop also
   carries its own padding, which supplies the gap down to the wordmark. */
.icons {
  display: flex;
  width: var(--row);
}
.icon {
  width: calc(var(--row) / 3);
  height: auto;
}

.wordmark-wrap {
  margin: 0;
  line-height: 0;
}
.wordmark {
  width: var(--unit);
  height: auto;
}

/* Per-element glow colour and strike delay, consumed by the animations below. */
[data-tube="1"] { --c: var(--glow-not);      --d: 200ms; }
[data-tube="2"] { --c: var(--glow-your);     --d: 500ms; }
[data-tube="3"] { --c: var(--glow-business); --d: 800ms; }
[data-tube="4"] { --c: var(--glow-word);     --d: 1150ms; }
[data-tube="5"] { --c: var(--glow-your);     --d: 1900ms; }
[data-tube="6"] { --c: var(--glow-word);     --d: 1550ms; }

/* Unlit: the tube is present but dark, as an unpowered sign would be.
   Two animations, one declaration: strike runs once after its delay, then
   breathe takes over. breathe uses the default fill of none, so it adds
   nothing during its own delay and strike's `both` fill holds the lit state
   until it starts -- at which point breathe, being later in the list, wins
   the filter property. That handoff is why this needs no JavaScript. */
.tube {
  /* The settled glow and its peak on the breathing cycle. These must be
     declared HERE, not on :root: a custom property's var() references are
     substituted against the element that declares it, so --c has to be in
     scope on the same element or --lit computes to guaranteed-invalid and
     filter silently falls back to none. */
  --lit: drop-shadow(0 0 6px rgb(var(--c) / 0.5))
         drop-shadow(0 0 18px rgb(var(--c) / 0.28));
  --lit-bright: drop-shadow(0 0 9px rgb(var(--c) / 0.62))
                drop-shadow(0 0 26px rgb(var(--c) / 0.38));

  opacity: 0.04;
  animation: strike 900ms var(--d) both,
             breathe 4s ease-in-out calc(var(--d) + 900ms) infinite;
}

/* Arrives after the wordmark, before the nav. This is the homepage's only
   real body copy -- everything else here is imagery or a nav label. */
.tagline {
  opacity: 0;
  animation: arrive 700ms var(--d) both;
  margin: calc(var(--unit) * 0.05) 0 0;
  max-width: 44ch;
  text-align: center;
  text-wrap: balance;
  color: var(--link-rest);
  font: 400 clamp(12.5px, 1.5vw, 14.5px)/1.5 ui-sans-serif, system-ui,
        -apple-system, "Helvetica Neue", sans-serif;
}

/* The nav just arrives -- no strike. A coloured drop-shadow over dim grey
   text muddies it and fights the hover text-shadow. */
.nav {
  opacity: 0;
  animation: arrive 700ms var(--d) both;
  /* The links carry 14px of vertical padding for a usable tap target, so the
     nav's own offset is trimmed to keep the optical gap unchanged. Trimmed
     again once the tagline landed above it and supplied the separation. */
  /* One size drives the labels and the divider. The divider previously used
     1em, which resolved against .nav's inherited 16px rather than the link
     type, so it never scaled with the labels. */
  --nav-size: clamp(8.8px, 1.12vw, 10.4px);
  margin-top: calc(var(--unit) * 0.022);
  display: flex;
  align-items: center;
  gap: clamp(8px, 1.44vw, 14.4px);
}

.nav-rule {
  width: 1px;
  height: calc(var(--nav-size) * 1.23);
  background: var(--link-rest);
  opacity: 0.5;
}

.link {
  color: var(--link-rest);
  font: 500 var(--nav-size)/1 ui-sans-serif, system-ui,
        -apple-system, "Helvetica Neue", sans-serif;
  letter-spacing: 0.38em;
  /* letter-spacing adds a trailing space after the last glyph; cancelling it
     keeps each label optically centred against the divider. */
  margin-right: -0.38em;
  /* An 11px line of text is not a tap target; this brings it to ~39px
     without changing anything you can see. */
  padding: 14px 6px;
  text-transform: uppercase;
  text-decoration: none;
  transition: color 450ms ease, text-shadow 450ms ease;
}
.link:hover,
.link:focus-visible {
  color: #fff;
  text-shadow: 0 0 8px rgb(var(--c) / 0.75), 0 0 22px rgb(var(--c) / 0.45);
}

/* ---- contact dialog ---- */

.sheet {
  width: min(440px, calc(100vw - 32px));
  margin: auto;
  padding: clamp(24px, 4vw, 34px);
  border: 1px solid rgb(255 255 255 / 0.22);
  border-radius: 12px;
  background: #07090A;
  color: #fff;
  box-shadow: 0 0 40px rgb(255 255 255 / 0.07);
}
.sheet::backdrop {
  background: rgb(0 0 0 / 0.74);
  backdrop-filter: blur(3px);
}

.sheet-title {
  margin: 0 0 clamp(18px, 3vw, 24px);
  color: var(--link-rest);
  font: 500 11px/1 ui-sans-serif, system-ui, -apple-system,
        "Helvetica Neue", sans-serif;
  letter-spacing: 0.38em;
  margin-right: -0.38em;
  text-transform: uppercase;
}

.sheet-close {
  position: absolute;
  top: 10px;
  right: 12px;
  /* 44px of target under a 20px glyph. */
  width: 44px;
  height: 44px;
  border: 0;
  background: none;
  color: var(--link-rest);
  font: 400 20px/1 ui-sans-serif, system-ui, sans-serif;
  cursor: pointer;
  transition: color 250ms ease;
}
.sheet-close:hover,
.sheet-close:focus-visible { color: #fff; }

.field {
  display: flex;
  flex-direction: column;
  gap: 7px;
  margin: 0 0 16px;
}
.field label {
  color: var(--link-rest);
  font: 500 10px/1 ui-sans-serif, system-ui, -apple-system, sans-serif;
  letter-spacing: 0.2em;
  text-transform: uppercase;
}
.field input,
.field textarea {
  width: 100%;
  padding: 11px 12px;
  border: 1px solid rgb(255 255 255 / 0.16);
  border-radius: 3px;
  background: rgb(255 255 255 / 0.03);
  color: #fff;
  font: 400 14px/1.5 ui-sans-serif, system-ui, -apple-system,
        "Helvetica Neue", sans-serif;
  transition: border-color 250ms ease;
}
.field textarea { resize: vertical; min-height: 96px; }
.field input:focus,
.field textarea:focus {
  outline: none;
  border-color: rgb(255 255 255 / 0.55);
}
.field [aria-invalid="true"] { border-color: rgb(var(--glow-not) / 0.7); }

.field-error:empty { display: none; }
.field-error {
  color: rgb(var(--glow-not) / 0.95);
  font: 400 12px/1.4 ui-sans-serif, system-ui, -apple-system, sans-serif;
}

/* The honeypot. Kept out of the layout without display:none, which some bots
   check for, and hidden from assistive tech by aria-hidden on the wrapper. */
.trap {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.sheet-send {
  width: 100%;
  padding: 13px;
  border: 1px solid rgb(255 255 255 / 0.38);
  border-radius: 3px;
  background: rgb(255 255 255 / 0.06);
  color: #fff;
  font: 500 11px/1 ui-sans-serif, system-ui, -apple-system, sans-serif;
  letter-spacing: 0.28em;
  text-indent: 0.28em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background-color 250ms ease, border-color 250ms ease;
}
.sheet-send:hover:not(:disabled),
.sheet-send:focus-visible:not(:disabled) {
  background: rgb(255 255 255 / 0.13);
  border-color: rgb(255 255 255 / 0.7);
}
.sheet-send:disabled { opacity: 0.5; cursor: default; }

.sheet-status:empty { display: none; }
.sheet-status {
  margin: 14px 0 0;
  text-align: center;
  font: 400 13px/1.5 ui-sans-serif, system-ui, -apple-system, sans-serif;
}
.sheet-status[data-tone="good"] { color: #fff; }
.sheet-status[data-tone="bad"]  { color: rgb(var(--glow-not) / 0.95); }
.sheet-status[data-tone="busy"] { color: var(--link-rest); }

/* ---- projects index ---- */

/* body centres the splash lockup; this page starts at the top instead. */
.page-index { align-items: start; }

.index {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(52px, 11vh, 104px);
  width: min(680px, 90vw);
  padding: clamp(36px, 7vh, 76px) 4vw 12vmin;
}

.index-head {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(14px, 2.4vw, 20px);
}

.home-mark {
  width: min(420px, 74vw);
  height: auto;
  /* Written out rather than reusing --lit: that lives on .tube, and a custom
     property only resolves var(--c) against the element declaring it. */
  filter: drop-shadow(0 0 5px rgb(var(--glow-word) / 0.45))
          drop-shadow(0 0 14px rgb(var(--glow-word) / 0.24));
  transition: filter 450ms ease;
}
.home:focus-visible .home-mark,
.home:hover .home-mark {
  filter: drop-shadow(0 0 8px rgb(var(--glow-word) / 0.6))
          drop-shadow(0 0 22px rgb(var(--glow-word) / 0.36));
}

.index-title {
  margin: 0;
  color: var(--link-rest);
  font: 500 clamp(11px, 1.4vw, 13px)/1 ui-sans-serif, system-ui,
        -apple-system, "Helvetica Neue", sans-serif;
  letter-spacing: 0.38em;
  margin-right: -0.38em;
  text-transform: uppercase;
}

.portfolio {
  list-style: none;
  margin: 0;
  padding: 0;
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: clamp(76px, 11vw, 116px);
}

.entry {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(14px, 2.2vw, 18px);
}

/* The logo sits inside an h2 so its alt text serves as the heading, giving
   each project a real name in the document outline instead of leaving it in
   an attribute. Purely structural -- it must not show. */
.entry-name {
  margin: 0;
  font-size: inherit;
  font-weight: inherit;
  line-height: 0;
}

.entry-link {
  display: block;
  text-decoration: none;
  transition: filter 450ms ease, transform 450ms ease;
}
.entry-link:hover,
.entry-link:focus-visible {
  /* These logotypes carry their own colour, so the lift comes from a neutral
     white bloom rather than a tint that would fight the artwork. */
  filter: drop-shadow(0 0 14px rgb(255 255 255 / 0.22));
  transform: translateY(-2px);
}

/* The logos run from 2.4:1 to 6.4:1. Fitting each inside a shared box sizes
   them by optical weight; giving them a shared width instead would let the
   long wordmarks tower over the compact ones. */
.entry-logo-box {
  --logo-scale: 1;
  --logo-width: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  height: calc(clamp(50px, 8.5vw, 66px) * var(--logo-scale));
  width: calc(min(300px, 72vw) * var(--logo-width));
}

/* Mutter (2.7:1) and DigiFrens (2.4:1) are far squarer than the long
   wordmarks, so filling the same box height leaves them reading small.
   They get more of it. */
.entry-logo-box.is-compact { --logo-scale: 1.35; }

/* LevX is bound by width rather than height, so trimming its width
   allowance is the knob that actually shrinks it. */
.entry-logo-box.is-narrow { --logo-width: 0.88; }
.entry-logo {
  max-height: 100%;
  max-width: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
}

.tags {
  list-style: none;
  margin: 2px 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
}

/* Deliberately not uppercased: these carry their own casing ("iOS",
   "MetaDAO", "ORE") and text-transform would flatten it. */
.tag {
  padding: 5px 12px;
  border: 1px solid rgb(255 255 255 / 0.14);
  border-radius: 999px;
  color: var(--link-rest);
  font: 500 11px/1 ui-sans-serif, system-ui, -apple-system,
        "Helvetica Neue", sans-serif;
  letter-spacing: 0.06em;
  white-space: nowrap;
}

.entry-desc {
  margin: 0;
  max-width: 46ch;
  text-align: center;
  text-wrap: balance;
  color: var(--link-rest);
  font: 400 clamp(13px, 1.7vw, 14px)/1.6 ui-sans-serif, system-ui,
        -apple-system, "Helvetica Neue", sans-serif;
}

/* A real tube strikes, stutters, overshoots, then settles. */
@keyframes strike {
  0%   { opacity: 0.04; filter: none; }
  8%   { opacity: 1;    filter: drop-shadow(0 0 3px rgb(var(--c) / 0.35)); }
  14%  { opacity: 0.06; filter: none; }
  22%  { opacity: 1;    filter: drop-shadow(0 0 4px rgb(var(--c) / 0.4)); }
  30%  { opacity: 0.12; filter: none; }
  38%  { opacity: 1;    filter: drop-shadow(0 0 10px rgb(var(--c) / 0.75))
                                drop-shadow(0 0 30px rgb(var(--c) / 0.5)); }
  52%  { opacity: 1;    filter: drop-shadow(0 0 5px rgb(var(--c) / 0.45))
                                drop-shadow(0 0 14px rgb(var(--c) / 0.22)); }
  100% { opacity: 1;    filter: var(--lit); }
}

@keyframes breathe {
  0%, 100% { opacity: 1; filter: var(--lit); }
  50%      { opacity: 1; filter: var(--lit-bright); }
}

@keyframes arrive {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .tube {
    animation: none;
    opacity: 1;
    filter: var(--lit);
  }
  .nav,
  .tagline {
    animation: none;
    opacity: 1;
  }
  .link { transition: none; }
}
