/* ============================================================================
   Base — reset, primitives, and the ambient layers (WebGL canvas, boot).
   ========================================================================== */

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

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  scrollbar-color: var(--ink-6) transparent;
  scrollbar-width: thin;
}

body {
  margin: 0;
  min-height: 100dvh;
  background: var(--void);
  color: var(--text-0);
  font-family: var(--font-ui);
  font-size: var(--t-15);
  line-height: var(--lh-15);
  /* NOTE: Inter's ss03 ("round quotes & commas") makes a thousands separator
     render as a dot at UI sizes — "3,077" reads as "3.077". Never enable it. */
  font-feature-settings: 'cv05' 1, 'cv08' 1, 'calt' 1, 'liga' 1;
  font-variation-settings: 'opsz' 16;
  letter-spacing: var(--tracking-tight);
  /* Deliberately NOT -webkit-font-smoothing: antialiased. On a dark surface it
     thins every stroke, and the first casualty is the tail of a comma: three
     independent reviewers read "3,077" as "3.077" at 13px. Legibility beats the
     slightly softer look. */
  -moz-osx-font-smoothing: auto;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
  overscroll-behavior-y: none;
}

h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }
img, svg, video, canvas { display: block; max-width: 100%; }
img { border-style: none; }

button, input, select, textarea {
  font: inherit;
  letter-spacing: inherit;
  color: inherit;
  background: none;
  border: 0;
  margin: 0;
  /* Chrome's UA button padding is 1px 6px. Left in, it insets every image
     inside a <button> tile by 6px a side and 1px top and bottom, which shows
     up as a coloured band down the edge of a media grid. */
  padding: 0;
}
button { cursor: pointer; -webkit-tap-highlight-color: transparent; }
button:disabled { cursor: not-allowed; }
textarea { resize: none; }

a { color: inherit; text-decoration: none; }

::selection { background: var(--accent); color: var(--accent-ink); }

/* --- Focus: visible, on-brand, and never on mouse clicks ------------------ */
:focus { outline: none; }
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r-xs);
}

/* --- Scrollbars ----------------------------------------------------------- */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--ink-4);
  border: 3px solid transparent;
  background-clip: padding-box;
  border-radius: 99px;
}
::-webkit-scrollbar-thumb:hover { background: var(--ink-7); background-clip: padding-box; }

/* --- Skip link ------------------------------------------------------------ */
.skip-link {
  position: fixed; top: -100px; left: var(--s-6); z-index: var(--z-toast);
  padding: var(--s-4) var(--s-6);
  background: var(--accent); color: var(--accent-ink);
  border-radius: var(--r-sm); font-weight: 650;
  transition: top var(--d-2) var(--ease-out);
}
.skip-link:focus-visible { top: var(--s-6); }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  max-width: 1px; max-height: 1px;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}
/* `width: 1px` is a *suggestion* to a table — auto table layout still takes the
   min-content width of the columns, so a visually-hidden data table happily
   sits 758px wide and hands the document a horizontal scrollbar. Never put
   .sr-only on a table directly; if it happens anyway, this contains it. */
table.sr-only { table-layout: fixed; contain: strict; }

/* ============================================================================
   THE BRAND MARK, AS INK RATHER THAN AS A PICTURE

   Both brand files are monochrome — the lockup is three `fill="currentColor"`
   paths under a single `color="#ffffff"` on its root, the rooster one
   `fill:white`. Loaded through `<img>` that white is unreachable: an image is
   an image, no stylesheet reaches inside it, and `currentColor` resolves
   against the SVG's own root rather than the page. So the wordmark was white on
   every theme, including the eighteen whose background is also white, and the
   product's own name disappeared the moment a member chose Paper.

   The fix is to stop drawing the file and start using it as a stencil: the same
   URL as a `mask-image`, and `currentColor` as the paint. The mark is then
   text, and takes whatever ink the theme puts on the surface it sits on — all
   thirty-six of them, with no second asset to keep in sync and no `invert()`
   filter that would have to be undone the day the mark gains a colour.

   Being monochrome is what makes this safe, and it is a real constraint on the
   artwork: the day someone adds a green leaf to the lockup, this stops being
   correct and the file needs a real two-tone treatment. Hence the check in
   tools/_themetest.mjs.
   ========================================================================== */
.brandink {
  display: block;
  background-color: currentColor;
  /* Prefixed first, unprefixed last: Safari still needs -webkit-mask-image. */
  -webkit-mask-repeat: no-repeat;  mask-repeat: no-repeat;
  -webkit-mask-position: center;   mask-position: center;
  -webkit-mask-size: 100% 100%;    mask-size: 100% 100%;
}
/* The aspect ratios are the files' own viewBoxes. An <img> got these for free
   from the intrinsic size; a masked box has no intrinsic anything, so the
   height has to come from somewhere and it may as well come from the artwork
   rather than from a number typed at each call site. */
.brandink--lockup {
  aspect-ratio: 1095 / 231.5;
  -webkit-mask-image: url('/assets/brand/tgis-lockup.svg');
  mask-image: url('/assets/brand/tgis-lockup.svg');
}
.brandink--mark {
  aspect-ratio: 1;
  -webkit-mask-image: url('/assets/brand/tgis-icon-white.svg');
  mask-image: url('/assets/brand/tgis-icon-white.svg');
}

/* ============================================================================
   Ambient layers
   ========================================================================== */

#gl {
  position: fixed; inset: 0;
  width: 100%; height: 100%;
  z-index: var(--z-gl);
  pointer-events: none;
  /* The scene is the room the app sits in — it should never fight the content. */
  opacity: 0;
  transition: opacity 1600ms var(--ease-out);
}
#gl.is-ready { opacity: .62; }

.app-root {
  position: relative;
  z-index: var(--z-content);
  min-height: 100dvh;
}

.portal-root { position: relative; z-index: var(--z-modal); }

.toast-root {
  position: fixed; z-index: var(--z-toast);
  left: 50%; bottom: var(--s-9);
  transform: translateX(-50%);
  display: flex; flex-direction: column; gap: var(--s-4);
  align-items: center;
  pointer-events: none;
}

/* ============================================================================
   Boot splash
   ========================================================================== */
.boot {
  position: fixed; inset: 0; z-index: var(--z-toast);
  display: grid; place-content: center; gap: var(--s-9);
  justify-items: center;
  background: var(--bg-0);
  transition: opacity var(--d-5) var(--ease-out), visibility var(--d-5);
}
.boot.is-gone { opacity: 0; visibility: hidden; pointer-events: none; }
/* No drop-shadow: a critique round removed the accent glow this used to carry,
   on the grounds that no other element on the product repeats it. That ruling
   lived as a `filter: none` override in app.css and so only reached index.html;
   folded in here it reaches every entry point, and the override is gone. */
.boot__lockup {
  width: min(300px, 62vw);
  animation: bootPulse 2.4s var(--ease-in-out) infinite;
}
.boot__bar {
  width: 120px; height: 2px; border-radius: 2px;
  background: var(--ink-4); overflow: hidden;
}
.boot__bar i {
  display: block; height: 100%; width: 40%;
  background: var(--accent);
  border-radius: inherit;
  animation: bootSlide 1.1s var(--ease-in-out) infinite;
}
@keyframes bootPulse {
  0%, 100% { opacity: .55; transform: scale(.98); }
  50%      { opacity: 1;   transform: scale(1.02); }
}
@keyframes bootSlide {
  0%   { transform: translateX(-120%); }
  100% { transform: translateX(320%); }
}

/* ============================================================================
   Shared primitives
   ========================================================================== */

/* Frosted panel — used by headers, menus, sheets. The saturate() is what makes
   backdrop blur look like real glass instead of grey mud. */
.glass {
  background: var(--glass);
  backdrop-filter: blur(18px) saturate(180%);
  -webkit-backdrop-filter: blur(18px) saturate(180%);
}
@supports not (backdrop-filter: blur(1px)) {
  .glass { background: var(--glass-strong); }
}

/* A raised surface with a physical top bevel. */
.surface {
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--e-2), var(--bevel);
}

.hairline { border-bottom: 1px solid var(--line); }

/* Text utilities that carry the type system rather than ad-hoc sizes. */
.t-display { font-family: var(--font-dis); font-weight: 800; letter-spacing: var(--tracking-display); }
.t-brand   { font-family: var(--font-brand); font-weight: 400; letter-spacing: 0; }
.t-mono    { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.t-num     { font-variant-numeric: tabular-nums; font-feature-settings: 'tnum' 1; }
.t-dim     { color: var(--text-1); }
.t-meta    { color: var(--text-2); font-size: var(--t-13); line-height: var(--lh-13); }

.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.clamp-2 { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.clamp-3 { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }

/* Content-visibility on long lists keeps the feed cheap past a few hundred posts. */
.cv-auto { content-visibility: auto; contain-intrinsic-width: none; contain-intrinsic-height: auto 220px; }
