:root {
  --bg: #0f1320;
  --panel: #171c2e;
  --text: #e7e9f2;
  --muted: #9aa1b8;
  --accent: #7aa2ff;
  --field-color: #f2b84b;
  --paper-color: #7aa2ff;
  --edge-field: #3a4160;
  --edge-cites: #ff8fb1;
  --edge-coupling: #9d7bff;
  --edge-semantic: #4fc38a;
  --edge-coauthor: #f2b84b;
  --edge-related: #49addf;
}

* {
  box-sizing: border-box;
}

/* The UA rule for [hidden] is just `display: none`, which any class-level
   `display` (.legend, .controls, .history, .intro are all flex) outranks on
   specificity. Without this, elements report hidden === true while staying
   fully visible on screen. */
[hidden] {
  display: none !important;
}

html, body {
  height: 100%;
  margin: 0;
}

body {
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  display: flex;
  flex-direction: column;
}

/* One compact row: title, search, then the status line beneath. */
.topbar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem 1rem;
  padding: 0.55rem 1rem 0;
}

.topbar h1 {
  margin: 0;
  font-size: 1.02rem;
  white-space: nowrap;
}

/* The title doubles as the way back to the start page. */
.home-link {
  padding: 0;
  border: none;
  background: none;
  color: var(--text);
  font: inherit;
  cursor: pointer;
  border-radius: 4px;
  /* Eases into the accent colour on hover rather than snapping to it. */
  transition: color 0.45s ease;
}

.home-link:hover {
  color: var(--accent);
  transition: color 0.25s ease;
}

.home-link:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

/* The account button, where Popular used to be. It opens the account window. */
.account {
  min-width: 0;
  margin-left: auto;
}

.account-button {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  max-width: 14rem;
  padding: 0.25rem 0.75rem 0.25rem 0.6rem;
  border: 1px solid #3d4878;
  border-radius: 999px;
  background: var(--panel);
  color: var(--text);
  font-size: 0.76rem;
  cursor: pointer;
}

.account-button svg {
  flex: none;
  color: var(--muted);
}

.account-button:hover {
  border-color: var(--accent);
  background: #1d233b;
}

.account-button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* A long name gives way before the caret does. */
.account-button-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Guests and unconfirmed accounts: Log in. The shape of Map it (a rectangle with
   8px corners) in the plain accent colour, with none of its effects, and
   smaller, so the search stays the main button in the header. */
.account-login {
  padding: 0.3rem 0.8rem;
  border: 1px solid var(--accent);
  border-radius: 8px;
  background: var(--accent);
  color: #0f1320;
  font-size: 0.76rem;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
}

.account-login:hover {
  background: #93b4ff;
}

.account-login:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* --- account window --------------------------------------------------------
   Shares the sign-in dialog's frame (.sign-in); these are its insides. */
.account-window-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

.account-window-close {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.8rem;
  height: 1.8rem;
  padding: 0;
  border: none;
  border-radius: 6px;
  background: none;
  color: var(--muted);
  cursor: pointer;
}

.account-window-close:hover {
  background: #1d233b;
  color: var(--text);
}

.account-window-body {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  margin-top: 0.7rem;
}

.account-window-name {
  color: var(--text);
  font-size: 0.86rem;
  font-weight: 600;
  overflow-wrap: anywhere;
}

.account-window-detail,
.account-window-status {
  color: var(--muted);
  font-size: 0.74rem;
  overflow-wrap: anywhere;
}

.account-window-status.is-confirmed {
  color: var(--edge-semantic);
}

.account-window-status.is-unconfirmed {
  color: var(--field-color);
}

.account-window-actions {
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
  margin-top: 0.7rem;
}

.account-window-actions button {
  padding: 0.5rem 0.8rem;
  border: 1px solid #3d4878;
  border-radius: 8px;
  background: transparent;
  color: var(--text);
  font-size: 0.84rem;
  cursor: pointer;
}

.account-window-actions button:hover {
  border-color: var(--accent);
  background: #1d233b;
}

.account-window-actions .is-primary {
  border-color: var(--accent);
  background: var(--accent);
  color: #0f1320;
  font-weight: 600;
}

.account-window-actions .is-primary:hover {
  background: #93b4ff;
}

/* "Email not confirmed", with Resend email right beside it. */
.account-window-status-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.2rem 0.6rem;
}

.account-window-small {
  padding: 0;
  border: none;
  background: none;
  color: var(--accent);
  font-size: 0.74rem;
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}

.account-window-small:hover {
  color: var(--text);
}

.account-window-small:disabled {
  color: var(--muted);
  text-decoration: none;
  cursor: default;
}

/* Delete account: small and red, below everything else. */
.account-window-danger {
  align-self: center;
  margin-top: 0.6rem;
  padding: 0;
  border: none;
  background: none;
  color: #ff8a8a;
  font-size: 0.74rem;
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}

.account-window-danger:hover {
  color: #ffb3b3;
}

/* At the top of the log-in dialog, for an email not confirmed yet. */
.sign-in-pending {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.2rem 0.6rem;
  margin: 0;
  color: var(--field-color);
  font-size: 0.78rem;
  overflow-wrap: anywhere;
}

.sign-in-actions .is-danger {
  border-color: #ff8a8a;
  background: #ff8a8a;
  color: #0f1320;
  font-weight: 600;
}

.search-form {
  display: flex;
  gap: 0.4rem;
  flex: 1;
  max-width: 560px;
  margin: 0;
  padding: 0;
  /* A flex item's automatic minimum is its min-content width, and here that is
     the input's minimum plus the whole of the nowrap "Map it" button. At 320px
     that floor came to 310px against 288px of room, so the row could not shrink
     and ran 22px off the right edge of the page. Zero lets the row give, and
     the input — which already carries min-width: 0 — is what absorbs it. */
  min-width: 0;
}

/* Holds the field and the spinner that sits inside it. The spinner is layered
   on top rather than added to the row, so starting a search never resizes the
   search box. */
.search-field {
  position: relative;
  display: flex;
  flex: 1;
  min-width: 0;
}

/* The search bar is the one thing a first-time visitor must find, so it is
   lifted off the header rather than blending into it. */
.search-form input {
  flex: 1;
  min-width: 0;
  /* Right padding is permanent, not added when the spinner appears: reserving
     the space up front means the text never jumps when a search starts. */
  padding: 0.5rem 2.1rem 0.5rem 0.8rem;
  border-radius: 8px;
  border: 1px solid #3d4878;
  background: #1a2036;
  color: var(--text);
  font-size: 0.88rem;
  box-shadow:
    inset 0 1px 2px rgba(0, 0, 0, 0.35),
    0 0 0 1px rgba(122, 162, 255, 0.08);
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.search-form input::placeholder {
  color: #7f88a8;
}

.search-form input:hover {
  border-color: #4b5896;
}

.search-form input:focus {
  outline: none;
  border-color: var(--accent);
  background: #1d2440;
  box-shadow:
    inset 0 1px 2px rgba(0, 0, 0, 0.3),
    0 0 0 3px rgba(122, 162, 255, 0.28);
}

/* A deeper shade of the page's own accent, so it belongs to the same family as
   the resize handles, chips and links rather than introducing a second hue.
   White on #3a5fb8 is 5.98:1, so the label is never the weak point. */
.search-form button {
  position: relative;
  overflow: hidden;
  padding: 0.5rem 1.25rem;
  border-radius: 8px;
  border: 1px solid #7aa2ff;
  background: linear-gradient(180deg, #3a5fb8, #2a4aa0);
  color: #ffffff;
  font-weight: 700;
  font-size: 0.88rem;
  letter-spacing: 0.015em;
  white-space: nowrap;
  cursor: pointer;
  box-shadow:
    0 0 0 1px rgba(122, 162, 255, 0.35),
    0 2px 12px rgba(74, 114, 214, 0.45),
    inset 0 1px 0 rgba(255, 255, 255, 0.22);
  transition: box-shadow 0.18s ease, transform 0.08s ease, filter 0.18s ease;
}

/* Diagonal stripes that sweep across on hover. Parked off to the left and
   translated in, rather than animated forever — a permanent animation in the
   header would pull the eye away from the map. */
.search-form button::after {
  content: '';
  position: absolute;
  inset: -60% -140%;
  background: repeating-linear-gradient(
    115deg,
    rgba(255, 255, 255, 0) 0 14px,
    rgba(255, 255, 255, 0.16) 14px 22px,
    rgba(255, 255, 255, 0) 22px 36px
  );
  transform: translateX(-70%);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.18s ease;
}

.search-form button:hover {
  filter: brightness(1.12);
  border-color: #b9d0ff;
  box-shadow:
    0 0 0 1px rgba(185, 208, 255, 0.65),
    0 4px 20px rgba(90, 132, 232, 0.65),
    inset 0 1px 0 rgba(255, 255, 255, 0.32);
}

.search-form button:hover::after {
  opacity: 1;
  animation: sheen 1.1s linear infinite;
}

@keyframes sheen {
  from { transform: translateX(-70%); }
  to { transform: translateX(0%); }
}

/* Respect a stated preference for less motion; the button still lights up. */
@media (prefers-reduced-motion: reduce) {
  .search-form button:hover::after {
    animation: none;
    transform: translateX(-35%);
  }
}

.search-form button:active {
  transform: translateY(1px);
}

.search-form button:focus-visible {
  outline: 2px solid #ffffff;
  outline-offset: 2px;
}

/* Takes the rest of the row on wide screens, wraps to its own line when tight. */
/* One line, always. Wrapping to a second pushes the map down and then pulls it
   back up a moment later, which is worse than a shortened title. app.js
   measures against this and shortens the middle of the sentence to fit; the
   ellipsis here is only a backstop for a line it could not shrink far enough. */
.status {
  flex: 1 1 100%;
  color: var(--muted);
  padding: 0;
  font-size: 0.74rem;
  line-height: 1.3;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.status.error {
  color: #ff8a8a;
}

/* Under the status line when a map was built without the AI screening. */
.screening-note {
  flex: 1 1 100%;
  margin: 0;
  color: var(--field-color);
  font-size: 0.74rem;
  line-height: 1.3;
}

.controls {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1rem;
  align-items: center;
  justify-content: center;
  /* Slim: this is a strip of controls above the map, and every row of it is
     height the map does not get. */
  margin: 0.2rem 1rem 0.25rem;
  padding: 0.28rem 0.8rem;
  background: var(--panel);
  border: 1px solid #2a3050;
  border-radius: 10px;
  font-size: 0.78rem;
  color: var(--muted);
}

.control {
  display: flex;
  align-items: center;
  gap: 0.45rem;
}

.control b {
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

.control select {
  background: #1d233b;
  color: var(--text);
  border: 1px solid #2a3050;
  border-radius: 6px;
  padding: 0.3rem 0.5rem;
  font-size: 0.78rem;
}

.control input[type='range'] {
  accent-color: var(--accent);
  width: 130px;
}

/* Label, then slider, then the two readouts. The tier name used to sit between
   the label and the slider, which read as a caption followed by an unrelated
   control; trailing it instead makes the slider visibly belong to "Show links".
   Every column is still a fixed width, so no amount of text change inside can
   move the slider under a dragging cursor. Widths are sized to the longest
   possible content: "moderate or stronger" and "888 of 888 links". */
.strength-control {
  display: grid;
  grid-template-columns: auto 130px 8.5rem;
  align-items: center;
  gap: 0.45rem;
}

.strength-prefix {
  white-space: nowrap;
  /* Pulled tight against the slider that follows it, so the two read as one
     control rather than a caption and something unrelated. */
  margin-right: -0.15rem;
}

.strength-value {
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  /* ...and the readout stands slightly clear of it on the other side. */
  margin-left: 0.2rem;
}


/* The six checkboxes, now stacked inside the dropdown rather than strung
   across the controls row. */
.edge-filters {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.dropdown {
  position: relative;
}

.dropdown-summary {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.3rem 0.6rem;
  border: 1px solid #2a3050;
  border-radius: 6px;
  background: #1d233b;
  cursor: pointer;
  white-space: nowrap;
  list-style: none;
}

/* Safari draws its own disclosure triangle through a pseudo-element that
   ::marker does not cover. */
.dropdown-summary::-webkit-details-marker {
  display: none;
}

.dropdown-summary::marker {
  content: '';
}

.dropdown-summary::after {
  content: '▾';
  font-size: 0.7em;
  color: var(--muted);
}

.dropdown[open] .dropdown-summary::after {
  content: '▴';
}

.dropdown-summary:hover {
  border-color: var(--accent);
}

.dropdown-summary b {
  color: var(--text);
  font-variant-numeric: tabular-nums;
  /* "shared author" is the widest state this can hold; fixing the width stops
     the summary resizing every time a box is ticked. */
  min-width: 3.5rem;
}

/* When something is filtered out the control has to look different closed, or
   a hidden connection type is invisible until you reopen the menu. */
.dropdown.is-filtered .dropdown-summary {
  border-color: var(--accent);
}

.dropdown.is-filtered .dropdown-summary b {
  color: var(--accent);
}

/* Floated over the map rather than laid out in the row, so opening the menu
   does not reflow the controls and push the map down. */
.dropdown-panel {
  position: absolute;
  z-index: 40;
  top: calc(100% + 0.35rem);
  left: 0;
  min-width: 11rem;
  padding: 0.6rem;
  border: 1px solid #2a3050;
  border-radius: 8px;
  background: var(--panel);
  box-shadow: 0 10px 28px rgba(4, 7, 18, 0.6);
}

/* Sits between the rows and the buttons: it explains what unticking does, and
   the reader meets it on the way to "Clear". */
.dropdown-note {
  margin: 0.55rem 0 0;
  padding-top: 0.5rem;
  border-top: 1px solid #2a3050;
  color: #868da8;
  font-size: 0.72rem;
  line-height: 1.45;
}

.dropdown-actions {
  display: flex;
  gap: 0.4rem;
  margin-top: 0.55rem;
  padding-top: 0.5rem;
  border-top: 1px solid #2a3050;
}

.dropdown-actions button {
  flex: 1;
  padding: 0.25rem 0.4rem;
  border: 1px solid #2a3050;
  border-radius: 5px;
  background: #1d233b;
  color: var(--text);
  font-size: 0.72rem;
  cursor: pointer;
}

.dropdown-actions button:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.edge-filters label {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  cursor: pointer;
}

.edge-filters input {
  accent-color: var(--accent);
  margin: 0;
}

.control-btn {
  padding: 0.3rem 0.7rem;
  border-radius: 6px;
  border: 1px solid #2a3050;
  background: #1d233b;
  color: var(--text);
  font-size: 0.75rem;
  cursor: pointer;
}

.control-btn:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.control-btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.control-btn:disabled:hover {
  border-color: #2a3050;
  color: var(--text);
}

/* The Layout group: three named layouts, then the two things you do to one.
   Under a single label, because five buttons in a row otherwise read as five
   unrelated controls. */
.layout-control {
  gap: 0.4rem;
}

/* The phone toolbar's settings fold. Absent everywhere else: on a window wide
   enough to show the controls in a row there is nothing to fold. */
.controls-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border-radius: 10px;
  border: 1px solid #333b5e;
  background: #1d233b;
  color: var(--muted);
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}

.controls-toggle:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* Lit while the fold is open, so the button says which state it is in rather
   than only what it does. */
.controls-toggle[aria-expanded="true"] {
  border-color: var(--accent);
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 18%, #1d233b);
}

@media (max-width: 640px) {
  .controls-toggle {
    display: inline-flex;
  }
}

/* A verb, not a place. Square, icon-only, and visually quieter than the mode
   segments beside it so the eye still lands on "which layout am I in?" first. */
.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border-radius: 6px;
  border: 1px solid #2a3050;
  background: #1d233b;
  color: var(--muted);
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}

.icon-btn:hover:not(:disabled) {
  border-color: var(--accent);
  color: var(--accent);
  background: #202944;
}

.icon-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.icon-btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* The three layouts, as one segmented control. Read as a single thing with a
   selected segment rather than as three independent buttons, which is what
   says "pick one of these" instead of "press any of these". */
.mode-switch {
  display: inline-flex;
  padding: 2px;
  border-radius: 8px;
  border: 1px solid #2a3050;
  background: #141a2c;
}

.mode-btn {
  padding: 0.32rem 0.7rem;
  border: none;
  border-radius: 6px;
  background: transparent;
  color: var(--muted);
  font-size: 0.78rem;
  font-family: inherit;
  cursor: pointer;
  /* Held at the width of the longest label so the highlight moving between
     segments never resizes the control and shoves its neighbours along. */
  min-width: 4.6rem;
  text-align: center;
  transition: background 0.15s ease, color 0.15s ease;
}

.mode-btn:hover:not(.is-on) {
  color: #cbd6f5;
  background: #1d2440;
}

.mode-btn.is-on {
  background: color-mix(in srgb, var(--accent) 24%, #1d233b);
  color: var(--accent);
  font-weight: 600;
}

.mode-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* --- add papers: the button, and the panel it opens --------------------- */
/* Bottom-right of the map. The detail panel takes the top-right corner, so
   the two never cover each other. */
.add-fab {
  position: absolute;
  right: 1rem;
  bottom: 1rem;
  /* Below .detail-panel (20) on purpose — see the note in index.html. */
  z-index: 15;
  width: 56px;
  height: 56px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid var(--accent);
  background: #1d233b;
  color: var(--accent);
  font-size: 2rem;
  line-height: 1;
  cursor: pointer;
  box-shadow: 0 6px 18px rgba(4, 7, 18, 0.55);
  transition: transform 0.15s ease, background 0.15s ease;
}

.add-fab:hover:not(:disabled) {
  background: color-mix(in srgb, var(--accent) 22%, #1d233b);
}

/* Turns the + into an × without swapping the glyph, so the button keeps its
   label for a screen reader and its shape for everyone else. */
.add-fab.is-open {
  transform: rotate(45deg);
}

.add-fab:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.add-fab:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.add-panel {
  position: absolute;
  right: 1rem;
  bottom: 5rem;
  z-index: 31;
  display: flex;
  flex-direction: column;
  width: 26rem;
  max-width: calc(100% - 2rem);
  max-height: calc(100% - 6rem);
  padding: 0.9rem;
  border: 1px solid #2a3050;
  border-radius: 10px;
  background: #1d233b;
  box-shadow: 0 14px 36px rgba(0, 0, 0, 0.45);
}

.add-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 0.6rem;
}

.add-head h3 {
  margin: 0;
  font-size: 0.9rem;
  color: var(--text);
}

/* "More of this field" — the add that needs nothing typed. Full width and
   given the accent tint, because it is the likelier of the two things a reader
   opens this panel wanting and should not look like a footnote to the search
   box under it. */
.add-more {
  width: 100%;
  padding: 0.45rem 0.6rem;
  border-radius: 8px;
  border: 1px solid #3d4878;
  background: linear-gradient(180deg, rgba(122, 162, 255, 0.16), rgba(122, 162, 255, 0.06));
  color: #cbd6f5;
  font: inherit;
  font-size: 0.78rem;
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}

.add-more:hover:not(:disabled) {
  border-color: var(--accent);
  color: #ffffff;
}

.add-more:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.add-more:disabled {
  opacity: 0.45;
  cursor: default;
}

/* Separates the two ways in without a rule across the panel. */
.add-or {
  margin: 0.5rem 0 0.35rem;
  font-size: 0.68rem;
  color: var(--muted);
  text-align: center;
}

.add-search input {
  width: 100%;
  background: #161b30;
  color: var(--text);
  border: 1px solid #2a3050;
  border-radius: 6px;
  padding: 0.45rem 0.6rem;
  font-size: 0.82rem;
}

.add-search input::placeholder {
  color: #7f88a8;
}

.add-search input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 2px rgba(122, 162, 255, 0.18);
}

/* No reserved height: empty, this sat as a blank band between the search box
   and the selection. It only takes space when it has something to say. */
.add-hint {
  margin: 0;
  font-size: 0.72rem;
  color: var(--muted);
}

.add-hint:not(:empty) {
  margin: 0.4rem 0 0;
}

.add-hint.is-error {
  color: #ff8a8a;
}

/* Results scroll; the panel does not, so the search field and the Add button
   stay put however many rows come back. */
.add-results {
  flex: 1;
  overflow-y: auto;
  margin: 0.4rem 0 0;
  padding: 0;
  list-style: none;
  scrollbar-width: thin;
  scrollbar-color: rgba(122, 162, 255, 0.45) transparent;
}

.add-results::-webkit-scrollbar {
  width: 8px;
}

.add-results::-webkit-scrollbar-thumb {
  background: rgba(122, 162, 255, 0.45);
  border-radius: 4px;
}

.add-results::-webkit-scrollbar-track {
  background: transparent;
}

.add-result label {
  display: flex;
  gap: 0.55rem;
  align-items: flex-start;
  padding: 0.45rem 0.35rem;
  border-radius: 6px;
  cursor: pointer;
}

.add-result label:hover {
  background: #232a45;
}

.add-result input {
  margin-top: 0.15rem;
  accent-color: var(--accent);
  flex: 0 0 auto;
}

.add-result-text {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  min-width: 0;
}

.add-result-text b {
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--text);
  line-height: 1.3;
}

.add-result-text small {
  font-size: 0.7rem;
  color: #868da8;
}

.add-result-text em {
  font-size: 0.68rem;
  font-style: normal;
  color: var(--accent);
}

.add-result.is-on-map label {
  cursor: default;
}

.add-result.is-on-map .add-result-text b {
  color: var(--muted);
}

.add-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  margin-top: 0.6rem;
  padding-top: 0.6rem;
  border-top: 1px solid #2a3050;
}

.add-count {
  font-size: 0.72rem;
  color: var(--muted);
}

/* The one button in the panel that commits anything, so it is the one thing in
   it that should look pressable rather than like another piece of chrome. */
#add-submit {
  padding: 0.4rem 1rem;
  font-size: 0.86rem;
  font-weight: 600;
  border-color: var(--accent);
  background: var(--accent);
  color: #0b1020;
}

#add-submit:hover:not(:disabled) {
  background: #9cb8ff;
  border-color: #9cb8ff;
  color: #0b1020;
}

#add-submit:disabled {
  background: #1d233b;
  border-color: #2a3050;
  color: var(--text);
}

/* --- picks, held across searches --------------------------------------- */
.add-picked {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  margin-top: 0.45rem;
  padding-bottom: 0.5rem;
  border-bottom: 1px solid #2a3050;
}

.add-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  max-width: 100%;
  padding: 0.18rem 0.3rem 0.18rem 0.45rem;
  border: 1px solid var(--accent);
  border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 16%, #1d233b);
  font-size: 0.7rem;
  color: var(--text);
}

.add-chip span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.add-chip-drop {
  flex: 0 0 auto;
  padding: 0 0.15rem;
  border: none;
  background: none;
  color: var(--muted);
  font-size: 0.85rem;
  line-height: 1;
  cursor: pointer;
}

.add-chip-drop:hover {
  color: var(--text);
}

/* --- counts beside each connection type --------------------------------- */
/* "44 of 47" rather than "(44)" — see syncEdgeFilterCounts. Pushed to the
   right and held on one line so the six of them stack into a column the eye
   reads down, where the overlap is obvious. */
.filter-count {
  margin-left: auto;
  padding-left: 0.6rem;
  color: #868da8;
  font-size: 0.7rem;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.edge-filters label {
  display: flex;
  align-items: center;
  gap: 0.35rem;
}

/* Says what the list below is, once. Quiet enough not to compete with the
   controls it introduces. */
.dropdown-lead {
  margin: 0 0 0.5rem;
  padding-bottom: 0.45rem;
  border-bottom: 1px solid #262d4a;
  font-size: 0.68rem;
  line-height: 1.35;
  color: #8d97bb;
}

/* A type this map produced none of is still listed — it is a real kind of
   link — but it should not read as an option worth unticking. */
.edge-filters label.is-empty {
  opacity: 0.5;
}

/* --- the intro illustration ---------------------------------------------- */
/* Sits in the margins either side of the intro text, filling space the copy was
   leaving empty rather than competing with it. */
.intro-art {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
}

.intro-cluster {
  position: absolute;
  width: 210px;
  height: 300px;
  opacity: 0.85;
}

/* A slow breathing halo on a couple of circles — life without motion that
   pulls the eye. Different offsets so they do not pulse in lockstep. */
.intro-glow {
  transform-box: fill-box;
  transform-origin: center;
  animation: intro-pulse 4.5s ease-in-out infinite;
}

.intro-cluster.is-right .intro-glow {
  animation-duration: 5.8s;
  animation-delay: -2s;
}

@keyframes intro-pulse {
  0%, 100% { opacity: 0.28; transform: scale(0.82); }
  50%      { opacity: 0.85; transform: scale(1.12); }
}

@media (prefers-reduced-motion: reduce) {
  .intro-glow { animation: none; opacity: 0.5; }
}

/* Staggered rather than level: one sits high and one low, so the pair reads as
   two corners of a map rather than as a frame around the text. */
.intro-cluster.is-left {
  left: 2.5%;
  top: 26%;
}

.intro-cluster.is-right {
  right: 2.5%;
  top: 44%;
}

/* Below this the intro text needs the full width, and decoration behind it
   would only make it harder to read. */
@media (max-width: 1100px) {
  .intro-art {
    display: none;
  }
}

/* --- the reading path on the start page ---------------------------------- */
/* The guide's sections in reading order, as the round badges the map draws,
   joined by arrows in the guide's accent blue. */
.reading-path {
  --path-badge: 2.35rem;
  --path-gap: 1.7rem;        /* between steps, where the arrow sits */
  --path-pad: 0.45rem;       /* inside the capsule around the paired step */
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
  /* Clear of the heading above and the paragraph below, so the figure reads
     as its own thing rather than as part of either. */
  margin: 1.9rem 0 1.7rem;
}

.path {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.6rem;
}

.path-route {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  gap: var(--path-gap);
  margin: 0;
  padding: 0;
  list-style: none;
}

.path-step {
  position: relative;
  display: flex;
  gap: 0.35rem;
}

/* Deep dive and Surrounding field: one step, held in one capsule. Pulled up by
   its own padding and border, so its badges sit level with everyone else's. */
.path-step.is-paired {
  margin-top: calc(-1 * var(--path-pad) - 1px);
  padding: var(--path-pad) 0.55rem;
  border: 1px solid rgba(122, 162, 255, 0.24);
  border-radius: 1.6rem;
  background: rgba(122, 162, 255, 0.06);
}

.path-station {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
  width: 5.3rem;
}

.path-badge {
  display: grid;
  place-items: center;
  width: var(--path-badge);
  height: var(--path-badge);
  border: 1.6px solid var(--role);
  border-radius: 50%;
  background: #141a30;
  color: var(--role);
  font-size: 1.05rem;
  font-weight: 700;
  line-height: 1;
  box-shadow: 0 0 18px -2px color-mix(in srgb, var(--role) 45%, transparent);
}

.path-name {
  font-size: 0.78rem;
  font-weight: 600;
  line-height: 1.25;
  color: #c3cbe6;
  text-align: center;
}

/* In the gap after a step, level with the centre of its badges. */
.path-arrow {
  position: absolute;
  left: calc(100% + var(--path-gap) / 2);
  top: calc(var(--path-badge) / 2);
  width: 1.3rem;
  height: 0.55rem;
  overflow: visible;
  transform: translate(-50%, -50%);
}

.path-step.is-paired .path-arrow {
  top: calc(var(--path-pad) + var(--path-badge) / 2);
}


/* Too narrow for four steps in a row: the route runs downward instead, the
   way the guide layout runs, and the arrows turn to point down. */
@media (max-width: 640px) {
  .path-route {
    flex-direction: column;
    align-items: center;
  }

  .path-step.is-paired {
    margin-top: 0;
  }

  .path-arrow,
  .path-step.is-paired .path-arrow {
    left: 50%;
    top: calc(100% + var(--path-gap) / 2);
    transform: translate(-50%, -50%) rotate(90deg);
  }
}

/* --- the map-mode message ------------------------------------------------ */
.map-toast {
  position: absolute;
  z-index: 44;
  top: 1rem;
  left: 50%;
  transform: translateX(-50%);
  white-space: nowrap;
  max-width: calc(100% - 2rem);
  overflow: hidden;
  text-overflow: ellipsis;
  padding: 0.5rem 0.9rem;
  border: 1px solid #2a3050;
  border-radius: 999px;
  background: rgba(29, 35, 59, 0.94);
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.4);
  font-size: 0.78rem;
  color: var(--text);
  /* Announcements should not be clickable furniture sitting over the map. */
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.4s ease;
}

.map-toast.is-shown {
  opacity: 1;
}

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

/* --- the busy overlay ---------------------------------------------------- */
.busy {
  position: absolute;
  z-index: 45;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.7rem 1.1rem;
  border: 1px solid #2a3050;
  border-radius: 10px;
  background: rgba(29, 35, 59, 0.94);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.45);
  font-size: 0.82rem;
  color: var(--text);
}

.spinner {
  width: 16px;
  height: 16px;
  border: 2px solid #3a4160;
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}

@keyframes spin {
  to { transform: rotate(360deg); }
}

/* The same ring, inside the search field at its right edge — a search takes
   seconds, and until now the only sign it was running was a line of status
   text. Absolutely positioned, so it costs the row no width. */
.search-busy {
  position: absolute;
  /* Centred with a margin, not translateY: `spin` animates `transform`, so a
     centring transform here is overwritten every frame and the ring bobbed
     between centred and not as it turned. */
  top: 50%;
  margin-top: -9px;
  right: 0.6rem;
  width: 15px;
  height: 15px;
  border: 2px solid #3a4160;
  border-top-color: var(--accent);
  border-radius: 50%;
  pointer-events: none;
  animation: spin 0.8s linear infinite;
}

/* A spinner is decoration; for anyone who has asked not to see motion, the
   text alone still says what is happening. */
@media (prefers-reduced-motion: reduce) {
  .spinner,
  .search-busy {
    animation: none;
    border-top-color: #3a4160;
  }
}

/* Sits above "View paper" in the detail panel, as the other thing you can do
   with the paper you just opened. */
.expand-btn {
  display: block;
  width: 100%;
  margin-top: 0.6rem;
  padding: 0.4rem 0.6rem;
  border-radius: 6px;
  border: 1px dashed #3a4160;
  background: transparent;
  color: var(--text);
  font-size: 0.78rem;
  cursor: pointer;
}

.expand-btn:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.expand-btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}



.map-area {
  flex: 1;
  display: flex;
  gap: 0.75rem;
  margin: 0.4rem 1rem 1rem;
  min-height: 0;
}

/* Docked to the bottom edge, outside the flex column, so the map reclaims
   the space below whenever the badge itself is not being shown. */
.footer-dock {
  --footer-hint-h: 20px;
  position: fixed;
  left: 50%;
  bottom: 0;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  z-index: 20;
}

.footer-panel {
  display: flex;
  justify-content: center;
  background: var(--bg);
  /* The panel sits directly above the hint bar rather than flush with the
     viewport edge, so sliding it away by its own height alone still leaves
     the hint bar's height peeking into view — add that back in. */
  transform: translateY(calc(100% + var(--footer-hint-h)));
  transition: transform 0.25s ease;
}

.footer-dock.is-revealed .footer-panel {
  transform: translateY(0);
}

.footer-dock img {
  display: block;
  height: 28px;
}

/* The permanently-visible handle: hovering it (or the panel above it once
   revealed) keeps the badge up; leaving starts the 2s hide timer. */
.footer-hint {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: var(--footer-hint-h);
  font-size: 1.15rem;
  line-height: 1;
  opacity: 0.5;
  transition: transform 0.2s ease, opacity 0.15s ease;
}

.footer-dock:hover .footer-hint {
  opacity: 0.9;
}

.footer-dock.is-revealed .footer-hint {
  transform: rotate(180deg);
}

/* The map and its detail overlay. min-width:0 stops the canvas from refusing
   to shrink below its content width and squeezing the sidebar. */
.map-canvas {
  position: relative;
  flex: 1;
  min-width: 0;
}

/* The map reads as its own surface rather than another flat panel: a faint
   dot grid for depth, a soft pool of light behind the graph, and a vignette
   so nodes near the edge sit back. */
#network {
  width: 100%;
  height: 100%;
  border-radius: 12px;
  border: 1px solid #29314f;
  background-color: #10152a;
  background-image:
    radial-gradient(circle at 1px 1px, rgba(140, 158, 214, 0.10) 1px, transparent 0),
    radial-gradient(ellipse 70% 60% at 50% 42%, rgba(86, 110, 190, 0.20), transparent 70%),
    radial-gradient(ellipse 100% 90% at 50% 50%, transparent 45%, rgba(6, 9, 20, 0.75) 100%);
  background-size: 22px 22px, 100% 100%, 100% 100%;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.04),
    0 12px 34px rgba(0, 0, 0, 0.4);
}

/* Empty state, centred over the blank canvas until the first map is drawn. */
.intro {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 0.55rem;
  max-width: 640px;
  margin: 0 auto;
  padding: 1.5rem 2rem;
  text-align: center;
}

/* The product's name, once, at the size a landing page earns. Set in the
   grotesk end of the system stack rather than a downloaded face: a webfont
   here would be a network round trip before the first useful pixel. */
.intro .intro-wordmark {
  margin: 0 0 0.35rem;
  font-family: "Segoe UI Variable Display", "SF Pro Display", -apple-system,
    "Segoe UI", Inter, system-ui, sans-serif;
  font-size: clamp(2rem, 4.2vw, 3.1rem);
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1.05;
  background: linear-gradient(96deg, #eaf0ff 8%, var(--accent) 58%, #9d7bff 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  /* Browsers that cannot clip a gradient to text render transparent text on a
     transparent background, i.e. nothing at all. */
  -webkit-text-fill-color: transparent;
}

@supports not ((background-clip: text) or (-webkit-background-clip: text)) {
  .intro .intro-wordmark {
    background: none;
    color: var(--text);
    -webkit-text-fill-color: currentColor;
  }
}

.intro h2 {
  margin: 0 0 0.15rem;
  font-size: 1.25rem;
  font-weight: 600;
  letter-spacing: -0.005em;
  color: var(--muted);
}

.intro p {
  margin: 0;
  font-size: 0.88rem;
  line-height: 1.55;
  color: var(--muted);
}

.intro strong {
  color: #c3cbe6;
  font-weight: 600;
}

.intro-examples {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  align-items: center;
  justify-content: center;
  margin-top: 0.7rem;
  font-size: 0.8rem;
  color: var(--muted);
}

.intro-examples button {
  padding: 0.3rem 0.7rem;
  border-radius: 999px;
  border: 1px solid #2a3050;
  background: var(--panel);
  color: var(--accent);
  font-size: 0.8rem;
  cursor: pointer;
}

.intro-examples button:hover {
  border-color: var(--accent);
  background: #1d233b;
}

/* The start page's call to sign up: its own row under the introduction, and
   the largest button on the page. */
.intro .intro-account {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.45rem;
  margin-top: 0.9rem;
}

.intro-signup {
  padding: 0.75rem 2.2rem;
  border: 1px solid var(--accent);
  border-radius: 10px;
  background: var(--accent);
  color: #0f1320;
  font-size: 1.05rem;
  font-weight: 700;
  cursor: pointer;
  box-shadow: 0 6px 22px rgba(122, 162, 255, 0.35);
}

.intro-signup:hover {
  background: #93b4ff;
}

.intro-signup:focus-visible {
  outline: 2px solid #ffffff;
  outline-offset: 3px;
}

.intro .intro-account-note {
  margin: 0;
  font-size: 0.78rem;
}

/* For people who already have an account: small, under the big button. */
.intro .intro-account-login {
  margin: 0;
  font-size: 0.8rem;
}

.intro-account-login button {
  padding: 0;
  border: none;
  background: none;
  color: var(--accent);
  font: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}

.sign-in-privacy {
  margin: 0;
  font-size: 0.74rem;
  text-align: center;
}

.sign-in-privacy a,
.account-window-privacy {
  color: var(--muted);
}

.account-window-privacy {
  display: block;
  margin-top: 0.8rem;
  font-size: 0.74rem;
  text-align: center;
}

/* --- sign up / log in dialog ------------------------------------------- */
.sign-in {
  width: min(22rem, calc(100vw - 2rem));
  padding: 1.2rem 1.3rem 1rem;
  border: 1px solid #2a3050;
  border-radius: 12px;
  background: var(--panel);
  color: var(--text);
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.5);
}

.sign-in::backdrop {
  background: rgba(8, 10, 18, 0.6);
}

/* The × in the top-right corner of the sign-in and delete dialogs. A modal
   dialog is fixed to the screen, so it is what this is positioned against. */
.dialog-close {
  position: absolute;
  top: 0.75rem;
  right: 0.75rem;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.8rem;
  height: 1.8rem;
  padding: 0;
  border: none;
  border-radius: 6px;
  background: none;
  color: var(--muted);
  cursor: pointer;
}

.dialog-close:hover {
  background: #1d233b;
  color: var(--text);
}

.dialog-close:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 0;
}

.sign-in-form {
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
  margin: 0;
}

.sign-in h2 {
  margin: 0;
  font-size: 1.1rem;
}

.sign-in-lede,
.sign-in-switch {
  margin: 0;
  color: var(--muted);
  font-size: 0.8rem;
  line-height: 1.45;
}

.sign-in label {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  color: var(--muted);
  font-size: 0.78rem;
}

.sign-in input {
  padding: 0.5rem 0.7rem;
  border: 1px solid #3d4878;
  border-radius: 8px;
  background: #1a2036;
  color: var(--text);
  font-size: 0.88rem;
}

.sign-in input:focus {
  outline: none;
  border-color: var(--accent);
}

/* Same red as a failed search in the status line. */
.sign-in-error {
  margin: 0;
  color: #ff8a8a;
  font-size: 0.78rem;
  line-height: 1.4;
}

.sign-in-actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
}

.sign-in-actions button {
  padding: 0.4rem 0.9rem;
  border: 1px solid #3d4878;
  border-radius: 8px;
  background: transparent;
  color: var(--text);
  font-size: 0.82rem;
  cursor: pointer;
}

.sign-in-actions .sign-in-primary {
  border-color: var(--accent);
  background: var(--accent);
  color: #0f1320;
  font-weight: 600;
}

.sign-in-actions button:disabled {
  opacity: 0.6;
  cursor: progress;
}

.sign-in-switch button {
  padding: 0;
  border: none;
  background: none;
  color: var(--accent);
  font: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}

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

/* Holds the password and its eye button, layered inside the field the way the
   spinner sits inside the search box. */
.password-field {
  position: relative;
  display: flex;
}

/* Room on the right so typed characters never run under the eye. */
.password-field input {
  flex: 1;
  min-width: 0;
  padding-right: 2.5rem;
}

.password-toggle {
  position: absolute;
  top: 50%;
  right: 0.3rem;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.9rem;
  height: 1.9rem;
  padding: 0;
  border: none;
  border-radius: 6px;
  background: none;
  color: var(--muted);
  cursor: pointer;
  transform: translateY(-50%);
}

.password-toggle:hover {
  color: var(--text);
}

.password-toggle:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 0;
}

/* The eye is crossed out only while the password is showing. */
.password-toggle[aria-pressed="false"] .password-toggle-slash {
  display: none;
}

.sign-in-sent {
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
}

.sign-in-lede strong {
  color: var(--text);
  font-weight: 600;
  overflow-wrap: anywhere;
}

.sign-in-provider {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  padding: 0.55rem 0.9rem;
  border: 1px solid #3d4878;
  border-radius: 8px;
  background: #1a2036;
  color: var(--text);
  font-size: 0.86rem;
  font-weight: 600;
  cursor: pointer;
}

.sign-in-provider:hover {
  border-color: var(--accent);
}

.sign-in-provider:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.sign-in-provider:disabled {
  opacity: 0.6;
  cursor: progress;
}

/* A rule either side of the words: "or with email", between the two ways in. */
.sign-in-or {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin: 0;
  color: var(--muted);
  font-size: 0.74rem;
}

.sign-in-or::before,
.sign-in-or::after {
  content: "";
  flex: 1;
  height: 1px;
  background: #2a3050;
}

/* Search history rail, mirroring the legend on the other side. */
/* Matches PANEL_DEFAULTS.history in app.js, which is what actually sets the
   width once the script runs — this is the value the panel has for the frame
   before that, and the two disagreeing is a visible jump on load. */
.history {
  flex: 0 0 212px;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  min-height: 0;
  font-size: 0.78rem;
  color: var(--muted);
}

.history-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

.history h4 {
  margin: 0;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: #b9c0d8;
}

.history-clear {
  border: none;
  background: none;
  color: var(--muted);
  font-size: 0.72rem;
  cursor: pointer;
  padding: 0;
}

.history-clear:hover {
  color: var(--accent);
}

/* flex: 1 1 auto with min-height: 0 lets this take the space left over and
   then actually scroll. Without min-height a flex child refuses to shrink
   below its content, so the list would push the arrow setting out of view. */
.history-list {
  list-style: none;
  margin: 0;
  padding: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  gap: 0.3rem;
}

/* flex: 0 0 auto is the fix for rows being squashed instead of scrolled.
   As flex children they default to flex-shrink: 1, so a long history crushed
   every row — 47px down to 7px at 40 entries — cutting the titles in half
   rather than producing a scrollbar. */
.history-item {
  display: flex;
  flex: 0 0 auto;
  align-items: stretch;
  gap: 0.2rem;
  background: var(--panel);
  border: 1px solid #2a3050;
  border-radius: 8px;
  overflow: hidden;
}

.history-item.is-pinned {
  border-color: #4a5375;
}

.history-open {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  padding: 0.4rem 0.5rem;
  border: none;
  background: none;
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.history-open:hover {
  background: #1d233b;
}

.history-field {
  font-size: 0.78rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.history-meta {
  font-size: 0.68rem;
  color: var(--muted);
}

/* #7d86b5 clears 4.5:1 on the panel. The old #5a6284 was 2.83:1, under even
   the 3:1 WCAG asks of icons — and this one has to be findable to be used. */
.history-pin {
  flex: 0 0 auto;
  width: 26px;
  border: none;
  background: none;
  color: #7d86b5;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}

.history-pin:hover {
  color: var(--accent);
}

.history-del {
  flex: 0 0 auto;
  width: 24px;
  border: none;
  background: none;
  color: #7d86b5;
  font-size: 1.05rem;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}

.history-del:hover {
  color: #ff8f8f;
}

.history-empty {
  margin: 0.2rem 0 0;
  font-size: 0.72rem;
  color: #868da8;
}

/* A setting that belongs to the map, parked under the saved maps. It must not
   shrink either, or a long history would squeeze it away instead of scrolling. */
.panel-setting {
  flex: 0 0 auto;
  margin-top: auto;
  padding-top: 0.7rem;
  border-top: 1px solid #2a3050;
}

.switch {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  cursor: pointer;
}

/* Two switches stacked in the panel footer need air between them. */
.panel-setting > .switch + .switch {
  margin-top: 0.55rem;
}

.switch input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.switch-track {
  flex: 0 0 auto;
  position: relative;
  width: 30px;
  height: 17px;
  border-radius: 999px;
  background: #2a3050;
  border: 1px solid #39406a;
  transition: background 0.15s ease, border-color 0.15s ease;
}

.switch-knob {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: #8d97bb;
  transition: transform 0.15s ease, background 0.15s ease;
}

.switch input:checked + .switch-track {
  background: rgba(122, 162, 255, 0.35);
  border-color: var(--accent);
}

.switch input:checked + .switch-track .switch-knob {
  transform: translateX(13px);
  background: var(--accent);
}

.switch input:focus-visible + .switch-track {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.switch-label {
  font-size: 0.74rem;
  color: var(--text);
  line-height: 1.25;
}

/* .switch-note is gone with the note it styled: the arrow switch names the
   relation it draws ("cited → citing") and no longer needs a line under it
   restating which way the heads point. It carried a min-height to stop the
   switch jumping as the text changed, which is the kind of fix a control needs
   only when it is explaining itself twice. */

/* Scrollbars in the same pale blue as the resize handles, so the panels read
   as one system rather than borrowing the OS chrome. */
.legend,
.history-list,
.detail-links,
.detail-abstract,
.detail-panel {
  scrollbar-width: thin;
  scrollbar-color: rgba(122, 162, 255, 0.45) transparent;
}

.legend::-webkit-scrollbar,
.history-list::-webkit-scrollbar,
.detail-links::-webkit-scrollbar,
.detail-abstract::-webkit-scrollbar,
.detail-panel::-webkit-scrollbar {
  width: 8px;
  height: 8px;
}

.legend::-webkit-scrollbar-track,
.history-list::-webkit-scrollbar-track,
.detail-links::-webkit-scrollbar-track,
.detail-abstract::-webkit-scrollbar-track,
.detail-panel::-webkit-scrollbar-track {
  background: transparent;
}

.legend::-webkit-scrollbar-thumb,
.history-list::-webkit-scrollbar-thumb,
.detail-links::-webkit-scrollbar-thumb,
.detail-abstract::-webkit-scrollbar-thumb,
.detail-panel::-webkit-scrollbar-thumb {
  background: rgba(122, 162, 255, 0.45);
  border-radius: 999px;
}

.legend::-webkit-scrollbar-thumb:hover,
.history-list::-webkit-scrollbar-thumb:hover,
.detail-links::-webkit-scrollbar-thumb:hover,
.detail-abstract::-webkit-scrollbar-thumb:hover,
.detail-panel::-webkit-scrollbar-thumb:hover {
  background: var(--accent);
}

.history-item.is-pinned .history-pin {
  color: var(--field-color);
}

.detail-panel {
  position: absolute;
  top: 1rem;
  right: 1rem;
  /* Above the add button (15) and the map furniture, so a paper you are
     reading is never hidden behind a control. */
  z-index: 30;
  width: 320px;
  min-width: 240px;
  min-height: 140px;
  max-height: calc(100% - 2rem);
  /* The panel itself is a fixed frame; .detail-body inside it scrolls. */
  display: flex;
  flex-direction: column;
  overflow: visible;
  background: #1d233b;
  border: 1px solid #2a3050;
  border-radius: 10px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.35);
}

/* The top bar: drag to move, and it holds the close button. */
.detail-grip {
  position: relative;
  flex: 0 0 auto;
  height: 1.7rem;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: move;
  border-radius: 10px 10px 0 0;
  border-bottom: 1px solid #2a3050;
}

.detail-grip:hover {
  background: rgba(122, 162, 255, 0.08);
}

.detail-grip-dots {
  width: 26px;
  height: 4px;
  border-radius: 2px;
  background: repeating-linear-gradient(
    90deg, #4b5378 0 3px, transparent 3px 6px);
}

.detail-grip .close-btn {
  top: 50%;
  right: 0.5rem;
  transform: translateY(-50%);
}

.detail-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: 0.9rem 1.1rem 1.1rem;
}

.detail-panel.is-dragging,
.detail-panel.is-resizing {
  user-select: none;
  box-shadow: 0 16px 40px rgba(0, 0, 0, 0.5);
}

/* Resize handles on the three non-top edges. Thin, invisible, and only the
   cursor gives them away — same as a window chrome. */
.detail-resize {
  position: absolute;
  z-index: 2;
}

.detail-resize-e { top: 0; right: -3px; width: 8px; height: 100%; cursor: ew-resize; }
.detail-resize-w { top: 0; left: -3px; width: 8px; height: 100%; cursor: ew-resize; }
.detail-resize-s { left: 0; bottom: -3px; height: 8px; width: 100%; cursor: ns-resize; }
.detail-resize-se { right: -4px; bottom: -4px; width: 14px; height: 14px; cursor: nwse-resize; }
.detail-resize-sw { left: -4px; bottom: -4px; width: 14px; height: 14px; cursor: nesw-resize; }


/* The title is the link to the paper. A separate "View paper" row underneath
   said the same thing twice and put the click somewhere other than the thing
   it describes. */
#detail-title {
  display: block;
  margin: 0 0.5rem 0.4rem 0;
  font-size: 1.05rem;
  font-weight: 700;
  line-height: 1.3;
  color: var(--text);
  text-decoration: none;
}

#detail-title::after {
  content: " ↗";
  color: var(--accent);
  font-size: 0.8em;
  white-space: nowrap;
}

#detail-title:hover {
  color: var(--accent);
  text-decoration: underline;
}

#detail-title:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}

/* Authors, collapsed to the first and the last until asked otherwise. Two
   lines at most: a consortium paper used to fill the panel with names before
   anything else could be read. */
.detail-authors {
  color: var(--muted);
  font-size: 0.82rem;
  line-height: 1.4;
  margin: 0 0 0.25rem;
}

.detail-authors .detail-author-list {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Expanded, the list is allowed to run — but not to push the abstract off the
   panel, so it scrolls instead. */
.detail-authors.is-expanded .detail-author-list {
  display: block;
  max-height: 7.5rem;
  overflow-y: auto;
}

.detail-authors-toggle {
  display: inline-block;
  margin-top: 0.15rem;
  padding: 0;
  border: none;
  background: none;
  color: var(--accent);
  font-size: 0.74rem;
  font-family: inherit;
  cursor: pointer;
  white-space: nowrap;
}

.detail-authors-toggle:hover {
  text-decoration: underline;
}

/* When and where. Its own line, so a long author list cannot push it off the
   end of a wrapped paragraph — it is one of the first things looked for. */
.detail-meta {
  color: var(--muted);
  font-size: 0.82rem;
  margin: 0 0 0.3rem;
}

/* The identifier, stated rather than hidden in the title's href. */
/* The identifier, with the copy button sitting immediately after it. */
.detail-doi {
  display: flex;
  align-items: flex-start;
  flex-wrap: wrap;
  gap: 0.3rem 0.4rem;
  margin: 0 0 0.6rem;
  font-size: 0.74rem;
  color: #868da8;
}

.detail-doi-text {
  flex: 0 1 auto;
  min-width: 0;
  word-break: break-all;
}

/* Icon only: the row is narrow, and the two-sheet glyph needs no caption. */
.detail-doi-copy {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  padding: 0;
  border: none;
  border-radius: 5px;
  background: none;
  color: var(--accent);
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}

.detail-doi-copy:hover {
  background: color-mix(in srgb, var(--accent) 18%, transparent);
}

.detail-doi-copy:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* "Copied." — said in words, beside the button that did it. */
.detail-doi-said {
  flex: 0 0 auto;
  align-self: center;
  font-style: normal;
  font-size: 0.7rem;
  white-space: nowrap;
  color: var(--accent);
}

/* What the paper is for, above the numbers. A tinted rule on the left rather
   than a filled card: the panel already sits on a panel, and a third surface
   inside it made the abstract below look like a footnote. */
.detail-role {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  margin: 0 0 0.7rem;
  padding: 0.5rem 0.6rem;
  border-left: 3px solid #3a4160;
  border-radius: 0 6px 6px 0;
  background: rgba(122, 162, 255, 0.06);
  font-size: 0.78rem;
  line-height: 1.45;
  color: #c3cbe6;
}

.detail-role-tag {
  align-self: flex-start;
  padding: 0.1rem 0.4rem;
  border: 1px solid;
  border-radius: 999px;
  font-size: 0.68rem;
  letter-spacing: 0.03em;
  text-transform: uppercase;
}

.detail-metrics {
  display: flex;
  gap: 0.5rem;
  list-style: none;
  margin: 0 0 0.75rem;
  padding: 0;
}

.detail-metrics li {
  flex: 1;
  background: #171c2e;
  border: 1px solid #2a3050;
  border-radius: 8px;
  padding: 0.4rem 0.2rem;
  text-align: center;
}

.detail-metrics b {
  display: block;
  font-size: 1rem;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

.detail-metrics span {
  font-size: 0.68rem;
  color: var(--muted);
}

.detail-abstract {
  font-size: 0.85rem;
  line-height: 1.45;
  color: #cfd3e6;
  max-height: 160px;
  overflow-y: auto;
}

.detail-subhead {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
  margin: 0.9rem 0 0.35rem;
}

.detail-links {
  list-style: none;
  margin: 0;
  padding: 0;
  max-height: 190px;
  overflow-y: auto;
  font-size: 0.78rem;
  line-height: 1.35;
}

.detail-links li {
  display: flex;
  gap: 0.45rem;
  align-items: baseline;
  padding: 0.3rem 0.25rem;
  border-radius: 6px;
  color: #cfd3e6;
}

.detail-links li.clickable:hover {
  background: #232a45;
  cursor: pointer;
}

.detail-links li.muted {
  color: var(--muted);
}

.detail-links em {
  color: var(--muted);
  font-style: normal;
}

.detail-links b {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.link-dot {
  flex: 0 0 auto;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  margin-top: 0.25rem;
}

.close-btn {
  position: absolute;
  top: 0.5rem;
  right: 0.6rem;
  background: none;
  border: none;
  color: var(--muted);
  font-size: 1.3rem;
  cursor: pointer;
  line-height: 1;
}

.close-btn:hover {
  color: var(--text);
}

/* Drag bar between a side panel and the map. */
.resizer {
  flex: 0 0 9px;
  align-self: stretch;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: col-resize;
  border-radius: 5px;
  touch-action: none;
  transition: background 0.12s ease;
}

/* The arrow is the whole handle. It points the way a click would fold the
   panel, and back the other way once collapsed — so the same mark says both
   "put this away" and "bring it back". The plain drag line that used to sit
   under it said neither, and two marks for one control read as clutter. */
.resizer::before {
  content: '';
  width: 0;
  height: 0;
  border-top: 6px solid transparent;
  border-bottom: 6px solid transparent;
  transition: border-color 0.12s ease;
}

.resizer[data-side="left"]::before {
  border-right: 7px solid #4b5378;
}

.resizer[data-side="right"]::before {
  border-left: 7px solid #4b5378;
}

.resizer[data-side="left"].is-collapsed::before {
  border-right: none;
  border-left: 7px solid #868da8;
}

.resizer[data-side="right"].is-collapsed::before {
  border-left: none;
  border-right: 7px solid #868da8;
}

/* Colour only. A `background` here would paint through the transparent
   top/bottom borders — background-clip is border-box by default — and the
   triangle squared off into a rectangle the moment you grabbed it. */
.resizer:hover::before,
.resizer:focus-visible::before,
.resizer.is-dragging::before {
  border-right-color: var(--accent);
  border-left-color: var(--accent);
}

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

/* Collapsed: the bar becomes the handle that brings the panel back. */
.resizer.is-collapsed {
  flex-basis: 14px;
  cursor: pointer;
  background: var(--panel);
  border: 1px solid #2a3050;
}


.legend.is-collapsed,
.history.is-collapsed {
  flex-basis: 0 !important;
  width: 0;
  overflow: hidden;
  border: none;
  padding: 0;
  opacity: 0;
  pointer-events: none;
}

/* Side rail, not a bottom strip — the map keeps its full height. */
.legend {
  flex: 0 0 236px;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  overflow-y: auto;
  overscroll-behavior: contain;
  margin: 0;
  font-size: 0.78rem;
  color: var(--muted);
}

/* --- the reading guide ------------------------------------------------- */
/* First card in the left rail, because "what do I read?" is the question the
   map exists to answer and every other key on this rail is in service of it. */
.guide-rail {
  flex: 0 0 auto;
  display: contents;
}

.guide-group {
  border-color: #37406b;
  background: linear-gradient(180deg, rgba(122, 162, 255, 0.07), transparent 60%), var(--panel);
}

/* The toolkit section, last in the guide.
   No rule and no tint: it was given a bronze bar above it to mark it as not a
   step of the path, and in a rail where every section is already separated by
   a hairline the extra weight read as a divider between two panels rather than
   as a note about one section. Its own glyph and colour on the heading say
   what it is; the map says the rest by setting it in a column of its own. */

/* Collapsed by default apart from the openers: five expanded sections is a
   rail nobody scrolls to the end of. */
.guide-section {
  border-top: 1px solid #262d4a;
  padding: 0.45rem 0 0.15rem;
}

.guide-section:first-of-type {
  border-top: none;
  padding-top: 0;
}

.guide-section > summary {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  cursor: pointer;
  list-style: none;
  padding: 0.15rem 0;
  font-size: 0.76rem;
  color: #cbd3ec;
}

.guide-section > summary::-webkit-details-marker {
  display: none;
}

.guide-section > summary:hover {
  color: #ffffff;
}

/* The same glyph the badge on the circle carries, in the same colour, so the
   two are recognisably one thing. */
.guide-mark {
  flex: 0 0 auto;
  width: 1.05rem;
  text-align: center;
  font-style: normal;
  font-size: 0.8rem;
  line-height: 1;
}

.guide-name {
  flex: 1;
  min-width: 0;
}

.guide-count {
  flex: 0 0 auto;
  padding: 0 0.32rem;
  border-radius: 999px;
  background: #2a3050;
  color: #9fb4e8;
  font-size: 0.64rem;
  font-variant-numeric: tabular-nums;
}

/* A second target inside the title bar, separate from the rest of it: this
   triangle shows the section's papers here, in the rail; the rest of the bar
   takes you to them on the map. A real button rather than the browser's own
   disclosure marker (suppressed above), so it can be clicked on its own
   without also firing the title bar's action. */
.guide-toggle {
  flex: 0 0 auto;
  width: 1.1rem;
  height: 1.1rem;
  margin: 0;
  padding: 0;
  border: none;
  background: none;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

/* Three borders, not four: a fourth (transparent) bottom border was giving
   this box twice the triangle's own height, so rotating it pivoted around a
   point below the shape and the triangle visibly swung on an arc instead of
   spinning in place. Left+right for the width, top alone for the point —
   the box is now exactly the triangle, so its centre is too. */
.guide-toggle::after {
  content: "";
  width: 0;
  height: 0;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 4px solid #8d97bb;
  transition: transform 0.12s ease;
}

.guide-toggle:hover::after {
  border-top-color: #ffffff;
}

.guide-toggle:focus-visible {
  outline: 2px solid var(--accent);
}

.guide-section[open] > summary .guide-toggle::after {
  transform: rotate(180deg);
}

.guide-blurb {
  margin: 0.2rem 0 0.45rem 1.45rem;
  font-size: 0.68rem;
  line-height: 1.42;
  color: #868da8;
}

.guide-branch {
  margin: 0.5rem 0 0.25rem 1.45rem;
  font-size: 0.66rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: #8d97bb;
}

.guide-papers {
  margin: 0 0 0.35rem;
  padding: 0 0 0 1.45rem;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0.18rem;
}

/* Citation first in bold, then as much of the title as fits. Two lines at
   most: the full title is one click away in the detail panel, and a rail of
   wrapped four-line titles stops being a list. */
.guide-paper {
  display: flex;
  flex-direction: column;
  gap: 0.05rem;
  padding: 0.28rem 0.4rem;
  border-radius: 6px;
  border-left: 2px solid transparent;
  cursor: pointer;
  line-height: 1.32;
}

.guide-paper:hover {
  background: #232a45;
  border-left-color: var(--accent);
}

.guide-paper b {
  font-size: 0.72rem;
  color: #d6deF6;
  color: #d6def6;
}

.guide-paper span {
  font-size: 0.68rem;
  color: #868da8;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* The "links branch X and Y" line under a bridge paper. Not clamped, and in
   the bridge colour so it reads as the point of the row. */
.guide-paper span.guide-note {
  margin-top: 0.15rem;
  color: #7fd8c6;
  -webkit-line-clamp: 3;
  line-clamp: 3;
}

/* The role key in the Papers card: the badge glyph in a ring, matching the
   one drawn on a circle's rim. */
.role-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border: 1px solid;
  border-radius: 50%;
  background: #141a30;
  font-style: normal;
  font-size: 0.6rem;
  line-height: 1;
}

.legend-group {
  flex: 0 0 auto;
  background: var(--panel);
  border: 1px solid #2a3050;
  border-radius: 10px;
  padding: 0.7rem 0.8rem 0.75rem;
}

.legend-group h4 {
  margin: 0 0 0.55rem;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: #b9c0d8;
}

/* The legend folds away — reference, not something you re-read each map. */
.legend-summary {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  cursor: pointer;
  list-style: none;
}

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

.legend-summary h4 {
  margin: 0;
}

.legend-summary::after {
  content: "";
  margin-left: auto;
  width: 0;
  height: 0;
  border: 4px solid transparent;
  border-top-color: #8d97bb;
  transform: translateY(1px);
  transition: transform 0.12s ease;
}

details.legend-group:not([open]) .legend-summary::after {
  transform: rotate(-90deg);
}

details.legend-group:not([open]) .legend-summary h4 {
  margin: 0;
}

details.legend-group[open] .legend-summary {
  margin-bottom: 0.55rem;
}

.legend-summary:hover h4 {
  color: #ffffff;
}

.legend-rows {
  display: flex;
  flex-direction: column;
  gap: 0.38rem;
}

/* "Filtered out" sits right under the six connection swatches with nothing
   between them, so with no subhead of its own it read as a seventh swatch —
   the opposite of what it is. The rule and the gap say "related to the row
   above, but not one of them" without adding a heading for a single row. */
.legend-rows-note {
  margin-top: 0.55rem;
  padding-top: 0.5rem;
  border-top: 1px solid #2a3050;
}

.legend-subhead {
  margin: 0.7rem 0 0.45rem !important;
  font-size: 0.66rem !important;
  color: #8d97bb !important;
}

/* What the swatches under it are a key TO. Without it a column of six colours
   reads as six kinds of line rather than as the colours one line can take. */
.legend-lead {
  margin: 0 0 0.5rem;
  font-size: 0.68rem;
  line-height: 1.4;
  color: #8d97bb;
}

.legend-item {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  line-height: 1.3;
}

/* Label takes the slack so the info icon stays pinned to the right edge. */
.legend-item > .legend-label {
  flex: 1;
  min-width: 0;
}

/* #868da8, not the darker #6d7392 it used to be: at 3.64:1 on the panel
   this smallest persistent text failed WCAG AA. This clears it at 5.14:1. */
.legend-note {
  margin: 0.6rem 0 0;
  font-size: 0.72rem;
  line-height: 1.4;
  color: #868da8;
  white-space: normal;
}

.legend .line {
  display: inline-block;
  width: 20px;
  height: 0;
  border-top: 2.5px solid;
  vertical-align: middle;
}

.legend .dot {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  vertical-align: middle;
}

/* The swatch for a paper you added is the ring itself, not a filled dot. */
.legend .dot.added-demo {
  width: 15px;
  height: 15px;
  background: transparent;
  border: 1.5px dashed #9fb0e6;
}

.legend .dot.size-demo {
  width: 15px;
  height: 15px;
  background: hsl(232, 65%, 56%);
  border: 2px solid hsl(232, 55%, 38%);
}

/* The age ramp, labelled at both ends so the direction is unambiguous. */
.legend .ramp-wrap {
  display: flex;
  /* A column, so the bar spans the rail and the two years sit beneath its
     ends. Side by side, the years cost the bar a third of the panel. */
  flex-direction: column;
  gap: 4px;
  /* Takes the row to itself, since it has neither a label nor a bubble beside
     it to share the width with. */
  flex: 1 1 100%;
}

.legend .ramp-ends {
  display: flex;
  justify-content: space-between;
}

.legend .ramp-wrap em {
  font-style: normal;
  font-size: 0.6rem;
  line-height: 1;
  color: #868da8;
}

/* Spans the panel, because this is the only legend row where the middle of the
   gradient carries meaning and a short bar reads as a third swatch instead of
   as a scale. The two end captions are the only other things on the row. */
.legend .ramp {
  display: block;
  width: 100%;
  height: 10px;
  border-radius: 5px;
}

.legend .diamond {
  display: inline-block;
  width: 11px;
  height: 11px;
  transform: rotate(45deg);
  background: hsl(226, 65%, 62%);
  border: 2px solid hsl(226, 55%, 42%);
  vertical-align: middle;
}

/* Info icons — hover or keyboard-focus opens the explanation. */
.info-wrap {
  position: relative;
  display: inline-flex;
}

.info {
  width: 14px;
  height: 14px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid #4a5375;
  background: transparent;
  color: #8b93b0;
  font-size: 9px;
  font-weight: 700;
  font-style: italic;
  font-family: Georgia, "Times New Roman", serif;
  line-height: 1;
  cursor: help;
}

.info:hover,
.info:focus-visible {
  background: var(--accent);
  border-color: var(--accent);
  color: #0f1320;
  outline: none;
}

/* position:fixed, with coordinates set in JS on hover. The rail scrolls, and a
   scroll container clips absolutely-positioned children on BOTH axes — setting
   overflow-x:visible does not help, since a scrolling y-axis forces x to
   compute as auto. Fixed positioning escapes the clip entirely. */
.tip {
  position: fixed;
  width: 250px;
  padding: 0.6rem 0.7rem;
  background: #10152a;
  border: 1px solid #38406a;
  border-radius: 8px;
  box-shadow: 0 10px 26px rgba(0, 0, 0, 0.5);
  color: #d2d7ea;
  font-size: 0.73rem;
  line-height: 1.45;
  /* pre-line, so a bullet list keeps its line breaks while long lines still
     wrap inside the bubble. */
  white-space: pre-line;
  text-align: left;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.12s ease;
  z-index: 20;
}


.info:hover + .tip,
.info:focus-visible + .tip {
  opacity: 1;
  visibility: visible;
}

/* Swatches keep their size when the label wraps.

   .ramp is deliberately NOT in this list. It was, and being the later rule it
   quietly cancelled the `flex: 1 1 auto` set on it above, pinning the year bar
   at its 72px min-width while the rule meant to stretch it looked correct. The
   other swatches are fixed-size marks that share a row with a label; the ramp
   is the one row with no label to share with, and its middle carries meaning,
   so it should span the panel. */
.legend .line,
.legend .dot,
.legend .diamond,
.info-wrap {
  flex: 0 0 auto;
}

/* --- narrow windows ------------------------------------------------------
   The panels float OVER the map instead of taking width from it.

   They used to stack: `.map-area` became a column, so the reading guide and
   the recent maps rail turned into full-width bands and the map went under
   them. It did not merely get small — the panels keep the flex-basis app.js
   writes inline, which in a column is a HEIGHT, so 236px of legend and 212px
   of history were reserved before the map got anything. The three of them
   plus the drag bars came to more than the viewport, the page grew a
   scrollbar, and the map sat below the fold where the reported symptom was
   "I just see the reading guide and legend".

   Overlaying instead of stacking fixes it at the root and costs nothing new:
   the detail panel and the add panel already float over `.map-canvas`, which
   is why it is `position: relative`. It also means the inline flex-basis
   stops mattering — an absolutely positioned box is out of flow, so flex
   sizing does not apply to it — rather than needing JS to know about widths.

   The map keeps the whole area at every size, which is the thing the window
   is for. */
@media (max-width: 900px) {
  /* Only once a map is on screen. On the landing page there is no map to
     protect and a floating panel would cover the intro text instead, so there
     the panels stay in the flow and the page scrolls — which is what a landing
     page is allowed to do and a map is not. app.js sets `has-map`. */
  body.has-map .legend,
  body.has-map .history {
    position: absolute;
    z-index: 20;
    top: 0;
    width: min(280px, 72vw);
    max-height: 100%;
    padding: 0.6rem;
    border-radius: 12px;
    border: 1px solid #29314f;
    background: rgba(16, 21, 42, 0.94);
    box-shadow: 0 14px 40px rgba(0, 0, 0, 0.55);
    overflow-y: auto;
  }

  body.has-map .legend { left: 0; }
  body.has-map .history { right: 0; }

  /* Collapsing has to out-specify the overlay above it. `.legend.is-collapsed`
     is (0,2,0) and `body.has-map .legend` is (0,2,1), so the floating width
     quietly won and clicking a bar did nothing — the panels stayed over the
     map with the handle insisting they were shut. */
  body.has-map .legend.is-collapsed,
  body.has-map .history.is-collapsed {
    width: 0;
    padding: 0;
    border: none;
    box-shadow: none;
    opacity: 0;
    pointer-events: none;
  }

  /* The bars float too, pinned to the inner edge of the panel they belong to.
     Dragging one wider is meaningless when the panel is already over the map,
     but the bar is ALSO the collapse handle — hiding it left two panels
     permanently covering two thirds of the map with no way to dismiss them,
     which is the same complaint in a new shape. Collapsed, `.is-collapsed`
     zeroes the panel and the bar stays put as the way to bring it back. */
  body.has-map .resizer {
    position: absolute;
    z-index: 21;
    top: 0;
    height: 100%;
    flex-basis: 9px;
  }

  body.has-map .resizer[data-side="left"] { left: min(280px, 72vw); }
  body.has-map .resizer[data-side="right"] { right: min(280px, 72vw); }

  body.has-map .resizer[data-side="left"].is-collapsed { left: 0; }
  body.has-map .resizer[data-side="right"].is-collapsed { right: 0; }

  /* Nothing is left in the flex row but the map. */
  .map-area {
    position: relative;
  }
}

/* --- phones --------------------------------------------------------------
   A second breakpoint, below the 900px one above, because the two sizes want
   different things. At 900 you have a tablet: the map is still wide and the
   only problem is that side panels were stealing width from it, which floating
   them fixed. At 640 and below you have a phone held in one hand, and the
   problem is height — the header and the control strip come to a fixed 324px
   whatever the screen, so the map got 56% of a tall phone, 45% in landscape
   and 37% of a 320x568 one. The map IS the product; it should not be the
   minority of the screen.

   Reusing the 900px breakpoint for this would have handed tablets a phone
   layout, which is why there are two. */
@media (max-width: 640px) {
  /* Side margins were 1rem a side, which on a 320px screen is a tenth of the
     width spent on nothing. */
  .topbar {
    gap: 0.35rem 0.6rem;
    padding: 0.5rem 0.7rem 0;
  }

  .map-area {
    margin: 0.35rem 0.6rem 0.6rem;
    gap: 0.5rem;
  }

  /* The search row gets a line to itself rather than sharing one with the
     title. Sharing means the title's 174px of nowrap comes out of the input. */
  .topbar h1 {
    font-size: 0.95rem;
  }

  .search-form {
    flex: 1 1 100%;
    max-width: none;
  }

  /* The account corner comes after the search form in the page, so on its own
     it would wrap onto a third row. Reordered to share the title's row, and
     the status line kept last. */
  .topbar .search-form {
    order: 1;
  }

  .topbar .status {
    order: 2;
  }

  .topbar .screening-note {
    order: 3;
  }

  /* --- the landing page scrolls -----------------------------------------
     `.intro` is `position: absolute; inset: 0` with `justify-content: center`,
     which is right on a desktop where the text is comfortably shorter than the
     canvas. On a phone it is taller, and a centred flex column that overflows
     spills EQUALLY IN BOTH DIRECTIONS — so the wordmark sat above the top edge
     of its own box, over the header, where no scrolling can reach it, and the
     example chips were cut off the bottom.

     So on a phone the intro stops being a centred overlay and becomes ordinary
     flow content that the document scrolls. Nothing is ever above the top, and
     reaching the bottom is a scroll rather than an impossibility. */
  body:not(.has-map) {
    height: auto;
    min-height: 100%;
  }

  body:not(.has-map) .map-area {
    flex: 0 0 auto;
  }

  body:not(.has-map) .intro {
    position: relative;
    inset: auto;
    justify-content: flex-start;
    max-width: none;
    /* Horizontal padding clears the drawer tabs, which are fixed to the screen
       edges and would otherwise sit on top of the text — measured at four line
       boxes running up to 10px under the Recent tab. 2.5rem is the 34px tab
       plus a margin. Bottom padding clears the fixed footer dock, so the last
       example chip is not underneath it. */
    padding: 1.1rem 2.5rem 3rem;
  }

  /* The canvas now takes its height from the intro inside it, so the dotted
     backdrop has to be told to fill that rather than a percentage of a box
     with no height of its own. */
  body:not(.has-map) #network {
    position: absolute;
    inset: 0;
  }

  /* Everything below applies only once a map is on screen. The landing page
     keeps its full header: there the chips and the title ARE the content, and
     the page is allowed to scroll. A map is not. */

  body.has-map .topbar {
    padding-top: 0.4rem;
  }

  /* The title stays — it is what makes the page recognizable, and it is the
     way back to the start — but at a map-sized weight rather than a
     landing-page one. */
  body.has-map .topbar h1 {
    font-size: 0.86rem;
  }

  body.has-map .status {
    font-size: 0.68rem;
  }

  .controls {
    margin: 0.2rem 0.6rem 0.25rem;
    padding: 0.3rem 0.5rem;
    gap: 0.4rem 0.7rem;
  }

  /* The strength row is a grid of `auto 130px 8.5rem` — three fixed tracks
     coming to ~340px, which is wider than a 320px phone has and cannot shrink,
     so it was the last 8px of horizontal scroll. Given the row to itself, the
     slider takes whatever is left over instead of demanding 130px. */
  .strength-control {
    flex: 1 1 100%;
    grid-template-columns: auto 1fr auto;
  }

  .strength-control input[type="range"] {
    width: 100%;
    min-width: 0;
  }

  /* The layout row came to 339px on a 320px screen — three segments held at
     min-width: 4.6rem each, plus a label and two icon buttons — and it was the
     last thing making the page scroll sideways.

     That min-width is there so the highlight moving between segments never
     resizes the control. Equal flex shares keep the same promise by a
     different route: the three are always the same width as each other, so
     nothing moves when the selection does. */
  .layout-control {
    flex: 1 1 100%;
  }

  .mode-switch {
    flex: 1;
    min-width: 0;
  }

  .mode-btn {
    flex: 1;
    min-width: 0;
    padding: 0.32rem 0.2rem;
  }

  /* --- the side panels become drawers ------------------------------------
     At 900px the panels float over the map, which stopped them stealing its
     width. On a phone that is still not enough: a 280px panel over a 343px
     canvas covers it, and the 9px bar that opens one reads as a scrollbar
     rather than a control — so the pair sat there squeezing the page with no
     obvious way to dismiss them. Worse, the history rail stays on the landing
     page (it holds your saved maps), so coming back from a map left a stray
     bar narrowing the intro until the text overflowed.

     So on a phone they stop being panels beside the map and become drawers:
     fixed to the screen edge, slid fully off it when shut, over the map rather
     than beside it, one at a time, with a scrim that dismisses them on a tap.
     Nothing they do changes the size of anything else, which is the whole
     point — the map keeps the full canvas whether a drawer is open or not.

     Both selectors are listed because the 900px block above scopes its own
     versions to `body.has-map`, and these have to win in both cases: a drawer
     is wanted on the landing page too, or there is no way back to a saved map
     from there. */
  .legend,
  .history,
  body.has-map .legend,
  body.has-map .history {
    position: fixed;
    top: 0;
    bottom: 0;
    height: auto;
    max-height: none;
    width: min(320px, 86vw);
    z-index: 60;
    margin: 0;
    padding: 0.85rem 0.8rem;
    border: none;
    border-radius: 0;
    background: #141a2c;
    box-shadow: 0 0 44px rgba(0, 0, 0, 0.62);
    overflow-y: auto;
    /* A flick that reaches the end of the drawer stops there instead of
       scrolling the page behind it. */
    overscroll-behavior: contain;
    opacity: 1;
    transition: transform 0.24s ease;
  }

  .legend,
  body.has-map .legend {
    left: 0;
    right: auto;
    border-right: 1px solid #29314f;
  }

  .history,
  body.has-map .history {
    right: 0;
    left: auto;
    border-left: 1px solid #29314f;
  }

  /* Shut is pushed off its own edge, NOT collapsed to zero width. Animating
     width would reflow the panel's contents on every frame and reads as the
     panel being crushed; sliding it out of the way reads as putting it away,
     and is the motion the gesture implies. */
  .legend.is-collapsed,
  body.has-map .legend.is-collapsed,
  .history.is-collapsed,
  body.has-map .history.is-collapsed {
    width: min(320px, 86vw);
    padding: 0.85rem 0.8rem;
    opacity: 1;
    box-shadow: none;
    pointer-events: none;
  }

  .legend.is-collapsed,
  body.has-map .legend.is-collapsed {
    border: none;
    border-right: 1px solid #29314f;
    transform: translateX(-100%);
  }

  .history.is-collapsed,
  body.has-map .history.is-collapsed {
    border: none;
    border-left: 1px solid #29314f;
    transform: translateX(100%);
  }

  /* The bar becomes a proper tab: a thumb-sized button on the screen edge that
     rides out with the drawer it opens. It keeps the arrow it already had,
     which points the way a tap will move the panel and flips once shut — so
     the same mark still says both "put this away" and "bring it back". */
  .resizer,
  body.has-map .resizer {
    position: fixed;
    top: 50%;
    transform: translateY(-50%);
    height: 88px;
    width: 34px;
    flex: 0 0 auto;
    z-index: 62;
    cursor: pointer;
    background: rgba(26, 32, 54, 0.96);
    border: 1px solid #333b5e;
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.45);
    transition: left 0.24s ease, right 0.24s ease;
  }

  .resizer[data-side="left"],
  body.has-map .resizer[data-side="left"] {
    left: min(320px, 86vw);
    border-radius: 0 10px 10px 0;
    border-left: none;
  }

  .resizer[data-side="right"],
  body.has-map .resizer[data-side="right"] {
    right: min(320px, 86vw);
    border-radius: 10px 0 0 10px;
    border-right: none;
  }

  .resizer[data-side="left"].is-collapsed,
  body.has-map .resizer[data-side="left"].is-collapsed {
    left: 0;
  }

  .resizer[data-side="right"].is-collapsed,
  body.has-map .resizer[data-side="right"].is-collapsed {
    right: 0;
  }

  /* Bigger arrow to match the bigger tab. */
  .resizer::before {
    border-top-width: 8px;
    border-bottom-width: 8px;
  }

  .resizer[data-side="left"]::before,
  .resizer[data-side="right"].is-collapsed::before {
    border-right-width: 9px;
  }

  .resizer[data-side="right"]::before,
  .resizer[data-side="left"].is-collapsed::before {
    border-left-width: 9px;
  }

  /* --- the controls strip becomes a bottom toolbar -----------------------
     Four stacked rows of controls cost 138px at the top of a 812px phone —
     a sixth of the screen, permanently, for settings you touch once. And the
     layout switch, which is the most-used control on the page, sat at the far
     end of the hardest part of the screen to reach one-handed.

     So the strip docks to the bottom edge and keeps only the layout switch
     visible. Everything else folds behind the settings button and opens
     upward over the map, the way a phone app puts its verbs in thumb reach
     and its settings one tap behind them.

     No wrapper element was needed: the strip is already a wrapping flex row,
     so `order` puts the fold on the rows above and the bar on the row below,
     and `bottom: 0` means growing the fold extends the box upward. */
  body.has-map .controls {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 50;
    margin: 0;
    /* env() keeps the bar clear of the home indicator on a notched phone; the
       fallback is what every other device gets. */
    padding: 0.42rem 0.6rem calc(0.42rem + env(safe-area-inset-bottom, 0px));
    border: none;
    border-top: 1px solid #29314f;
    border-radius: 14px 14px 0 0;
    background: rgba(20, 26, 44, 0.97);
    box-shadow: 0 -8px 28px rgba(0, 0, 0, 0.45);
    justify-content: flex-start;
    gap: 0.5rem 0.6rem;
  }

  /* Folded away by default. */
  body.has-map .controls > .control:not(.layout-control) {
    display: none;
  }

  body.has-map.controls-open .controls > .control:not(.layout-control) {
    display: flex;
    flex: 1 1 100%;
    order: 1;
  }

  body.has-map .controls > .layout-control {
    order: 2;
    flex: 1 1 auto;
  }

  body.has-map .controls-toggle {
    order: 3;
  }

  /* The word "Layout" goes; the three named buttons beside it say it already,
     and the group keeps its aria-label for anyone who cannot see them. */
  body.has-map .layout-control > span {
    display: none;
  }

  /* The Connections menu opens upward.

     It is anchored below its summary (`top: 100%`), which is right in a strip
     across the top of the page and useless in one docked to the bottom: the
     panel ran from y=675 to y=1085 in a 734px viewport, so 351px of it — the
     whole list — was off the bottom of the screen.

     Always up rather than flipped on measurement: down here there is never
     room below, so a JS flip would compute the same answer every time.

     The cap is for a phone held sideways, where the panel is taller than the
     whole viewport and flipping alone would only move which end is lost. */
  body.has-map .dropdown-panel {
    top: auto;
    bottom: calc(100% + 0.35rem);
    max-height: 60vh;
    overflow-y: auto;
    overscroll-behavior: contain;
  }

  /* The map stops short of the docked bar rather than running under it.
     4.3rem is the closed bar measured: 44px of control (the finger-sized
     minimum below), 0.42rem of padding either side, and a few pixels of gap so
     the two edges are not flush. Only the closed bar is reserved for — the
     fold opens over the map on purpose, the same as a drawer. */
  body.has-map .map-area {
    margin-bottom: calc(4.3rem + env(safe-area-inset-bottom, 0px));
  }

  /* The attribution badge lives on the bottom edge and is revealed by hovering
     it, which is not a gesture a touch screen has. With a map up that edge now
     belongs to the toolbar; on the landing page the badge keeps it. */
  body.has-map .footer-dock {
    display: none;
  }

  /* --- the detail panel becomes a bottom sheet ---------------------------
     A 320px floating window inside a 356px canvas covered the whole map, and
     it carried a move bar and five resize handles — pointer idioms that on
     touch do nothing but swallow the scroll.

     Half height is the point: the sheet takes the bottom of the screen and
     leaves the top half of the map visible, so the circle you tapped is still
     there while you read about it. It sits over the toolbar rather than above
     it, because while you are reading a paper the layout switch is not what
     you want, and closing the sheet brings it straight back. */
  body.has-map .detail-panel {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    top: auto;
    z-index: 70;
    width: auto;
    min-width: 0;
    height: 52vh;
    max-height: 52vh;
    border-radius: 16px 16px 0 0;
    border: none;
    border-top: 1px solid #333b5e;
    box-shadow: 0 -12px 40px rgba(0, 0, 0, 0.6);
  }

  body.has-map .detail-body {
    padding-bottom: calc(0.8rem + env(safe-area-inset-bottom, 0px));
  }

  /* The five edge handles have nothing to grab them with. */
  body.has-map .detail-resize {
    display: none;
  }

  /* The bar stays — it is where the close button lives — but it stops
     advertising a drag, and grows a grabber line so the sheet reads as a
     sheet. */
  body.has-map .detail-grip {
    height: 2.1rem;
    cursor: default;
  }

  /* --- the add panel becomes a sheet too --------------------------------- */
  body.has-map .add-panel {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    top: auto;
    z-index: 70;
    width: auto;
    max-width: none;
    max-height: 72vh;
    overflow-y: auto;
    overscroll-behavior: contain;
    border-radius: 16px 16px 0 0;
    border: none;
    border-top: 1px solid #333b5e;
    box-shadow: 0 -12px 40px rgba(0, 0, 0, 0.6);
    padding-bottom: calc(0.9rem + env(safe-area-inset-bottom, 0px));
  }

  /* The + sits clear of the docked toolbar. */
  body.has-map .add-fab {
    bottom: 0.75rem;
    right: 0.75rem;
  }

  /* --- targets a finger can actually hit ---------------------------------
     41 controls measured under 44x44 on a phone, the worst being the six
     connection checkboxes at 13x13. A fingertip contact patch is around 8mm,
     roughly 45px, so those were being aimed at rather than pressed.

     44px is Apple's HIG figure and WCAG's Level AAA (2.5.5). Where reaching it
     would wreck a dense layout — the legend's little "i" markers, the title —
     the target is grown with padding and pulled back with a matching negative
     margin, so the hit area grows and the layout does not move. Those land at
     or above 24px, which is WCAG 2.2 Level AA (2.5.8). */
  .search-form input,
  .search-form button,
  body.has-map .mode-btn,
  body.has-map .icon-btn,
  body.has-map #size-metric,
  body.has-map .dropdown-summary,
  body.has-map .dropdown-actions button,
  .legend summary,
  .legend-summary,
  .history-clear,
  .history-pin,
  .history-del {
    min-height: 44px;
  }

  body.has-map .icon-btn,
  .history-clear,
  .history-pin,
  .history-del {
    min-width: 44px;
  }

  /* The slider's own box is 16px tall; a taller box keeps the same track and
     thumb but gives the drag somewhere to start. */
  body.has-map #threshold {
    height: 44px;
  }

  /* Clicking a label toggles its checkbox, so the row is the real target —
     make the row finger-sized and the box itself big enough to see. */
  .edge-filters label {
    min-height: 44px;
    align-items: center;
    gap: 0.55rem;
  }

  .edge-filters input[type="checkbox"] {
    width: 24px;
    height: 24px;
    flex: 0 0 auto;
  }

  /* Grown, not moved: the padding enlarges the hit box and the negative margin
     hands the space back to the layout, so the header does not get taller. */
  .home-link {
    display: inline-flex;
    align-items: center;
    padding: 0.72rem 0.45rem;
    margin: -0.72rem -0.45rem;
  }

  /* `.info` is a fixed 14px circle, and with border-box sizing padding sits
     inside that — it cannot be grown that way. So the hit area is a
     transparent overlay centred on it: the circle stays 14px, the target
     becomes 30px, and the legend rows do not move.

     30 rather than 44 on purpose. These sit about 30px apart down the legend,
     and a 44px overlay on each would have neighbours overlapping — one
     marker's target swallowing the next one's is worse than a smaller target.
     30 clears WCAG 2.2 AA (24px) with room and stays disjoint. */
  .legend .info {
    position: relative;
  }

  .legend .info::after {
    content: '';
    position: absolute;
    left: 50%;
    top: 50%;
    width: 30px;
    height: 30px;
    transform: translate(-50%, -50%);
  }

  .drawer-scrim {
    position: fixed;
    inset: 0;
    z-index: 55;
    background: rgba(6, 9, 20, 0.55);
    /* Fades with the drawer's own slide rather than snapping on. */
    animation: scrim-in 0.24s ease;
  }

  /* The drawer still opens and shuts; it simply arrives rather than travels.
     Same preference the search button's sheen and the intro glow respect. */
  @media (prefers-reduced-motion: reduce) {
    .legend,
    .history,
    body.has-map .legend,
    body.has-map .history,
    .resizer,
    body.has-map .resizer {
      transition: none;
    }

    .drawer-scrim {
      animation: none;
    }
  }

  /* With a drawer open the page behind it should not scroll away underneath.
     Only on the landing page does the body scroll at all, but the rule costs
     nothing where it does not apply. */
  body.drawer-open {
    overflow: hidden;
  }
}

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

/* The scrim exists only in the phone layout; if a window is resized up while a
   drawer is open, the element is still in the DOM and must not paint. */
@media (min-width: 641px) {
  .drawer-scrim {
    display: none;
  }
}

/* --- short screens -------------------------------------------------------
   A phone turned sideways is 812px wide, so no width query will ever catch it,
   and it is the size where the header hurts most: 95px of chrome over a 375px
   viewport left the map 171px, or 46%. The scarce dimension there is height,
   so this asks about height and says nothing about width.

   Deliberately not folded into the block above with a comma. That block makes
   rows full-width to stop them overflowing a narrow screen, which on a wide
   short one would stack the controls taller — the opposite of what is wanted
   here. The two blocks compact different things for different reasons. */
@media (max-height: 480px) {
  body.has-map .topbar {
    padding-top: 0.35rem;
    gap: 0.3rem 0.8rem;
  }

  body.has-map .topbar h1 {
    font-size: 0.88rem;
  }

  body.has-map .map-area {
    margin: 0.3rem 0.7rem 0.5rem;
  }

  body.has-map .controls {
    margin: 0.15rem 0.7rem 0.2rem;
    padding: 0.22rem 0.6rem;
    gap: 0.3rem 0.8rem;
  }

  /* Sideways, the strip is still along the top and the Connections menu still
     opens downward, which is right — there is room below and none above. It
     measures 339px of a 375px viewport, though, so it clears the bottom edge
     by 36px and a slightly shorter phone would clip it. Capped so such a
     device scrolls the list instead of losing the end of it. */
  body.has-map .dropdown-panel {
    max-height: 70vh;
    overflow-y: auto;
    overscroll-behavior: contain;
  }
}

/* vis-network hover tooltip for a connection on the map. */
div.vis-tooltip {
  background: #10152a !important;
  border: 1px solid #38406a !important;
  border-radius: 8px !important;
  box-shadow: 0 10px 26px rgba(0, 0, 0, 0.5) !important;
  color: #d2d7ea !important;
  font-family: inherit !important;
  font-size: 0.74rem !important;
  padding: 0.6rem 0.7rem !important;
  max-width: 290px !important;
  white-space: normal !important;
}

.edge-tip strong {
  display: block;
  margin-bottom: 0.3rem;
  font-size: 0.78rem;
}

.edge-tip-score {
  color: var(--muted);
  font-weight: 400;
}

.edge-tip p {
  margin: 0;
  line-height: 1.45;
}

/* What a line consists of, one signal per row: the name of the signal on the
   left and what it actually contains on the right. A two-column grid rather
   than a sentence, because these are read by scanning down for the one you
   care about, not left to right. */
.edge-tip-shared {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.1rem 0.5rem;
  margin: 0.4rem 0 0 !important;
  padding: 0.35rem 0 0;
  border-top: 1px solid #2a3050;
  list-style: none;
  font-variant-numeric: tabular-nums;
}

.edge-tip-shared li {
  display: contents;
}

.edge-tip-shared em {
  color: #868da8;
  font-style: normal;
  /* The keys are a column of labels, so they read as a column rather than as
     the start of each sentence. */
  text-align: right;
  white-space: nowrap;
}

.edge-tip-shared span {
  color: #c9d2ef;
}

/* The same rows inside the detail panel's connection list, where they sit
   under a paper title rather than alone in a bubble. */
.link-shared {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0 0.45rem;
  margin: 0.2rem 0 0;
  padding: 0;
  list-style: none;
  font-size: 0.72rem;
  line-height: 1.45;
  font-variant-numeric: tabular-nums;
}

.link-shared li {
  display: contents;
}

.link-shared em {
  /* #868da8, not the darker #6d7392: that one lands at 3.64:1 on the panel,
     inside the band where text is too faint to read but too solid to be
     decoration. The palette suite sweeps for exactly this. */
  color: #868da8;
  font-style: normal;
  text-align: right;
  white-space: nowrap;
}

.link-shared span {
  color: #99a1c0;
  min-width: 0;
  overflow-wrap: anywhere;
}
