@import url("https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,100..900&family=IBM+Plex+Mono:wght@400;500;600&display=swap");

:root {
  --void: #0b0e11;
  --void-2: #10151a;
  --vertex: #e8e6de;
  --vertex-dim: #8a8f96;
  /* Generic status feedback — error/success/info, used for login errors,
     onboarding results, conflict/unresolved badges, focus rings. Kept on
     the conventional red/green/blue: users (colorblind users especially)
     rely on that convention for pass/fail scanning, so it stays put even
     though the hues don't relate to the brand palette. Not the same thing
     as an edge's exposure level below, even though they used to share
     these same three variables — see --exposure-* for that. */
  --track-public: #6fcf97;
  --track-shared: #56a8e8;
  --track-private: #e0574f;
  /* The site's burgundy accent, formalized as a real token here (it only
     ever existed as a bare hex before) so the exposure ramp below is
     genuinely derived from it, not from an invented color. A plain
     lightness bump off the true #5c0a18 (the old value here, #c9556a)
     desaturates fast enough in sRGB/HSL to read as pink rather than wine
     once it's light enough to clear contrast against --void — this value
     instead raises lightness in OKLCH space at the true color's own real
     hue/chroma (perceptually uniform, so chroma doesn't wash out the way
     naive lightening does), which is what keeps it reading as a muted
     brick/wine tone instead. */
  --accent-text: #ba6265;
  /* A fact's exposure level (public_identity/project_shared/private_only)
     — graph node/edge strokes, the timeline row border, and both exposure
     badges (timeline row + detail panel). Reads as one accent color at
     increasing intensity against the neutral gray already used everywhere
     else, not a third hue: public is --vertex-dim verbatim, private is
     --accent-text verbatim, and shared is a literal 50/50 RGB blend of
     those exact two values (#8a8f96 + #ba6265 -> #a2787e) — not a
     hand-picked "bridge" color. An earlier pass used a separately-chosen
     plum tone here that didn't derive from anything in the real palette
     and still read as an unrelated hue; this doesn't. */
  --exposure-public: var(--vertex-dim);
  --exposure-shared: #a2787e;
  --exposure-private: var(--accent-text);
  --grid: #232a31;
  --grid-bright: #333d47;
}

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  height: 100%;
  background: var(--void);
  color: var(--vertex);
  font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;
  overflow: hidden;
}

.mono {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
}

/* --- layout --- */

#app {
  display: flex;
  flex-direction: column;
  height: 100vh;
}

#topbar {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  padding: 0.75rem 1.25rem;
  border-bottom: 1px solid var(--grid);
  flex: none;
  z-index: 2;
}

#wordmark {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-family: "IBM Plex Mono", monospace;
  font-weight: 600;
  letter-spacing: 0.08em;
  font-size: 0.9rem;
  color: var(--vertex);
  text-decoration: none;
}
#wordmark:hover {
  text-decoration: none;
}
/* the reversed/knockout mark (body -> --vertex, accents -> a lighter
   burgundy) — same asset and generation recipe as site/ASSETS.md, since
   this app's topbar is the same dark ground the marketing site's header
   is. The true two-tone mark lives in arachnest_logo.png for contexts
   with a lighter background (favicons, OG image), not here. */
#wordmark img {
  display: block;
  width: 1.3rem;
  height: 1.3rem;
  /* logo-mark.png carries its transparent padding entirely at the top, so
     the visible mark sits low against the wordmark text's cap-height when
     centered as a box (see site/components/SiteHeader.module.css for the
     same root cause on the marketing site — the offset here is smaller
     since this icon renders at 1.3rem vs. that header's 1.75rem, measured
     separately rather than reused). */
  transform: translateY(-3px);
}
#wordmark::after {
  content: "·";
  color: var(--track-public);
  margin-left: 0.15em;
}

#search-form {
  position: relative;
  flex: 1;
  max-width: 26rem;
}

#search-input {
  width: 100%;
  background: var(--void-2);
  border: 1px solid var(--grid);
  color: var(--vertex);
  font-family: "IBM Plex Mono", monospace;
  font-size: 0.85rem;
  padding: 0.5rem 0.75rem;
  border-radius: 3px;
  outline: none;
}
#search-input::placeholder {
  color: var(--vertex-dim);
}
#search-input:focus {
  border-color: var(--track-shared);
}

#search-results {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  background: var(--void-2);
  border: 1px solid var(--grid);
  border-radius: 3px;
  max-height: 20rem;
  overflow-y: auto;
  display: none;
}
#search-results.open {
  display: block;
}
.search-result {
  padding: 0.5rem 0.75rem;
  font-family: "IBM Plex Mono", monospace;
  font-size: 0.78rem;
  border-bottom: 1px solid var(--grid);
  cursor: pointer;
}
.search-result:last-child {
  border-bottom: none;
}
.search-result:hover {
  background: var(--void);
}

#add-memories-btn {
  margin-left: auto;
}

/* Same visual pattern (shape/sizing/hover) for both — different accent
   color so the two are distinguishable at a glance when both show at once,
   not just by reading the text: red for a dispute, blue for "needs your
   input" (matches --track-shared's existing neutral/informational use
   elsewhere, e.g. the timeline link). */
#conflict-badge,
#unresolved-badge {
  font-size: 0.72rem;
  border-radius: 3px;
  padding: 0.2rem 0.5rem;
  text-decoration: none;
  white-space: nowrap;
}
#conflict-badge {
  color: var(--track-private);
  border: 1px solid var(--track-private);
}
#conflict-badge:hover {
  background: var(--track-private);
  color: var(--void);
}
#unresolved-badge {
  color: var(--track-shared);
  border: 1px solid var(--track-shared);
}
#unresolved-badge:hover {
  background: var(--track-shared);
  color: var(--void);
}
#conflict-badge[hidden],
#unresolved-badge[hidden] {
  display: none;
}

#whoami {
  font-family: "IBM Plex Mono", monospace;
  font-size: 0.78rem;
  color: var(--vertex-dim);
}

/* Same EN/TR pattern as site/components/LocaleSwitcher.tsx — plain text
   buttons, no button chrome, active one picked out in the accent color. */
#lang-switcher {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.78rem;
}
#lang-switcher span {
  color: var(--grid-bright);
}
#lang-switcher button {
  background: transparent;
  border: none;
  padding: 0;
  color: var(--vertex-dim);
  font-family: inherit;
  font-size: inherit;
  cursor: pointer;
}
#lang-switcher button:hover {
  color: var(--vertex);
}
#lang-switcher button[aria-current="true"] {
  color: var(--track-public);
  cursor: default;
}

#main {
  flex: 1;
  position: relative;
  min-height: 0;
}

/* --- timeline view: same data, a log-strip lens instead of orbits --- */

#timeline-list {
  width: 100%;
  height: 100%;
  overflow-y: auto;
  max-width: 48rem;
  margin: 0 auto;
  padding: 1.5rem 2rem 3rem;
}
.timeline-empty {
  color: var(--vertex-dim);
  font-size: 0.85rem;
  text-align: center;
  margin-top: 4rem;
}
.timeline-filter-banner {
  color: var(--vertex-dim);
  font-size: 0.78rem;
  margin-bottom: 1rem;
}
.timeline-filter-banner a {
  color: var(--track-shared);
}
.timeline-row {
  display: grid;
  grid-template-columns: 9rem 1fr auto;
  gap: 1rem;
  align-items: baseline;
  border-left: 3px solid var(--grid-bright);
  border-bottom: 1px solid var(--grid);
  padding: 0.6rem 0 0.6rem 1rem;
  cursor: pointer;
  font-size: 0.8rem;
}
.timeline-row:hover {
  background: var(--void-2);
}
.timeline-row.status-tentative .timeline-fact {
  font-style: italic;
}
.timeline-row.status-superseded,
.timeline-row.status-rejected,
.timeline-row.status-historical {
  opacity: 0.4;
}
.timeline-date {
  color: var(--vertex-dim);
  white-space: nowrap;
}
.timeline-fact {
  font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;
  font-size: 0.9rem;
  color: var(--vertex);
}
.timeline-subject {
  font-weight: 500;
}
.timeline-predicate {
  color: var(--vertex-dim);
  font-family: "IBM Plex Mono", monospace;
  font-size: 0.72rem;
  margin: 0 0.4rem;
}
.timeline-object {
  font-weight: 500;
}
.timeline-meta {
  white-space: nowrap;
  color: var(--vertex-dim);
  font-size: 0.72rem;
  text-align: right;
}

#graph {
  width: 100%;
  height: 100%;
  display: block;
  cursor: grab;
}
#graph:active {
  cursor: grabbing;
}

/* --- graph elements --- */

.track {
  stroke-width: 1.4px;
  fill: none;
  cursor: pointer;
}
.track.status-tentative {
  stroke-dasharray: 3 3;
}
.track.status-superseded,
.track.status-rejected {
  opacity: 0.35;
}

.hit {
  cursor: pointer;
}
.hit circle.core {
  stroke-width: 1.5px;
}
.hit text {
  font-family: "IBM Plex Mono", monospace;
  font-size: 11px;
  fill: var(--vertex);
  pointer-events: none;
}
.vertex-group circle.pulse {
  fill: none;
  stroke: var(--vertex);
  opacity: 0;
  animation: pulse 2.6s ease-out infinite;
}
.vertex-group circle.core {
  fill: var(--vertex);
}
.vertex-group text {
  font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;
  font-weight: 500;
  font-size: 14px;
  fill: var(--vertex);
  pointer-events: none;
}

@keyframes pulse {
  0% {
    r: 20px;
    opacity: 0.5;
  }
  100% {
    r: 90px;
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .vertex-group circle.pulse {
    animation: none;
    opacity: 0;
  }
}

/* --- detail panel --- */

#panel {
  position: absolute;
  top: 1rem;
  right: 1rem;
  bottom: 1rem;
  width: 22rem;
  background: var(--void-2);
  border: 1px solid var(--grid);
  border-radius: 6px;
  padding: 1.1rem;
  overflow-y: auto;
  transform: translateX(calc(100% + 2rem));
  transition: transform 0.25s ease;
}
#panel.open {
  transform: translateX(0);
}

#panel h2 {
  font-size: 1.05rem;
  margin: 0 0 0.9rem;
  font-weight: 500;
}

.field {
  margin-bottom: 0.85rem;
}
.field .label {
  font-family: "IBM Plex Mono", monospace;
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--vertex-dim);
  margin-bottom: 0.2rem;
}
.field .value {
  font-family: "IBM Plex Mono", monospace;
  font-size: 0.85rem;
}
.field-note {
  color: var(--vertex-dim);
  font-size: 0.75rem;
  margin: 0.3rem 0 0;
}

.badge {
  display: inline-block;
  padding: 0.15rem 0.5rem;
  border-radius: 3px;
  font-family: "IBM Plex Mono", monospace;
  font-size: 0.72rem;
  border: 1px solid currentColor;
}
.badge.exposure-public_identity {
  color: var(--exposure-public);
}
.badge.exposure-project_shared {
  color: var(--exposure-shared);
}
.badge.exposure-private_only {
  color: var(--exposure-private);
}

select.exposure-select {
  background: var(--void);
  color: var(--vertex);
  border: 1px solid var(--grid-bright);
  font-family: "IBM Plex Mono", monospace;
  font-size: 0.78rem;
  padding: 0.25rem 0.4rem;
  border-radius: 3px;
}

button {
  font-family: "IBM Plex Mono", monospace;
  font-size: 0.78rem;
  background: transparent;
  color: var(--vertex);
  border: 1px solid var(--grid-bright);
  border-radius: 3px;
  padding: 0.4rem 0.7rem;
  cursor: pointer;
}
button:hover {
  border-color: var(--vertex-dim);
}
button:focus-visible,
input:focus-visible,
select:focus-visible {
  outline: 2px solid var(--track-shared);
  outline-offset: 1px;
}

#evidence-body {
  font-family: "IBM Plex Mono", monospace;
  font-size: 0.78rem;
  white-space: pre-wrap;
  margin-top: 0.6rem;
  border-top: 1px solid var(--grid);
  padding-top: 0.6rem;
}

/* --- onboarding (empty-graph state) --- */

#onboarding {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--void);
}
#onboarding[hidden] {
  display: none;
}
.onboarding-inner {
  position: relative;
  width: 26rem;
  max-width: 90vw;
  /* #onboarding vertically centers this via flex align-items — without a
     cap, content taller than the container (three forms instead of two)
     overflows symmetrically in BOTH directions, pushing the top (title,
     close button) up past #onboarding's own top edge and into the header
     bar above it instead of just extending the bottom. A visible card frame
     (background+border, matching #panel's own look elsewhere) so there's an
     actual modal box for the header/close button to read as part of,
     instead of floating text on the bare page background. */
  max-height: calc(100% - 2rem);
  background: var(--void-2);
  border: 1px solid var(--grid);
  border-radius: 6px;
  display: flex;
  flex-direction: column;
  overflow: hidden; /* clips .onboarding-scroll's corners to this card's border-radius */
}
/* A separate, non-scrolling row — NOT padding/z-index tricks on a single
   scrolling box — so the close button structurally can never share space
   with .onboarding-scroll's own scrollbar; they're different flex rows. */
.onboarding-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex: none;
  padding: 0.85rem 0.85rem 0.85rem 1.25rem;
  border-bottom: 1px solid var(--grid);
}
.onboarding-scroll {
  overflow-y: auto;
  min-height: 0; /* lets this flex child actually shrink and scroll instead of stretching .onboarding-inner to fit its content */
  padding: 1.25rem;
}
#onboarding-close {
  padding: 0.2rem 0.55rem;
  font-size: 1rem;
  line-height: 1;
  flex: none;
  align-self: center; /* overrides the generic ".onboarding-inner button { align-self: flex-start }" below — this one sits in a header row, not stacked in a form */
}
.onboarding-header h2 {
  font-family: "Fraunces", ui-serif, Georgia, serif;
  font-optical-sizing: auto;
  font-size: 1.25rem;
  font-weight: 500;
  margin: 0;
}
.onboarding-scroll > p {
  color: var(--vertex-dim);
  font-size: 0.88rem;
  margin: 0 0 1.5rem;
}
.onboarding-inner form {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  border: 1px solid var(--grid);
  border-radius: 6px;
  padding: 1rem;
  margin-bottom: 1rem;
}
.onboarding-inner h3 {
  font-family: "IBM Plex Mono", monospace;
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--vertex-dim);
  margin: 0 0 0.2rem;
  font-weight: 500;
}
.onboarding-inner input,
.onboarding-inner textarea,
.onboarding-inner select {
  background: var(--void-2);
  border: 1px solid var(--grid);
  color: var(--vertex);
  font-size: 0.82rem;
  padding: 0.5rem 0.7rem;
  border-radius: 3px;
  resize: vertical;
}
.onboarding-inner button {
  align-self: flex-start;
}
.onboarding-status {
  font-family: "IBM Plex Mono", monospace;
  font-size: 0.75rem;
  min-height: 1em;
}
.onboarding-status.error {
  color: var(--track-private);
}
.onboarding-status.ok {
  color: var(--track-public);
}

/* --- onboarding intro: two-card first-run orientation, shown only on the
   empty-graph cold-start (see app.js's openOnboardingIntro) --- */

.intro-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}
/* .intro-card{display:flex} and the UA stylesheet's [hidden]{display:none}
   have EQUAL specificity (one class vs. one attribute selector) — without
   this, the later-loaded author rule wins regardless of the hidden
   attribute, and a "hidden" card still renders. Higher specificity
   (class+attribute) is what actually re-hides it; confirmed directly, both
   cards rendered stacked before this rule existed. */
.intro-card[hidden] {
  display: none;
}
/* Revisits the "no icon" call above this comment's old spot: at full size
   the supplied mark+badge illustrations read as a composited clip-art
   badge, not a deliberate icon treatment. Small and consistent with the
   card's own text scale reads as an intentional accent instead —
   decorative only (alt="" on the <img>; the heading right next to it
   already says the same thing in words), not a second icon language
   competing with the real typography. -white variants only: this app has
   no light/dark toggle (confirmed — no prefers-color-scheme or
   data-theme anywhere in this file), so the -black pair stays unused.
   Row, not stacked above the heading: h3 keeps flex-grow at its default
   0 (NOT flex:1 — that grows the heading to consume the whole row,
   which is what pushed the icon out to the far right edge before; a
   previous version of this rule made exactly that mistake) so it and
   the icon pack together at the start of the row instead of spreading
   across it. min-width:0 is still needed so a longer heading (Turkish
   runs longer than English here) can shrink and wrap onto a second line
   within its own box rather than overflowing the row — the icon (fixed
   size, flex:none) just ends up centered beside however tall that
   wrapped box turns out to be. */
.intro-card-heading-row {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  /* Midpoint between the two explicit gap values this row has actually
     had: 0.4rem (too snug, previous round) and 0.75rem (two rounds ago,
     before that round's separate justify-content:space-between bug made
     the REAL visual gap much wider than its own 0.75rem declaration —
     see that commit). (0.4 + 0.75) / 2 = 0.575rem exactly. */
  gap: 0.575rem;
  width: 100%;
  margin: 0 0 0.5rem;
}
.intro-card-icon {
  height: 48px;
  width: auto;
  flex: none;
}
.intro-card h3 {
  font-family: "Fraunces", ui-serif, Georgia, serif;
  font-optical-sizing: auto;
  /* .onboarding-inner h3 (the real form-section headings, e.g. "TELL US
     ABOUT YOURSELF") sets text-transform/letter-spacing for its own mono
     label style — same specificity as this rule, so both need to be reset
     explicitly here or they leak through onto this serif heading, which
     is exactly what happened before this line existed (all-caps, wide
     tracking on a serif display font — confirmed via screenshot). */
  text-transform: none;
  letter-spacing: normal;
  font-size: 1.1rem;
  font-weight: 500;
  color: var(--vertex);
  margin: 0;
  flex: 0 1 auto;
  min-width: 0;
}
.intro-card p {
  color: var(--vertex-dim);
  font-size: 0.88rem;
  line-height: 1.55;
  margin: 0 0 1.1rem;
}
/* A cheap, real enter animation — not a transition on [hidden], which
   can't animate display:none. Set on whichever card just became active
   (see renderIntroCard), disabled outright under reduced motion rather
   than just shortened, same policy as every other motion in this app. */
.intro-card[data-animate="right"] {
  animation: introEnterRight 0.2s ease both;
}
.intro-card[data-animate="left"] {
  animation: introEnterLeft 0.2s ease both;
}
@keyframes introEnterRight {
  from {
    opacity: 0;
    transform: translateX(14px);
  }
}
@keyframes introEnterLeft {
  from {
    opacity: 0;
    transform: translateX(-14px);
  }
}
@media (prefers-reduced-motion: reduce) {
  .intro-card[data-animate] {
    animation: none;
  }
}

.intro-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: 0.5rem;
}
/* Same treatment as #onboarding-close — the one icon-only button this
   modal already establishes — not a custom fixed-size circular control.
   Only align-self and the smaller line-box need overriding; background,
   border, radius, color, cursor, and hover all come from the real global
   button/button:hover rules, unmodified. */
.intro-arrow {
  align-self: center; /* overrides .onboarding-inner button's flex-start */
  padding: 0.2rem 0.55rem;
  font-size: 1rem;
  line-height: 1;
}
/* Keep the slot (so the dots stay centered whether or not an arrow is
   showing) — visibility, not display:none, which [hidden] normally sets;
   an attribute selector this specific beats the UA stylesheet's plain
   [hidden] rule. */
.intro-arrow[hidden] {
  display: flex;
  visibility: hidden;
}
.intro-dots {
  display: flex;
  gap: 0.4rem;
}
.intro-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--grid-bright);
}
.intro-dot.active {
  background: var(--accent-text);
}

/* --- API keys modal — reuses .onboarding-inner/.onboarding-header/
   .onboarding-scroll/.onboarding-status (class selectors, not id-scoped,
   so they apply cleanly here too) for a consistent look with zero risk of
   touching the existing onboarding modal's own rules. --- */
#api-keys-modal {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--void);
}
#api-keys-modal[hidden] {
  display: none;
}

/* --- Members modal — same overlay treatment as #api-keys-modal above.
   Never had one: without it #members-modal was a plain static div, stacked
   in normal document flow after #graph (height:100% of #main), which
   pushed its whole contents below the fold — the modal "worked" (correct
   data, hidden attr toggled right) but was never actually on screen. --- */
#members-modal {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--void);
}
#members-modal[hidden] {
  display: none;
}

/* --- Account settings modal — same overlay treatment as the two above. --- */
#account-modal {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--void);
}
#account-modal[hidden] {
  display: none;
}
#new-api-key-box {
  border: 1px solid var(--track-public);
  border-radius: 6px;
  padding: 1rem;
  margin-top: 0.5rem;
}
#new-api-key-box .label {
  font-family: "IBM Plex Mono", monospace;
  font-size: 0.75rem;
  color: var(--vertex-dim);
  margin-bottom: 0.5rem;
}
.new-api-key-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
#new-api-key-value {
  flex: 1;
  background: var(--void-2);
  border: 1px solid var(--grid);
  border-radius: 3px;
  padding: 0.5rem 0.7rem;
  font-size: 0.82rem;
  word-break: break-all;
  user-select: all;
}

/* --- login gate --- */

#login-gate {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--void);
  z-index: 10;
}
#login-gate.hidden {
  display: none;
}

/* Neutral holding state while a handed-off token (from the marketing
   site's login/register) is being validated — NOT the login form. Same
   full-viewport overlay shape as #login-gate (a sibling in the same "which
   screen is showing" state machine), hidden by default; the inline script
   at the top of <body> reveals it instead of #login-gate the instant it
   sees a token worth trying, before the d3 script (a real network fetch)
   or anything else has had a chance to paint the wrong screen first. */
#boot-loading {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--void);
  z-index: 10;
}
#boot-loading.hidden {
  display: none;
}
#boot-loading span {
  font-family: "IBM Plex Mono", monospace;
  font-size: 0.85rem;
  letter-spacing: 0.08em;
  color: var(--vertex-dim);
}
#login-form {
  width: 20rem;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}
#login-form h1 {
  font-family: "IBM Plex Mono", monospace;
  font-size: 1rem;
  letter-spacing: 0.08em;
  margin-bottom: 0.5rem;
}
#login-form input {
  background: var(--void-2);
  border: 1px solid var(--grid);
  color: var(--vertex);
  font-family: "IBM Plex Mono", monospace;
  font-size: 0.85rem;
  padding: 0.5rem 0.7rem;
  border-radius: 3px;
}
#login-error {
  color: var(--track-private);
  font-family: "IBM Plex Mono", monospace;
  font-size: 0.78rem;
  min-height: 1em;
}

/* --- account menu (topbar) --- */
#account-menu {
  position: relative;
}
#account-menu-btn {
  background: transparent;
  border: none;
  padding: 0;
  color: var(--vertex-dim);
  font-family: "IBM Plex Mono", monospace;
  font-size: 0.78rem;
  cursor: pointer;
}
#account-menu-btn:hover {
  color: var(--vertex);
}
#account-menu-dropdown {
  position: absolute;
  top: calc(100% + 0.5rem);
  right: 0;
  display: flex;
  flex-direction: column;
  background: var(--void-2);
  border: 1px solid var(--grid);
  border-radius: 6px;
  padding: 0.35rem;
  gap: 0.15rem;
  min-width: 10rem;
  z-index: 3;
}
#account-menu-dropdown[hidden] {
  display: none;
}
#account-menu-dropdown button {
  background: transparent;
  border: none;
  text-align: left;
  padding: 0.45rem 0.6rem;
  border-radius: 3px;
}
#account-menu-dropdown button:hover {
  background: var(--grid);
  border-color: transparent;
}

/* --- account settings: delete-account section. Same red treatment as the
   fact panel's own "Delete permanently" button (shared.js's #delete-edge-btn
   inline style) — here as a class since three buttons in this flow need it. --- */
#account-danger-zone {
  margin-top: 1.5rem;
  border-top: 1px solid var(--grid);
  padding-top: 1rem;
}
#account-danger-zone h3 {
  color: var(--track-private);
}
#account-delete-blocked,
#account-delete-confirm {
  border: 1px solid var(--track-private);
  border-radius: 6px;
  padding: 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}
/* An id selector outranks the UA stylesheet's own [hidden]{display:none} —
   without this, toggling .hidden on these two sections (app.js) does
   nothing visually, since the unconditional display:flex above always
   wins. Same bug class as #members-modal's own fix earlier this project:
   see that rule's comment. */
#account-delete-blocked[hidden],
#account-delete-confirm[hidden] {
  display: none;
}
#account-blocked-list {
  margin: 0;
  padding-left: 1.2rem;
  font-size: 0.85rem;
}
#account-delete-actions {
  display: flex;
  gap: 0.5rem;
}
button.danger {
  color: var(--track-private);
  border-color: var(--track-private);
}
