/* =========================================================================
   Grace — Specimen Index
   Visual language: herbarium / speculative biology field notebook.
   No HUD, no glassmorphism, no emoji, no sci-fi blue.
   ========================================================================= */

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

:root {
  /* Substrate — wet petri / forest floor at dusk */
  --ink-void:    #07090a;
  --ink-deep:    #0c1010;
  --ink-mid:     #11171a;
  --ink-soil:    #19211f;

  /* Pigments — desaturated, kept under control */
  --pigment-bone:   #d6d2c4;
  --pigment-paper:  #b9b4a2;
  --pigment-faint:  #8f8f85;   /* lifted from #6b6b62 (3.66:1) → 4.85:1 against ink-void; passes WCAG AA */
  --pigment-moss:   #6b8a73;
  --pigment-spore:  #8fa898;
  --pigment-coral:  #c97a55;   /* used VERY sparingly — single accent */
  --pigment-rust:   #8a4a35;

  /* Hairlines */
  --hair-faint:  rgba(214, 210, 196, 0.10);
  --hair-mid:    rgba(214, 210, 196, 0.22);
  --hair-strong: rgba(214, 210, 196, 0.45);
  --hair-active: rgba(214, 210, 196, 0.55);   /* visible-by-default border for interactive UI; ≥3:1 non-text contrast */

  /* Type */
  --font-mono:  "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;
  --font-serif: "Cormorant Garamond", "EB Garamond", Georgia, serif;

  /* Motion — slow, weighty, never bouncy */
  --ease:       cubic-bezier(0.22, 0.61, 0.36, 1);
  --ease-slow:  cubic-bezier(0.16, 1, 0.3, 1);
}

/* -------------------------------------------------------------------------
   Page substrate
   ------------------------------------------------------------------------- */

html, body {
  height: 100%;
  background: var(--ink-void);
  color: var(--pigment-bone);
}

body {
  font-family: var(--font-serif);
  font-weight: 300;
  font-size: 16px;
  line-height: 1.55;
  letter-spacing: 0.01em;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
  /* Allow vertical scroll — the network homepage is a tall map */
  overflow-y: auto;
  /* Long-grain paper-like haze */
  background:
    radial-gradient(ellipse 90% 60% at 50% -10%, rgba(107, 138, 115, 0.06), transparent 60%),
    radial-gradient(ellipse 70% 50% at 90% 110%, rgba(201, 122, 85, 0.04), transparent 60%),
    var(--ink-void);
  cursor: none; /* we render our own halo */
}

/* Halo hidden until the first real cursor movement (set by cursor.js).
   Body starts with `no-halo` in markup so the halo never appears
   centred on load. Native cursor stays hidden via `cursor: none` on
   body — once the user starts moving, cursor.js removes `no-halo`
   and the halo fades in. */
body.no-halo { cursor: default; }
body.no-halo .cursor-halo {
  opacity: 0;
  pointer-events: none;
}

/* Fine grain on top of everything — like film grain on a botanical plate */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 9000;
  opacity: 0.035;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

::selection {
  background: rgba(201, 122, 85, 0.35);
  color: var(--pigment-bone);
}

/* -------------------------------------------------------------------------
   Background canvas — mycelium / particle field
   ------------------------------------------------------------------------- */

#bg-canvas {
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100vh;
  z-index: 0;
  pointer-events: none;
}

/* Cursor halo — three nested layers: a diffuse pollen ring, a hard
   centre dot, and a tiny label slot that appears over interactive
   targets. The wrapper is positioned by JS each frame; visual
   transitions live entirely in CSS so hover state is buttery. */
.cursor-halo {
  position: fixed;
  top: 0;
  left: 0;
  width: 0;
  height: 0;
  pointer-events: none;
  z-index: 9500;
  transform: translate3d(-100px, -100px, 0) translate(-50%, -50%);
  will-change: transform, opacity;
  /* Halo fades in when cursor.js removes `no-halo` from <body> */
  opacity: 1;
  transition: opacity 0.55s var(--ease-slow);
}

.cursor-halo-ring {
  position: absolute;
  top: 50%; left: 50%;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  background:
    radial-gradient(circle, rgba(214, 210, 196, 0.42) 0%, rgba(214, 210, 196, 0.08) 38%, transparent 72%);
  mix-blend-mode: screen;
  transition:
    width 0.5s var(--ease-slow),
    height 0.5s var(--ease-slow),
    background 0.5s var(--ease-slow),
    opacity 0.4s var(--ease);
}

.cursor-halo-dot {
  position: absolute;
  top: 50%; left: 50%;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--pigment-bone);
  transform: translate(-50%, -50%);
  transition:
    width 0.35s var(--ease),
    height 0.35s var(--ease),
    background 0.35s var(--ease),
    opacity 0.3s var(--ease);
}

.cursor-halo-label {
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%) scale(0.85);
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--pigment-bone);
  white-space: nowrap;
  opacity: 0;
  transition:
    opacity 0.35s var(--ease),
    transform 0.45s var(--ease-slow);
  pointer-events: none;
}

/* Pointing state — over an interactive element. Ring expands and
   dims, dot disappears, label slides in. */
.cursor-halo.is-pointing .cursor-halo-ring {
  width: 64px;
  height: 64px;
  background:
    radial-gradient(circle, rgba(201, 122, 85, 0.32) 0%, rgba(201, 122, 85, 0.06) 45%, transparent 80%);
}
.cursor-halo.is-pointing .cursor-halo-dot {
  width: 0; height: 0; opacity: 0;
}
.cursor-halo.is-pointing .cursor-halo-label {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1);
}

/* Typing state — let the native cursor through */
.cursor-halo.is-typing { opacity: 0; }

/* Legacy class still emitted by some hover handlers — keep map */
.cursor-halo.over-specimen .cursor-halo-ring {
  width: 64px;
  height: 64px;
  background:
    radial-gradient(circle, rgba(201, 122, 85, 0.32) 0%, rgba(201, 122, 85, 0.06) 45%, transparent 80%);
}
.cursor-halo.over-specimen .cursor-halo-dot {
  width: 0; height: 0; opacity: 0;
}

/* -------------------------------------------------------------------------
   Header — field notebook leaf
   ------------------------------------------------------------------------- */

.leaf-header {
  position: relative;
  z-index: 5;
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  padding: 28px 40px 0;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 400;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--pigment-faint);
}

.leaf-row {
  display: flex;
  gap: 18px;
  align-items: center;
}

.leaf-row--right { flex-direction: column; align-items: flex-end; gap: 4px; }

.leaf-mark { color: var(--pigment-coral); opacity: 0.7; }

.leaf-meta { color: var(--pigment-faint); text-decoration: none; }
.leaf-meta:hover { color: var(--pigment-bone); }

/* View toggle: network · index */
.view-toggle {
  display: flex;
  gap: 12px;
  align-items: center;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
}
.view-toggle-item {
  color: var(--pigment-faint);
  text-decoration: none;
  padding-bottom: 3px;
  border-bottom: 1px solid transparent;
  transition:
    color 0.4s var(--ease),
    border-color 0.4s var(--ease);
}
.view-toggle-item:hover { color: var(--pigment-bone); }
.view-toggle-item.is-current {
  color: var(--pigment-bone);
  border-bottom-color: var(--pigment-coral);
}
.view-toggle-sep { color: var(--pigment-faint); opacity: 0.5; }

/* Inline link in body copy — kept very quiet */
.inline-link {
  color: var(--pigment-bone);
  text-decoration: none;
  border-bottom: 1px solid var(--hair-mid);
  transition: border-color 0.4s var(--ease);
}
.inline-link:hover { border-bottom-color: var(--pigment-coral); }

/* -------------------------------------------------------------------------
   Masthead — herbarium label
   ------------------------------------------------------------------------- */

.masthead {
  position: relative;
  z-index: 5;
  max-width: 760px;
  margin: 96px 40px 88px;
  padding: 36px 0;
}

.masthead-rule {
  height: 1px;
  background: var(--hair-mid);
  width: 100%;
}

.masthead-title {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 16px;
  margin: 28px 0 24px;
  font-family: var(--font-serif);
  font-style: italic;
  font-weight: 300;
  font-size: clamp(40px, 6.5vw, 84px);
  line-height: 1;
  letter-spacing: -0.01em;
  color: var(--pigment-bone);
}

.masthead-genus { font-style: italic; }

.masthead-species {
  font-style: normal;
  font-family: var(--font-mono);
  font-size: clamp(11px, 1vw, 13px);
  font-weight: 400;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--pigment-faint);
  padding-bottom: 0.25em;
}

.masthead-abstract {
  max-width: 56ch;
  margin: 0 0 28px;
  font-size: 17px;
  line-height: 1.65;
  color: var(--pigment-paper);
}

/* -------------------------------------------------------------------------
   Specimen wall — asymmetric scattered placement
   ------------------------------------------------------------------------- */

.specimens {
  position: relative;
  z-index: 5;
  /* CSS columns gives an organic, magazine-stagger feel without a rigid grid */
  column-count: 3;
  column-gap: 56px;
  padding: 0 40px 160px;
  max-width: 1480px;
  margin: 0 auto;
}

@media (max-width: 1100px) { .specimens { column-count: 2; column-gap: 40px; } }
@media (max-width: 720px)  { .specimens { column-count: 1; padding: 0 24px 96px; } }

/* -------------------------------------------------------------------------
   Specimen card — herbarium plate
   ------------------------------------------------------------------------- */

.specimen {
  break-inside: avoid;
  position: relative;
  display: block;
  margin: 0 0 88px;
  text-decoration: none;
  color: inherit;
  cursor: pointer;
  /* Hairline frame + soft inner well */
  background: linear-gradient(180deg, rgba(25, 33, 31, 0.0), rgba(25, 33, 31, 0.25));
  padding: 18px 18px 22px;
  border: 1px solid var(--hair-faint);
  transition:
    border-color 0.6s var(--ease-slow),
    background 0.6s var(--ease-slow),
    transform 0.8s var(--ease-slow);
}

/* Variable column-stagger — different specimens hang at slightly different heights */
.specimen[data-stagger="1"] { margin-top: 28px; }
.specimen[data-stagger="2"] { margin-top: 64px; }
.specimen[data-stagger="3"] { margin-top: 12px; }

.specimen:hover {
  border-color: var(--hair-mid);
  background: linear-gradient(180deg, rgba(25, 33, 31, 0.05), rgba(25, 33, 31, 0.45));
}

/* Tiny tick at top-left and bottom-right — tape marks on a herbarium sheet */
.specimen::before,
.specimen::after {
  content: "";
  position: absolute;
  width: 14px;
  height: 1px;
  background: var(--hair-mid);
}
.specimen::before { top: -1px; left: 14px; }
.specimen::after  { bottom: -1px; right: 14px; }

/* --- Plate (image area) --- */

.specimen-plate {
  position: relative;
  width: 100%;
  background: var(--ink-deep);
  border: 1px solid var(--hair-faint);
  overflow: hidden;
}
.specimen-plate-inner {
  position: relative;
  width: 100%;
  /* Variable aspect — irregular like a real specimen sheet */
  aspect-ratio: 4 / 5;
}
.specimen[data-aspect="3-4"] .specimen-plate-inner { aspect-ratio: 3 / 4; }
.specimen[data-aspect="1-1"] .specimen-plate-inner { aspect-ratio: 1 / 1; }
.specimen[data-aspect="4-3"] .specimen-plate-inner { aspect-ratio: 4 / 3; }

.specimen-plate img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter:
    grayscale(0.18)
    contrast(1.05)
    brightness(0.86)
    saturate(0.85);
  transition: filter 1.2s var(--ease-slow), transform 1.6s var(--ease-slow);
  will-change: transform, filter;
}
.specimen:hover .specimen-plate img {
  filter: grayscale(0) contrast(1.1) brightness(0.95) saturate(1);
  transform: scale(1.015);
}

/* The per-card canvas overlay — ink-in-water micro-motion on hover */
.specimen-plate canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  mix-blend-mode: screen;
  opacity: 0;
  transition: opacity 1.2s var(--ease-slow);
}
.specimen:hover .specimen-plate canvas { opacity: 1; }

/* Frame corners — just barely visible registration marks */
.plate-corner {
  position: absolute;
  width: 10px;
  height: 10px;
  border: 0 solid var(--hair-mid);
  pointer-events: none;
  opacity: 0.6;
  transition: opacity 0.6s var(--ease-slow), border-color 0.6s var(--ease-slow);
}
.specimen:hover .plate-corner { opacity: 1; border-color: var(--hair-strong); }
.plate-corner.tl { top: 6px;    left: 6px;    border-top-width:    1px; border-left-width:  1px; }
.plate-corner.tr { top: 6px;    right: 6px;   border-top-width:    1px; border-right-width: 1px; }
.plate-corner.bl { bottom: 6px; left: 6px;    border-bottom-width: 1px; border-left-width:  1px; }
.plate-corner.br { bottom: 6px; right: 6px;   border-bottom-width: 1px; border-right-width: 1px; }

/* Floating tick — a tiny axis label inside the plate, lower-left */
.plate-tick {
  position: absolute;
  bottom: 10px;
  left: 12px;
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.16em;
  color: rgba(214, 210, 196, 0.45);
  text-transform: uppercase;
  pointer-events: none;
}

/* --- Caption (label) --- */

.specimen-caption {
  margin-top: 18px;
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: baseline;
  column-gap: 16px;
  row-gap: 8px;
}

.specimen-no {
  grid-column: 1 / -1;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--pigment-faint);
}

.specimen-name {
  font-family: var(--font-serif);
  font-style: italic;
  font-weight: 300;
  font-size: 24px;
  line-height: 1.15;
  color: var(--pigment-bone);
  letter-spacing: -0.005em;
}

.specimen-binomial {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: 13px;
  color: var(--pigment-faint);
  letter-spacing: 0.02em;
  white-space: nowrap;
}

.specimen-meta {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  margin-top: 4px;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--pigment-faint);
}
.specimen-meta span { white-space: nowrap; }
.specimen-meta .dot { color: var(--pigment-coral); opacity: 0.6; }

.specimen-abstract {
  grid-column: 1 / -1;
  margin-top: 6px;
  max-height: 0;
  overflow: hidden;
  font-size: 14.5px;
  line-height: 1.55;
  color: var(--pigment-paper);
  opacity: 0;
  transition:
    max-height 0.9s var(--ease-slow),
    opacity 0.9s var(--ease-slow),
    margin-top 0.9s var(--ease-slow);
}
.specimen:hover .specimen-abstract {
  max-height: 240px;
  opacity: 1;
  margin-top: 14px;
}

/* -------------------------------------------------------------------------
   Footer
   ------------------------------------------------------------------------- */

.leaf-footer {
  position: relative;
  z-index: 5;
  display: flex;
  justify-content: space-between;
  padding: 24px 40px 40px;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--pigment-faint);
  border-top: 1px solid var(--hair-faint);
  margin: 0 40px;
}

/* -------------------------------------------------------------------------
   Reduced motion — honor it
   ------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
  }
  body { cursor: default; }
  .cursor-halo { display: none; }
}

/* -------------------------------------------------------------------------
   Small screens
   ------------------------------------------------------------------------- */

@media (max-width: 720px) {
  body { cursor: default; }
  .cursor-halo { display: none; }
  .leaf-header, .leaf-footer { padding-left: 24px; padding-right: 24px; margin-left: 24px; margin-right: 24px; }
  .leaf-footer { margin-left: 0; margin-right: 0; padding: 24px; }
  .masthead { margin: 56px 24px 56px; }
  .specimen { margin-bottom: 56px; }
  .specimen[data-stagger] { margin-top: 0; }
}

/* =========================================================================
   Network homepage — gravity field of fruiting bodies + mycelial threads
   ========================================================================= */

.page-network {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

/* The network is a tall map. Scroll down to read the rest. */
.network {
  position: relative;
  z-index: 5;
  flex: 1;
  width: 100%;
  max-width: 1480px;
  margin: 0 auto;
  padding: 56px 56px 96px;
  overflow: visible;
}

@media (min-width: 720px) {
  .network { min-height: 220vh; }
}

/* --- Threads (SVG paths) ------------------------------------------ */
/* Always-visible mycelial threads. Group-coloured. The page reads as a
   biome map at rest — kinship is the primary signal, not a hover reward. */

.net-svg {
  position: absolute;
  top: 0;
  left: 0;
  pointer-events: none;          /* the path elements re-enable below */
  overflow: visible;
  z-index: 1;
}

/* Mycelium strands — geometry is set by JS each frame; growth is driven
   by stroke-dasharray (length 0..1 with pathLength=1).
   Group colours stay a small, deliberate palette but lean spore-warm. */

.net-thread {
  fill: none;
  stroke: #b8b29c;             /* default cream-spore */
  stroke-width: 0.95;
  stroke-opacity: 0.60;
  stroke-linecap: round;
  filter: blur(0.2px);
  pointer-events: stroke;
  cursor: help;
  transition:
    stroke-opacity 0.5s var(--ease-slow),
    stroke 0.5s var(--ease-slow),
    stroke-width 0.5s var(--ease-slow),
    filter 0.5s var(--ease-slow);
}

.net-thread[data-group="bio-fabric"]  { stroke: #9eb29a; }   /* spore green */
.net-thread[data-group="marine"]      { stroke: #7a98a3; }   /* tide-teal */
.net-thread[data-group="site"]        { stroke: var(--pigment-coral); stroke-opacity: 0.62; }
.net-thread[data-group="speculative"] { stroke: #9b81a8; }   /* fungal violet */
.net-thread[data-group="method"]      { stroke: #c0b89e; }   /* dry-spore — also dashed but the dash is the growth itself */
.net-thread[data-group="practice"]    { stroke: var(--pigment-paper); stroke-opacity: 0.55; }

/* Hover / hot — strand is the active filament under attention */
.net-thread.is-active,
.net-thread.is-hot {
  stroke: var(--pigment-coral);
  stroke-opacity: 0.95;
  stroke-width: 1.35;
  filter: blur(0px);
}
.net-thread.is-muted { stroke-opacity: 0.18; }

/* Mature — the strand has fully connected; very gentle pulse so it
   reads as living rather than fixed wireframe. */
@keyframes mycelium-pulse {
  0%, 100% { stroke-width: 0.95; }
  50%      { stroke-width: 1.18; }
}
.net-thread.is-mature {
  animation: mycelium-pulse 5.2s ease-in-out infinite;
}
.net-thread.is-mature.is-active,
.net-thread.is-mature.is-hot {
  animation: none;
}

/* Ghost strand — a thinner, paler parallel filament that gives the
   thread "volume" rather than reading as a single 1px line. */
.net-thread--ghost {
  stroke-width: 0.55;
  stroke-opacity: 0.18;
  filter: blur(0.6px);
  pointer-events: none;
  cursor: default;
  animation: none !important;
}

/* Side filaments — short branches sprouting mid-strand. Even quieter
   than the ghost so they read as fringe, not as primary connections. */
.net-thread--branch {
  stroke-width: 0.45;
  stroke-opacity: 0.55;
  filter: blur(0.3px);
  pointer-events: none;
  cursor: default;
  animation: none !important;
  transition: opacity 0.6s var(--ease-slow);
}

/* Hyphal tip — a small glowing point that leads the growth.
   The colour is set by group via the stroke attribute, so SVG fill
   inherits the bone radial gradient defined in <defs>. */
.net-thread-tip {
  pointer-events: none;
  transition: opacity 0.4s var(--ease-slow);
}

/* Joint markers — small filled circles where the strand meets each
   node dot. They share the strand's group colour and fade in alongside
   the strand, so the line termination reads as "rooted" rather than
   floating. Colours mirror .net-thread[data-group=…]. */
.net-thread-joint {
  fill: #b8b29c;
  pointer-events: none;
  filter: blur(0.4px);
  opacity: 0;
  transition: opacity 0.5s var(--ease-slow), fill 0.5s var(--ease-slow);
}
.net-thread-joint[data-group="bio-fabric"]  { fill: #9eb29a; }
.net-thread-joint[data-group="marine"]      { fill: #7a98a3; }
.net-thread-joint[data-group="site"]        { fill: var(--pigment-coral); }
.net-thread-joint[data-group="speculative"] { fill: #9b81a8; }
.net-thread-joint[data-group="method"]      { fill: #c0b89e; }
.net-thread-joint[data-group="practice"]    { fill: var(--pigment-paper); }
.net-thread-group:has(.net-thread.is-active) .net-thread-joint,
.net-thread-group:has(.net-thread.is-hot) .net-thread-joint {
  fill: var(--pigment-coral);
  filter: blur(0px);
}
.net-thread-group:has(.net-thread.is-muted) .net-thread-joint {
  opacity: 0 !important;
}

/* When a kinship is hovered/active, lift its whole group: ghost
   gets brighter, branches gain opacity, mature pulse pauses. */
.net-thread-group:has(.net-thread.is-active) .net-thread--ghost,
.net-thread-group:has(.net-thread.is-hot) .net-thread--ghost {
  stroke-opacity: 0.42;
  filter: blur(0.3px);
}
.net-thread-group:has(.net-thread.is-muted) .net-thread--ghost,
.net-thread-group:has(.net-thread.is-muted) .net-thread--branch {
  stroke-opacity: 0.06;
}

/* --- Mid-line kinship labels (HTML, positioned by JS) ------------- */

.net-thread-labels {
  position: absolute;
  top: 0;
  left: 0;
  width: 0; height: 0;
  pointer-events: none;
  z-index: 2;
}
.net-thread-label {
  position: absolute;
  top: 0; left: 0;
  pointer-events: auto;          /* hoverable */
  cursor: help;
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  white-space: nowrap;
  padding: 3px 6px;
  background: rgba(7, 9, 10, 0.7);
  color: var(--pigment-paper);
  opacity: 0.55;
  transition:
    opacity 0.5s var(--ease-slow),
    color 0.5s var(--ease-slow),
    background 0.5s var(--ease-slow);
}
.net-thread-label.is-active,
.net-thread-label.is-hot {
  opacity: 1;
  color: var(--pigment-bone);
  background: rgba(7, 9, 10, 0.92);
  box-shadow: 0 0 0 1px var(--pigment-coral);
}
.net-thread-label.is-muted { opacity: 0.18; }

/* --- Marks (MLF-style: dot + concentric ripples + caption) -------- */

.net-mark {
  position: absolute;
  top: 0; left: 0;
  width: 0; height: 0;          /* mark is a point; children spill out */
  text-decoration: none;
  color: inherit;
  cursor: pointer;
  z-index: 3;
  transition:
    opacity 0.6s var(--ease-slow),
    filter  0.6s var(--ease-slow);
  will-change: transform;
}
.net-mark:focus { outline: none; }
.net-mark.is-muted { opacity: 0.42; filter: saturate(0.6); }

/* Hit area — invisible disc around the dot so hover isn't pixel-perfect */
.net-mark::before {
  content: '';
  position: absolute;
  top: 50%; left: 50%;
  width: 220px; height: 120px;
  transform: translate(-50%, -50%);
  pointer-events: auto;
}

/* --- Dot --- */

.net-mark-dot {
  position: absolute;
  top: 50%; left: 50%;
  width: 9px; height: 9px;
  border-radius: 50%;
  background: var(--pigment-bone);
  transform: translate(-50%, -50%);
  transition:
    background 0.4s var(--ease),
    box-shadow 0.5s var(--ease),
    transform 0.5s var(--ease-slow);
  z-index: 2;
}
.net-mark:hover .net-mark-dot,
.net-mark.is-related .net-mark-dot {
  background: var(--pigment-coral);
  box-shadow: 0 0 0 4px rgba(201, 122, 85, 0.18);
  transform: translate(-50%, -50%) scale(1.15);
}
.net-mark:focus .net-mark-dot {
  box-shadow: 0 0 0 4px rgba(201, 122, 85, 0.4);
}

/* --- Concentric ripples — small "peak" rings around the dot --- */

.net-mark-rings {
  position: absolute;
  top: 50%; left: 50%;
  pointer-events: none;
  z-index: 1;
}
.net-mark-ring {
  position: absolute;
  top: 50%; left: 50%;
  width: calc(28px + var(--i, 0) * 22px);
  height: calc(28px + var(--i, 0) * 22px);
  border-radius: 50%;
  border: 1px solid rgba(214, 210, 196, 0.22);
  transform: translate(-50%, -50%) scale(1);
  opacity: calc(0.55 - var(--i, 0) * 0.1);
  animation: ring-breathe 5.5s ease-in-out infinite;
  animation-delay: calc(var(--i, 0) * -1.2s);
}
@keyframes ring-breathe {
  0%, 100% { transform: translate(-50%, -50%) scale(1); opacity: var(--ring-op, 0.4); }
  50%      { transform: translate(-50%, -50%) scale(1.10); opacity: 0.08; }
}
.net-mark:hover .net-mark-ring,
.net-mark.is-related .net-mark-ring {
  border-color: rgba(201, 122, 85, 0.5);
}
.net-mark:hover .net-mark-ring {
  animation-duration: 3.5s;
}

/* --- Caption — sits to the right (or left) of the dot --- */

.net-mark-caption {
  position: absolute;
  top: 50%;
  display: flex;
  flex-direction: column;
  gap: 3px;
  pointer-events: none;
  white-space: nowrap;
  transition: filter 0.6s var(--ease-slow);
}
.net-mark.is-side-right .net-mark-caption {
  left: 22px;
  transform: translateY(-50%);
  text-align: left;
  align-items: flex-start;
}
.net-mark.is-side-left .net-mark-caption {
  right: 22px;
  transform: translateY(-50%);
  text-align: right;
  align-items: flex-end;
}

/* Soft dark halo around every always-visible label so a hyphal strand
   passing under the text gets knocked back instead of crossing through
   it. Multi-layer text-shadow is cheaper than backdrop-filter and
   doesn't add a visible box. */
.net-mark-kind,
.net-mark-name,
.net-mark-binomial,
.net-mark-meta,
.net-mark-abstract {
  text-shadow:
    0 0 6px rgba(7, 9, 10, 0.92),
    0 0 3px rgba(7, 9, 10, 0.92),
    0 0 1px rgba(7, 9, 10, 0.92);
}

.net-mark-kind {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.22em;
  color: var(--pigment-faint);
  text-transform: uppercase;
}

.net-mark-name {
  font-family: var(--font-serif);
  font-style: italic;
  font-weight: 300;
  font-size: 22px;
  line-height: 1.1;
  color: var(--pigment-bone);
  letter-spacing: -0.005em;
  margin-top: 2px;
}

.net-mark-binomial {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: 12px;
  color: var(--pigment-spore);
  opacity: 0.85;
}

.net-mark-meta {
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.18em;
  color: var(--pigment-faint);
  text-transform: uppercase;
  margin-top: 2px;
  opacity: 0;
  max-height: 0;
  overflow: hidden;
  transition:
    opacity 0.5s var(--ease-slow),
    max-height 0.5s var(--ease-slow),
    margin-top 0.5s var(--ease-slow);
}
.net-mark:hover .net-mark-meta,
.net-mark.is-related .net-mark-meta {
  opacity: 1;
  max-height: 20px;
  margin-top: 4px;
}

.net-mark-abstract {
  font-family: var(--font-serif);
  font-size: 13px;
  line-height: 1.5;
  color: var(--pigment-paper);
  white-space: normal;
  max-width: 30ch;
  opacity: 0;
  max-height: 0;
  overflow: hidden;
  transition:
    opacity 0.6s var(--ease-slow),
    max-height 0.6s var(--ease-slow),
    margin-top 0.6s var(--ease-slow);
}
.net-mark:hover .net-mark-abstract {
  opacity: 1;
  max-height: 120px;
  margin-top: 8px;
}

/* --- Hover thumbnail — small specimen plate that fades in opposite the caption --- */

.net-mark-thumb {
  position: absolute;
  top: 50%;
  width: 140px;
  aspect-ratio: 4 / 5;
  background: var(--ink-deep);
  border: 1px solid var(--hair-mid);
  overflow: hidden;
  pointer-events: none;
  opacity: 0;
  transform: translateY(-50%) scale(0.96);
  transition:
    opacity 0.6s var(--ease-slow),
    transform 0.6s var(--ease-slow),
    border-color 0.5s var(--ease);
}
.net-mark.is-side-right .net-mark-thumb { right: 22px; }
.net-mark.is-side-left  .net-mark-thumb { left: 22px; }

.net-mark-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: grayscale(0.18) brightness(0.92) contrast(1.04);
}
.net-mark-thumb::before,
.net-mark-thumb::after {
  content: '';
  position: absolute;
  width: 10px; height: 1px;
  background: var(--hair-mid);
}
.net-mark-thumb::before { top: -1px; left: 10px; }
.net-mark-thumb::after  { bottom: -1px; right: 10px; }

.net-mark:hover .net-mark-thumb {
  opacity: 1;
  transform: translateY(-50%) scale(1);
  border-color: var(--hair-strong);
}

/* --- Intro & legend tucked in corners ----------------------------- */

.net-intro {
  position: absolute;
  top: 28px;
  left: 0;
  max-width: 380px;
  padding: 18px 28px 22px 16px;
  pointer-events: none;
  /* Above the SVG threads (z=1) and the kinship labels (z=2). The
     subtle ink panel knocks back any thread that runs under the
     copy without making the rest of the page feel boxed in. */
  z-index: 4;
  background: linear-gradient(
    to right,
    rgba(7, 9, 10, 0.88) 0%,
    rgba(7, 9, 10, 0.82) 65%,
    rgba(7, 9, 10, 0)    100%
  );
}
.net-intro > * { pointer-events: auto; }

.net-intro-eyebrow {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--pigment-faint);
  margin: 0 0 14px;
}
.net-intro-title {
  margin: 0 0 16px;
  font-family: var(--font-serif);
  font-weight: 300;
  font-size: 30px;
  line-height: 1.1;
  letter-spacing: -0.005em;
  color: var(--pigment-bone);
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.net-intro-title em {
  font-style: italic;
  font-size: 44px;
  line-height: 1;
}
.net-intro-title span {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--pigment-faint);
  font-style: normal;
}
.net-intro-abstract {
  margin: 0;
  font-size: 14px;
  line-height: 1.6;
  color: var(--pigment-paper);
  max-width: 36ch;
}

.net-legend {
  position: absolute;
  bottom: 28px;
  left: 16px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  pointer-events: none;
  z-index: 3;
  opacity: 0.7;
}
.net-legend-row {
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--pigment-faint);
}
.net-legend-line {
  display: inline-block;
  width: 18px;
  height: 0;
  border-top: 1px solid var(--pigment-paper);
  opacity: 0.7;
}
.net-legend-line[data-group="bio-fabric"]  { border-top-color: #8fa898; }
.net-legend-line[data-group="marine"]      { border-top-color: #6b8e9d; }
.net-legend-line[data-group="speculative"] { border-top-color: #8a6b9c; }
.net-legend-line[data-group="site"]        { border-top-color: var(--pigment-coral); }
.net-legend-line[data-group="method"]      { border-top-style: dashed; border-top-color: var(--pigment-paper); }
.net-legend-line[data-group="practice"]    { border-top-color: var(--pigment-paper); opacity: 0.5; }

/* --- Mobile: collapse network to a vertical link list ------------- */

@media (max-width: 720px) {
  .network {
    min-height: auto;
    padding: 24px 24px 56px;
  }
  .net-svg, .net-thread-labels { display: none; }
  .net-intro { position: static; max-width: none; padding: 0 0 24px; }
  .net-legend { display: none; }
  .net-mark {
    position: static;
    transform: none !important;
    width: 100%;
    height: auto;
    display: grid;
    grid-template-columns: 96px 1fr;
    align-items: center;
    gap: 16px;
    padding: 14px 0;
    border-top: 1px solid var(--hair-faint);
  }
  .net-mark:last-child { border-bottom: 1px solid var(--hair-faint); }
  .net-mark::before { display: none; }
  .net-mark-rings, .net-mark-dot { display: none; }
  .net-mark-thumb {
    position: static;
    transform: none;
    width: 96px;
    aspect-ratio: 4 / 5;
    opacity: 1;
  }
  .net-mark-caption {
    position: static;
    transform: none;
    text-align: left !important;
    align-items: flex-start !important;
    white-space: normal;
  }
  .net-mark-meta { opacity: 1; max-height: none; margin-top: 4px; }
  .net-mark-abstract { opacity: 1; max-height: none; margin-top: 8px; }
}

/* -------------------------------------------------------------------------
   Page transitions — soft fade between index / work / project-detail.

   Driven by transitions.js: the body gains `.is-leaving` on internal
   nav clicks, the page content fades out, then the browser navigates.
   On the destination page, the content elements run the page-enter
   animation once on load.

   The bg-canvas (substrate) and cursor-halo (cursor) are deliberately
   excluded — substrate stays steady so the herbarium feels continuous,
   and the cursor never blinks out from under the user.
   ------------------------------------------------------------------------- */

@keyframes page-enter {
  from { opacity: 0; transform: translateY(10px); filter: blur(2.5px); }
  to   { opacity: 1; transform: translateY(0);    filter: blur(0);     }
}

body > .leaf-header,
body > main,
body > .leaf-footer {
  /* Slowed from 0.55s for a more deliberate herbarium reveal — the
     content waits for the loading veil to begin lifting before it
     starts settling in. */
  animation: page-enter 0.95s var(--ease-slow) both;
  animation-delay: 0.55s;     /* hold while the veil is on top */
  will-change: opacity, transform, filter;
}
body > main          { animation-delay: 0.65s; }
body > .leaf-footer  { animation-delay: 0.78s; }

body.is-leaving > .leaf-header,
body.is-leaving > main,
body.is-leaving > .leaf-footer {
  opacity: 0;
  transform: translateY(-8px);
  filter: blur(2px);
  transition:
    opacity 0.42s var(--ease),
    transform 0.42s var(--ease),
    filter 0.42s var(--ease);
  animation: none;
}

/* -------------------------------------------------------------------------
   Page veil — herbarium-style loading overlay.

   Sits above everything (z 10000) on first paint with a centred mark
   ("◦   FOLIO") drawn in the field-notebook palette. transitions.js
   adds `is-loaded` to <body> after a short hold; the veil then lifts
   (opacity 0 + slight upward drift) over 0.7s and is removed from the
   DOM. The cursor halo stays in `no-halo` mode until first mousemove
   AND `is-loaded` so it never appears mid-veil.
   ------------------------------------------------------------------------- */

.page-veil {
  position: fixed;
  inset: 0;
  z-index: 10000;
  background: var(--ink-void);
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: auto;
  transition:
    opacity 0.7s var(--ease-slow),
    transform 0.9s var(--ease-slow);
}
.page-veil-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.32em;
  text-transform: uppercase;
  color: var(--pigment-faint);
  opacity: 0;
  animation: veil-mark-in 0.6s var(--ease-slow) 0.12s forwards;
}
.page-veil-mark {
  color: var(--pigment-coral);
  font-size: 14px;
  letter-spacing: 0;
}
.page-veil-line {
  display: inline-block;
  width: 0;
  height: 1px;
  background: var(--hair-strong);
  animation: veil-line-extend 0.85s var(--ease-slow) 0.32s forwards;
}
.page-veil-text {
  color: var(--pigment-paper);
}

@keyframes veil-mark-in {
  to { opacity: 1; }
}
@keyframes veil-line-extend {
  to { width: 92px; }
}

body.is-loaded .page-veil {
  opacity: 0;
  transform: translateY(-4px);
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  .page-veil-inner,
  .page-veil-line { animation: none; opacity: 1; }
  .page-veil-line { width: 92px; }
  body.is-loaded .page-veil { transition: opacity 0.2s linear; }
}

@media (prefers-reduced-motion: reduce) {
  body > .leaf-header,
  body > main,
  body > .leaf-footer {
    animation: none;
  }
  body.is-leaving > .leaf-header,
  body.is-leaving > main,
  body.is-leaving > .leaf-footer {
    transition: none;
    opacity: 1;
    transform: none;
    filter: none;
  }
}

/* =========================================================================
   PROFILE PAGE — herbarium-style CV. Two-column body sits below the
   masthead, with each "entry" (school, role, project) styled like a
   field-notebook line: title flush-left, dates / locale flush-right,
   a quiet bullet list beneath. Sections are separated by hairline
   rules so the page reads as a continuous folio rather than a deck of
   cards.
   ========================================================================= */

.profile {
  position: relative;
  z-index: 5;
  width: 100%;
  max-width: 1280px;
  margin: 0 auto;
  padding: 56px 56px 96px;
}

/* --- Masthead (name, contact, portrait, summary) ------------------- */

.profile-mast {
  margin: 0 0 56px;
}

.profile-mast-rule {
  height: 1px;
  background: var(--hair-mid);
  opacity: 0.7;
}

.profile-mast-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 48px;
  align-items: start;
  padding: 36px 0 28px;
}

.profile-mast-text {
  display: flex;
  flex-direction: column;
  gap: 18px;
}

.profile-mast-eyebrow {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--pigment-faint);
}

.profile-mast-title {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
  font-family: var(--font-serif);
  font-weight: 300;
  color: var(--pigment-bone);
}
.profile-mast-name {
  font-size: 56px;
  line-height: 1;
  letter-spacing: -0.01em;
}
.profile-mast-name em {
  font-style: italic;
  color: var(--pigment-paper);
  font-size: 0.78em;
}
.profile-mast-role {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--pigment-faint);
  font-style: normal;
}

.profile-mast-meta {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 18px 28px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--pigment-paper);
}
.profile-mast-meta li {
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.profile-mast-key {
  font-size: 9px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--pigment-faint);
}
.profile-link {
  color: var(--pigment-bone);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color 0.4s var(--ease), color 0.4s var(--ease);
}
.profile-link:hover {
  border-bottom-color: var(--pigment-coral);
  color: var(--pigment-coral);
}

.profile-portrait {
  margin: 0;
  width: 200px;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  background: var(--ink-deep);
  border: 1px solid var(--hair-mid);
  filter: grayscale(0.35) contrast(0.96);
}
.profile-portrait img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.profile-summary {
  margin: 0;
  max-width: 78ch;
  padding: 0 0 28px;
  font-family: var(--font-serif);
  font-size: 15px;
  line-height: 1.7;
  color: var(--pigment-paper);
}

/* --- Two-column body of timeline entries --------------------------- */

.profile-body {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  gap: 48px 56px;
  margin-top: 12px;
}
.profile-col {
  display: flex;
  flex-direction: column;
  gap: 44px;
  min-width: 0;
}

.profile-section {
  display: flex;
  flex-direction: column;
  gap: 22px;
}
.profile-section-title {
  margin: 0;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--hair-mid);
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--pigment-faint);
}

.profile-entry {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.profile-entry-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 20px;
  align-items: baseline;
}
.profile-entry-title {
  margin: 0;
  font-family: var(--font-serif);
  font-style: italic;
  font-weight: 300;
  font-size: 19px;
  line-height: 1.25;
  color: var(--pigment-bone);
}
.profile-entry-when {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--pigment-faint);
  white-space: nowrap;
}
.profile-entry-role {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--pigment-spore);
}

.profile-entry-list {
  list-style: none;
  margin: 4px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
  font-family: var(--font-serif);
  font-size: 14px;
  line-height: 1.6;
  color: var(--pigment-paper);
}
.profile-entry-list li {
  position: relative;
  padding-left: 18px;
}
.profile-entry-list li::before {
  content: '·';
  position: absolute;
  left: 4px;
  top: -1px;
  color: var(--pigment-coral);
  font-size: 18px;
  line-height: 1;
}
.profile-entry-list strong {
  font-style: normal;
  font-weight: 400;
  color: var(--pigment-bone);
  border-bottom: 1px solid var(--hair-mid);
}

.profile-tag {
  display: inline-block;
  margin-right: 10px;
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--pigment-coral);
}

/* --- Skills grid (definition list) --------------------------------- */

.profile-skills {
  margin: 4px 0 0;
  display: grid;
  grid-template-columns: minmax(120px, 0.6fr) minmax(0, 1fr);
  row-gap: 12px;
  column-gap: 18px;
  font-family: var(--font-serif);
  font-size: 14px;
  line-height: 1.55;
  color: var(--pigment-paper);
}
.profile-skills dt {
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--pigment-faint);
  padding-top: 3px;
}
.profile-skills dd {
  margin: 0;
}

/* --- Profile responsive --------------------------------------------- */

@media (max-width: 980px) {
  .profile { padding: 48px 32px 72px; }
  .profile-mast-grid {
    grid-template-columns: 1fr;
    gap: 28px;
  }
  .profile-portrait {
    width: 160px;
    order: -1;
  }
  .profile-mast-name { font-size: 44px; }
  .profile-body {
    grid-template-columns: 1fr;
    gap: 40px;
  }
}

@media (max-width: 560px) {
  .profile { padding: 36px 20px 56px; }
  .profile-mast-name { font-size: 36px; }
  .profile-entry-head {
    grid-template-columns: 1fr;
    gap: 4px;
  }
  .profile-entry-when { white-space: normal; }
  .profile-skills {
    grid-template-columns: 1fr;
    row-gap: 4px;
  }
  .profile-skills dt { padding-top: 8px; }
}
