/* dotted.space — s3.
   One page, one version: the letter, centred, over the atmosphere build.
   Derived from site2.css with the version machinery removed, so s2 stays
   untouched as the comparison set. */
*,*::before,*::after{ box-sizing:border-box; }
html,body{ height:100%; }
body{
  margin:0; overflow:hidden;
  background:var(--ground); color:var(--fg);
  font-family:"Archivo",system-ui,sans-serif;
  -webkit-font-smoothing:antialiased;
}
canvas.field{ position:fixed; inset:0; width:100%; height:100%; display:block; }

.chrome{ position:fixed; inset:0; pointer-events:none; opacity:0; transition:opacity 1.1s ease; }
.chrome.is-up{ opacity:1; }
/* returning visitor, or reduced motion: the page is already resolved, so the
   chrome arrives with it instead of taking a second to catch up */
body.instant .chrome{ transition:opacity .25s ease; }

/* The wordmark's <h1>: meaning only. With no margin and no position of its
   own, the absolutely positioned .brand inside still measures from .chrome,
   exactly where it sat before the heading existed. */
.brandwrap{ margin:0; font:inherit; }
.brand{
  position:absolute; top:34px; left:38px;
  font-family:"Anton",system-ui,sans-serif;
  font-weight:400; font-size:30px; letter-spacing:.005em;
  color:var(--fg); line-height:1;
  background:none; border:0; padding:0; margin:0;
  pointer-events:auto; cursor:pointer;
}
.brand:hover{ opacity:.82; }
.brand:focus-visible{ outline:2px solid var(--fg); outline-offset:6px; }

/* sits under the wordmark: the bottom of the screen is the letter's now */
.hint{
  position:absolute; top:80px; left:38px;
  font-size:11px; font-weight:500; letter-spacing:.2em;
  text-transform:uppercase; color:var(--fg); opacity:.58;
  transition:opacity .5s ease;
}
.hint.is-done{ opacity:0; }

/* ---- the three ---- */
.stations{ position:fixed; inset:0; pointer-events:none; }
.station{
  position:absolute; top:0; left:0; width:0; height:0;
  display:block; text-decoration:none;
  opacity:0; transition:opacity 1s ease;
}
.stations.is-revealed .station{ opacity:1; }

.station .ring{
  position:absolute; left:-96px; top:-96px; width:192px; height:192px;
  overflow:visible; pointer-events:none;
}
.station .curve{
  font-family:"Archivo",system-ui,sans-serif;
  font-weight:600; font-size:20px; letter-spacing:.155em;
  fill:var(--accent); transition:fill .25s ease;
}
.station .halo{
  fill:none; stroke:var(--accent); stroke-width:1;
  opacity:0; transition:opacity .3s ease;
}
/* A point is not a target until it is actually on the screen. During the
   intro the hit areas are invisible but were still taking hovers, clicks and
   Tab stops — so the page could be operated before it existed. `is-live` is
   set once the reveal has finished painting, never earlier; the JS guards the
   handlers as well, so neither half can let an early press through alone. */
.station .hit{
  position:absolute; left:-44px; top:-44px; width:88px; height:88px;
  border-radius:50%; pointer-events:none; cursor:pointer;
  -webkit-tap-highlight-color:transparent;
}
.stations.is-live .station .hit{ pointer-events:auto; }

/* A focus ring you can actually see, on all three. The <a> itself is
   suppressed: it is a zero-size element wrapping the label and the card, so
   the browser's own ring traced a stepped box around all of it. */
a.station:focus-visible{ outline:none; }
.station .hit:focus-visible,
a.station:focus-visible .hit{
  outline:2px solid var(--accent); outline-offset:6px; border-radius:50%;
}

/* ---- descriptions: hover only, under their dot ---- */
.deck{ position:fixed; inset:0; pointer-events:none; }
.deck .col{
  position:absolute; top:0; left:0; width:320px;
  transform:translateX(-50%);
  opacity:0; visibility:hidden;
  transition:opacity .3s ease, visibility 0s .3s;
}
.deck .col.is-open{
  opacity:1; visibility:visible;
  transition:opacity .3s ease, visibility 0s 0s;
}
.deck .col .card{ text-align:center; }
.card p{ margin:0; font-size:13.5px; line-height:1.62; color:var(--fg); opacity:.78; }

/* Phone only. With a mouse the dot is plainly a link — the cursor says so and
   one click follows it. With a finger there is no cursor and no hover, so the
   only way out was to tap the dot a second time, which nothing tells you and
   which collides with the obvious reading of a second tap: close this.
   A project with nowhere to go gets no button; as of 2026-09-23 all three have
   somewhere to go. */
.card .visit{ display:none; }

/* ---------------------------------------------------------------
   The letter — centred in the screen, under the three
   --------------------------------------------------------------- */
.letter{
  position:absolute; left:50%; transform:translateX(-50%);
  bottom:38px;
  /* As wide as the page allows, so it never reads as another description card.
     940px is where it settles to six lines with no paragraph past two; the
     subtraction is just a gutter on windows narrower than that. */
  width:min(940px, calc(100vw - 120px));
  text-align:center;
  /* Nothing in here is interactive since the address moved to the header, and
     on a short phone its box reached up over the third point and swallowed the
     tap — a full-width block on a fixed layer will always find something to
     cover eventually. */
  pointer-events:none;
}
.letter p{
  margin:0 0 12px; font-size:14.5px; line-height:1.62;
  color:var(--fg); opacity:.62;
}
.letter p.lead{ opacity:.78; }
.letter p:last-child{ margin-bottom:0; }
/* Opposite the wordmark. Under the letter it added 30px to the block's height;
   in a bottom corner it caps how wide the letter may grow, and the width is
   what keeps the letter clear of the project descriptions. Up here it costs
   the letter nothing at either end. */
.chrome .contact{
  position:absolute; top:40px; right:38px; margin:0;
  /* 11.5px at .42 opacity measured 3.5:1 against the ground — under the 4.5:1
     floor for text this size, and it read as a smudge. 13px at .68 is 7.0:1. */
  font-size:13px; letter-spacing:.04em; color:var(--fg); opacity:.68;
  pointer-events:auto;
}
.chrome .contact a{
  color:var(--fg); text-decoration:none;
  border-bottom:1px solid currentColor; padding-bottom:2px;
}
.chrome .contact a:hover{ opacity:.85; }
/* The company line, under the contact and a step quieter: the address is the
   thing to act on, this is who stands behind it. .58 on the ground is ~6:1. */
.chrome .company{
  position:absolute; top:62px; right:38px; margin:0;
  font-size:12px; letter-spacing:.04em; color:var(--fg); opacity:.58;
  pointer-events:auto;
}
.chrome .company a{
  color:var(--fg); text-decoration:none;
  border-bottom:1px solid currentColor; padding-bottom:1px;
}
.chrome .company a:hover{ opacity:.85; }
.chrome .company a:focus-visible,
.chrome .contact a:focus-visible{ outline:2px solid var(--fg); outline-offset:4px; }

/* ---------------------------------------------------------------
   Phones. The header stacks at the top left — there is no width to
   put the address opposite the wordmark on a 360px screen without
   it colliding on the narrowest ones — and the three points run
   down the left with their description beside them, because under
   them the third card runs into the letter.
   --------------------------------------------------------------- */
@media (max-width:760px){
  .brand{ font-size:22px; top:22px; left:20px; }
  .chrome .contact{ top:56px; right:auto; left:20px; font-size:12px; }
  /* Opposite the wordmark, in two short lines. Under the contact it pushed the
     hint down into the top point's drift: at 375x667 the label rose to 0.1px
     from the hint (measured over the bob cycle, not at one instant). Up here
     the left-hand stack is exactly what it was. */
  .chrome .company{
    top:22px; right:20px; left:auto; font-size:11px; line-height:1.4; text-align:right;
  }
  .chrome .company .co-name, .chrome .company .co-links{ display:block; }
  .chrome .company .co-sep{ display:none; }
  .hint{ top:82px; left:20px; font-size:9.5px; }
  .station .ring{ left:-74px; top:-74px; width:148px; height:148px; }
  .station .curve{ font-size:15px; letter-spacing:.11em; }
  .deck .col{ width:min(320px, 86vw); }
  /* the letter stands aside for it, so it can be read at full size */
  .card p{ font-size:13.5px; line-height:1.6; }
  .card .visit{
    /* 14px of padding, not 11: at 11 the box came out 39px tall, under the
       44px a finger needs. */
    display:inline-block; margin-top:16px; padding:14px 18px;
    border:1px solid currentColor; border-radius:2px;
    color:var(--accent); text-decoration:none;
    font-size:12.5px; font-weight:600; letter-spacing:.03em;
    /* the deck is a pass-through layer; this is the one thing in it that is
       meant to be touched */
    pointer-events:auto; -webkit-tap-highlight-color:transparent;
  }
  .card .visit:focus-visible{ outline:2px solid var(--accent); outline-offset:3px; }
  .letter{ left:20px; right:20px; transform:none; width:auto; bottom:20px; }
  .letter p{ font-size:12.5px; line-height:1.5; margin-bottom:9px; }
  /* The letter stands down while a description is open — see site3.js. The
     entrance fade comes down to match, so closing one does not take a second
     and a bit to bring the letter back. */
  .letterwrap{ transition:opacity .3s ease; }
  body.any-open .letterwrap{ opacity:0; }
}

/* A short phone (SE-sized, 667pt) has to fit the same four paragraphs into
   200 fewer points, so the whole page comes down a step. */
@media (max-width:760px) and (max-height:719px){
  .brand{ font-size:20px; }
  .chrome .contact{ top:50px; font-size:11px; }
  .chrome .company{ top:20px; font-size:10.5px; line-height:1.35; }
  .hint{ top:74px; font-size:9px; }
  .letter p{ font-size:11.5px; line-height:1.46; margin-bottom:7px; }
  .card p{ font-size:12.5px; line-height:1.55; }
  .card .visit{ margin-top:13px; padding:14px 16px; font-size:11.5px; }
}

/* reduced motion gets the resolved page and nothing that moves */
@media (prefers-reduced-motion:reduce){
  .chrome,.station,.deck .col,.station .halo,.station .curve{ transition:none; }
}
