/* --- Layout shell --- */
.app-shell {
  min-height: 100vh;
}

.site-content {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  /* Reserve a gutter so page content never starts underneath the floating
     nav widget, regardless of viewport width. */
  padding-left: 5.5rem;
}

.site-main {
  flex: 1 0 auto;
}

/* --- Nav (floats in the top-left corner on desktop, collapsible, collapsed
   by default; becomes a full-width top bar on mobile — see media query) --- */
.site-nav {
  position: fixed;
  top: 1rem;
  left: 1rem;
  max-height: calc(100vh - 2rem);
  display: flex;
  flex-direction: column;
  background: transparent;
  border: 1px solid var(--border-glow);
  border-radius: 16px;
  box-shadow: 1px 0 30px rgba(47, 111, 238, 0.08);
  overflow-y: auto;
  overflow-x: hidden;
  z-index: 20;
}

.site-nav.expanded {
  background: var(--bg-panel);
}

.site-nav-footer {
  display: flex;
  justify-content: center;
  padding: 0.75rem 0.5rem 1rem;
}

.site-nav-header {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  gap: 0.6rem;
  padding: 1rem 0.5rem;
}

.site-logo {
  display: block;
  flex: 0 0 auto;
  line-height: 0;
}

.site-logo-mark {
  display: block;
  width: 34px;
  height: 34px;
  border-radius: 9px;
  transition: filter 0.2s ease;
}

.site-logo:hover .site-logo-mark,
.site-logo:focus-visible .site-logo-mark {
  filter: drop-shadow(0 0 8px rgba(47, 111, 238, 0.6)) drop-shadow(0 0 14px rgba(0, 229, 255, 0.45));
}

.site-logo-mark-light {
  display: none;
}

[data-theme="light"] .site-logo-mark-cyber {
  display: none;
}

[data-theme="light"] .site-logo-mark-light {
  display: block;
}

.theme-toggle {
  flex: 0 0 auto;
  width: 26px;
  height: 26px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: 700;
  line-height: 1;
  background: none;
  border: 1px solid var(--border-glow);
  border-radius: 50%;
  padding: 0;
  cursor: pointer;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.theme-toggle:hover,
.theme-toggle:focus-visible {
  border-color: var(--accent-cyan);
  box-shadow: 0 0 10px rgba(47, 111, 238, 0.4), 0 0 16px rgba(0, 229, 255, 0.35);
}

/* The swatch previews the theme a click would switch TO, using that
   theme's own fixed colors (background + accent) rather than the
   currently-active palette. */
.theme-toggle[data-target-theme="cyber"] {
  background: #0b0c1e;
  color: #00e5ff;
  border-color: #2f6fee;
}

.theme-toggle[data-target-theme="light"] {
  background: #f3f8f8;
  color: #5fb8be;
  border-color: #83ccd2;
}

.theme-toggle[data-target-theme="cyber"]:hover,
.theme-toggle[data-target-theme="cyber"]:focus-visible {
  border-color: #00e5ff;
  box-shadow: 0 0 10px rgba(47, 111, 238, 0.5), 0 0 16px rgba(0, 229, 255, 0.4);
}

.theme-toggle[data-target-theme="light"]:hover,
.theme-toggle[data-target-theme="light"]:focus-visible {
  border-color: #5fb8be;
  box-shadow: 0 0 10px rgba(131, 204, 210, 0.5), 0 0 16px rgba(95, 184, 190, 0.4);
}

/* Mobile top bar owns its own toggle instance; the sidebar-footer one only
   makes sense once the nav is a vertical column again. */
.theme-toggle-mobile {
  display: none;
}

.nav-toggle {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  gap: 4px;
  background: none;
  border: 1px solid var(--border-glow);
  border-radius: 8px;
  padding: 8px 10px;
  cursor: pointer;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.nav-toggle:hover,
.nav-toggle:focus-visible {
  border-color: var(--accent-cyan);
  box-shadow: 0 0 10px rgba(47, 111, 238, 0.4), 0 0 16px rgba(0, 229, 255, 0.35);
}

.nav-toggle span {
  width: 18px;
  height: 2px;
  background: var(--accent-blue);
  display: block;
}

.nav-links {
  display: none;
  flex-direction: column;
  align-items: center;
  gap: 0.3rem;
  padding: 0.5rem;
}

.site-nav.expanded .nav-links {
  display: flex;
}

.nav-links a {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  font-size: 0.75rem;
  border-radius: 8px;
  border: 1px solid var(--border-glow);
  color: var(--fg-primary);
  white-space: nowrap;
  transition: color 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
}

.nav-links a:hover,
.nav-links a:focus-visible {
  color: var(--accent-cyan);
  border-color: var(--accent-cyan);
  box-shadow: 0 0 16px rgba(0, 229, 255, 0.4);
}

.nav-links a.active {
  color: var(--accent-blue);
  border-color: var(--accent-blue);
  box-shadow: 0 0 20px rgba(47, 111, 238, 0.5), 0 0 12px rgba(0, 229, 255, 0.35);
}

@media (max-width: 700px) {
  .site-content {
    padding-left: 0;
  }

  .site-nav {
    position: sticky;
    top: 0;
    left: 0;
    width: 100%;
    max-height: none;
    overflow: visible;
    border: none;
    border-radius: 0;
    border-bottom: 1px solid var(--border-glow);
    z-index: 30;
    /* Opaque. On desktop the nav is a floating widget over empty margin, so
       a transparent background is fine there -- but here it is a bar the
       page scrolls underneath, and without a background the content ran
       straight through the logo and the menu button. */
    background: var(--bg-panel);
    /* Slides out of the way while reading downwards and returns on any
       upward scroll -- see nav.js. Transform rather than `top`, so it
       animates on the compositor and never reflows the page under a thumb. */
    transition: transform 0.25s ease;
  }

  .site-nav.nav-hidden {
    transform: translateY(-100%);
  }

  @media (prefers-reduced-motion: reduce) {
    .site-nav {
      transition: none;
    }
  }

  .site-nav.expanded {
    width: 100%;
  }

  .site-nav-header {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    padding: 1rem 0.85rem;
  }

  /* Desktop shrinks nav entries to compact icon-sized squares; the mobile
     flyout keeps full-width, thumb-friendly rows instead. */
  .nav-links {
    align-items: stretch;
  }

  .nav-links a {
    display: block;
    width: auto;
    height: auto;
    padding: 0.6rem 1rem;
    border-radius: 999px;
  }

  .site-logo {
    justify-self: center;
  }

  .nav-toggle {
    justify-self: end;
  }

  .theme-toggle-mobile {
    display: inline-flex;
    justify-self: start;
  }

  .site-nav-footer {
    display: none;
  }

  .nav-links {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    background: var(--bg-panel);
    border-bottom: 1px solid var(--border-glow);
    box-shadow: 0 12px 24px rgba(0, 0, 0, 0.35);
  }
}

/* --- Footer --- */
.site-footer {
  border-top: 1px solid var(--border-glow);
  padding: 1.25rem;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 0.5rem;
  color: var(--fg-muted);
  font-size: 0.9rem;
}

.footer-connections {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1rem;
}

.footer-label {
  font-family: var(--font-mono);
  color: var(--accent-blue);
  margin-right: 0.25rem;
}

.footer-sep {
  margin: 0 0.5rem;
}

.footer-links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
}

.footer-link {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.25rem;
  border-radius: 999px;
  background: var(--bg-panel-alt);
  border: 1px solid var(--border-glow);
  color: var(--fg-primary);
  text-decoration: none;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.footer-link:hover,
.footer-link:focus-visible {
  color: var(--fg-primary);
  border-color: var(--accent-cyan);
  box-shadow: 0 0 14px rgba(0, 229, 255, 0.3);
  text-decoration: none;
}

.footer-link-icon {
  flex: 0 0 auto;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--icon-bg, var(--accent-blue));
  box-shadow: 0 0 10px var(--icon-glow, rgba(47, 111, 238, 0.45));
  transition: box-shadow 0.2s ease;
}

.footer-link-glyph {
  display: block;
  width: 62%;
  height: 62%;
  background-color: var(--on-accent);
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
}

/* mask-image is set per platform here (not inline) so it works under a
   strict style-src CSP that disallows inline style="" attributes. */
.footer-link[data-platform="tryhackme"] .footer-link-glyph {
  -webkit-mask-image: url("/static/icons/tryhackme.svg");
  mask-image: url("/static/icons/tryhackme.svg");
}

.footer-link[data-platform="hackthebox"] .footer-link-glyph {
  -webkit-mask-image: url("/static/icons/hackthebox.svg");
  mask-image: url("/static/icons/hackthebox.svg");
}

.footer-link[data-platform="gitlab"] .footer-link-glyph {
  -webkit-mask-image: url("/static/icons/gitlab.svg");
  mask-image: url("/static/icons/gitlab.svg");
}

.footer-link[data-platform="researchgate"] .footer-link-glyph {
  -webkit-mask-image: url("/static/icons/researchgate.svg");
  mask-image: url("/static/icons/researchgate.svg");
}

.footer-link:hover .footer-link-icon,
.footer-link:focus-visible .footer-link-icon {
  box-shadow: 0 0 18px var(--icon-glow, rgba(47, 111, 238, 0.65));
}

.footer-link[data-platform="tryhackme"] .footer-link-icon {
  --icon-bg: var(--accent-magenta);
  --icon-glow: rgba(255, 46, 196, 0.45);
}

.footer-link[data-platform="hackthebox"] .footer-link-icon {
  --icon-bg: var(--accent-cyan);
  --icon-glow: rgba(0, 229, 255, 0.45);
}

.footer-link[data-platform="gitlab"] .footer-link-icon {
  --icon-bg: var(--accent-orange);
  --icon-glow: rgba(255, 138, 61, 0.45);
}

.footer-link[data-platform="researchgate"] .footer-link-icon {
  --icon-bg: var(--accent-blue);
  --icon-glow: rgba(47, 111, 238, 0.45);
}

.footer-link-name {
  display: inline-block;
  max-width: 0;
  opacity: 0;
  overflow: hidden;
  white-space: nowrap;
  font-size: 0.85rem;
  transition: max-width 0.25s ease, opacity 0.2s ease, margin 0.25s ease;
}

.footer-link:hover .footer-link-name,
.footer-link:focus-visible .footer-link-name {
  max-width: 220px;
  opacity: 1;
  margin-right: 0.4rem;
}

/* --- Home: intro strip --- */
.intro-strip {
  padding: 1.5rem 0 0.5rem;
  text-align: center;
}

.intro-prompt {
  font-family: var(--font-mono);
  font-size: 0.85rem;
  letter-spacing: 0.05em;
  margin: 0 0 0.5rem;
  background: linear-gradient(135deg, var(--accent-blue), var(--accent-cyan));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.intro-name {
  font-size: clamp(1.75rem, 6vw, 2.75rem);
  font-weight: 800;
  margin: 0 0 0.4rem;
  background: linear-gradient(135deg, var(--accent-blue), var(--accent-cyan));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  filter: drop-shadow(0 0 18px rgba(47, 111, 238, 0.35));
}

.intro-version {
  display: inline-block;
  vertical-align: super;
  font-family: var(--font-mono);
  font-size: 0.9rem;
  font-weight: 400;
  letter-spacing: 0.02em;
  margin-left: 0.5rem;
  background: none;
  -webkit-background-clip: initial;
  background-clip: initial;
  color: var(--fg-muted);
  filter: none;
}

.intro-title {
  color: var(--fg-primary);
  margin: 0 0 0.5rem;
}

.intro-tagline {
  font-family: var(--font-mono);
  color: var(--accent-cyan);
  font-style: italic;
  margin: 0;
}

/* --- Home: threat map --- */
/* Sized purely by aspect-ratio against the available width, never by a
   min-height. A min-height combined with an aspect-ratio silently becomes a
   min-WIDTH (min-height x ratio): the old `min-height: 280px` gave this box
   a 498px floor, and `220px` on phones still meant 391px. On a 375px
   viewport that forced .site-main out to 421px, so the map, the headings and
   the tagline all ran past the right edge and were clipped by the
   overflow-x guard on <body> — the "map is cut off on mobile" symptom.

   Instead the proportion itself changes with the display: wide and
   cinematic where there's room, progressively squarer on narrow screens so
   the map still gets useful height without ever dictating a width. */
.threat-map {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  max-height: 640px;
  margin-top: 1.5rem;
  border-radius: 4px;
  background: transparent;
  overflow: hidden;
}

@media (max-width: 700px) {
  .threat-map {
    aspect-ratio: 3 / 2;
  }
}

@media (max-width: 480px) {
  .threat-map {
    aspect-ratio: 4 / 3;
  }
}

#threat-map-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

.threat-target {
  position: absolute;
  transform: translate(-50%, -50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
  text-decoration: none;
  color: var(--fg-primary);
}

/* Positions matching each target's data-x/data-y — kept out of inline
   style="" attributes so they work under a strict style-src CSP. */
.threat-target-cv { left: 50%; top: 26%; }
.threat-target-projects { left: 60%; top: 66%; }
.threat-target-visitor-info { left: 76%; top: 36%; }
.threat-target-live-map { left: 18%; top: 32%; }

.threat-target-ring {
  width: 18px;
  height: 18px;
  border: 2px solid var(--accent-cyan);
  border-radius: 50%;
  position: relative;
  box-shadow: 0 0 8px rgba(0, 229, 255, 0.5);
}

.threat-target-ring::before {
  content: "";
  position: absolute;
  inset: -6px;
  border: 1px solid var(--accent-cyan);
  border-radius: 50%;
  opacity: 0.5;
}

.threat-target-label {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.05em;
  background: transparent;
  padding: 0.3rem 0.9rem;
  border: 1px solid var(--border-glow);
  border-radius: 999px;
  white-space: nowrap;
  transition: color 0.2s ease, border-color 0.2s ease;
}

@media (max-width: 480px) {
  /* Keeps the longest label ("VISITOR-INFO") from overflowing the map on
     narrow phones. */
  .threat-target-label {
    font-size: 0.6rem;
    padding: 0.2rem 0.5rem;
    letter-spacing: 0.02em;
  }
}

.threat-target.under-attack .threat-target-ring {
  border-color: var(--accent-red);
  box-shadow: 0 0 16px rgba(255, 56, 96, 0.8);
}

.threat-target.under-attack .threat-target-label {
  color: var(--accent-red);
  border-color: var(--accent-red);
}

.threat-map-caption {
  font-family: var(--font-mono);
  position: absolute;
  bottom: 0.75rem;
  left: 0.75rem;
  right: 0.75rem;
  margin: 0;
  color: var(--fg-muted);
  font-size: 0.8rem;
  text-align: center;
}

/* --- Live map (real captured connections) --- */
/* Same sizing rule as .threat-map above, and for the same reason -- see the
   note there on why min-height and aspect-ratio must not be combined. */
.live-map {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  max-height: 520px;
  margin-top: 1.5rem;
  border: 1px solid var(--border-glow);
  border-radius: 4px;
  background: transparent;
  overflow: hidden;
}

@media (max-width: 700px) {
  .live-map {
    aspect-ratio: 3 / 2;
  }
}

@media (max-width: 480px) {
  .live-map {
    aspect-ratio: 4 / 3;
  }
}

#live-map-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

.live-map-expand-toggle {
  position: absolute;
  top: 0.6rem;
  right: 0.6rem;
  z-index: 3;
  width: 1.9rem;
  height: 1.9rem;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.9rem;
  line-height: 1;
  color: var(--fg-muted);
  background: rgba(var(--fg-muted-rgb), 0.08);
  border: 1px solid var(--border-glow);
  border-radius: 4px;
  cursor: pointer;
}

.live-map-expand-toggle:hover,
.live-map-expand-toggle:focus-visible {
  color: var(--accent-cyan);
  border-color: var(--accent-cyan);
}

/* Expanded is done in CSS rather than through the Fullscreen API: iOS
   Safari refuses requestFullscreen() on anything but a <video>, and a map
   that expands everywhere except the phone it is most needed on is not
   much of a feature. Fixed to the viewport, above the sticky nav. */
.live-map[data-expanded="true"] {
  position: fixed;
  inset: 0;
  z-index: 60;
  /* The aspect-ratio that sizes the inline map must not apply here, or the
     box keeps its 4/3 shape instead of filling the screen. */
  aspect-ratio: auto;
  max-height: none;
  margin: 0;
  border-radius: 0;
  background: var(--bg-void);
}

/* Swap the glyph so the control reads as a toggle rather than a one-way
   door: font-size 0 hides the button's own text, the pseudo-element
   restores a readable size for the replacement. */
.live-map[data-expanded="true"] .live-map-expand-toggle {
  font-size: 0;
}

.live-map[data-expanded="true"] .live-map-expand-toggle::before {
  content: "\2921";
  font-size: 0.9rem;
}

/* Stops the page behind scrolling under the expanded map on touch. */
body[data-live-map-expanded="true"] {
  overflow: hidden;
}

/* Sits at the top because the legend now occupies the bottom-left corner --
   the caption's box spans the full width, so the two would overlap. */
.live-map-caption {
  font-family: var(--font-mono);
  position: absolute;
  top: 0.75rem;
  left: 0.75rem;
  right: 0.75rem;
  margin: 0;
  color: var(--fg-muted);
  font-size: 0.8rem;
  text-align: center;
}

.live-map-legend-wrap {
  position: absolute;
  bottom: 0.75rem;
  left: 0.75rem;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.35rem;
}

.live-map-legend-toggle {
  font-family: var(--font-mono);
  font-size: 0.65rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--fg-muted);
  background: rgba(var(--fg-muted-rgb), 0.08);
  border: 1px solid var(--border-glow);
  border-radius: 4px;
  padding: 0.2rem 0.5rem;
  cursor: pointer;
}

.live-map-legend-toggle::after {
  content: " \25BE";
}

.live-map-legend-wrap[data-collapsed="true"] .live-map-legend-toggle::after {
  content: " \25B8";
}

.live-map-legend-toggle:hover,
.live-map-legend-toggle:focus-visible {
  color: var(--accent-cyan);
  border-color: var(--accent-cyan);
}

/* Collapsed hides the swatches only -- the toggle stays put, so the control
   never moves out from under a thumb that is about to tap it again. */
.live-map-legend-wrap[data-collapsed="true"] .live-map-legend {
  display: none;
}

.live-map-legend {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.4rem;
  padding: 0.5rem 0.65rem;
  background: rgba(var(--fg-muted-rgb), 0.08);
  border: 1px solid var(--border-glow);
  border-radius: 4px;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  color: var(--fg-muted);
}

.live-map-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  white-space: nowrap;
}

.live-map-legend-swatch {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
}

.live-map-legend-swatch[data-color="blue"] { background: var(--accent-blue); }
.live-map-legend-swatch[data-color="cyan"] { background: var(--accent-cyan); }
.live-map-legend-swatch[data-color="magenta"] { background: var(--accent-magenta); }
.live-map-legend-swatch[data-color="orange"] { background: var(--accent-orange); }
.live-map-legend-swatch[data-color="red"] { background: var(--accent-red); }

/* Positioning context for the scroll arrow. The arrow cannot live inside
   .live-map-table-wrap: that is the scrolling box, so the arrow would slide
   away with the very content it is pointing at. */
.live-map-table-scroll {
  /* Cell metrics live here, on the ancestor shared by the table and the
     scroll arrow, so the arrow can size itself to the header row from the
     same numbers the header row uses. Declaring them on .live-map-table
     would put them out of the arrow's inheritance chain -- the mistake that
     left the kill-chain connector off-axis. */
  --live-map-cell-pad-y: 0.5rem;
  --live-map-header-font: 0.7rem;
  position: relative;
  margin-top: 1.5rem;
}

.live-map-table-wrap {
  overflow-x: auto;
  border: 1px solid var(--border-glow);
  border-radius: 4px;
}

/* Parked in the header row rather than floating against the middle of the
   table: the header is the one band that stays put while rows churn every
   second, so the arrow reads as a property of the table instead of a mark
   over whichever row happens to be under it. Height is derived from the
   header's own padding and font size, so restyling the header moves the
   arrow with it. Inset by 1px to sit inside the wrapper's border. */
.live-map-scroll-hint {
  position: absolute;
  top: 1px;
  right: 1px;
  height: calc(var(--live-map-cell-pad-y) * 2 + var(--live-map-header-font) * 1.6);
  display: flex;
  align-items: center;
  padding: 0 0.6rem;
  /* Opaque, and matching the header, so column titles scroll under it
     rather than colliding with the glyph. */
  background: var(--bg-panel);
  /* Sits over a scrollable area -- must never eat a swipe or a click. */
  pointer-events: none;
  font-family: var(--font-mono);
  font-size: 1.35rem;
  line-height: 1;
  color: var(--accent-cyan);
  text-shadow: 0 0 10px rgba(var(--accent-cyan-rgb), 0.55);
  opacity: 0;
  transition: opacity 0.25s ease;
}

/* Shown only when live-map.js has confirmed the table really does overflow
   and has not been scrolled to the end -- an arrow pointing at nothing is
   worse than no arrow. */
.live-map-table-scroll[data-scrollable="true"] .live-map-scroll-hint {
  opacity: 1;
  animation: live-map-scroll-nudge 1.8s ease-in-out infinite;
}

/* Horizontal only now that the hint is top-aligned rather than centred --
   the old keyframes carried a -50% Y offset that positioning no longer uses. */
@keyframes live-map-scroll-nudge {
  0%, 100% { transform: translateX(0); }
  50% { transform: translateX(0.25rem); }
}

@media (prefers-reduced-motion: reduce) {
  .live-map-table-scroll[data-scrollable="true"] .live-map-scroll-hint {
    animation: none;
  }
}

.live-map-table {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--font-mono);
  font-size: 0.8rem;
  white-space: nowrap;
}

.live-map-table th,
.live-map-table td {
  padding: var(--live-map-cell-pad-y) 0.75rem;
  text-align: left;
  border-bottom: 1px solid var(--border-glow);
}

.live-map-table thead th {
  background: var(--bg-panel);
  color: var(--accent-blue);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-size: var(--live-map-header-font);
}

.live-map-table tbody tr:hover {
  background: var(--bg-panel-alt);
}

.live-map-table tbody tr:last-child td {
  border-bottom: none;
}

/* Rows carry the colour of the lines drawn for them, so a burst of arcs on
   the map can be traced to the row that produced it. Driven by a data-color
   attribute set in live-map.js rather than an inline style, which the site's
   CSP (style-src 'self') would block. The reserved transparent border keeps
   every row the same width, so rows don't shift as colours change. */
.live-map-table tbody tr td[data-cell="time"] {
  border-left: 3px solid transparent;
}

.live-map-table tbody tr[data-color="blue"] td[data-cell="time"] { border-left-color: var(--accent-blue); }
.live-map-table tbody tr[data-color="cyan"] td[data-cell="time"] { border-left-color: var(--accent-cyan); }
.live-map-table tbody tr[data-color="magenta"] td[data-cell="time"] { border-left-color: var(--accent-magenta); }
.live-map-table tbody tr[data-color="orange"] td[data-cell="time"] { border-left-color: var(--accent-orange); }
.live-map-table tbody tr[data-color="red"] td[data-cell="time"] { border-left-color: var(--accent-red); }

/* The service name itself is coloured too: that is the column the legend
   maps colours to, so it ties row, legend and line together. */
.live-map-table tbody tr[data-color="blue"] td[data-cell="service"] { color: var(--accent-blue); }
.live-map-table tbody tr[data-color="cyan"] td[data-cell="service"] { color: var(--accent-cyan); }
.live-map-table tbody tr[data-color="magenta"] td[data-cell="service"] { color: var(--accent-magenta); }
.live-map-table tbody tr[data-color="orange"] td[data-cell="service"] { color: var(--accent-orange); }
.live-map-table tbody tr[data-color="red"] td[data-cell="service"] { color: var(--accent-red); }

#live-map-empty td {
  color: var(--fg-muted);
  text-align: center;
  font-style: italic;
}

/* --- Section headings --- */
.section-heading {
  font-family: var(--font-mono);
  background: linear-gradient(135deg, var(--accent-blue), var(--accent-cyan));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  border-bottom: 1px solid var(--border-glow);
  filter: drop-shadow(0 0 12px rgba(47, 111, 238, 0.45));
  padding-bottom: 0.5rem;
  margin-bottom: 1.5rem;
}

.section-heading::before {
  content: "$ ";
  background: linear-gradient(135deg, var(--accent-blue), var(--accent-cyan));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.subheading {
  font-family: var(--font-mono);
  color: var(--accent-cyan);
  font-size: 1rem;
  margin: 1.5rem 0 0.5rem;
}

.bio {
  max-width: 72ch;
}

.log-line {
  font-family: var(--font-mono);
  color: var(--fg-primary);
}

.about-meta {
  margin-bottom: 1.5rem;
}

.reading-list {
  padding-left: 1.25rem;
}

/* --- Tags --- */
.tag-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.tag-chip {
  font-family: var(--font-mono);
  border: 1px solid var(--border-glow);
  color: var(--accent-blue);
  padding: 0.15rem 0.6rem;
  border-radius: 3px;
  font-size: 0.8rem;
  background: var(--bg-panel-alt);
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.tag-chip:hover {
  border-color: var(--accent-blue);
  box-shadow: 0 0 8px rgba(47, 111, 238, 0.35);
}

.skill-group {
  margin-bottom: 1.25rem;
}

/* --- CV: cyber kill chain --- */
.kill-chain {
  /* Declared here, on the common ancestor, because three separate things
     have to line up on the same vertical axis: the numbered circle, the
     connector drawn inside each node's marker, and .kc-connector -- which
     sits BETWEEN nodes and so is a sibling of .kc-node, not a descendant of
     the marker. Putting this on .kc-node-marker (as it first was) left
     .kc-connector out of scope, so shrinking the marker on narrow screens
     moved the circles but not the line between them: the timeline ran ~4px
     off-axis on every phone. */
  --kc-marker-size: 2.5rem;
  margin-top: 1.5rem;
}

.kc-node {
  display: flex;
  gap: 1rem;
}

.kc-node-marker {
  /* Width, circle size, connector origin and the between-node connector all
     come from --kc-marker-size on .kill-chain. They were four separate
     2.5rem literals, so shrinking the circle on narrow screens left the
     line starting 8px below it and 4px to the side of it. */
  flex: 0 0 auto;
  width: var(--kc-marker-size);
  display: flex;
  justify-content: center;
  position: relative;
}

.kc-node:not(:last-of-type) .kc-node-marker::after {
  content: "";
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  top: var(--kc-marker-size);
  bottom: 0;
  width: 3px;
  border-radius: 2px;
  background: var(--accent-blue);
  box-shadow: 0 0 10px rgba(47, 111, 238, 0.6);
}

.kc-node-index {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--kc-marker-size);
  height: var(--kc-marker-size);
  border: 1px solid var(--accent-blue);
  border-radius: 50%;
  color: var(--accent-blue);
  font-size: 0.85rem;
  box-shadow: 0 0 16px rgba(47, 111, 238, 0.35);
}

.kc-node-body {
  flex: 1 1 auto;
  background: var(--bg-panel);
  border: 1px solid var(--border-glow);
  border-radius: 4px;
  padding: 1rem 1.25rem;
  margin-bottom: 0.25rem;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

@media (max-width: 480px) {
  /* Give the timeline card more of a narrow phone's limited width instead
     of losing a big chunk of it to the marker column and card padding. */
  .kc-node {
    gap: 0.6rem;
  }

  /* Set on .kill-chain, not .kc-node-marker: .kc-connector sits between
     nodes and would otherwise keep the desktop width and drift off-axis. */
  .kill-chain {
    --kc-marker-size: 2rem;
  }

  .kc-node-index {
    font-size: 0.75rem;
  }

  .kc-node-body {
    padding: 0.85rem 1rem;
  }
}

.kc-node-body:hover {
  border-color: var(--accent-blue);
  box-shadow: 0 0 20px rgba(47, 111, 238, 0.12);
}

.kc-node-header {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.5rem;
}

.kc-stage-name {
  margin: 0;
  font-size: 1.1rem;
}

.kc-period {
  color: var(--fg-muted);
  font-size: 0.85rem;
}

.kc-role {
  color: var(--accent-cyan);
  margin: 0.35rem 0;
}

.kc-summary {
  color: var(--fg-muted);
  font-style: italic;
  margin: 0.25rem 0 0.75rem;
}

.kc-bullets {
  font-family: var(--font-mono);
  list-style: none;
  padding: 0;
  margin: 0 0 0.75rem;
  color: var(--fg-primary);
}

.kc-bullets li {
  margin-bottom: 0.35rem;
}

.kc-skills {
  margin-top: 0.5rem;
}

.kc-skill-group-title {
  font-family: var(--font-mono);
  color: var(--accent-cyan);
  font-size: 0.85rem;
  margin: 0.75rem 0 0.4rem;
}

.kc-connector {
  /* Must match the marker column exactly, or the segment between two nodes
     centres on a different axis than the circles it joins. */
  width: var(--kc-marker-size);
  display: flex;
  justify-content: center;
  height: 1.5rem;
}

.kc-connector-fill {
  display: block;
  width: 3px;
  height: 100%;
  background: var(--accent-blue);
  border-radius: 2px;
  box-shadow: 0 0 10px rgba(47, 111, 238, 0.6);
}

/* --- Projects --- */
.project-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 1.25rem;
}

.project-card {
  background: var(--bg-panel-alt);
  border: 1px solid var(--border-glow);
  border-radius: 4px;
  padding: 1.25rem;
  transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
}

.project-card:hover {
  border-color: var(--accent-blue);
  box-shadow: 0 0 24px rgba(47, 111, 238, 0.18);
  transform: translateY(-2px);
}

.project-card-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  border: 1px solid var(--accent-blue);
  border-radius: 50%;
  box-shadow: 0 0 12px rgba(47, 111, 238, 0.3);
  margin-bottom: 0.6rem;
}

.project-card-glyph {
  display: block;
  width: 55%;
  height: 55%;
  background-color: var(--accent-blue);
  /* Hardcoded (not inline) so it works under a strict style-src CSP. */
  -webkit-mask-image: url("/static/icons/gitlab.svg");
  mask-image: url("/static/icons/gitlab.svg");
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
}

.project-name {
  margin: 0 0 0.25rem;
  color: var(--accent-blue);
  font-size: 1.05rem;
}

.project-status {
  color: var(--accent-magenta);
  font-size: 0.8rem;
  margin: 0 0 0.5rem;
}

.project-description {
  color: var(--fg-muted);
  font-size: 0.9rem;
  margin: 0 0 0.75rem;
}

.project-details-disabled {
  display: inline-block;
  margin-top: 0.75rem;
  color: var(--fg-muted);
  border: 1px solid var(--fg-muted);
  padding: 0.2rem 0.6rem;
  font-size: 0.8rem;
  opacity: 0.5;
  cursor: not-allowed;
}

.project-details-link {
  display: inline-block;
  margin-top: 0.75rem;
  color: var(--accent-cyan);
  border: 1px solid var(--border-glow);
  padding: 0.2rem 0.6rem;
  font-size: 0.8rem;
  border-radius: 3px;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.project-details-link:hover,
.project-details-link:focus-visible {
  color: var(--accent-cyan);
  border-color: var(--accent-cyan);
  box-shadow: 0 0 12px rgba(0, 229, 255, 0.35);
  text-decoration: none;
}

/* --- Projects: Secure SDLC lifecycle --- */
.shipped-section {
  margin-top: 2.5rem;
}

.ssdlc-flow {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  gap: 0.6rem;
  margin: 1.5rem 0 0;
}

.ssdlc-stage {
  flex: 1 1 140px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.45rem;
  text-align: center;
  background: var(--bg-panel-alt);
  border: 1px solid var(--border-glow);
  border-radius: 4px;
  padding: 0.85rem 0.75rem;
  transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
}

.ssdlc-stage:hover {
  border-color: var(--accent-blue);
  box-shadow: 0 0 20px rgba(47, 111, 238, 0.18);
  transform: translateY(-2px);
}

.ssdlc-stage-index {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  border: 1px solid var(--accent-blue);
  border-radius: 50%;
  color: var(--accent-blue);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  box-shadow: 0 0 12px rgba(47, 111, 238, 0.3);
}

.ssdlc-stage-name {
  font-family: var(--font-mono);
  font-weight: bold;
  color: var(--fg-primary);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  font-size: 0.85rem;
}

.ssdlc-stage-desc {
  margin: 0;
  font-size: 0.78rem;
  color: var(--fg-muted);
}

.ssdlc-arrow {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  color: var(--accent-cyan);
  font-size: 1.2rem;
}

.ssdlc-loop-node {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.25rem;
  width: 110px;
  height: 110px;
  border-radius: 50%;
  border: 2px dashed var(--accent-blue);
  background: var(--bg-panel-alt);
  box-shadow: 0 0 24px rgba(47, 111, 238, 0.35);
}

.ssdlc-loop-icon {
  font-size: 1.6rem;
  color: var(--accent-blue);
}

.ssdlc-loop-text {
  font-family: var(--font-mono);
  font-size: 0.65rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--fg-muted);
  text-align: center;
  line-height: 1.3;
}

@media (max-width: 700px) {
  .ssdlc-arrow {
    display: none;
  }
}

/* --- Visitor info: ATT&CK-style matrix --- */
.recon-intro {
  color: var(--fg-muted);
  margin-bottom: 1.5rem;
}

.recon-summary {
  font-family: var(--font-mono);
  background: var(--bg-panel);
  border: 1px solid var(--border-glow);
  border-radius: 4px;
  padding: 1rem 1.25rem;
  margin: 0 0 1.5rem;
}

.recon-summary-heading {
  color: var(--accent-cyan);
  font-size: 0.9rem;
  margin: 0 0 0.75rem;
}

.recon-summary-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 0.75rem 1.5rem;
  margin: 0;
}

.recon-summary-item {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  padding-bottom: 0.5rem;
  border-bottom: 1px dotted var(--border-glow);
  font-size: 0.85rem;
}

.recon-summary-item dt {
  color: var(--fg-muted);
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.recon-summary-item dd {
  margin: 0;
  color: var(--fg-primary);
  word-break: break-word;
}

.attck-matrix-wrap {
  font-family: var(--font-mono);
  overflow-x: auto;
  margin: 0 0 2rem;
  border: 1px solid var(--border-glow);
  border-radius: 4px;
  scrollbar-color: var(--accent-blue) var(--bg-panel-alt);
  scrollbar-width: thin;
}

.attck-matrix-wrap::-webkit-scrollbar {
  height: 10px;
}

.attck-matrix-wrap::-webkit-scrollbar-track {
  background: var(--bg-panel-alt);
}

.attck-matrix-wrap::-webkit-scrollbar-thumb {
  background: var(--accent-blue);
  border-radius: 999px;
  box-shadow: 0 0 8px rgba(47, 111, 238, 0.5);
}

.attck-matrix-wrap::-webkit-scrollbar-thumb:hover {
  background: var(--accent-cyan);
  box-shadow: 0 0 10px rgba(0, 229, 255, 0.6);
}

.attck-matrix {
  display: flex;
  min-width: max-content;
}

.attck-column {
  flex: 0 0 220px;
  border-right: 1px solid var(--border-glow);
}

.attck-column:last-child {
  border-right: none;
}

@media (max-width: 700px) {
  /* Below tablet width, ten 220px-wide columns just isn't viewable without
     scrolling sideways to read anything. Stack tactics vertically instead
     — full width, natural up-and-down scroll like the rest of the page. */
  .attck-matrix-wrap {
    overflow-x: visible;
  }

  .attck-matrix {
    flex-direction: column;
    min-width: 0;
  }

  .attck-column {
    flex: 1 1 auto;
    width: 100%;
    border-right: none;
    border-bottom: 1px solid var(--border-glow);
  }

  .attck-column:last-child {
    border-bottom: none;
  }
}

.attck-tactic-header {
  background: var(--bg-panel-alt);
  color: var(--fg-primary);
  font-size: 0.75rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 0.6rem 0.6rem;
  border-bottom: 2px solid var(--fg-muted);
  text-align: center;
}

.attck-column-active .attck-tactic-header {
  border-bottom-color: var(--accent-red);
  color: var(--accent-red);
  box-shadow: 0 2px 12px -2px rgba(255, 56, 96, 0.4);
}

.attck-cell {
  border-bottom: 1px dotted var(--border-glow);
  font-size: 0.78rem;
}

.attck-cell:last-child {
  border-bottom: none;
}

.attck-cell-summary {
  display: block;
  list-style: none;
  cursor: pointer;
  padding: 0.6rem 0.6rem 0.6rem 1.5rem;
  position: relative;
  transition: background 0.2s ease;
}

.attck-cell-summary::-webkit-details-marker {
  display: none;
}

.attck-cell-summary::before {
  content: "+";
  position: absolute;
  left: 0.55rem;
  top: 0.58rem;
  color: var(--fg-muted);
  width: 1em;
}

.attck-cell[open] .attck-cell-summary::before {
  content: "\2013";
  color: var(--accent-cyan);
}

.attck-cell-summary:hover,
.attck-cell-summary:focus-visible {
  background: var(--bg-panel-alt);
}

.attck-cell-summary-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

.attck-cell-id {
  color: var(--accent-cyan);
  font-weight: bold;
}

.attck-cell-status {
  flex: 0 0 auto;
  font-size: 0.6rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 0.1rem 0.4rem;
  border-radius: 2px;
  color: var(--fg-muted);
  border: 1px solid var(--border-glow);
}

.attck-cell-observed .attck-cell-status {
  color: var(--accent-red);
  border-color: var(--accent-red);
}

.attck-cell-name {
  display: block;
  color: var(--fg-muted);
  margin: 0.3rem 0 0;
}

.attck-cell-body {
  padding: 0 0.6rem 0.75rem 1.5rem;
}

.attck-cell-detail {
  color: var(--fg-primary);
  margin: 0 0 0.5rem;
}

.attck-cell-detail:last-child {
  margin-bottom: 0;
}

.attck-cell-detail strong {
  color: var(--accent-cyan);
}

.attck-cell-not-observed {
  color: var(--fg-muted);
  font-style: italic;
}

.attck-cell-value {
  color: var(--fg-primary);
  word-break: break-word;
  margin: 0 0 0.5rem;
}

.attck-cell-value strong {
  color: var(--accent-red);
}

.recon-disclaimer {
  color: var(--fg-muted);
  font-size: 0.85rem;
  border-top: 1px solid var(--border-glow);
  padding-top: 1rem;
}

/* --- Live-map statistics panel --- */
.stats-panel {
  margin-top: 2.5rem;
}

.stats-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 1rem 0 1.25rem;
}

.stats-tab {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--fg-muted);
  background: transparent;
  border: 1px solid var(--border-glow);
  border-radius: 999px;
  padding: 0.35rem 0.9rem;
  cursor: pointer;
}

.stats-tab:hover,
.stats-tab:focus-visible {
  color: var(--accent-cyan);
  border-color: var(--accent-cyan);
}

.stats-tab[aria-selected="true"] {
  color: var(--accent-blue);
  border-color: var(--accent-blue);
  background: rgba(var(--accent-blue-rgb), 0.08);
}

/* With JavaScript off every window stays visible, stacked, each under its
   own heading -- the tabs are an enhancement, not a requirement. */
.js .stats-window[data-active="false"] {
  display: none;
}

.stats-summary {
  font-family: var(--font-mono);
  font-size: 0.85rem;
  color: var(--fg-muted);
  margin: 0 0 0.75rem;
}

.stats-summary strong {
  color: var(--accent-cyan);
}

.stats-summary-note {
  opacity: 0.7;
}

.stats-chart {
  display: block;
  width: 100%;
  height: 90px;
  overflow: visible;
}

.stats-chart-line {
  stroke: var(--accent-cyan);
  stroke-width: 1.5;
}

.stats-chart-area {
  fill: rgba(var(--accent-cyan-rgb), 0.12);
  stroke: none;
}

.stats-chart-axis {
  display: flex;
  justify-content: space-between;
  font-family: var(--font-mono);
  font-size: 0.65rem;
  color: var(--fg-muted);
  margin: 0.2rem 0 1.25rem;
}

.stats-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 1.25rem;
}

.stats-block {
  border: 1px solid var(--border-glow);
  border-radius: 4px;
  padding: 0.85rem 1rem;
}

.stats-block-title {
  margin: 0 0 0.5rem;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--accent-blue);
}

.stats-table {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--font-mono);
  font-size: 0.75rem;
}

.stats-table td {
  padding: 0.18rem 0;
  vertical-align: middle;
}

.stats-label {
  color: var(--fg-primary);
  max-width: 11rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.stats-bar-cell {
  width: 40%;
  padding-left: 0.5rem !important;
}

.stats-count {
  text-align: right;
  color: var(--fg-muted);
  padding-left: 0.5rem !important;
  white-space: nowrap;
}

/* Bar widths come from a data attribute in 5% steps rather than an inline
   style, which the CSP (style-src 'self') would refuse. Twenty-one rules is
   a small price for not having to loosen it. */
.stats-bar {
  display: block;
  height: 6px;
  border-radius: 3px;
  background: linear-gradient(90deg, rgba(var(--accent-blue-rgb), 0.85), rgba(var(--accent-cyan-rgb), 0.85));
}

.stats-bar[data-bar="0"]  { width: 2px; }
.stats-bar[data-bar="1"]  { width: 5%; }
.stats-bar[data-bar="2"]  { width: 10%; }
.stats-bar[data-bar="3"]  { width: 15%; }
.stats-bar[data-bar="4"]  { width: 20%; }
.stats-bar[data-bar="5"]  { width: 25%; }
.stats-bar[data-bar="6"]  { width: 30%; }
.stats-bar[data-bar="7"]  { width: 35%; }
.stats-bar[data-bar="8"]  { width: 40%; }
.stats-bar[data-bar="9"]  { width: 45%; }
.stats-bar[data-bar="10"] { width: 50%; }
.stats-bar[data-bar="11"] { width: 55%; }
.stats-bar[data-bar="12"] { width: 60%; }
.stats-bar[data-bar="13"] { width: 65%; }
.stats-bar[data-bar="14"] { width: 70%; }
.stats-bar[data-bar="15"] { width: 75%; }
.stats-bar[data-bar="16"] { width: 80%; }
.stats-bar[data-bar="17"] { width: 85%; }
.stats-bar[data-bar="18"] { width: 90%; }
.stats-bar[data-bar="19"] { width: 95%; }
.stats-bar[data-bar="20"] { width: 100%; }

.stats-empty {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--fg-muted);
  margin: 0;
}

/* --- Visitor signals (passive request headers + what JS can read) --- */
.signals-panel {
  margin-top: 2.5rem;
}

.signal-table {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--font-mono);
  font-size: 0.78rem;
  margin-top: 1rem;
}

.signal-table td {
  padding: 0.45rem 0.6rem;
  border-bottom: 1px solid var(--border-glow);
  vertical-align: top;
}

.signal-table tr:last-child td {
  border-bottom: none;
}

.signal-label {
  color: var(--accent-blue);
  white-space: nowrap;
  width: 1%;
}

.signal-value {
  color: var(--fg-primary);
  word-break: break-word;
}

.signal-note {
  color: var(--fg-muted);
  font-family: var(--font-sans);
  font-size: 0.8rem;
  line-height: 1.45;
}

/* The notes are the point on a wide screen and noise on a narrow one, where
   the value itself needs the width. */
@media (max-width: 700px) {
  .signal-note {
    display: none;
  }

  .signal-label {
    white-space: normal;
  }
}

.client-verdicts {
  list-style: none;
  padding: 0;
  margin: 0.5rem 0 1.25rem;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.client-verdict {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  padding: 0.5rem 0.75rem;
  border-left: 3px solid var(--border-glow);
  background: rgba(var(--fg-muted-rgb), 0.06);
  color: var(--fg-primary);
}

/* Flagged rather than alarming: a VPN is not a problem, it is just visible. */
.client-verdict[data-tone="flag"] {
  border-left-color: var(--accent-orange);
}

.client-verdict[data-tone="neutral"] {
  border-left-color: var(--accent-blue);
}

.client-fingerprint-line {
  font-family: var(--font-mono);
  font-size: 0.85rem;
  color: var(--fg-muted);
  margin: 0 0 0.4rem;
}

.client-fingerprint-line code {
  color: var(--accent-magenta);
  word-break: break-all;
}

/* The visitor's own row. Marked by live-map.js after comparing anonymized
   tokens; the cell also carries a literal "(you)", so the meaning survives
   a screenshot, a high-contrast theme and a screen reader rather than
   resting on colour alone. */
.live-map-table tbody tr[data-you="true"] {
  background: rgba(var(--accent-cyan-rgb), 0.1);
}

.live-map-table tbody tr[data-you="true"]:hover {
  background: rgba(var(--accent-cyan-rgb), 0.16);
}

.live-map-table tbody tr[data-you="true"] td[data-cell="ip"] {
  color: var(--accent-cyan);
}

/* Overrides the service-colour stripe on the left edge: "this row is you"
   outranks "this row is https". */
.live-map-table tbody tr[data-you="true"] td[data-cell="time"] {
  border-left-color: var(--accent-cyan) !important;
}

/* --- Legal pages and their footer links --- */
/* Deliberately quiet, but no quieter: § 5 DDG wants the link easily
   recognisable and permanently available, so it inherits the footer's own
   muted colour rather than being dimmed further, and it is never hidden
   behind a menu or a breakpoint. */
.footer-legal-link {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.footer-legal-link:hover,
.footer-legal-link:focus-visible {
  color: var(--accent-cyan);
}

.legal-page {
  max-width: 46rem;
}

.legal-intro {
  color: var(--fg-muted);
  font-family: var(--font-mono);
  font-size: 0.85rem;
}

.legal-subheading {
  margin: 1.75rem 0 0.4rem;
  font-family: var(--font-mono);
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--accent-blue);
}

.legal-address {
  font-style: normal;
  font-family: var(--font-mono);
  font-size: 0.9rem;
  line-height: 1.7;
}

.legal-body,
.legal-list {
  line-height: 1.7;
}

.legal-list {
  padding-left: 1.1rem;
}

.legal-footnote {
  margin-top: 2rem;
  color: var(--fg-muted);
  font-size: 0.9rem;
}

/* Only rendered while the content still contains placeholders. */
.legal-warning {
  border-left: 3px solid var(--accent-red);
  background: rgba(var(--accent-red-rgb), 0.08);
  padding: 0.75rem 1rem;
  line-height: 1.6;
}

/* --- Legal pages and their footer links --- */
/* Deliberately quiet, but no quieter: § 5 DDG wants the link easily
   recognisable and permanently available, so it inherits the footer's own
   muted colour rather than being dimmed further, and it is never hidden
   behind a menu or a breakpoint. */
.footer-legal-link {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.footer-legal-link:hover,
.footer-legal-link:focus-visible {
  color: var(--accent-cyan);
}

.legal-page {
  max-width: 46rem;
}

.legal-intro {
  color: var(--fg-muted);
  font-family: var(--font-mono);
  font-size: 0.85rem;
}

.legal-subheading {
  margin: 1.75rem 0 0.4rem;
  font-family: var(--font-mono);
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--accent-blue);
}

.legal-address {
  font-style: normal;
  font-family: var(--font-mono);
  font-size: 0.9rem;
  line-height: 1.7;
}

.legal-body,
.legal-list {
  line-height: 1.7;
}

.legal-list {
  padding-left: 1.1rem;
}

.legal-footnote {
  margin-top: 2rem;
  color: var(--fg-muted);
  font-size: 0.9rem;
}

/* Only rendered while the content still contains placeholders. */
.legal-warning {
  border-left: 3px solid var(--accent-red);
  background: rgba(var(--accent-red-rgb), 0.08);
  padding: 0.75rem 1rem;
  line-height: 1.6;
}

/* --- Contact form --- */
.contact-form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  max-width: 34rem;
  margin-top: 1.5rem;
}

.contact-field {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.contact-label {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--accent-blue);
}

.contact-optional {
  color: var(--fg-muted);
  text-transform: none;
  letter-spacing: 0;
}

.contact-input {
  font-family: var(--font-mono);
  font-size: 0.9rem;
  color: var(--fg-primary);
  background: var(--bg-panel);
  border: 1px solid var(--border-glow);
  border-radius: 4px;
  padding: 0.55rem 0.7rem;
  width: 100%;
}

.contact-input:focus-visible {
  outline: none;
  border-color: var(--accent-cyan);
  box-shadow: 0 0 0 2px rgba(var(--accent-cyan-rgb), 0.25);
}

.contact-input[aria-invalid="true"] {
  border-color: var(--accent-red);
}

.contact-textarea {
  resize: vertical;
  min-height: 8rem;
  line-height: 1.6;
}

.contact-error {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--accent-red);
}

.contact-submit {
  align-self: flex-start;
  font-family: var(--font-mono);
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--accent-blue);
  background: rgba(var(--accent-blue-rgb), 0.08);
  border: 1px solid var(--accent-blue);
  border-radius: 999px;
  padding: 0.55rem 1.4rem;
  cursor: pointer;
}

.contact-submit:hover,
.contact-submit:focus-visible {
  color: var(--accent-cyan);
  border-color: var(--accent-cyan);
}

.contact-notice {
  border-left: 3px solid var(--accent-blue);
  background: rgba(var(--fg-muted-rgb), 0.06);
  padding: 0.75rem 1rem;
  line-height: 1.6;
}

.contact-notice[data-tone="ok"] {
  border-left-color: var(--accent-cyan);
}

.contact-notice[data-tone="warn"] {
  border-left-color: var(--accent-orange);
}

/* Honeypot: off-screen rather than display:none, which the more careful
   bots detect and skip. Kept out of the tab order and the accessibility
   tree by the markup itself. */
.contact-honeypot {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* --- Obfuscated email addresses --- */
/* The decoy fragments. display:none is doing real work here rather than
   merely hiding: browsers leave such text out of selection, out of
   copy-paste and out of the accessibility tree, while naive scraping —
   strip the tags, keep the text — picks it up. So a person gets the real
   address and a harvester gets a dead one. */
.mail-decoy {
  display: none;
}

.mail-address {
  white-space: nowrap;
}
