/* ─────────────────────────────────────────────────────────────
   siteo · link hub
   Mobile-first. Designed at 390×844, then allowed to breathe on
   desktop rather than stretched into it.
   ───────────────────────────────────────────────────────────── */

@font-face{
  font-family:"Overused Grotesk";
  src:url("assets/OverusedGrotesk.woff2") format("woff2");
  font-weight:100 900; font-display:swap;
}
@font-face{
  font-family:"Supply Mono";
  src:url("assets/SupplyMono.woff2") format("woff2");
  font-weight:400; font-display:swap;
}

:root{
  --paper:#F0F0EF;
  --ink:#111110;
  --mut:#63615D;
  --dim:#9B9892;
  --line:rgba(17,17,16,.13);
  --line-hi:rgba(17,17,16,.26);
  --card:rgba(255,255,255,.60);
  --card-hi:rgba(255,255,255,.78);
  --green:#2F6B4F;
  --green-line:rgba(47,107,79,.38);
  --green-wash:rgba(47,107,79,.07);
  --ease:cubic-bezier(.22,.61,.36,1);
}

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

html{
  -webkit-text-size-adjust:100%;
  /* iOS paints the CANVAS during rubber-band overscroll, and a position:fixed
     layer does not follow it — so bouncing past the top or bottom exposed flat
     paper with no grid, reading as pale bars. Painting the same tile on the
     canvas means anything exposed still looks like graph paper. */
  background:var(--paper) url("assets/grid-background.png") repeat 0 0;
  background-size:132px 132px;
  overscroll-behavior:none;        /* and stop the bounce happening at all */
}

body{
  min-height:100vh;
  min-height:100dvh;              /* mobile Safari: excludes the toolbars */
  background:transparent;          /* the canvas already carries the tile */
  color:var(--ink);
  font-family:"Overused Grotesk",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
  display:flex; flex-direction:column;
  overflow-x:hidden;              /* nothing may scroll sideways */
  -webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale;
}

/* ── the graph paper ──────────────────────────────────────────
   Fixed and heavily oversized, because the cursor can now drag the
   paper a long way. The overscan is the budget; script.js measures it
   at runtime and clamps the total offset inside it, so an edge cannot
   be exposed however the spring is later retuned. The tile repeats
   seamlessly, so there is no seam to catch either.                 */
.grid{
  position:fixed; inset:-16%;
  background-image:url("assets/grid-background.png");
  background-repeat:repeat;
  background-size:160px 160px;
  will-change:transform;
  transform:translate3d(0,0,0);
  z-index:0;
  pointer-events:none;
}
@media (min-width:521px){ html{ background-size:160px 160px } }
@media (max-width:520px){
  .grid{
    background-size:132px 132px;   /* denser on a small screen */
    /* a finger drags the paper much further than a cursor pulls it, so a
       phone needs MORE overscan than a desktop, not less */
    inset:-31%;   /* faster, wider drift needs more paper behind it */
  }
}

/* ── layout ───────────────────────────────────────────────── */
.wrap{
  position:relative; z-index:1;
  flex:1 1 auto;
  display:flex; flex-direction:column;
  align-items:center; justify-content:center;
  gap:clamp(38px,9vh,64px);
  width:100%;
  max-width:440px;                /* desktop stays a narrow column */
  margin:0 auto;
  padding:clamp(48px,10vh,88px) 24px clamp(24px,5vh,40px);
}

/* ── hero ─────────────────────────────────────────────────── */
.hero{ display:flex; flex-direction:column; align-items:center; text-align:center; }

/* ── the wordmark types itself ─────────────────────────────────
   The mark is a PNG, not text, so "typing" is a clip that snaps to the
   letter boundaries measured out of the image (the letters connect, so
   those stops were found by scanning the alpha channel). The extra o's
   are the mark's OWN final o, cropped and repeated — no font could have
   matched that shape. Height is fixed so all three spellings share a
   baseline; only width changes. */
.wordmark{
  display:flex; align-items:flex-end; justify-content:center;
  gap:5px; margin:0 0 15px; height:clamp(17px,4.7vw,21px);
  user-select:none;
}
.wm-clip{
  display:block; height:100%; width:0; overflow:hidden;
  will-change:width;
}
.wm-clip img{
  height:100%; width:auto; max-width:none; display:block;
  -webkit-user-drag:none;
}
/* Measured off the artwork: the round letters sit between 29% and 96% of
   the image height. The caret matches that band and then some, so it reads
   as a text cursor rather than a tick mark.
   NB the offset is px, not %. A percentage margin resolves against the
   container's WIDTH — on this ~342px row, margin-bottom:5% was shoving the
   caret up ~17px instead of ~1px, which is why it floated. */
.wm-caret{
  /* 74% is the tallest a caret can be and still sit centred on the letters:
     they centre at 62.9%, so anything over 2x37.1% would run past the box. */
  width:2.5px; height:74%; background:var(--ink);
  margin-bottom:0; border-radius:1px;
  animation:wmblink 1.05s steps(1,end) infinite;
}
@keyframes wmblink{ 0%,50%{opacity:1} 50.01%,100%{opacity:0} }

.tagline{
  margin:0;
  font-family:"Supply Mono",ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:11px;
  letter-spacing:.20em;
  text-transform:uppercase;
  color:var(--mut);
}

/* ── link cards ───────────────────────────────────────────── */
.links{ display:flex; flex-direction:column; gap:11px; width:100%; }

.card{
  display:grid;
  grid-template-columns:auto 1fr auto;
  align-items:center;
  gap:15px;
  padding:16px 18px;
  border:1px solid var(--line);
  border-radius:17px;
  /* X-RAY: the card is a sheet of film, not a panel. Barely any fill, so the
     graph paper runs straight through it and you read the structure behind
     the surface. A contrast() backdrop was tried first and did the opposite —
     it pivots at mid-grey, and everything here is near-white, so it pushed the
     grid TOWARD white and erased it. Transparency is the honest way. */
  background:rgba(255,255,255,.13);
  text-decoration:none;
  color:var(--ink);
  min-height:64px;                /* comfortable tap target */
  transition:transform .32s var(--ease), border-color .32s var(--ease),
             background-color .32s var(--ease);
}

.ico{ display:flex; align-items:center; justify-content:center;
      width:22px; height:22px; color:var(--ink); }

.meta{ display:flex; flex-direction:column; gap:3px; min-width:0; }
.platform{ font-size:15.5px; font-weight:550; letter-spacing:-.005em; line-height:1.15; }
.handle{
  font-family:"Supply Mono",ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:11px; letter-spacing:.09em; color:var(--mut); line-height:1.15;
}

.arw{ display:flex; align-items:center; justify-content:center;
      color:var(--dim);
      transition:transform .32s var(--ease), color .32s var(--ease); }

/* hover is desktop-only so touch never gets a stuck hover state */
@media (hover:hover) and (pointer:fine){
  .card:hover{
    transform:translateY(-2.5px);
    border-color:var(--line-hi);
    background:rgba(255,255,255,.30);   /* hover: the film develops */
  }
  .card:hover .arw{ transform:translate(2px,-2px); color:var(--ink); }
}

.card:active{ transform:translateY(0); background:var(--card-hi); }

.card:focus-visible{
  outline:2px solid var(--ink);
  outline-offset:3px;
}

/* ── contact ──────────────────────────────────────────────────
   Deliberately not a card. It is a quieter, optional action than the
   three follow links, so it gets a smaller, lighter form — and the
   only colour on the page, on hover, to make it feel inviting. */
.contact{
  align-self:center;
  display:inline-flex; align-items:center; gap:8px;
  margin-top:3px; padding:9px 15px 9px 13px;
  border:1px solid var(--line); border-radius:999px;
  background:rgba(255,255,255,.12);
  text-decoration:none; color:var(--mut);
  font-family:"Supply Mono",ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:11px; letter-spacing:.08em;
  transition:color .3s var(--ease), border-color .3s var(--ease),
             background-color .3s var(--ease), translate .3s var(--ease);
}
.contact svg{ width:13px; height:13px; flex:none; opacity:.75 }

@media (hover:hover) and (pointer:fine){
  .contact:hover{
    color:var(--green);
    border-color:var(--green-line);
    background:var(--green-wash);
    translate:0 -1.5px;
  }
  .contact:hover svg{ opacity:1 }
}
.contact:active{ background:var(--green-wash); color:var(--green) }
.contact:focus-visible{ outline:2px solid var(--green); outline-offset:3px }

/* ── latest video ─────────────────────────────────────────────
   Same card grammar as the social links, with a thumbnail standing in
   for the icon — so a new content type costs no new visual language. */
.latest{ display:flex; flex-direction:column; gap:11px; width:100%; }

.lbl{
  margin:0 0 1px 3px; font-weight:400;
  font-family:"Supply Mono",ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:10px; letter-spacing:.22em; text-transform:uppercase; color:var(--dim);
}

.card.vid{ padding:12px 18px 12px 12px; min-height:0; gap:13px; }
.thumb{
  width:56px; height:70px; flex:none; display:block;
  object-fit:cover; border-radius:9px; border:1px solid var(--line);
  background:#E6E5E2;
}
.card.vid .platform{ font-size:14.5px; line-height:1.28; }

/* ── footer ───────────────────────────────────────────────── */
.foot{
  position:relative; z-index:1;
  flex:0 0 auto;                  /* sits at the bottom, never mid-page */
  text-align:center;
  padding:0 24px calc(22px + env(safe-area-inset-bottom));
  font-family:"Supply Mono",ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:10px; letter-spacing:.18em; text-transform:uppercase;
  color:var(--dim);
}

/* ── entrance ───────────────────────────────────────────────
   The lift uses `translate`, not `transform`. A filling animation
   outranks normal declarations for whatever it animates, so an
   entrance that animated `transform` would leave `transform:none`
   stuck on the card forever and silently kill the hover lift.
   `translate` is an independent property, so the two compose. */
@keyframes rise{ from{opacity:0; translate:0 7px} to{opacity:1; translate:0 0} }

.wordmark,.tagline,.card,.lbl,.contact{ opacity:0; animation:rise .72s var(--ease) forwards; }
.wordmark{ animation-delay:.10s }
.tagline{ animation-delay:.26s }
.links .card:nth-child(1){ animation-delay:.40s }
.links .card:nth-child(2){ animation-delay:.48s }
.links .card:nth-child(3){ animation-delay:.56s }
.links .contact{ animation-delay:.64s }
.latest .lbl{ animation-delay:.72s }
.latest .card{ animation-delay:.78s }

/* ── wider screens ────────────────────────────────────────── */
@media (min-width:600px){
  .card{ padding:17px 20px; min-height:68px; }
  .platform{ font-size:16px; }
  .tagline{ font-size:11.5px; }
}

/* ── reduced motion ───────────────────────────────────────── */
@media (prefers-reduced-motion:reduce){
  .wordmark,.tagline,.card,.lbl,.contact{ animation:none; opacity:1; translate:none; }
  .wm-caret{ animation:none; opacity:0 }
  .card,.arw{ transition:none; }
  .grid{ transform:none !important; }
}
