/* ── Case File Investigation Board ─────────────────────────────── */

:root {
  --ink: #0a0a0a;
  --board-line-a: #0d0d0d;
  --board-line-b: #111;
  --paper: #f4ecd8;
  --paper-ink: #1a1a1a;
  --red: #c0392b;
  --red-dim: #8b2f2f;
  --bone: #d4d0c8;
  --muted: #6b6b6b;
  --status-green: #8fae5d;
  --sheet: #14120f; /* dossier "case-file sheet" surface */
  /* Reusable film grain (inline SVG feTurbulence), for the sheet + overlay. */
  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%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)' opacity='0.55'/%3E%3C/svg%3E");
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
}

html {
  /* Reserve the scrollbar gutter always, so locking scroll when a dossier
     opens (html.dossier-open { overflow: hidden }) no longer shifts layout
     and throws off the open/close FLIP measurement. */
  scrollbar-gutter: stable;
}

body {
  font-family: 'Courier New', monospace;
  background: var(--ink);
  color: var(--bone);
}

/* ── Board ──────────────────────────────────────────────────────── */

#board {
  position: relative;
  min-height: 100vh;
  background-image: repeating-linear-gradient(
    45deg,
    var(--board-line-a),
    var(--board-line-a) 10px,
    var(--board-line-b) 10px,
    var(--board-line-b) 20px
  );
  overflow: hidden;
}

.board-caption {
  position: absolute;
  top: 24px;
  left: 24px;
  font-size: 11px;
  letter-spacing: 1px;
  color: var(--muted);
  user-select: none;
}

/* ── Pinned cards ───────────────────────────────────────────────── */

.card {
  position: absolute;
  width: 210px;
  padding: 20px 18px 18px;
  background: var(--paper);
  color: var(--paper-ink);
  box-shadow: 4px 6px 16px rgba(0, 0, 0, 0.6);
  transform: rotate(var(--rot, 0deg));
  cursor: pointer;
  transition: transform 0.18s ease, box-shadow 0.18s ease;
}

/* 26 clears #strings-svg (25) so the lift actually rises above the strings,
   and stays under .string-note (31). */
.card:hover {
  transform: rotate(var(--rot, 0deg)) translateY(-3px);
  box-shadow: 5px 9px 22px rgba(0, 0, 0, 0.7);
  z-index: 26;
}

.card .pin,
#suspect-photo .pin {
  position: absolute;
  top: -6px;
  left: 50%;
  transform: translateX(-50%);
  width: 12px;
  height: 12px;
  background: var(--red);
  border-radius: 50%;
  box-shadow: 0 0 4px rgba(0, 0, 0, 0.5);
}

.card-label {
  font-size: 9px;
  letter-spacing: 2px;
  color: var(--red-dim);
  text-transform: uppercase;
  margin-bottom: 8px;
}

.card-title {
  font-family: 'Pirata One', serif;
  font-size: 24px;
  font-weight: normal;
  line-height: 1.15;
  margin: 0 0 8px;
}

.card-body {
  font-size: 11px;
  line-height: 1.7;
  color: #333;
}

.card-hint {
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px solid rgba(26, 26, 26, 0.15);
  font-size: 9px;
  letter-spacing: 1px;
  color: var(--muted);
  text-transform: uppercase;
}

/* ── Project cards ──────────────────────────────────────────────── */

.card--project {
  width: 200px;
  padding: 16px 16px 14px;
}

.card--project .card-title {
  font-size: 22px;
}

.card--unplaced {
  visibility: hidden;
}

.card--featured {
  width: 264px;
}

.card--featured .card-title {
  font-size: 28px;
}

.card--featured::after {
  content: 'PRIORITY';
  position: absolute;
  top: 45%;
  left: 50%;
  transform: translate(-50%, -50%) rotate(-12deg);
  font-size: 15px;
  letter-spacing: 4px;
  color: rgba(192, 57, 43, 0.32);
  border: 2px solid rgba(192, 57, 43, 0.28);
  padding: 4px 12px;
  pointer-events: none;
}

.card-desc {
  font-size: 11px;
  line-height: 1.7;
  color: #333;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.card--featured .card-desc {
  -webkit-line-clamp: 4;
  line-clamp: 4;
}

.status-row {
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px solid rgba(26, 26, 26, 0.15);
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 9px;
  letter-spacing: 2px;
  text-transform: uppercase;
}

.status-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
}

.status--completed { color: #4a6741; }
.status--completed .status-dot { background: #4a6741; }

.status--in-progress { color: #9a6d0b; }
.status--in-progress .status-dot { background: #9a6d0b; }

.status--archived { color: #767268; }
.status--archived .status-dot { background: #767268; }

/* ── Fixed triangle: about (apex), skills, contact ──────────────── */

#card-about {
  top: 60px;
  left: 50%;
  margin-left: -105px;
  --rot: -1.5deg;
}

#card-skills {
  top: 268px;
  left: 50%;
  margin-left: -300px;
  --rot: 2deg;
}

#card-contact {
  top: 268px;
  left: 50%;
  margin-left: 90px;
  --rot: -1deg;
}

/* ── Suspect photo ──── */

#suspect-photo {
  position: absolute;
  top: 64px;
  left: 50%;
  width: 158px;
  margin-left: 240px;
  padding: 8px 8px 6px;
  background: var(--paper);
  box-shadow: 4px 6px 14px rgba(0, 0, 0, 0.6);
  transform: rotate(1.5deg);
  cursor: pointer;
  z-index: 12;
  transition: transform 0.18s ease, box-shadow 0.18s ease;
}

#suspect-photo:hover {
  transform: rotate(1.5deg) translateY(-3px);
  box-shadow: 5px 9px 22px rgba(0, 0, 0, 0.7);
  z-index: 26; /* clears #strings-svg, same as .card:hover */
}

#suspect-photo img {
  display: block;
  width: 100%;
  height: 150px;
  object-fit: cover;
  object-position: center 30%;
  filter: saturate(0.85) contrast(1.05);
}

/* Hand-scrawled red marker circle: two offset uneven ellipses. */
#suspect-photo::before,
#suspect-photo::after {
  content: '';
  position: absolute;
  pointer-events: none;
  border: 3px solid var(--red);
  inset: -14px -10px -8px -12px;
  border-radius: 48% 52% 45% 55% / 55% 46% 54% 45%;
  opacity: 0.75;
  transform: rotate(-4deg);
}

#suspect-photo::after {
  border-width: 2px;
  inset: -10px -14px -12px -8px;
  border-radius: 52% 46% 55% 47% / 47% 55% 45% 53%;
  opacity: 0.5;
  transform: rotate(3deg);
}

.suspect-caption {
  margin-top: 6px;
  font-size: 8px;
  letter-spacing: 2px;
  color: var(--red-dim);
  text-align: center;
  text-transform: uppercase;
}

/* ── Dossier (full-screen case file) ────────────────────────────── */

html.dossier-open,
html.dossier-open body {
  overflow: hidden;
}

/* Camera push: the board scales up around the clicked card (inline
   transform/origin set by dossier.js). The push itself is transform-only
   (GPU-composited, no per-frame re-rasterization); the out-of-focus blur
   rack-focuses in on a delay as the camera lands, and clears immediately
   on the way back out. */
#board {
  will-change: transform;
  transition: transform 0.56s cubic-bezier(0.3, 0.7, 0.2, 1), filter 0.3s ease;
}

#board.board--zoomed {
  filter: blur(9px) brightness(0.5) saturate(0.8);
  transition: transform 1.05s cubic-bezier(0.5, 0.05, 0.15, 1), filter 0.55s ease 0.6s;
}

.card--ghost {
  opacity: 0;
  pointer-events: none;
}

.no-anim,
.no-anim * {
  transition: none !important;
  animation: none !important;
}

/* Full-bleed dossier surface: near-opaque, so the zoomed board reads
   as faintly still-there behind it, not as a separate dimmed layer.
   Its clip-path animates from the card's rectangle to the full screen. */
#dossier-overlay {
  position: fixed;
  inset: 0;
  z-index: 100;
  overflow-y: auto;
  background: rgba(10, 10, 10, 0.9);
  transition: clip-path 1.05s cubic-bezier(0.5, 0.05, 0.15, 1);
}

#dossier-overlay.closing {
  transition: clip-path 0.56s cubic-bezier(0.3, 0.7, 0.2, 1);
  pointer-events: none; /* a closing overlay must stop eating clicks */
}

.dossier-close {
  position: fixed;
  top: 18px;
  right: 28px;
  z-index: 110;
  font-size: 28px;
  line-height: 1;
  color: var(--muted);
  background: none;
  border: none;
  cursor: pointer;
  font-family: inherit;
}

.dossier-close:hover {
  color: var(--bone);
}

.dossier-panel {
  position: relative;
  z-index: 1;
  max-width: 720px;
  margin: 44px auto 60px; /* a gap above so the sheet floats, not pinned to the top */
  padding: 40px 34px 60px 58px; /* extra left for the spine + punch-holes */
  background: var(--sheet);
  border: 1px solid #29241c;
  box-shadow: 0 30px 80px rgba(0, 0, 0, 0.7), inset 0 0 60px rgba(0, 0, 0, 0.5);
  transition: transform 1.05s cubic-bezier(0.5, 0.05, 0.15, 1), opacity 0.7s ease,
    filter 0.9s ease;
  will-change: transform;
}

.dossier-panel::before { /* red spine down the binding edge */
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  width: 6px;
  background: var(--red-dim);
}

.dossier-panel::after { /* faint paper grain */
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0.05;
  background-image: var(--grain);
}

.dossier-holes {
  position: absolute;
  left: 20px;
  top: 0;
  bottom: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 120px;
  pointer-events: none;
}

.dossier-holes i {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: #050505;
  box-shadow: inset 0 1px 2px #000, 0 1px 0 #33302a;
}

.dossier-stamp {
  position: absolute;
  right: 30px;
  bottom: 28px;
  z-index: 2;
  font-family: 'Pirata One', serif;
  font-size: 26px;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--red);
  border: 3px solid var(--red);
  padding: 4px 14px;
  opacity: 0.34;
  transform: rotate(-11deg);
  pointer-events: none;
}

/* Paired 1:1 with the .badge--* set below; a stamp and its badge sit in
   the same panel and must not disagree. */
.stamp--completed { color: var(--status-green); border-color: var(--status-green); }
.stamp--in-progress { color: #d4a017; border-color: #d4a017; }
.stamp--archived { color: #6f6b60; border-color: #6f6b60; }

/* Filmic settle layers over the near-opaque overlay, behind the sheet. */
.dossier-vignette,
.dossier-grain {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

.dossier-vignette {
  background: radial-gradient(120% 90% at 50% 42%, transparent 46%, rgba(0, 0, 0, 0.72) 100%);
}

.dossier-grain {
  opacity: 0.035;
  background-image: var(--grain);
}

#dossier-overlay.closing .dossier-panel {
  transition: transform 0.56s cubic-bezier(0.3, 0.7, 0.2, 1), opacity 0.4s ease,
    filter 0.45s ease;
}

.dossier-top-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 6px;
  gap: 12px;
}

.dossier-case-num {
  font-size: 11px;
  letter-spacing: 2px;
  color: var(--muted);
}

.dossier-badge {
  font-size: 10px;
  letter-spacing: 1px;
  padding: 4px 10px;
  border: 1px solid;
  text-transform: uppercase;
  white-space: nowrap;
}

.badge--completed { color: var(--status-green); border-color: var(--status-green); }
.badge--in-progress { color: #d4a017; border-color: #d4a017; }
.badge--archived { color: #6f6b60; border-color: #6f6b60; }

.dossier-title {
  font-family: 'Pirata One', serif;
  font-size: 38px;
  font-weight: normal;
  margin: 8px 0 4px;
  line-height: 1.1;
  color: var(--bone);
}

.dossier-subtitle {
  font-size: 12px;
  color: var(--muted);
  margin-bottom: 28px;
}

.evidence-row {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  margin-bottom: 8px;
}

.photo {
  --rot: -1.5deg; /* custom prop so reveal keyframes can compose the tilt */
  flex: 1 1 180px;
  max-width: 100%;
  height: 150px;
  margin: 0;
  background: #1a1a1a;
  border: 1px solid #333;
  position: relative;
  transform: rotate(var(--rot));
  cursor: zoom-in;
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.photo:nth-child(2) { --rot: 1deg; }
.photo:nth-child(3) { --rot: -0.5deg; }

.photo:hover {
  transform: rotate(var(--rot)) translateY(-3px) scale(1.01);
  box-shadow: 0 10px 24px rgba(0, 0, 0, 0.6);
}

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

.photo-mag {
  position: absolute;
  top: 6px;
  right: 8px;
  z-index: 2;
  font-size: 9px;
  letter-spacing: 1px;
  color: #cdc7ba;
  text-shadow: 0 1px 3px #000;
  opacity: 0;
  transition: opacity 0.2s ease;
}

.photo:hover .photo-mag,
.photo:focus-visible .photo-mag { opacity: 0.9; }

.photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  filter: saturate(0.85) contrast(1.05);
}

.photo-tape {
  position: absolute;
  top: -7px;
  left: 50%;
  transform: translateX(-50%) rotate(-1deg);
  width: 44px;
  height: 15px;
  background: rgba(212, 208, 200, 0.16);
  z-index: 2;
}

.photo-label {
  position: absolute;
  bottom: 6px;
  left: 8px;
  font-size: 9px;
  letter-spacing: 1px;
  color: #9a968c;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
  z-index: 2;
}

.photo-empty {
  border: 1px dashed #333;
  color: #4a4a4a;
  font-size: 10px;
  letter-spacing: 2px;
  text-align: center;
  padding: 24px 12px;
}

.dossier-body h2,
.dossier-section-label {
  font-size: 10px;
  font-weight: normal;
  letter-spacing: 2px;
  color: var(--red-dim);
  text-transform: uppercase;
  margin: 28px 0 10px;
}

.dossier-body p,
.dossier-body li {
  font-size: 13px;
  line-height: 1.9;
  color: #b8b4ac;
  margin: 0 0 12px;
}

.dossier-body ul {
  margin: 0 0 12px;
  padding-left: 20px;
}

.dossier-body li {
  margin-bottom: 2px;
}

.redacted {
  background: #2a2a2a;
  color: transparent;
  border-radius: 2px;
  cursor: pointer;
  padding: 0 3px;
  user-select: none;
  transition: background 0.15s ease, color 0.15s ease;
}

/* Hover peeks, click/Enter pins it open. :focus is deliberately not here:
   clicking a redaction also focuses it, so a focus-reveal would keep the
   text exposed after the click that was meant to hide it again. */
.redacted:hover,
.redacted.revealed {
  background: transparent;
  color: #b8b4ac;
  outline: 1px dashed var(--red-dim);
  user-select: auto;
}

.redacted:focus-visible {
  outline: 2px solid var(--red);
  outline-offset: 1px;
}

.dossier-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 12px;
}

.dossier-tag {
  font-size: 10px;
  letter-spacing: 1px;
  border: 1px solid #444;
  color: #8a8a8a;
  padding: 5px 10px;
  text-transform: uppercase;
}

.dossier-links {
  display: flex;
  flex-wrap: wrap;
  gap: 18px;
  margin-top: 32px;
  padding-top: 20px;
  border-top: 1px solid #222;
}

.dossier-link {
  font-size: 12px;
  color: var(--bone);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.dossier-link:hover {
  color: var(--red);
}

/* Panel reveal: the file's contents land in a short stagger while the
   camera is still pushing in. Delays start at ~240ms because before then
   the panel's own opacity (0.25 → 1) mutes its children and the FLIP
   scale squashes their travel to about a pixel, so moving them earlier
   just makes them invisible. Same fill-mode rule as the briefing: base
   style is the settled state, keyframes only supply the entrance. */

@keyframes dossier-reveal {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: translateY(0); }
}

@keyframes dossier-photo-reveal {
  from { opacity: 0; transform: rotate(var(--rot, 0deg)) translateY(14px) scale(0.985); }
  to { opacity: 1; transform: rotate(var(--rot, 0deg)) translateY(0) scale(1); }
}

#dossier-overlay.dossier--enter .dossier-top-row {
  animation: dossier-reveal 0.3s ease-out 0.24s backwards;
}
#dossier-overlay.dossier--enter .dossier-title {
  animation: dossier-reveal 0.34s cubic-bezier(0.2, 0.8, 0.25, 1) 0.3s backwards;
}
#dossier-overlay.dossier--enter .dossier-subtitle {
  animation: dossier-reveal 0.3s ease-out 0.36s backwards;
}
#dossier-overlay.dossier--enter .photo,
#dossier-overlay.dossier--enter .photo-empty {
  animation: dossier-photo-reveal 0.4s cubic-bezier(0.2, 0.8, 0.25, 1)
    calc(0.44s + var(--i, 0) * 0.06s) backwards;
}
#dossier-overlay.dossier--enter .dossier-body {
  animation: dossier-reveal 0.34s ease-out 0.56s backwards;
}
#dossier-overlay.dossier--enter .dossier-section-label {
  animation: dossier-reveal 0.3s ease-out 0.64s backwards;
}
#dossier-overlay.dossier--enter .dossier-tag {
  animation: dossier-reveal 0.28s ease-out calc(0.68s + var(--i, 0) * 0.03s) backwards;
}
#dossier-overlay.dossier--enter .dossier-links {
  animation: dossier-reveal 0.32s ease-out 0.78s backwards;
}
/* The evidence heading precedes its photos, so it reveals earlier than the
   generic 0.64s section-label rule above (later, equal-specificity → wins). */
#dossier-overlay.dossier--enter .dossier-section-label--evidence {
  animation: dossier-reveal 0.3s ease-out 0.4s backwards;
}
/* The rubber stamp thuds down oversized and settles as the camera lands. */
@keyframes dossier-stamp-in {
  from { opacity: 0; transform: rotate(-11deg) scale(1.6); }
}
#dossier-overlay.dossier--enter .dossier-stamp {
  animation: dossier-stamp-in 0.3s cubic-bezier(0.2, 0.8, 0.25, 1) 0.9s backwards;
}
/* Vignette + grain fade up as the file comes into focus (base = settled). */
@keyframes dossier-layer-in { from { opacity: 0; } }
#dossier-overlay.dossier--enter .dossier-vignette {
  animation: dossier-layer-in 0.7s ease 0.45s backwards;
}
#dossier-overlay.dossier--enter .dossier-grain {
  animation: dossier-layer-in 0.7s ease 0.5s backwards;
}

/* ── Evidence close-up (stacks over an open dossier) ────────────── */

.evidence-lightbox {
  position: fixed;
  inset: 0;
  z-index: 130;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: zoom-out;
  background: rgba(6, 6, 6, 0);
  backdrop-filter: blur(0px);
  /* Matched to .evidence-figure's 0.4s FLIP below: teardown waits on this
     element, so a shorter duration here would cut the shrink-back short. */
  transition: background 0.4s ease, backdrop-filter 0.4s ease;
}

.evidence-lightbox.open {
  background: rgba(6, 6, 6, 0.82);
  backdrop-filter: blur(3px);
}

.evidence-lightbox.closing {
  pointer-events: none; /* a closing overlay must stop eating clicks */
}

.evidence-figure {
  position: relative;
  margin: 0;
  width: min(620px, 88vw);
  will-change: transform;
  transition: transform 0.4s cubic-bezier(0.2, 0.85, 0.25, 1), opacity 0.3s ease;
}

.evidence-frame {
  position: relative;
  aspect-ratio: 4 / 3;
  border: 1px solid #3a352c;
  box-shadow: 0 30px 80px rgba(0, 0, 0, 0.8);
  overflow: hidden;
  background: #1a1a1a;
}

.evidence-pin {
  position: absolute;
  top: -9px;
  left: 50%;
  transform: translateX(-50%);
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #e05a4c, #96271c);
  box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.6);
  z-index: 3;
}

.evidence-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  filter: saturate(0.85) contrast(1.05);
}

.evidence-caption {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  margin-top: 12px;
  font-size: 11px;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: #9a968c;
}

.evidence-dismiss {
  margin-top: 10px;
  text-align: center;
  font-size: 9px;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: #5a5a5a;
}

/* ── Small screens ──────────────────────────────────────────────── */

@media (max-width: 720px) {
  .card { width: 180px; padding: 16px 14px 14px; }
  .card--featured { width: 220px; }
  .card-title { font-size: 20px; }
  .card--featured .card-title { font-size: 24px; }

  #card-about { margin-left: -90px; top: 52px; }
  #card-skills { margin-left: calc(-50vw + 16px); top: 250px; }
  #card-contact { margin-left: calc(50vw - 196px); top: 250px; }
  #suspect-photo { top: 480px; width: 136px; margin-left: -68px; }
  #suspect-photo img { height: 128px; }

  .dossier-panel { margin: 24px auto 40px; padding: 32px 16px 40px 44px; }
  .dossier-title { font-size: 30px; }
  .dossier-close { top: 12px; right: 16px; }
  .photo { flex-basis: 100%; }
  .dossier-holes { left: 14px; gap: 90px; }
  .dossier-stamp { font-size: 20px; right: 16px; bottom: 20px; }

  #briefing-overlay {
    padding: 20px;
    --cone-w: clamp(280px, 110vw, 560px);
    --cone-top: 48px; /* smaller shade, so the cone starts higher */
  }
  .briefing-lamp { width: 84px; margin-left: -42px; }
  .briefing-shade { height: 28px; }
  .briefing-bulb { top: 26px; width: 14px; height: 14px; margin-left: -7px; }
  .briefing-pool { width: 92vw; height: 46vh; }
  /* The vignette radii must widen here: at 390px wide the desktop 42%
     radius is narrower than the card, and would darken the copy itself. */
  .briefing-vignette {
    background: radial-gradient(
      ellipse 74% 46% at 50% 50%,
      rgba(0, 0, 0, 0) 0%,
      rgba(0, 0, 0, 0.5) 64%,
      rgba(0, 0, 0, 0.9) 100%
    );
  }

  #help-btn { bottom: 16px; right: 16px; }
}

/* ── Red string connections ─────────────────────────────────────── */

#strings-svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 25;
}

#strings-svg path {
  fill: none;
  stroke: var(--red);
  stroke-width: 2;
  stroke-linecap: round;
  filter: drop-shadow(0 2px 2px rgba(0, 0, 0, 0.55));
}

#strings-svg .string-live {
  opacity: 0.85;
}

#strings-svg .string-live--reject {
  stroke: #8a8a8a;
  stroke-dasharray: 6 5;
}

#strings-svg .string-anchor {
  stroke-width: 1.6;
  opacity: 0.7;
}

.card .pin {
  cursor: grab;
  transition: transform 0.15s ease;
  touch-action: none; /* let pointermove drive the string drag on touch */
}

.card .pin:hover {
  transform: translateX(-50%) scale(1.4);
}

body.string-dragging,
body.string-dragging * {
  cursor: grabbing !important;
}

.string-note {
  position: absolute;
  z-index: 31;
  max-width: 170px;
  background: var(--paper);
  color: var(--paper-ink);
  padding: 8px 10px 7px;
  font-size: 9px;
  line-height: 1.6;
  letter-spacing: 0.5px;
  box-shadow: 3px 4px 10px rgba(0, 0, 0, 0.55);
  transform: translate(-50%, -50%) rotate(var(--note-rot, -1.5deg));
  cursor: grab;
  touch-action: none;
  user-select: none;
}

.string-note--dragging {
  cursor: grabbing;
  z-index: 32;
  box-shadow: 5px 7px 16px rgba(0, 0, 0, 0.65);
}

/* Overstretched release: spring back onto the string with a slight overshoot. */
.string-note--snap {
  transition:
    left 0.4s cubic-bezier(0.2, 0.9, 0.25, 1.3),
    top 0.4s cubic-bezier(0.2, 0.9, 0.25, 1.3);
}

.string-note-label {
  font-size: 8px;
  letter-spacing: 2px;
  color: var(--red-dim);
  margin-bottom: 4px;
}

.string-note-tags {
  margin-top: 5px;
  padding-top: 4px;
  border-top: 1px solid rgba(26, 26, 26, 0.15);
  color: #555;
  font-size: 8px;
  letter-spacing: 1px;
  text-transform: uppercase;
}

.string-note--pulse {
  animation: note-pulse 0.5s ease;
}

@keyframes note-pulse {
  0% { transform: translate(-50%, -50%) rotate(var(--note-rot, -1.5deg)) scale(1); }
  40% { transform: translate(-50%, -50%) rotate(var(--note-rot, -1.5deg)) scale(1.12); }
  100% { transform: translate(-50%, -50%) rotate(var(--note-rot, -1.5deg)) scale(1); }
}

.string-nolink {
  position: absolute;
  z-index: 32;
  padding: 7px 12px 6px;
  background: rgba(12, 12, 12, 0.88);
  border: 1px dashed rgba(212, 208, 200, 0.45);
  text-align: center;
  pointer-events: none;
  transform: translate(-50%, -50%) rotate(-3deg);
  animation: nolink-stamp 1.5s ease forwards;
}

.string-nolink-x {
  font-size: 10px;
  letter-spacing: 2px;
  color: #9a9a9a;
}

.string-nolink-sub {
  margin-top: 3px;
  font-size: 8px;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--muted);
}

.string-nolink--hold {
  animation: none;
}

@keyframes nolink-stamp {
  0% { opacity: 0; transform: translate(-50%, -50%) rotate(-3deg) scale(0.8); }
  10% { opacity: 1; transform: translate(-50%, -50%) rotate(-3deg) scale(1.06); }
  18%, 72% { opacity: 1; transform: translate(-50%, -50%) rotate(-3deg) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -50%) rotate(-3deg) scale(1); }
}

/* ── Onboarding briefing + help button ──────────────────────────── */

html.briefing-open,
html.briefing-open body {
  overflow: hidden;
}

/* Interrogation spotlight: the room drops to near-black, a hanging lamp
   snaps on with an electrical flicker and swings to a stop over the
   briefing paper. The overlay's own background is the darkened room.
   Its opacity transition is the "lights out" beat, so there's no
   separate scrim node. */
#briefing-overlay {
  position: fixed;
  inset: 0;
  z-index: 120;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 32px;
  overflow: hidden;
  background: rgba(4, 4, 4, 0.94);
  opacity: 0;
  transition: opacity 0.24s ease 0.08s; /* out: lights cut, then the room fades */
  --cone-w: clamp(360px, 64vw, 860px);
  /* The lamp and the cone are siblings that must swing as one object, so
     both derive transform-origin from a single pivot point expressed in
     viewport coordinates (above the top edge, since the lamp hangs from
     the ceiling). Change --pivot-y and both stay locked. */
  --pivot-y: -46px;
  --lamp-top: 26px;
  --cone-top: 58px;
}

#briefing-overlay.open {
  opacity: 1;
  transition: opacity 0.22s ease;
}

/* A fading overlay must never stay a full-screen click target. */
#briefing-overlay:not(.open) {
  pointer-events: none;
}

.briefing-pool,
.briefing-vignette,
.briefing-cone,
.briefing-lamp {
  pointer-events: none;
}

/* Below the card (z 1 vs 3): the pool is light falling on the surface
   around the paper, so it can never wash out the copy. */
.briefing-pool {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 1;
  width: clamp(380px, 46vw, 620px);
  height: clamp(300px, 42vh, 460px);
  border-radius: 50%;
  transform: translate(-50%, -50%);
  background: radial-gradient(
    ellipse at center,
    rgba(255, 206, 130, 0.34),
    rgba(255, 190, 100, 0.12) 45%,
    rgba(255, 180, 80, 0) 72%
  );
  mix-blend-mode: screen;
}

.briefing-vignette {
  position: absolute;
  inset: 0;
  z-index: 2;
  background: radial-gradient(
    ellipse 42% 46% at 50% 50%,
    rgba(0, 0, 0, 0) 0%,
    rgba(0, 0, 0, 0.55) 62%,
    rgba(0, 0, 0, 0.9) 100%
  );
}

/* Sibling of the lamp, not a child. See buildBriefing(). Same pivot and
   same pendulum keyframes keep the two locked together. */
.briefing-cone {
  position: absolute;
  top: var(--cone-top);
  left: 50%;
  z-index: 4;
  width: var(--cone-w);
  margin-left: calc(var(--cone-w) / -2);
  height: clamp(420px, 76vh, 900px);
  transform-origin: 50% calc(var(--pivot-y) - var(--cone-top));
  clip-path: polygon(46% 0, 54% 0, 100% 100%, 0 100%);
  background: linear-gradient(
    180deg,
    rgba(255, 203, 120, 0.22),
    rgba(255, 190, 95, 0.07) 52%,
    rgba(255, 180, 80, 0) 88%
  );
  filter: blur(12px);
  mix-blend-mode: screen;
}

.briefing-lamp {
  position: absolute;
  top: var(--lamp-top);
  left: 50%;
  z-index: 5;
  width: 112px;
  margin-left: -56px;
  transform-origin: 50% calc(var(--pivot-y) - var(--lamp-top));
}

/* Runs from the top edge down to the shade, so the lamp reads as hanging
   from a ceiling out of frame rather than fixed to the viewport. */
.briefing-cord {
  position: absolute;
  top: calc(-1 * var(--lamp-top));
  left: 50%;
  width: 2px;
  height: calc(var(--lamp-top) + 4px);
  margin-left: -1px;
  background: #232323;
}

.briefing-shade {
  position: relative;
  height: 36px;
  background: linear-gradient(180deg, #2e2e2e, #1b1b1b);
  clip-path: polygon(34% 0, 66% 0, 100% 100%, 0 100%);
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.7);
}

.briefing-bulb {
  position: absolute;
  top: 34px;
  left: 50%;
  width: 18px;
  height: 18px;
  margin-left: -9px;
  border-radius: 50%;
  background: radial-gradient(circle, #fff5d8, #ffca6b 55%, rgba(255, 190, 90, 0) 72%);
  box-shadow: 0 0 34px 16px rgba(255, 196, 105, 0.5);
}

.briefing-card {
  position: relative;
  z-index: 3;
  width: 340px;
  max-width: 100%;
  background: var(--paper);
  color: var(--paper-ink);
  padding: 28px;
  /* Lamp directly overhead, so the shadow drops straight down. */
  box-shadow: 0 20px 48px rgba(0, 0, 0, 0.85);
  transform: rotate(-1deg);
}

.briefing-pin {
  position: absolute;
  top: -6px;
  left: 50%;
  transform: translateX(-50%);
  width: 12px;
  height: 12px;
  background: var(--red);
  border-radius: 50%;
  box-shadow: 0 0 4px rgba(0, 0, 0, 0.5);
}

.briefing-label {
  font-size: 10px;
  letter-spacing: 2px;
  color: var(--red-dim);
  margin-bottom: 12px;
}

.briefing-headline {
  font-family: 'Pirata One', serif;
  font-size: 28px;
  font-weight: normal;
  margin: 0 0 6px;
  line-height: 1.2;
}

.briefing-subline {
  font-size: 11px;
  color: var(--muted);
  letter-spacing: 1px;
  margin-bottom: 18px;
  text-transform: uppercase;
}

.briefing-steps {
  font-size: 13px;
  line-height: 2;
  color: #333;
}

.briefing-steps b {
  color: var(--paper-ink);
}

.briefing-divider {
  margin-top: 22px;
  height: 1px;
  background: rgba(26, 26, 26, 0.15);
}

.briefing-cta-row {
  margin-top: 18px;
  display: flex;
  justify-content: flex-end;
}

.briefing-cta {
  font: inherit;
  font-size: 12px;
  letter-spacing: 1px;
  background: var(--ink);
  color: var(--paper);
  border: none;
  padding: 10px 18px;
  cursor: pointer;
  transition: transform 0.15s ease;
}

.briefing-cta:hover,
.briefing-cta:focus-visible {
  transform: translateY(-1px);
}

/* Entrance. Gated on .briefing--enter, added at construction time, so the
   first painted frame is already the 0% keyframe, with no flash of
   settled content. Every keyframe below supplies only the entrance and uses
   fill-mode `backwards`, never `forwards`/`both`: the base style is the
   settled state, so a cancelled or disabled animation can't strand an
   element invisible, and the property is released afterwards, which is
   what lets the exit transitions take over. */

#briefing-overlay.briefing--enter .briefing-lamp {
  will-change: transform;
  animation: briefing-lamp-in 0.35s ease-out 0.05s backwards,
    briefing-pendulum 2.4s cubic-bezier(0.3, 0.6, 0.4, 1) 0.35s backwards;
}

#briefing-overlay.briefing--enter .briefing-cone {
  will-change: transform, opacity;
  animation: briefing-flicker 0.5s steps(1, end) 0.1s backwards,
    briefing-pendulum 2.4s cubic-bezier(0.3, 0.6, 0.4, 1) 0.35s backwards;
}

#briefing-overlay.briefing--enter .briefing-bulb {
  animation: briefing-flicker 0.5s steps(1, end) 0.1s backwards;
}

#briefing-overlay.briefing--enter .briefing-pool {
  will-change: transform, opacity;
  animation: briefing-pool-in 0.7s ease-out 0.18s backwards;
}

#briefing-overlay.briefing--enter .briefing-vignette {
  animation: briefing-fade-in 0.6s ease 0.1s backwards;
}

#briefing-overlay.briefing--enter .briefing-card {
  animation: briefing-lit-in 0.42s ease-out 0.22s backwards;
}

/* Copy comes up fast, because the reader shouldn't wait out the 2.4s
   pendulum. Last line lands at 900ms, while the lamp is still settling. */
#briefing-overlay.briefing--enter .briefing-label {
  animation: briefing-rise 0.24s ease-out 0.34s backwards;
}
#briefing-overlay.briefing--enter .briefing-headline {
  animation: briefing-rise 0.26s ease-out 0.4s backwards;
}
#briefing-overlay.briefing--enter .briefing-subline {
  animation: briefing-rise 0.26s ease-out 0.46s backwards;
}
#briefing-overlay.briefing--enter .briefing-steps {
  animation: briefing-rise 0.28s ease-out 0.52s backwards;
}
#briefing-overlay.briefing--enter .briefing-divider {
  animation: briefing-rise 0.24s ease-out 0.58s backwards;
}
#briefing-overlay.briefing--enter .briefing-cta-row {
  animation: briefing-rise 0.28s cubic-bezier(0.2, 0.8, 0.25, 1.15) 0.62s backwards;
}

@keyframes briefing-lamp-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes briefing-fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes briefing-pendulum {
  0% { transform: rotate(-15deg); }
  22% { transform: rotate(10deg); }
  44% { transform: rotate(-6.5deg); }
  64% { transform: rotate(3.8deg); }
  82% { transform: rotate(-1.8deg); }
  100% { transform: rotate(0deg); }
}

/* steps(1, end): six discrete repaints, not thirty interpolated frames. */
@keyframes briefing-flicker {
  0% { opacity: 0; }
  14% { opacity: 1; }
  22% { opacity: 0.15; }
  30% { opacity: 0.9; }
  38% { opacity: 0.35; }
  50% { opacity: 1; }
  100% { opacity: 1; }
}

@keyframes briefing-pool-in {
  from { opacity: 0; transform: translate(-50%, -50%) scale(0.3); }
  to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}

@keyframes briefing-lit-in {
  0% { opacity: 0; transform: rotate(-1deg) translateY(9px); filter: brightness(0.25); }
  60% { filter: brightness(1.06); }
  100% { opacity: 1; transform: rotate(-1deg) translateY(0); filter: brightness(1); }
}

@keyframes briefing-rise {
  from { opacity: 0; transform: translateY(7px); }
  to { opacity: 1; transform: translateY(0); }
}

/* Exit: lights cut hard, then the room fades. */
#briefing-overlay.closing .briefing-lamp,
#briefing-overlay.closing .briefing-cone,
#briefing-overlay.closing .briefing-pool {
  opacity: 0;
  transition: opacity 0.1s steps(2, end);
}

#briefing-overlay.closing .briefing-card {
  opacity: 0.2;
  transform: rotate(-1deg) translateY(5px);
  transition: opacity 0.16s ease 0.04s, transform 0.24s ease;
}

#help-btn {
  position: fixed;
  bottom: 24px;
  right: 24px;
  z-index: 60;
  display: flex;
  align-items: center;
  gap: 8px;
  background: #1a1a1a;
  border: 1px solid #333;
  border-radius: 20px;
  padding: 10px 16px;
  cursor: pointer;
  font: inherit;
  font-size: 12px;
  color: var(--bone);
  transition: border-color 0.2s ease, transform 0.2s ease;
}

#help-btn:hover {
  border-color: #555;
  transform: translateY(-1px);
}

.help-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--status-green);
}

/* ── Fog (transition atmosphere) ────────────────────────────────── */

#fog {
  position: fixed;
  inset: -25%;
  z-index: 90;
  pointer-events: none;
}

.fog-layer {
  position: absolute;
  inset: 0;
  opacity: 0;
  filter: blur(28px);
  will-change: transform, opacity;
  background:
    radial-gradient(42% 34% at 28% 62%, rgba(196, 190, 180, 0.16), transparent 70%),
    radial-gradient(55% 42% at 74% 30%, rgba(178, 172, 162, 0.12), transparent 72%),
    radial-gradient(38% 30% at 55% 82%, rgba(160, 156, 148, 0.1), transparent 70%);
}

.fog-layer--b {
  background:
    radial-gradient(48% 38% at 68% 70%, rgba(190, 184, 174, 0.13), transparent 70%),
    radial-gradient(44% 36% at 22% 26%, rgba(168, 164, 156, 0.11), transparent 72%);
}

#fog.fog--in .fog-layer {
  animation: fog-roll 1.3s ease-out forwards;
}

#fog.fog--in .fog-layer--b {
  animation-name: fog-roll-b;
  animation-duration: 1.45s;
  animation-delay: 0.1s;
}

#fog.fog--out .fog-layer {
  animation: fog-roll 0.7s ease-out forwards;
}

#fog.fog--out .fog-layer--b {
  animation-name: fog-roll-b;
  animation-duration: 0.8s;
  animation-delay: 0.05s;
}

@keyframes fog-roll {
  0% { opacity: 0; transform: scale(1.04) translate(-2.5%, 1.5%); }
  40% { opacity: 1; }
  100% { opacity: 0; transform: scale(1.24) translate(2%, -1.5%); }
}

@keyframes fog-roll-b {
  0% { opacity: 0; transform: scale(1.06) translate(2%, -1%); }
  45% { opacity: 1; }
  100% { opacity: 0; transform: scale(1.26) translate(-2.5%, 2%); }
}

/* ── Reduced motion ─────────────────────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
  #board,
  #dossier-overlay,
  .dossier-panel,
  .card,
  #briefing-overlay,
  #briefing-overlay *,
  .evidence-lightbox,
  .evidence-figure,
  .string-note {
    transition: none !important;
  }

  /* Every entrance keyframe. Base styles are the settled state, so
     killing these leaves the whole scene in its final position (lamp
     lit, pool up, copy visible) rather than stranding anything at
     opacity 0. Nothing in the JS waits on animationend. */
  #briefing-overlay,
  #briefing-overlay *,
  #dossier-overlay *,
  .dossier-panel *,
  #fog .fog-layer,
  .string-note--pulse,
  .string-nolink {
    animation: none !important;
  }
}
