/* ---------------------------------------------------------------------------
 * Thotlink website (signed-in) section.
 *
 * Loaded after the page chrome so it can widen the retro container. The palette
 * and surface treatment deliberately match /support: near-black gradient body,
 * cyan accent, 5%-white surfaces, 1px hairline borders, 7-10px radii.
 * ------------------------------------------------------------------------- */

/* The [hidden] attribute is a UA style with almost no specificity, so any
 * class here that sets a display value silently defeats it -- and a hidden
 * .modal-backdrop is position:fixed inset:0, which means it keeps covering the
 * viewport and swallowing every click on the page. This rule makes the
 * attribute win wherever it is used (modals, the Trof picker rows, the chart
 * legend on a single-series chart). */
[hidden] { display: none !important; }

:root {
  --app-accent: #0ff;
  --app-surface: rgba(255, 255, 255, 0.05);
  --app-surface-hover: rgba(255, 255, 255, 0.08);
  --app-border: rgba(255, 255, 255, 0.12);
  --app-border-hover: rgba(255, 255, 255, 0.22);
  --app-muted: rgba(255, 255, 255, 0.62);
  --app-faint: rgba(255, 255, 255, 0.38);
  --app-radius: 10px;
  --app-shadow: 0 10px 30px rgba(0, 0, 0, 0.35);
  /* One line of a row, and the padding around a row's contents. A note row is
     three of these lines tall and its picture is a square of that height, and
     a message row is built to the same measure so the two lists agree. It
     lives here rather than on .note-tree because the message rows are not
     inside one: they were reading an undefined variable, which makes the whole
     declaration invalid -- the avatar fell back to the picture's own size, and
     the three lines fell back to three different heights. */
  --row-line: 1.4rem;
  --row-pad: 12px;
}

/* The retro chrome centres content in a 600px column; the dashboard needs room. */
body .container { max-width: 980px; }

/* The caret is the one mark on the page that is purely yours, so it is the
   accent everywhere something can be typed -- not only in the note editor. */
input,
textarea,
select,
[contenteditable] { caret-color: var(--app-accent); }

/* And what you have selected reads in the accent too, on a wash of it rather
   than the browser's blue. */
::selection {
  background: rgba(0, 255, 255, 0.22);
  color: var(--app-accent);
}

.app-nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  padding: 10px 0 18px;
  margin-bottom: 22px;
  border-bottom: 1px solid var(--app-border);
}

/* No outline of their own: the raised background is what says "this is a
   thing you can press", and an added border only boxed it in. */
.app-nav a {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 7px 12px;
  border-radius: 7px;
  color: rgba(255, 255, 255, 0.86);
  text-decoration: none;
  font-size: 0.95rem;
  border: 0;
  /* Only the colour moves on hover. A background arriving under the pointer
     looks like the item has been chosen; the raised cell belongs to the page
     you are actually on, and it appears when you get there. */
  transition: color 0.25s ease;
}

.app-nav a:hover,
.app-nav a:focus-visible {
  background: none;
  color: var(--app-accent);
  text-decoration: none;
  transition: none;
}

.app-nav a[aria-current="page"] {
  background: var(--app-surface);
  color: #fff;
}

.app-nav a[aria-current="page"]:hover { color: var(--app-accent); transition: none; }

.app-nav .app-nav-spacer { flex: 1; }

.badge {
  display: inline-block;
  min-width: 20px;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--app-accent);
  color: #04141a;
  font-size: 0.74rem;
  font-weight: 700;
  line-height: 1.5;
  text-align: center;
}

.badge.quiet {
  background: rgba(255, 255, 255, 0.14);
  color: rgba(255, 255, 255, 0.8);
}

.page-title {
  font-size: 2.1rem;
  line-height: 1.2;
  margin-bottom: 0.25rem;
}

.page-subtitle {
  color: var(--app-muted);
  margin-bottom: 1.75rem;
}

/* ------------------------------------------------------------------ panels */

.panel {
  border: 1px solid var(--app-border);
  border-radius: var(--app-radius);
  background: linear-gradient(180deg, var(--app-surface), rgba(255, 255, 255, 0.02));
  padding: 20px;
  margin-bottom: 18px;
  box-shadow: var(--app-shadow);
}

.panel > h2, .panel > h3 { margin-top: 0; }

.panel-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 14px;
}

.panel-header h2, .panel-header h3 { margin: 0; }

.panel-note {
  color: var(--app-muted);
  font-size: 0.92rem;
}

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 16px;
}

/* Dashboard entry tiles. */
.tile {
  display: block;
  border: 1px solid var(--app-border);
  border-radius: var(--app-radius);
  background: linear-gradient(180deg, var(--app-surface), rgba(255, 255, 255, 0.02));
  padding: 18px;
  color: inherit;
  text-decoration: none;
  box-shadow: var(--app-shadow);
  transition: transform 120ms ease, border-color 120ms ease, background 120ms ease;
}

.tile:hover {
  transform: translateY(-2px);
  border-color: var(--app-border-hover);
  background: linear-gradient(180deg, var(--app-surface-hover), rgba(255, 255, 255, 0.02));
  text-decoration: none;
}

.tile .tile-title {
  display: block;
  font-size: 1.1rem;
  font-weight: 700;
  margin-bottom: 4px;
}

.tile .tile-description {
  display: block;
  color: var(--app-muted);
  font-size: 0.92rem;
  line-height: 1.45;
}

/* ------------------------------------------------------------------- lists */

.stack { list-style: none; margin: 0; padding: 0; }

.stack > li {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 0;
  border-top: 1px solid rgba(255, 255, 255, 0.07);
}

.stack > li:first-child { border-top: 0; }

/* A list of notes rather than a list of people. A note should look like a note
   wherever it is drawn, so these rows behave the way the rows on the notes
   screen do: no rule between them, a backlight under the one being pointed at,
   and a title that answers with colour instead of growing an underline.
   The divider was doing the work the backlight does, and doing it to every row
   at once. */
.stack.is-notes > li {
  padding: var(--row-pad);
  border-top: 0;
  border-radius: 12px;
  margin-bottom: 2px;
  transition: background 120ms ease;
}

.stack.is-notes > li:last-child { margin-bottom: 0; }
.stack.is-notes > li:hover { background: rgba(255, 255, 255, 0.05); }

.stack.is-notes .stack-title:hover,
.stack.is-notes .stack-title:focus { text-decoration: none; }

.stack .stack-main { flex: 1; min-width: 0; }

.stack .stack-title {
  display: block;
  font-weight: 600;
  overflow-wrap: anywhere;
}

/* One line, centred on itself. It was a block of inline pieces, so a 20px
   avatar sat on the text baseline and dragged its neighbours off centre --
   which is what made the owner's name and the timestamp beside it look a
   pixel or two adrift. */
.stack .stack-meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  color: var(--app-faint);
  font-size: 0.85rem;
  margin-top: 3px;
  line-height: 1.4;
}

.stack .stack-meta .avatar.mini { margin-right: 6px; }

.stack .stack-preview {
  display: block;
  color: var(--app-muted);
  font-size: 0.9rem;
  margin-top: 4px;
  overflow-wrap: anywhere;
}

.stack .stack-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}

.empty {
  color: var(--app-faint);
  font-style: italic;
  padding: 10px 0;
}

.unread { border-left: 3px solid var(--app-accent); padding-left: 12px; }

/* --------------------------------------------------------- forms + buttons */

/* The retro chrome styles bare <form> as a card; inline control groups opt out. */
form.bare {
  background: none;
  border: 0;
  box-shadow: none;
  padding: 0;
  border-radius: 0;
  display: inline;
  animation: none;
}

.field { display: grid; gap: 6px; }

.field label,
.field-label {
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--app-muted);
  letter-spacing: 0.01em;
}

.field .hint {
  font-size: 0.84rem;
  color: var(--app-faint);
}

input[type="search"],
select,
textarea {
  padding: 0.85rem;
  font-size: 1rem;
  font-family: inherit;
  border: 1px solid transparent;
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.04);
  color: #fff;
  outline: none;
  width: 100%;
}

input[type="search"]:focus,
select:focus,
textarea:focus {
  border-color: var(--app-accent);
}

select option { background: #14161a; color: #fff; }

.checkbox-row {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 4px 0;
}

.checkbox-row input[type="checkbox"] {
  width: 18px;
  height: 18px;
  margin-top: 2px;
  accent-color: var(--app-accent);
  flex-shrink: 0;
}

.checkbox-row .checkbox-copy { flex: 1; }

.checkbox-row .checkbox-copy small {
  display: block;
  color: var(--app-faint);
  margin-top: 2px;
}

.actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
  margin-top: 6px;
}

.button,
button.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 0.7rem 1.1rem;
  font-size: 0.95rem;
  font-weight: 700;
  font-family: inherit;
  border-radius: 10px;
  border: 1px solid transparent;
  background: linear-gradient(90deg, var(--app-accent), #0ff);
  color: #04141a;
  cursor: pointer;
  text-decoration: none;
  width: auto;
  transition: opacity 140ms ease, background 140ms ease, border-color 140ms ease;
}

.button:hover { opacity: 0.9; text-decoration: none; }

.button.secondary {
  background: var(--app-surface);
  border-color: var(--app-border);
  color: rgba(255, 255, 255, 0.9);
}

.button.secondary:hover {
  background: var(--app-surface-hover);
  border-color: var(--app-border-hover);
}

.button.danger { background: none; border-color: rgba(255, 90, 90, 0.5); color: #ff8f8f; }
.button.danger:hover { background: rgba(255, 90, 90, 0.12); }
.button.small { padding: 0.45rem 0.8rem; font-size: 0.85rem; }

/* ------------------------------------------------------- callouts + badges */

/* Same callout language as /support. */
.callout {
  padding: 14px 18px;
  border-left: 7px solid rgba(255, 255, 255, 0.9);
  background: var(--app-surface);
  border-radius: 0 7px 7px 0;
  margin-bottom: 18px;
}

.callout.good { border-left-color: var(--app-accent); }
.callout.bad { border-left-color: #ff6b6b; }

.pill {
  display: inline-block;
  padding: 2px 9px;
  border-radius: 999px;
  font-size: 0.76rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  border: 1px solid var(--app-border-hover);
  color: var(--app-muted);
}

.pill.privacy-public { border-color: rgba(0, 255, 255, 0.5); color: var(--app-accent); }
.pill.privacy-protected { border-color: rgba(255, 208, 112, 0.5); color: #ffd070; }
.pill.privacy-private { border-color: rgba(255, 255, 255, 0.2); color: var(--app-faint); }
.pill.committed { border-color: rgba(0, 255, 255, 0.45); color: var(--app-accent); }
.pill.draft { border-color: rgba(255, 255, 255, 0.2); color: var(--app-faint); }

/* ----------------------------------------------------------------- avatars */

.avatar {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  object-fit: cover;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid var(--app-border);
  flex-shrink: 0;
}

.avatar.large { width: 96px; height: 96px; }

.avatar-fallback {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  color: var(--app-muted);
  text-transform: uppercase;
}

/* -------------------------------------------------------------- note + msg */

.note-body {
  border: 1px solid var(--app-border);
  border-radius: 7px;
  background: rgba(0, 0, 0, 0.25);
  padding: 16px 18px;
  overflow-wrap: anywhere;
}

.note-body img { max-width: 100%; height: auto; }
.note-body table { border-collapse: collapse; width: 100%; }
.note-body th, .note-body td { border: 1px solid var(--app-border); padding: 6px 8px; }
.note-body blockquote {
  margin: 0 0 1rem;
  padding-left: 14px;
  border-left: 3px solid var(--app-border-hover);
  color: var(--app-muted);
}
.note-body pre {
  background: rgba(255, 255, 255, 0.06);
  padding: 12px;
  border-radius: 7px;
  overflow-x: auto;
}

.thread { display: grid; gap: 10px; margin-bottom: 18px; }

/* A bubble holds what somebody said and nothing else. The border went because
   the fill already says where it starts and stops, and two marks for one edge
   is one too many.

   The radius is half a single line's height -- 10px of padding above and below
   1.4rem of text -- so one line is a capsule with genuinely round ends and
   nothing beyond that is rounded further. 50px overshot it: at two lines the
   corners ate into the words. */
.bubble {
  max-width: 78%;
  padding: 10px 16px;
  border-radius: 21px;
  border: 0;
  background: var(--app-surface);
  line-height: 1.4rem;
  overflow-wrap: anywhere;
  white-space: pre-wrap;
}

.bubble.mine { background: rgba(0, 255, 255, 0.10); }

/* A message and whatever came with it, stacked. Beside each other they read as
   two messages. */
.bubble-stack {
  display: flex;
  flex-direction: column;
  /* Each child as wide as it needs to be, not as wide as its widest sibling.
     A column of flex items stretches by default, which made the share card
     underneath a long message exactly as wide as that message -- a card whose
     width was decided by a sentence it has nothing to do with. */
  align-items: flex-start;
  gap: 6px;
  min-width: 0;
  max-width: 78%;
}

.bubble-row.mine .bubble-stack { align-items: flex-end; }
.bubble-stack .bubble,
.bubble-stack .snapshot-card { max-width: 100%; }

/* When it was said, hugging the bubble on its outer side and only while the row
   is hovered. Every line carrying a timestamp made a conversation read as a
   log; this is a thing you go looking for.

   Aligned to the top of the row and nudged down to the middle of the first
   line, so a bubble four lines tall still has its time beside the line it
   starts with rather than floating at its centre. */
/* The control hugs the bubble and the time sits beyond it, on both sides of
   the thread. That is one reversal per side, and they are not the same
   reversal:

     theirs   [ bubble ]  ⋯  2m ago      row normal,       aside reversed
     mine       4m ago  ⋯  [ bubble ]    row row-reverse,  aside normal

   Written as DOM order `when, more` either way. The row's own reversal already
   mirrors the pair for your own messages, so the aside there is left alone;
   for theirs the row does nothing, so the aside does the mirroring itself.
   Reversing both would cancel out -- which is exactly the mistake made the
   first time, when adding row-reverse to `.mine .bubble-aside` reversed an
   already-reversed thing and landed back where it started. */
.bubble-aside {
  display: inline-flex;
  flex-direction: row-reverse;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
  padding-top: 10px;
  opacity: 0;
  transition: opacity 160ms ease;
}

.bubble-row.mine .bubble-aside { flex-direction: row; }

.bubble-row:hover .bubble-aside,
.bubble-aside:focus-within { opacity: 1; }

.bubble-when { color: var(--app-faint); font-size: 0.76rem; white-space: nowrap; }

.bubble-more {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: none;
  color: var(--app-faint);
  font: inherit;
  /* Bigger than the words beside it: it is a target as well as a mark, and at
     the caption's size it was neither comfortably. */
  font-size: 1.4em;
  line-height: 1;
  cursor: pointer;
}

.bubble-more:hover { background: rgba(255, 255, 255, 0.09); color: #fff; }

/* A share is not a sentence, so it is not drawn inside one. Same shape it has
   on the Shared with you panel: the note's picture, its name, and where it came
   from. */
.snapshot-card {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  max-width: 78%;
  padding: 10px 14px;
  border: 1px solid var(--app-border);
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.04);
  color: inherit;
  text-decoration: none;
  transition: background 120ms ease, border-color 120ms ease;
}

.snapshot-card:hover {
  background: rgba(255, 255, 255, 0.08);
  border-color: var(--app-accent);
  text-decoration: none;
  color: inherit;
}

.snapshot-card.mine { margin-left: auto; }

.snapshot-card-main { display: flex; flex-direction: column; min-width: 0; }
.snapshot-card-title { color: var(--app-accent); font-weight: 600; }
.snapshot-card-from { color: var(--app-muted); font-size: 0.86rem; }

/* The composer. A faint edge at rest, the accent while it has the cursor, and
   the same half-second fade back out that the note editor's surface uses -- so
   the two places you write in this app behave the same way. */
.composer { display: block; }

.composer textarea {
  width: 100%;
  border: 1px solid var(--app-border);
  border-radius: 12px;
  transition: border-color 0.5s ease;
}

.composer textarea:focus {
  border-color: var(--app-accent);
  outline: none;
  transition: none;
}

.composer-actions { display: flex; justify-content: flex-end; margin-top: 10px; }

/* Nothing to send is not a thing to be refused after pressing -- the button
   says so before you reach for it. */
.composer button[disabled] {
  opacity: 0.4;
  cursor: default;
}

/* A conversation row is an alert row: something that arrived while you were
   not looking. The face takes the place the notification glyph takes. */
.alert-face,
.alert-glyph {
  flex-shrink: 0;
  width: 44px;
  height: 44px;
  border-radius: 999px;
  object-fit: cover;
  background: rgba(255, 255, 255, 0.08);
}

.alert-glyph {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--app-accent);
  font-size: 1.2rem;
}

.alert-unread { color: var(--app-accent); }

@media (max-width: 640px) {
  .stack > li { flex-wrap: wrap; }
  .bubble { max-width: 92%; }
  .page-title { font-size: 1.7rem; }
}

/* ===========================================================================
 * Icons
 *
 * The signed-in section loads /styles/icons.css -- the project's own set,
 * compiled from public/styles/icons/*.scss -- and nothing else. Every
 * .icon-<name> rule, including the font binding, comes from there, so there
 * are no aliases to keep in step here any more.
 * ======================================================================== */

/* ------------------------------------------------------------- popup menus */

.menu { position: relative; display: inline-flex; }

.menu-trigger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 8px;
  background: none;
  color: var(--app-faint);
  font-size: 1rem;
  cursor: pointer;
  transition: background 140ms ease, color 140ms ease, border-color 140ms ease;
}

.menu-trigger:hover,
.menu:where(.is-open) .menu-trigger {
  background: rgba(255, 255, 255, 0.10);
  border-color: var(--app-border);
  color: #fff;
}

.menu-panel {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 40;
  min-width: 190px;
  padding: 6px;
  border: 1px solid var(--app-border-hover);
  border-radius: 10px;
  background: #14161b;
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.55);
}

.menu-panel form { all: unset; display: block; width: 100%; }

/* A menu item inside a <form> is still a menu item. `all: unset` on the form
   dropped its width, so the button inside it sized to its text and the
   highlight stopped where the words did -- which is why Sign Out lit up over
   half a row. */
.menu-panel form > .menu-item { width: 100%; display: block; box-sizing: border-box; }

.menu-item {
  display: block;
  width: 100%;
  padding: 9px 12px;
  border: 0;
  border-radius: 7px;
  background: none;
  color: rgba(255, 255, 255, 0.88);
  font: inherit;
  font-size: 0.92rem;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}

/* Both items in the account menu behave as one control each: the first is an
   <a>, and the chrome's link styling was tinting it cyan and fading it while
   the button below it did neither. Stated here for both, so they match. */
.menu-panel .menu-item,
.menu-panel a.menu-item:link,
.menu-panel a.menu-item:visited {
  color: rgba(255, 255, 255, 0.88);
  transition: none;
  /* And the same box, stated at the same specificity. An <a> in this panel was
     picking up the chrome's own link padding while the <button> beside it kept
     the menu's -- two pixels of difference, which is exactly enough to make one
     label sit lower than the ones above it. */
  padding: 9px 12px;
  box-sizing: border-box;
}

.menu-panel .menu-item:hover,
.menu-panel a.menu-item:hover,
.menu-panel .menu-item:focus-visible {
  background: rgba(255, 255, 255, 0.09);
  color: #fff;
  text-decoration: none;
  transition: none;
}

.menu-item:hover { background: rgba(255, 255, 255, 0.09); color: #fff; text-decoration: none; }
.menu-item.is-danger { color: #ff8f8f; }

/* Destructive, and it says so under the pointer: full red, white words. A
   14%-opacity wash was the same shade of "are you sure" as everything else in
   the menu. */
.menu-item.is-danger:hover,
.menu-panel .menu-item.is-danger:hover {
  background: #f00;
  color: #fff;
}

/* ----------------------------------------------------------------- modals */

body.modal-open { overflow: hidden; }

.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(0, 0, 0, 0.62);
  backdrop-filter: blur(3px);
}

.modal {
  width: min(440px, 100%);
  padding: 26px;
  border: 1px solid var(--app-border-hover);
  border-radius: 14px;
  background: linear-gradient(180deg, #1b1e24, #15171c);
  box-shadow: 0 30px 70px rgba(0, 0, 0, 0.6);
}

.modal h3 { margin: 0 0 10px; font-size: 1.25rem; }
.modal p { color: var(--app-muted); margin-bottom: 18px; }
.modal .actions { justify-content: flex-end; margin-top: 4px; }

.modal-figure {
  display: block;
  max-width: 100%;
  max-height: 60vh;
  margin: 0 auto 18px;
  border-radius: 12px;
}

/* ------------------------------------------------- notifications / alerts */

/* Each notification is its own rounded card. The unread marker is the card's
   own left edge, so the accent follows the rounded corner instead of cutting
   across a list divider. Read notifications drop the fill entirely. */
/* A list of rows, edge to edge inside its panel. The rows used to be separate
   cards with a gap between them, which is why the hover outline read as a box
   floating inside another box. The content stays indented; the row does not. */
.alert-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: block;
}

/* When the list owns the panel's corners -- its own page -- the first and last
   rows carry them, so a hover at either end follows the panel's shape instead
   of cutting across it. On the dashboard the panel header owns the top, so
   this is switched off. */
.alert-list.is-rounded > .alert:first-child {
  border-top-left-radius: var(--app-radius);
  border-top-right-radius: var(--app-radius);
}

.alert-list.is-rounded > .alert:last-child {
  border-bottom-left-radius: var(--app-radius);
  border-bottom-right-radius: var(--app-radius);
}

.alert {
  position: relative;
  /* A stacking context per row, so everything inside a row is contained by it
     and rows are ordered against each other rather than their children being
     ordered against each other's. Without it the menu panel -- z-index 40, but
     inside `.alert-menu`'s own z-index 2 -- lost to the *next* row's text,
     which is also z-index 2 and comes later in the document. The panel was
     drawn; the row below simply painted over it. */
  z-index: 0;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 16px 14px 20px;
  border: 0;
  background-color: transparent;
  cursor: pointer;
  transition: background-color 0.3s ease;
}

/* The row you are on comes forward, which is the only row whose menu can be
   open. Focus counts, so the menu is reachable from a keyboard. */
.alert:hover,
.alert:focus-within { z-index: 5; }

/* The whole row is the link. It is laid over the row rather than wrapped
   around it, because the menu has to sit above it and a button inside an
   anchor is neither valid nor clickable in the way anyone expects. */
.alert-hit {
  position: absolute;
  inset: 0;
  z-index: 1;
  border-radius: inherit;
}

/* The words sit above the link that covers the row, so a click on them used to
   land on the text and do nothing -- "click anywhere" worked everywhere except
   where anyone would aim. They let the click through; the menu does not. */
.alert-main,
.alert-menu { position: relative; z-index: 2; }

.alert-main { pointer-events: none; }
.alert-menu { pointer-events: auto; }

/* Unread is a lighter row. Read takes the wash away rather than changing the
   words, so scanning the list is a matter of brightness rather than of reading
   every line to see which is which.

   The brief said rgba(255,255,255,0.4); rendered, that is a light grey slab
   that swallows the panel and the text on it -- so this is the same idea at a
   strength that still reads as "a touch brighter". The number is here, on its
   own line, if it should be turned up. */
.alert.is-unread { background-color: rgba(255, 255, 255, 0.09); }

.alert:hover { background-color: rgba(255, 255, 255, 0.07); }
.alert.is-unread:hover { background-color: rgba(255, 255, 255, 0.14); }

/* Hover marks the row from the inside: a straight 2px against its left edge,
   drawn rather than reserved, so nothing in the row shifts by a pixel when it
   appears.

   Drawn as a gradient rather than as an inset shadow. An inset shadow takes
   the shape of the border box, so on the first and last rows -- the ones
   carrying the list's rounded corners -- it bent around the curve and the
   "line" became a crescent. A gradient stop is a straight edge at a fixed
   distance from one side, and it stays straight whatever the corners do.

   -90deg points the gradient line leftwards, so 100% is the left edge and the
   last 2px of it are the colour: transparent all the way across, then the bar.
   Same construction as the SCSS leftLine() this borrows from.

   background-image, not background, so the wash above keeps its own transition
   -- background-image does not interpolate, and folding the two together would
   have traded a fading row for a snapping one. */
.alert:hover,
.alert:focus-within {
  background-image: linear-gradient(-90deg, transparent calc(100% - 2px), var(--app-accent) 0);
}

.alert-main { flex: 1; min-width: 0; }

.alert-body {
  display: block;
  color: rgba(255, 255, 255, 0.92);
  text-decoration: none;
  overflow-wrap: anywhere;
}

.alert.is-read .alert-body { color: var(--app-muted); }
.alert.is-unread .alert-body { color: #fff; }

.alert-meta {
  display: block;
  margin-top: 3px;
  font-size: 0.83rem;
  color: var(--app-faint);
}

.alert.is-unread .alert-meta { color: rgba(255, 255, 255, 0.75); }

/* The menu only appears on hover or keyboard focus, so a quiet list stays quiet. */
.alert .menu { opacity: 0; transition: opacity 120ms ease; }
.alert:hover .menu,
.alert:focus-within .menu,
.alert .menu.is-open { opacity: 1; }

@media (hover: none) {
  .alert .menu { opacity: 1; }
}

/* A panel that holds nothing but a list has no padding of its own to give: the
   rows reach its edges and provide their own.

   No `overflow: hidden` here, however tempting. It was clipping the row's own
   menu, which opens downwards out of the last row and has to be allowed to
   leave. The corners it was there to keep are kept by the first and last rows
   instead -- see `.alert-list.is-rounded` above -- which is where a rounded
   corner belongs anyway: on the thing being rounded. */
.panel.is-listing { padding: 0; }
.panel.is-listing .empty { padding: 20px; }

/* ------------------------------------------------- privacy segmented control */

/* Three cards rather than a dropdown: the choice and its consequence are both
   on screen at once, and the selected option is legible without opening a menu. */
.choice-set { display: grid; gap: 10px; margin-bottom: 18px; }

@media (min-width: 720px) {
  .choice-set { grid-template-columns: repeat(3, 1fr); }
}

.choice {
  position: relative;
  display: block;
  padding: 16px 16px 16px 44px;
  border: 1px solid var(--app-border);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.03);
  cursor: pointer;
  transition: border-color 140ms ease, background 140ms ease, transform 120ms ease;
}

.choice:hover { border-color: var(--app-border-hover); background: rgba(255, 255, 255, 0.06); }

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

/* The radio itself is redrawn so it matches the rest of the chrome. */
.choice::before {
  content: "";
  position: absolute;
  top: 18px;
  left: 16px;
  width: 16px;
  height: 16px;
  border: 2px solid rgba(255, 255, 255, 0.28);
  border-radius: 50%;
  transition: border-color 140ms ease, box-shadow 140ms ease;
}

.choice:has(input:checked) {
  border-color: rgba(0, 255, 255, 0.55);
  background: rgba(0, 255, 255, 0.07);
}

.choice:has(input:checked)::before {
  border-color: var(--app-accent);
  box-shadow: inset 0 0 0 3px var(--app-accent);
}

.choice:has(input:focus-visible) { outline: 2px solid rgba(0, 216, 255, 0.55); outline-offset: 2px; }

.choice-title {
  display: block;
  font-weight: 700;
  font-size: 1rem;
  margin-bottom: 3px;
}

.choice-description {
  display: block;
  color: var(--app-muted);
  font-size: 0.88rem;
  line-height: 1.45;
}

.choice:has(input:checked) .choice-title { color: var(--app-accent); }

/* Fallback for engines without :has() -- JS-free, just less decorated. */
@supports not selector(:has(*)) {
  .choice input:checked ~ .choice-title { color: var(--app-accent); }
}

/* ------------------------------------------------------------- watcher list */

.watcher-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 0;
}

.watcher-row + .watcher-row { border-top: 1px solid rgba(255, 255, 255, 0.06); }

/* ------------------------------------------------------------------ charts */

.chart { margin: 0; }

.chart-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 14px;
}

/* The pill wears the series' colour, so it is the key as well as the switch --
   there is no separate line beside the name to read. */
.chart-legend-item {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 6px 12px;
  border: 1px solid transparent;
  border-radius: 999px;
  color: #fff;
  cursor: pointer;
  font-size: 0.88rem;
  transition: opacity 140ms ease;
}

.chart-legend-item.is-off { opacity: 0.42; }

/* The checkbox is the checkmark. The input is still there and still what the
   keyboard and a screen reader use -- it is simply not what is drawn. */
.chart-legend-check {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* The switch is a fixed box either way: a checkmark when the series is on, an
   empty white square when it is off. Hiding the mark instead would have let the
   pill change width as things were toggled, which made the whole row shuffle. */
.chart-legend-tick {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 0.95rem;
  height: 0.95rem;
  flex: 0 0 auto;
  font-size: 0.95rem;
  line-height: 1;
}

/* The empty-box glyph does the work the CSS border used to: same font, same
   width, so switching a series cannot resize its pill. */

.chart-legend-check:focus-visible + .chart-legend-tick {
  outline: 2px solid #fff;
  outline-offset: 2px;
  border-radius: 3px;
}

.chart-legend-name { color: #fff; }
.chart-legend-total { color: rgba(255, 255, 255, 0.75); font-variant-numeric: tabular-nums; }

/* ------------------------------------------------------- filtering by person

   A row above the legend, answering the other question: not which note, but
   whose. Drawn like the legend pills but without their colour -- a person is
   not a line on the chart, they are however many lines happen to be theirs. */
.chart-people {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 10px;
}

.chart-person {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 5px 12px 5px 10px;
  border: 1px solid var(--app-border-hover);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.05);
  color: #fff;
  font: inherit;
  font-size: 0.88rem;
  cursor: pointer;
  transition: background 120ms ease, border-color 120ms ease, opacity 120ms ease;
}

.chart-person:hover { background: rgba(255, 255, 255, 0.1); border-color: var(--app-accent); }
.chart-person.is-off { opacity: 0.42; }
.chart-person .chart-legend-tick { color: var(--app-accent); }
.chart-person.is-off .chart-legend-tick { color: rgba(255, 255, 255, 0.45); }

.chart-person-face {
  width: 22px;
  height: 22px;
  flex-shrink: 0;
  border-radius: 999px;
  object-fit: cover;
  background: rgba(0, 0, 0, 0.25);
}

.chart-person-name { color: #fff; }

/* Whose note the line is. A round 20px face, the same one the meta lines use,
   sitting between the name and the count -- the pill then reads left to right
   as: whether it is on, what it is, whose it is, how much of it there is. */
.chart-legend-face {
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  border-radius: 999px;
  object-fit: cover;
  background: rgba(0, 0, 0, 0.25);
}

.chart-canvas { position: relative; width: 100%; }
.chart-canvas svg { display: block; width: 100%; overflow: visible; }

.chart-empty { color: var(--app-faint); font-style: italic; padding: 40px 0; text-align: center; }

.chart-tooltip {
  position: absolute;
  z-index: 20;
  min-width: 150px;
  padding: 10px 12px;
  border: 1px solid var(--app-border-hover);
  border-radius: 9px;
  background: rgba(12, 14, 18, 0.96);
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.5);
  pointer-events: none;
  font-size: 0.86rem;
}

.chart-tooltip-heading {
  color: var(--app-faint);
  margin-bottom: 6px;
  font-size: 0.8rem;
}

.chart-tooltip-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 2px 0;
}

.chart-tooltip-key { width: 12px; height: 3px; border-radius: 2px; flex-shrink: 0; }

/* Value leads, name follows: the reader already knows which line they chased. */
.chart-tooltip-value {
  font-weight: 700;
  color: #fff;
  font-variant-numeric: tabular-nums;
  min-width: 1.5em;
}

.chart-tooltip-name { color: var(--app-muted); overflow-wrap: anywhere; }

/* Two captions on one line, pushed to opposite ends. What the chart is about
   reads first because it is the answer; the tally is bookkeeping and sits
   where a footnote sits. */
.chart-caption {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  margin-top: 12px;
  color: var(--app-faint);
  font-size: 0.86rem;
}

.chart-caption-lead { color: var(--app-muted); }
.chart-caption-tally { text-align: right; }

/* What has happened between you and a note, as opposed to what the note has
   been doing. Same row shape as the lists elsewhere, with the date pushed to
   the far side so a column of dates reads down the page. */
.history-list { list-style: none; margin: 0; padding: 0; }

.history-item {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  padding: 10px 12px;
  border-radius: 10px;
  transition: background 120ms ease;
}

.history-item:hover { background: rgba(255, 255, 255, 0.05); }
.history-text { color: var(--app-muted); }
.history-when { color: var(--app-faint); font-size: 0.85rem; white-space: nowrap; }

/* Something that does a thing rather than goes somewhere, but reads as a link
   because that is what a reader expects "Forgot your password?" to be.

   It has to say the colour itself. `.hyperlink` is written on a dozen anchors
   across these views and styles nothing -- the cyan on all of them comes from
   the layout's bare `a` rule, which a <button> does not match. Copied here
   rather than by widening that selector: this is the one control of its kind,
   and a rule that made every button on the site cyan to fix one of them would
   be a large answer to a small question. */
.link-button {
  padding: 0;
  border: 0;
  background: none;
  color: var(--app-accent);
  font: inherit;
  text-decoration: none;
  cursor: pointer;
}

.link-button:hover,
.link-button:focus-visible { text-decoration: underline; text-underline-offset: 5px; }

.link-button:disabled { opacity: 0.55; cursor: default; text-decoration: none; }

/* A name that goes somewhere but is not trying to be a hyperlink: the accent
   says it is live, and the only thing hovering changes is the cursor. */
.plain-link {
  color: var(--app-accent);
  text-decoration: none;
  cursor: pointer;
}

.plain-link:hover,
.plain-link:focus { color: var(--app-accent); text-decoration: none; }

/* ------------------------------------------------------------------- help */

/* Prose, so it reads as prose: a measure narrow enough to follow and lines far
   enough apart to come back to. */
.help-panel { margin-bottom: 18px; }
.help-panel p { max-width: 68ch; color: var(--app-muted); line-height: 1.65; }
.help-panel p:last-child { margin-bottom: 0; }
.help-panel strong { color: #fff; font-weight: 600; }
.help-panel em { color: rgba(255, 255, 255, 0.8); }

.help-list {
  max-width: 68ch;
  margin: 0 0 12px;
  padding-left: 20px;
  color: var(--app-muted);
  line-height: 1.7;
}

.help-list li { margin-bottom: 6px; }

.help-contents { margin-bottom: 18px; gap: 4px; flex-wrap: wrap; }

/* The facts under a note's name, spaced rather than punctuated -- the same
   line the second row of a note card draws. */
.note-byline { display: flex; align-items: center; flex-wrap: wrap; }

.chart-table { margin-top: 14px; }
.chart-table summary { cursor: pointer; color: var(--app-muted); font-size: 0.88rem; }
.chart-table summary:hover { color: #fff; }
.chart-table table { width: 100%; border-collapse: collapse; margin-top: 12px; font-size: 0.86rem; }
.chart-table th, .chart-table td {
  padding: 6px 8px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.07);
  text-align: left;
}
.chart-table td { font-variant-numeric: tabular-nums; color: var(--app-muted); }
.chart-table th { color: var(--app-faint); font-weight: 600; }

/* --------------------------------------------------- profile picture editor */

.avatar-panel { position: relative; }

.avatar-editor {
  display: flex;
  gap: 22px;
  align-items: center;
  flex-wrap: wrap;
}

.avatar-stage {
  position: relative;
  width: 160px;
  height: 160px;
  border-radius: 50%;
  overflow: hidden;
  flex-shrink: 0;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid var(--app-border);
  cursor: pointer;
}

.avatar-stage img,
.avatar-stage canvas {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.avatar-stage canvas { cursor: grab; touch-action: none; }
.avatar-stage canvas.is-grabbing { cursor: grabbing; }

.avatar-placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  border: 0;
  background: none;
  color: var(--app-faint);
  font-size: 2.4rem;
  cursor: pointer;
  transition: color 140ms ease, background 140ms ease;
}

.avatar-placeholder:hover { color: var(--app-accent); background: rgba(255, 255, 255, 0.04); }

/* Drop target feedback covers the whole panel, not just the circle. */
.avatar-panel.is-dropping { outline: 2px dashed rgba(0, 255, 255, 0.6); outline-offset: 6px; }
.avatar-panel.is-dropping .avatar-stage { border-color: var(--app-accent); }

.avatar-side { flex: 1; min-width: 220px; }

.avatar-hint {
  display: block;
  min-height: 1.2em;
  margin-bottom: 12px;
  color: var(--app-faint);
  font-size: 0.88rem;
}

.avatar-hint.is-problem { color: #ff8f8f; }

.avatar-panel .visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* ------------------------------------------------- note editor: Trof picker */

.commit-bar { position: relative; }

.commit-row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
}

.combo { position: relative; flex: 1; min-width: 220px; }

.combo input[type="text"] {
  width: 100%;
  padding: 0.7rem 0.9rem;
  font-size: 0.95rem;
  font-family: inherit;
  border: 1px solid var(--app-border-hover);
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.04);
  color: #fff;
  outline: none;
}

.combo input[type="text"]:focus { border-color: var(--app-accent); }

.combo-list {
  position: absolute;
  z-index: 45;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  max-height: 280px;
  overflow-y: auto;
  margin: 0;
  padding: 6px;
  list-style: none;
  border: 1px solid var(--app-border-hover);
  border-radius: 10px;
  background: #14161b;
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.55);
}

/* A scrollbar that stays out of the way of what it is scrolling.
 *
 * The default one draws a track: an opaque strip in a colour of its own, down
 * the right of a list whose whole point is a run of alternating rows. Thin,
 * with a transparent track, leaves the rows to be the thing being looked at
 * and the thumb to be the only mark on them.
 *
 * Said twice because two engines are listening. `scrollbar-width` and
 * `scrollbar-color` are the standard properties; the ::-webkit- rules are what
 * older WebKit reads, and a browser that understands both takes the standard
 * ones. The thumb is drawn inside a transparent border so it reads as a mark
 * floating over the list rather than a bar filling a channel. */
.combo-list {
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, 0.28) transparent;
}

.combo-list::-webkit-scrollbar { width: 10px; }
.combo-list::-webkit-scrollbar-track { background: transparent; }
.combo-list::-webkit-scrollbar-corner { background: transparent; }

.combo-list::-webkit-scrollbar-thumb {
  background-color: rgba(255, 255, 255, 0.28);
  background-clip: padding-box;
  border: 3px solid transparent;
  border-radius: 999px;
}

.combo-list::-webkit-scrollbar-thumb:hover { background-color: rgba(255, 255, 255, 0.45); }

.combo-list li { margin: 0; }

.combo-option {
  display: flex;
  align-items: baseline;
  gap: 8px;
  width: 100%;
  padding: 9px 12px;
  border: 0;
  border-radius: 7px;
  background: none;
  color: rgba(255, 255, 255, 0.88);
  font: inherit;
  font-size: 0.92rem;
  text-align: left;
  cursor: pointer;
}

.combo-option:hover { background: rgba(255, 255, 255, 0.09); color: #fff; }
.combo-option-prefix { color: var(--app-accent); font-weight: 700; font-size: 0.82rem; }
.combo-option-label { overflow-wrap: anywhere; }
.combo-empty { padding: 9px 12px; color: var(--app-faint); font-size: 0.9rem; font-style: italic; }

/* ------------------------------------------------------- TinyMCE chrome tint
 *
 * The oxide-dark skin ships a slate-blue toolbar and border. These pull it onto
 * the same dark cyan as the writing surface set in content_style, so the editor
 * reads as part of the page rather than a component dropped onto it.
 */
.tox.tox-tinymce {
  border-color: rgba(255, 255, 255, 0.12) !important;
  border-radius: 12px !important;
  overflow: hidden;
}

.tox .tox-editor-header,
.tox .tox-toolbar,
.tox .tox-toolbar__primary,
.tox .tox-toolbar-overlord,
.tox .tox-statusbar {
  background: #0e1f21 !important;
  border-color: rgba(255, 255, 255, 0.10) !important;
}

/* Button colour and background are settled further down, in one place. This
   block used to force the colour here with !important, which quietly beat the
   hover rule no matter what it said -- the icons never reached cyan. */
.tox .tox-tbtn svg { fill: currentColor !important; }
.tox .tox-statusbar a,
.tox .tox-statusbar__wordcount { color: rgba(255, 255, 255, 0.5) !important; }

/* Menus and dialogs the editor opens (block format, link, table). */
.tox .tox-menu,
.tox .tox-dialog { background: #122426 !important; border-color: rgba(255, 255, 255, 0.14) !important; }
/* One highlight for every menu in the app. The skin tinted its own with the
   accent, which made the block-format list the only menu here that lit up a
   different colour from the account menu and the row menus. */
.tox .tox-collection__item--active,
.tox .tox-collection__item:hover,
.tox .tox-collection__item:focus-visible {
  background: rgba(255, 255, 255, 0.09) !important;
  color: #fff !important;
}

/* The block-format list styles each row as a preview of what it does, inline,
   including a background on some of them. The preview is worth keeping for the
   size and weight of the type; the background is not, because it competes with
   the highlight that says where the pointer is. */
.tox .tox-collection__item-label > *,
.tox .tox-collection__item-label {
  background: none !important;
  background-color: transparent !important;
}

/* The format you are already in is marked with a checkmark. It used to also
   carry a filled background, which was the same fact said twice -- and the
   second saying looked like a hover that had got stuck. */
.tox .tox-collection__item--enabled:not(:hover):not(:focus-visible),
.tox .tox-collection__item[aria-checked="true"]:not(:hover):not(:focus-visible) {
  background: transparent !important;
}

/* Fallback when TinyMCE could not load: make the bare textarea usable. */
textarea.plain-editor {
  min-height: 420px;
  font-family: ui-monospace, "JetBrains Mono", monospace;
  font-size: 0.9rem;
  line-height: 1.6;
}

/* ------------------------------------------------------ form chrome inside panels
 *
 * The retro chrome styles every bare <form> as a card (background, radius,
 * padding, shadow). Inside a .panel that renders a card within a card, which
 * is what made the privacy options and the picture buttons look boxed-in.
 * Keep the grid layout the chrome provides; drop the decoration.
 */
.panel form:not(.bare) {
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  padding: 0;
  animation: none;
}

/* A disabled control has to look disabled. "Save picture" sits disabled until
 * an image is chosen, and full-strength cyan reads as "press me". */
button:disabled,
.button:disabled,
button[disabled],
.button[disabled] {
  opacity: 0.42;
  cursor: not-allowed;
  filter: grayscale(0.3);
}

button:disabled:hover,
.button:disabled:hover { opacity: 0.42; }

/* ===========================================================================
 * Revision: privacy in a modal, picture cropping in a modal, two-column
 * profile header, larger menu glyphs.
 * ======================================================================== */

/* The menu glyph at double size. */
.menu-trigger {
  width: 42px;
  height: 42px;
  font-size: 2rem;
}

/* A privacy pill that opens the privacy modal has to look pressable. */
.pill.is-action {
  cursor: pointer;
  border-style: solid;
  transition: background 140ms ease, border-color 140ms ease, color 140ms ease;
}

.pill.is-action:hover {
  background: rgba(255, 255, 255, 0.09);
  border-color: var(--app-border-hover);
}

/* Heavier veil behind a modal: the page should read as out of reach. */
.modal-backdrop {
  background: rgba(0, 0, 0, 0.68);
  backdrop-filter: blur(6px);
}

.modal.is-wide { width: min(720px, 100%); }

/* ------------------------------------------------------- picture crop modal */

.avatar-crop {
  position: relative;
  width: 320px;
  height: 320px;
  max-width: 100%;
  margin: 0 auto 14px;
  overflow: hidden;
  border-radius: 14px;
  background: #0a0b0d;
}

.avatar-crop canvas {
  display: block;
  width: 100%;
  height: 100%;
  touch-action: none;
}

/* Only draggable when the picture is not square -- a square one covers the
   circle exactly, so there is no slack to move. */
.avatar-crop canvas.is-draggable { cursor: move; }
.avatar-crop canvas.is-draggable.is-grabbing { cursor: grabbing; }

/* The ring is a circle whose enormous outer shadow darkens everything the crop
   will discard. The parent clips the shadow back to the square. */
.avatar-crop-ring {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, 0.4);
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.62);
  pointer-events: none;
}

.avatar-crop-hint {
  margin-bottom: 18px;
  color: var(--app-muted);
  font-size: 0.9rem;
  text-align: center;
}

/* --------------------------------------------------------- profile top row */

.profile-top { display: grid; gap: 18px; align-items: start; }

@media (min-width: 760px) {
  .profile-top { grid-template-columns: 1fr 1fr; }
}

.profile-top .panel { margin-bottom: 0; }

.avatar-panel .avatar-stage { margin-top: 4px; }

.account-email {
  display: block;
  margin-top: 6px;
  /* Asked for roughly 3x the caption size; clamped so a long address still
     fits a half-width panel instead of overflowing it. */
  font-size: clamp(1.35rem, 3.4vw, 2.3rem);
  line-height: 1.25;
  font-weight: 600;
  color: #fff;
  overflow-wrap: anywhere;
}

/* ------------------------------------------------------------ stacking fix
 *
 * The retro chrome puts backdrop-filter on every bare <form>, which creates a
 * stacking context. That trapped the Trof picker's dropdown inside the form, so
 * the footer -- later in the document -- painted over it. Dropping the filter on
 * this one form lets the dropdown's own z-index apply against the page.
 */
/* The retro chrome styles a bare <form> as a card. The editor is already a
   surface of its own, so the card around it was a box inside a box -- and its
   padding pushed everything in from the edge the writing lines up with. */
/* The retro chrome gives every <input> a display of its own, which un-hides
   hidden ones: they take no height but they still open a line box, and four of
   them were quietly adding two blank lines between the editor and the button
   that saves it. */
#note-form input[type="hidden"] { display: none !important; }

form#note-form {
  backdrop-filter: none;
  /* The chrome lays a form out as a flex column with a 1rem gap, which put a
     blank line above and below the caption no margin could take back. The
     editor page spaces itself. */
  display: block;
  gap: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
  animation: none;
}

.commit-bar { position: relative; z-index: 50; }
.combo-list { z-index: 60; }

/* ------------------------------------------------------ friend page heading */

.profile-heading {
  display: flex;
  align-items: center;
  gap: 18px;
  margin-bottom: 18px;
}

.profile-heading-main { flex: 1; min-width: 0; }
.profile-heading .menu { flex-shrink: 0; }

/* An option that would do nothing says so and then stays said: no highlight,
   no brightening, no pointer. Lighting up under the cursor is a promise. */
.menu-item.is-static { color: var(--app-faint); cursor: default; }

.menu-item.is-static:hover,
.menu-panel .menu-item.is-static:hover,
.menu-item.is-static:focus-visible {
  background: none;
  color: var(--app-faint);
}

/* The retro chrome lays forms out as a grid, which stretches a direct-child
 * submit button across the whole panel. Inside panels and modals a button
 * should be the size of its label. (The login form is left alone -- there the
 * full-width button is the intended look.) */
/* ...but not a menu item: those are rows in a menu that happens to be inside a
   panel, and this rule was quietly sizing them to their text -- which is why a
   menu item's highlight stopped where its words did. */
.panel form:not(.bare) > button:not(.menu-item),
.panel form:not(.bare) > .actions,
.modal form > button:not(.menu-item),
.modal form > .actions {
  justify-self: start;
  width: auto;
}

.modal form > .actions { justify-self: stretch; }

/* Same flattening inside modals: the dialog is already the card. */
.modal form {
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  padding: 0;
  backdrop-filter: none;
  animation: none;
}

/* With the picture panel now half width and carrying no buttons, the circle
   reads better centred than tucked against the left edge. */
.avatar-panel .avatar-stage { margin: 4px auto 0; }
.avatar-panel .avatar-hint { text-align: center; }

/* ===========================================================================
 * Revision 3
 * ======================================================================== */

/* Menu glyph dialled back from 2rem. */
.menu-trigger { width: 36px; height: 36px; font-size: 1.5rem; }

.account-email { font-size: clamp(1.35rem, 3.4vw, 1.5rem); }

/* Panel headers: centre the title against the glyph rather than sitting them
 * on a shared baseline, which the taller button threw out. */
.panel-header {
  align-items: center;
  min-height: 36px;
  margin-bottom: 14px;
}

.panel-header > .menu,
.panel-header > .button { flex-shrink: 0; }

/* Equal-height columns, and one gap value used between the two panels and
 * between the row and whatever follows it. */
.profile-top {
  align-items: stretch;
  gap: 18px;
  margin-bottom: 18px;
}

.profile-top > .panel { margin-bottom: 0; height: 100%; }

/* ------------------------------------------------------------- action pills
 *
 * A pill that opens a modal is a <button>, so the retro chrome was painting it
 * with the cyan submit gradient -- yellow "PROTECTED" text on a cyan slab.
 * Reset the button chrome entirely; the .privacy-* classes still supply the
 * colour, and hover only changes the cursor.
 */
button.pill {
  padding: 2px 9px;
  border: 1px solid var(--app-border-hover);
  border-radius: 999px;
  background: none;
  color: var(--app-muted);
  font-family: inherit;
  font-size: 0.76rem;
  font-weight: 700;
  line-height: 1.5;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  box-shadow: none;
  transition: none;
}

button.pill.is-action { cursor: pointer; }
button.pill.is-action:hover,
button.pill.is-action:focus { background: none; opacity: 1; }
button.pill.is-action:focus-visible { outline: 2px solid rgba(0, 216, 255, 0.55); outline-offset: 2px; }

/* --------------------------------------------------- disabled submit styling
 *
 * Forms that stay disabled until something changes need to read as inert
 * rather than merely dimmed-cyan.
 */
button:disabled,
.button:disabled,
button[disabled],
.button[disabled] {
  opacity: 0.5;
  cursor: not-allowed;
  background: rgba(255, 255, 255, 0.16);
  color: rgba(255, 255, 255, 0.75);
  filter: none;
}

button:disabled:hover,
.button:disabled:hover { opacity: 0.5; background: rgba(255, 255, 255, 0.16); }

/* A chart needs at least two points to say anything. */
.chart-insufficient {
  font-style: italic;
  color: var(--app-faint);
  padding: 18px 0;
}

/* ===========================================================================
 * Notes
 * ======================================================================== */

/* ------------------------------------------------------------- filter chips */

.filter-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 16px;
}

/* ---------------------------------------------- one shape for choosing a view

   Structured/Recent, Most recent/Least recent, You/Friends, Your friends/Search
   are all the same control: not a filter over one list but a choice of which
   list you are looking at. They were three different shapes doing it. This is
   the activity tabs' shape, applied to all of them -- and it works on a <span>
   or a <button> as well as an <a>, because whichever of the options is already
   in force has nowhere to go and should not be a link.

   Only the colour moves on hover. A background arriving under the pointer looks
   like the item has been chosen; the raised cell belongs to the choice you have
   actually made. */
.view-tabs {
  display: flex;
  align-items: center;
  gap: 4px;
  border-bottom: 0;
  padding-bottom: 0;
}

.view-tabs a,
.view-tabs span,
.view-tabs button {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 7px 12px;
  border: 0;
  border-radius: 7px;
  background: none;
  color: rgba(255, 255, 255, 0.86);
  font: inherit;
  font-size: 0.95rem;
  text-decoration: none;
  cursor: pointer;
  transition: color 0.25s ease;
}

.view-tabs a:hover,
.view-tabs button:hover,
.view-tabs a:focus-visible,
.view-tabs button:focus-visible {
  background: none;
  color: var(--app-accent);
  text-decoration: none;
  transition: none;
}

.view-tabs [aria-current="page"],
.view-tabs .is-current {
  background: var(--app-surface);
  color: #fff;
  cursor: default;
}

.view-tabs [aria-current="page"]:hover,
.view-tabs .is-current:hover { color: var(--app-accent); transition: none; }

/* The filter pills are the same idea with one difference worth keeping: a
   filter is a shape as well as a word, so the one in force stays a pill. The
   rest are plain -- white until the pointer arrives, then the accent, like
   every other choice on the page. */
.filter-bar .chip {
  border: 0;
  background: none;
  color: #fff;
  font-weight: 400;
  transition: color 0.25s ease;
}

.filter-bar a.chip:hover,
.filter-bar a.chip:focus-visible {
  background: none;
  color: var(--app-accent);
  transition: none;
}

.filter-bar .chip.is-on {
  background: var(--app-surface);
  color: #fff;
  font-weight: 400;
}

.filter-bar .chip.is-on:hover { color: var(--app-accent); }
.filter-bar .chip .chip-count { color: var(--app-faint); font-weight: 400; }
.filter-bar .chip.is-on .chip-count { color: var(--app-faint); }

/* A name at the size a name deserves when the row is about the person rather
   than about something they did. */
.row-title.is-large { font-size: 1.9em; line-height: 1.2; }

/* An aside that does not wait for the pointer -- where the button is the only
   thing on the row that does anything. */
.row-aside.is-always { opacity: 1; }

/* The sign-in page is a column: one form, read once, with nothing beside it.
   Narrower than the rest of the app on a desktop and exactly as wide as the
   rest of it on a phone, where 60% of a narrow screen is not a column, it is a
   crack. The 40px is air above the heading, which had none. */
.sign-in-column { margin-top: 40px; }

@media (min-width: 700px) {
  .sign-in-column {
    width: 60%;
    max-width: 60%;
    margin-left: auto;
    margin-right: auto;
  }
}

/* The search box and its button are one gesture, so they sit on one line. */
.search-row { display: flex; gap: 10px; align-items: stretch; }
.search-row input[type="search"] { flex: 1; min-width: 0; }

/* The box and the X inside it. The wrapper is what flexes; the input fills it
   and leaves room on the right for the button laid over it. */
.search-field { position: relative; display: flex; flex: 1; min-width: 0; }
.search-field input[type="search"] { padding-right: 38px; }

/* WebKit's own clear button is a grey pill that matches nothing else here, and
   there is no way to restyle it -- only to remove it and draw our own. */
.search-field input[type="search"]::-webkit-search-cancel-button,
.search-field input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; appearance: none; }

.search-clear {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--app-accent);
  font-size: 0.85rem;
  line-height: 1;
  cursor: pointer;
}

.search-clear[hidden] { display: none; }
.search-clear:hover { color: #7ff; }

/* Nothing to search for, so nothing to press. Said in the button rather than
   discovered by pressing it. */
.search-row .button:disabled {
  opacity: 0.45;
  cursor: default;
}

/* A list of answers rather than a list of things you have. Every other
   row-list is yours -- your friends, your messages -- and the accent, the
   weight and the backlight all say "this is one of yours, open it". A search
   result is not yet any of that: it is a person the site found, and the row
   should say only that. So the name stays white on both sides of a hover, and
   the row does not light up under the pointer. */
.row-list.is-plain > .row-item:hover { background: none; }

.row-list.is-plain .row-title,
.row-list.is-plain .row-title .plain-link,
.row-list.is-plain > .row-item:hover .row-title,
.row-list.is-plain .row-title .plain-link:hover,
.row-list.is-plain .row-title .plain-link:focus {
  color: #fff;
  font-weight: 400;
}

/* Air above the first result. The tabs, the note and the search row are all
   stacked tight above this, and the answers arriving hard against the box they
   were typed into read as part of the form rather than as its reply. */
.row-list.is-results { margin-top: 20px; }
.panel > .row-list.is-results:last-child { margin-top: 20px; }

/* The other answer a search can give. "Nobody found" sat hard against the box
   it was typed into, which read as part of the form rather than as its reply --
   the same distance the results get. */
.search-row + .empty { margin-top: 20px; }

/* A row about a person is wider inside than a row about a thing. The picture
   is a 66px circle against a 12px edge, which reads as a face pressed into the
   corner of the panel; the extra 12 gives it the same margin the words below
   it already have. */
.row-list.is-people > .row-item > .row-link { padding-left: 24px; padding-right: 24px; }

.chip {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 5px 13px;
  border: 1px solid var(--app-border-hover);
  border-radius: 999px;
  color: var(--app-muted);
  font-size: 0.86rem;
  text-decoration: none;
}

a.chip:hover {
  border-color: var(--app-accent);
  color: #fff;
  text-decoration: none;
}

/* The filter already in force is a statement, not a link. Rendered as a
   <span>, so it cannot be clicked, cannot be tabbed to, and does not offer a
   round trip to where you already are. */
.chip.is-on {
  border-color: transparent;
  color: var(--app-accent);
  font-weight: 700;
  background: rgba(255, 255, 255, 0.16);
  cursor: default;
}

.chip-count {
  font-size: 0.76rem;
  font-weight: 700;
  color: var(--app-faint);
}

.chip.is-on .chip-count { color: var(--app-accent); }

/* Views on the left, filters on the right. */
.list-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 16px;
}

.app-nav.bare-nav {
  border-bottom: 0;
  padding-bottom: 0;
  margin-bottom: 0;
}

.app-nav.bare-nav .is-current {
  padding: 6px 12px;
  border-radius: 8px;
  color: var(--app-accent);
  font-weight: 700;
  background: rgba(255, 255, 255, 0.16);
  cursor: default;
}

.list-controls .filter-bar { margin-bottom: 0; }

/* --------------------------------------------------------------- note rows */

/* The Structured view indents by sponsorship depth. The depth arrives as a
   custom property on the row rather than as a generated class, so the tree can
   be any depth without the stylesheet knowing how deep. Capped so a long chain
   never squeezes the text off the right edge.
 *
 * Written as ".stack > li.note-row" rather than ".note-row" on purpose: the
 * list rule ".stack > li" sets the padding shorthand, and one class plus one
 * element outranks a lone class -- so a bare .note-row selector lost, and every
 * row sat flush left however deep it was. */
.stack > li.note-row { padding-left: calc(min(var(--depth, 0), 6) * 22px); }

/* A row kept only because something beneath it matched the filter. It is
   context, not a result, and should read that way. */
.note-row.is-context { opacity: 0.55; }

.note-emblem {
  flex-shrink: 0;
  width: 44px;
  height: 44px;
  border-radius: 8px;
  object-fit: cover;
  border: 1px solid var(--app-border);
}

/* No picture yet: a quiet placeholder that keeps the row aligned and says what
   the slot is for, rather than a gap or a loud hatch pattern. */
.note-emblem.is-empty {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, 0.09);
  border-color: transparent;
  color: rgba(255, 255, 255, 0.24);
}

.note-emblem.is-empty [class^="icon-"] { font-size: 1.15rem; }

.note-id {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.8rem;
  color: var(--app-faint);
}

.pill.supreme {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  border-color: rgba(0, 255, 255, 0.5);
  color: var(--app-accent);
}

.pill.supreme [class^="icon-"] { font-size: 0.95rem; }
.pill.watched { border-color: rgba(255, 208, 112, 0.5); color: #ffd070; text-decoration: none; }
a.pill.watched:hover { background: rgba(255, 208, 112, 0.12); }

/* ---------------------------------------------------------- note references */

/* A reference inside a note body. Distinct from an outbound link, because it
   goes somewhere inside Thotlink rather than out of it. */
.note-body a.note-ref,
.mce-content-body a.note-ref {
  color: #7ef;
  text-decoration: none;
  border-bottom: 1px dotted currentColor;
}

/* The reference marker.
 *
 * The codepoint is written out rather than pulled from --icon-noteref: the
 * generated custom property holds the bare character, and `content` needs a
 * quoted string, so var() cannot be used here. The icon class cannot be put on
 * the anchor either -- [class^="icon-"] would set the icon font on the link's
 * own text. If the font is ever renumbered, this is the one place to follow.
 * icon-data.scss: noteref. */
.note-body a.note-ref::before {
  font-family: 'thotlink-icons';
  content: "\e951";
  margin-right: 4px;
  font-size: 0.9em;
  opacity: 0.8;
}

/* ------------------------------------------------------------ editor pieces */

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

.field-header label { margin-bottom: 0; }

.icon-button {
  display: inline-flex;
  align-items: center;
  gap: 7px;
}

.icon-button [class^="icon-"] { font-size: 1.1rem; line-height: 1; }

.note-search .combo { min-width: 0; }

/* Inside a modal the list has room to sit in the flow, so it does not need to
   float over anything -- and floating would clip against the dialog's edge. */
.combo-list.is-static {
  position: static;
  box-shadow: none;
  margin-top: 8px;
  max-height: 320px;
}

.combo-option {
  flex-wrap: wrap;
  justify-content: space-between;
}

.combo-option-meta {
  color: var(--app-faint);
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

/* Who a save will reach, stated before the button that does it. */
/* What a save is about to do, said in two numbers on the line under the
   editor. It used to be a sentence in a tinted box above the buttons, which was
   a paragraph to read every time in order to learn one number that had usually
   not changed. No box, no border, no background: two words, a middot, and each
   number a way into the list behind it. */
.editor-reach {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
  font-size: 0.86rem;
  white-space: nowrap;
}

.reach-count {
  padding: 0;
  border: 0;
  background: none;
  color: var(--app-accent);
  font: inherit;
  cursor: pointer;
}

.reach-count:hover { color: #7ff; text-decoration: none; }

/* Inside the dialogs: a note row that is there to be read rather than worked
   on, so the controls that would create or move one stay out of it. */
.note-tree.is-inert .row-tools,
.note-tree.is-inert .row-tool { display: none; }
.note-tree.is-inert .note-card { cursor: default; }

.reach-reveal {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 2px 0 0 12px;
  padding: 4px 0;
  border: 0;
  background: none;
  color: var(--app-accent);
  font: inherit;
  font-size: 0.84rem;
  cursor: pointer;
}

.reach-reveal:hover { color: #7ff; }

/* Faces along a line that scrolls rather than wrapping into a block: how many
   there are is the shape of the row, and a row that grows downwards loses it. */
.reach-faces {
  display: flex;
  gap: 8px;
  margin: 6px 0 4px 12px;
  padding-bottom: 4px;
  overflow-x: auto;
  overflow-y: hidden;
}

.reach-face {
  flex-shrink: 0;
  width: 34px;
  height: 34px;
  border-radius: 999px;
  overflow: hidden;
  background: rgba(255, 255, 255, 0.08);
}

.reach-face img { width: 100%; height: 100%; object-fit: cover; display: block; }

.reach-notes {
  margin: 4px 0 4px 12px;
  padding-left: 18px;
  color: var(--app-muted);
  font-size: 0.9rem;
}

/* ------------------------------------------------------- emblem and glimpses */

.glimpse-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 14px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.glimpse {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px;
  border: 1px solid var(--app-border);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.03);
}

.glimpse.is-emblem { border-color: rgba(0, 255, 255, 0.45); }

.glimpse img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: 8px;
  background: rgba(0, 0, 0, 0.35);
}

.glimpse-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}

[data-note-images].is-dropping {
  border-color: var(--app-accent);
  box-shadow: 0 0 0 2px rgba(0, 255, 255, 0.18) inset;
}

[data-image-hint].is-problem { color: #ff8a8a; }

/* TinyMCE renders its toolbar in the page, not the content iframe, so the
   glyphs registered as toolbar icons inherit the page's icon font. */
.tox .tox-icon [class^="icon-"] { font-size: 18px; line-height: 1; }
.tox .tox-tbtn { color: rgba(255, 255, 255, 0.85); }

/* ===========================================================================
 * Sponsor selector
 * ======================================================================== */

/* Reads as a statement until it is clicked, then becomes a search box. */
.sponsor-current {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  max-width: 100%;
  padding: 9px 14px;
  border: 1px solid var(--app-border-hover);
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.04);
  color: #fff;
  font: inherit;
  font-size: 0.98rem;
  text-align: left;
  cursor: pointer;
}

.sponsor-current:hover { border-color: var(--app-accent); background: rgba(0, 255, 255, 0.07); }
.sponsor-current [class^="icon-"] { font-size: 1.15rem; color: var(--app-accent); }
.sponsor-current span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.sponsor-select .combo { display: block; }
.sponsor-select { position: relative; }

/* Left mid-search. What was typed is still there, and the box says so rather
   than quietly putting the old value back. */
.sponsor-select.is-incomplete .sponsor-search input[type="text"] {
  border-color: #ff9f45;
  box-shadow: 0 0 0 1px rgba(255, 159, 69, 0.35);
}

.combo-option.is-active {
  background: rgba(0, 255, 255, 0.12);
  color: var(--app-accent);
}

.combo-option [class^="icon-"] { color: var(--app-faint); font-size: 1rem; }
.combo-option.is-active [class^="icon-"] { color: var(--app-accent); }

/* ===========================================================================
 * Image manager
 * ======================================================================== */

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

.modal-head h3 { margin: 0; }

/* A dialog opened on top of another one. Its own veil would double up on the
   one already there, so it carries none. */
.modal-backdrop.is-stacked {
  background: rgba(0, 0, 0, 0.55);
  backdrop-filter: none;
  z-index: 110;
}

/* Scoped to the modal. The ceiling and the scrollbar belong to a grid inside a
   dialog, which has to fit on the screen; the Images screen's grid is the page
   itself and must not inherit either -- it was, and the page ended in a box
   that scrolled inside a page that also scrolled. */
.modal .image-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 12px;
  margin: 0;
  padding: 0;
  list-style: none;
  max-height: 52vh;
  overflow-y: auto;
}

.image-cell {
  position: relative;
  margin: 0;
  border-radius: 12px;
  overflow: hidden;
  border: 1px solid var(--app-border);
  background: rgba(0, 0, 0, 0.35);
  cursor: pointer;
}

.image-cell img {
  display: block;
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
}

.image-cell:hover { border-color: var(--app-accent); }
.image-cell.is-emblem { border-color: rgba(0, 255, 255, 0.6); }
.image-cell.is-dragging { opacity: 0.4; }

.image-cell.is-drop-target {
  outline: 2px dashed var(--app-accent);
  outline-offset: -3px;
}

/* Marks which picture is the emblem, without a word of explanation. */
.emblem-badge {
  position: absolute;
  top: 8px;
  left: 8px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.5);
  color: #05161a;
}

.emblem-badge [class^="icon-"] { font-size: 1rem; }

.image-viewer { position: relative; padding-top: 46px; }

.image-viewer-more {
  position: absolute;
  top: 10px;
  right: 12px;
  font-size: 1.5rem;
}

.image-viewer-figure {
  display: block;
  width: 100%;
  max-height: 68vh;
  object-fit: contain;
  border-radius: 10px;
  background: rgba(0, 0, 0, 0.4);
}

/* --------------------------------------------------------- context menu */

/* Opens where the pointer is, so it sits above even a stacked dialog. */
.context-menu {
  position: fixed;
  z-index: 200;
  min-width: 190px;
  padding: 6px;
  border: 1px solid var(--app-border-hover);
  border-radius: 10px;
  background: #14161b;
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.6);
}

.context-menu-item {
  display: block;
  width: 100%;
  padding: 9px 12px;
  border: 0;
  border-radius: 7px;
  background: none;
  color: rgba(255, 255, 255, 0.88);
  font: inherit;
  font-size: 0.92rem;
  text-align: left;
  cursor: pointer;
}

.context-menu-item:hover { background: rgba(255, 255, 255, 0.09); color: #fff; }

/* The one item that destroys something. Its glyph takes the label's colour
   rather than the accent every other icon wears -- an X in the same cyan as
   "Add emblem" reads as one more thing to do, and this is not one more thing
   to do. Both are inherited, so the pair moves together and there is no second
   place to remember when either changes.

   Hovering it is unambiguous: white on red. Nothing else in this menu goes
   near that colour, so the row you are about to press does not look like the
   rows either side of it. */
.context-menu-item.is-danger { color: #ff8f8f; }
.context-menu-item.is-danger .context-menu-icon,
.context-menu-item.is-danger .context-menu-trailing { color: inherit; }

.context-menu-item.is-danger:hover,
.context-menu-item.is-danger:focus-visible { background: #ff0000; color: #fff; }

/* An item built out of one of the row's own controls carries the same mark the
   row does, so reading the menu and reading the row are the same act. */
.context-menu-item {
  display: flex;
  align-items: center;
  gap: 10px;
}

.context-menu-icon {
  width: 1.15rem;
  flex-shrink: 0;
  text-align: center;
  color: var(--app-accent);
  font-size: 1.05rem;
}

.context-menu-label { flex: 1; }

/* The mark on the right of an option that is either the chosen one or not. The
   tick and the empty box are the same width, so the labels beside them do not
   shift as the choice moves. */
.context-menu-trailing {
  flex-shrink: 0;
  margin-left: 6px;
  color: var(--app-accent);
  font-size: 0.95rem;
}

/* The unchosen options keep the space and draw nothing. A visible mark beside
   them would be a second thing to interpret; the tick alone says which one. */
.context-menu-trailing.icon-blank { visibility: hidden; }

/* An item that opens a menu of its own says so with a chevron, drawn rather
   than reserved -- there is nothing else on this line competing for the space. */
.context-menu-item.has-submenu::after {
  content: "";
  flex-shrink: 0;
  margin-left: 6px;
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  border-left: 5px solid rgba(255, 255, 255, 0.5);
}

.context-menu-item.has-submenu:hover::after { border-left-color: var(--app-accent); }

/* The second level sits beside the first rather than over it. */
.context-menu.is-sub { z-index: 201; }

/* Offered and plainly not available -- the menu keeps its shape, and the
   reason is on the tooltip rather than discovered by clicking. */
.context-menu-item.is-static,
.context-menu-item:disabled {
  color: var(--app-faint);
  cursor: default;
}

.context-menu-item.is-static .context-menu-icon,
.context-menu-item:disabled .context-menu-icon { color: var(--app-faint); }

.context-menu-item.is-static:hover,
.context-menu-item:disabled:hover { background: none; color: var(--app-faint); }

/* --------------------------------------------------------------- toasts */

/* For the actions that deliberately do not reload the page. A page that
   reloads reports itself; one that does not has to say so, or the only
   evidence of a finished upload is a picture quietly changing in the corner of
   somebody's eye. */
.toast-tray {
  position: fixed;
  right: 18px;
  bottom: 18px;
  z-index: 300;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 8px;
  pointer-events: none;
}

.toast {
  max-width: min(360px, calc(100vw - 36px));
  padding: 11px 16px;
  border: 1px solid var(--app-border-hover);
  border-radius: 10px;
  background: #14161b;
  color: rgba(255, 255, 255, 0.92);
  font-size: 0.92rem;
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.55);
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 0.3s ease, transform 0.3s ease;
}

.toast.is-shown { opacity: 1; transform: none; }
.toast.is-problem { border-color: rgba(255, 143, 143, 0.5); color: #ff8f8f; }

/* A row with an upload in flight. Dimmed rather than blocked: nothing about
   the row is unsafe to click while a picture is on its way. */
.note-card.is-uploading { opacity: 0.55; }

[data-images-hint].is-problem { color: #ff8a8a; }

/* ===========================================================================
 * Messages
 * ======================================================================== */

.thread-heading {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 14px;
}

/* Picture and name are one link, rather than a separate line underneath
   repeating where it goes. */
.thread-person {
  display: flex;
  align-items: center;
  gap: 14px;
  flex: 1;
  min-width: 0;
  text-decoration: none;
  color: inherit;
}

.thread-person:hover .page-title { color: inherit; }
.thread-person .page-title { margin-bottom: 0; font-size: 1.7rem; }

.bubble-row {
  display: flex;
  align-items: flex-start;
  gap: 8px;
}

/* Your own messages run the other way, and the row is pushed to the far edge
   rather than the bubble being given an automatic margin -- a margin inside a
   reversed row separates the bubble from the controls that are supposed to be
   touching it, which is exactly what it did. */
.bubble-row.mine {
  flex-direction: row-reverse;
  justify-content: flex-start;
}


.bubble-row:not(.mine) { justify-content: flex-start; }

/* The delete control stays out of the way until the message is hovered, so a
   conversation reads as a conversation rather than as a list of controls. */
.bubble-delete { opacity: 0; transition: opacity 0.12s ease; }
.bubble-row:hover .bubble-delete,
.bubble-delete:focus-within { opacity: 1; }

button.icon-only {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: none;
  color: var(--app-faint);
  font-size: 0.8rem;
  cursor: pointer;
}

button.icon-only:hover { background: rgba(255, 107, 107, 0.16); color: #ff8a8a; }

/* ===========================================================================
 * Messages
 * ======================================================================== */

.thread-heading {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 14px;
}

/* Picture and name are one link, rather than a separate line underneath
   repeating where it goes. */
.thread-person {
  display: flex;
  align-items: center;
  gap: 14px;
  flex: 1;
  min-width: 0;
  text-decoration: none;
  color: inherit;
}

.thread-person:hover .page-title { color: inherit; }
.thread-person .page-title { margin-bottom: 0; font-size: 1.7rem; }

.bubble-row {
  display: flex;
  align-items: flex-start;
  gap: 8px;
}

/* Your own messages run the other way, and the row is pushed to the far edge
   rather than the bubble being given an automatic margin -- a margin inside a
   reversed row separates the bubble from the controls that are supposed to be
   touching it, which is exactly what it did. */
.bubble-row.mine {
  flex-direction: row-reverse;
  justify-content: flex-start;
}


.bubble-row:not(.mine) { justify-content: flex-start; }

/* The delete control stays out of the way until the message is hovered, so a
   conversation reads as a conversation rather than as a list of controls. */
.bubble-delete { opacity: 0; transition: opacity 0.12s ease; }
.bubble-row:hover .bubble-delete,
.bubble-delete:focus-within { opacity: 1; }

button.icon-only {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: none;
  color: var(--app-faint);
  font-size: 0.8rem;
  cursor: pointer;
}

button.icon-only:hover { background: rgba(255, 107, 107, 0.16); color: #ff8a8a; }

/* ===========================================================================
 * The structured note tree
 *
 * No card around the rows, and none around each row: the indentation is what
 * shows the shape, and a border drawn over it would only compete.
 * ======================================================================== */

.note-tree {
  margin: 0 0 24px;
  padding: 0;
  list-style: none;
}

.tree-row {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 14px;
  margin: 0;
  padding: 12px 44px 12px 0;
  /* Deeper than the flat list's indent: this is the only thing saying which
     note sits under which. Capped so a long chain cannot push the text off. */
  margin-left: calc(min(var(--depth, 0), 8) * 34px);
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}

.tree-row:hover { background: rgba(255, 255, 255, 0.03); }

/* Kept only because something beneath it matched the filter. */
.tree-row.is-context { opacity: 0.55; }

.tree-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }

/* Two lines, then stop. A note's first paragraph is a hint at what is inside,
   not the note itself. */
.tree-preview {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  color: var(--app-muted);
  font-size: 0.92rem;
  line-height: 1.45;
}

.tree-row .note-emblem {
  width: 52px;
  height: 52px;
  flex-shrink: 0;
}

/* Appears on hover, so a still page is just the notes. Focus counts as hover,
   or the control would be unreachable from the keyboard. */
.tree-toggle {
  position: absolute;
  top: 14px;
  right: 6px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: none;
  color: var(--app-muted);
  font-size: 1.1rem;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.12s ease;
}

.tree-row:hover .tree-toggle,
.tree-toggle:focus-visible { opacity: 1; }
.tree-toggle:hover { background: rgba(255, 255, 255, 0.1); color: #fff; }

/* How many notes this one sponsors. */
.count-badge {
  display: inline-flex;
  align-items: baseline;
  gap: 2px;
}

.count-badge [class^="icon-"] { font-size: 1rem; color: var(--app-muted); }

.count-badge sub {
  font-weight: 700;
  color: var(--app-accent);
  font-size: 0.78em;
  bottom: -0.15em;
}

/* ===========================================================================
 * The account menu
 * ======================================================================== */

.avatar.tiny {
  width: 34px;
  height: 34px;
  border-radius: 999px;
  object-fit: cover;
  flex-shrink: 0;
}

.avatar-trigger {
  width: 40px;
  height: 40px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  overflow: visible;
  background: none;
  /* `outline` rather than a ring drawn with shadows: outline-offset holds the
     gap open with the page showing through it, so the ring does not need to
     know what colour it is standing on. */
  outline: 2px solid transparent;
  outline-offset: 4px;
  /* Arrives at once, leaves slowly: a hover that fades *in* feels like the page
     is thinking about it. */
  transition: outline-color 0.8s ease;
}

.avatar-trigger .avatar.tiny { width: 40px; height: 40px; }

.avatar-trigger:hover,
.menu:where(.is-open) .avatar-trigger {
  background: none;
  outline-color: var(--app-accent);
  transition: none;
}
.account-menu .menu-panel { min-width: 240px; }

/* An item that leads with a round mark -- your face for your profile, an icon
   in a grey disc for everything else. Both discs are the same size, so the
   items line up whatever is in them. */
/* Stated for the one inside a <form> as well. `.menu-panel form > .menu-item`
   sets `display: block` to give a submit button the panel's full width, and it
   outranks a bare `.menu-item.with-mark` -- so Sign Out was laying out as a
   block while the two links above it were flex rows, which put its label seven
   pixels to the left of theirs. */
.menu-item.with-mark,
.menu-panel form > .menu-item.with-mark {
  display: flex;
  align-items: center;
  gap: 11px;
}

.menu-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  flex-shrink: 0;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.72);
  color: #14161b;
  font-size: 1.05rem;
}

/* ===========================================================================
 * Note cards
 *
 * One row shape for both views. Three lines of text -- title, description,
 * details -- and a thumbnail exactly as tall as those three lines, so every
 * row is the same height whatever is in it.
 * ======================================================================== */

.note-tree {
  --row-line: 1.4rem;
  --row-pad: 12px;
  margin: 0 0 24px;
  padding: 0;
  list-style: none;
}

.note-card {
  position: relative;
  margin: 0 0 2px;
  border-radius: 12px;
  border: 2px solid transparent;
  /* Indentation is what shows the shape, so there is no rule between rows and
     no box around them -- a border here would compete with it. */
  margin-left: calc(min(var(--depth, 0), 8) * 34px);
  transition: background 120ms ease, border-color 120ms ease;
}

.note-tree.is-flat .note-card { margin-left: 0; }

.note-card:hover { background: rgba(255, 255, 255, 0.05); }

/* Kept only because something beneath it matched the filter. */
.note-card.is-context { opacity: 0.5; }

.note-card-link {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  padding: var(--row-pad);
  color: inherit;
  text-decoration: none;
  cursor: pointer;
}

.note-card-link:hover { text-decoration: none; color: inherit; }

.note-card-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0;
}

/* One line each, and an ellipsis rather than a wrap, so the row height never
   depends on how much somebody wrote. */
.note-card-title,
.note-card-preview {
  display: block;
  line-height: var(--row-line);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.note-card-title { font-weight: 600; color: var(--app-accent); }
.note-card:hover .note-card-title { color: #7ff; }
.note-card-preview { color: var(--app-muted); font-size: 0.92rem; }

/* The details line.
 *
 * Every item is its own inline-flex box with the icon centred inside it and
 * line-height pinned to the row. Left as plain inline text, an icon font's
 * ascent drags the baseline around and the line reads a pixel or two off from
 * the ones above it -- which is what made "edited 4h ago" look misaligned. */
.note-card-meta {
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
  overflow: hidden;
  /* No gap: the spacing is written into the line as non-breaking space, so the
     items sit apart the way words do rather than on a grid of their own. */
  gap: 0;
  height: var(--row-line);
  line-height: var(--row-line);
  color: var(--app-faint);
  font-size: 0.84rem;
}

.meta-item {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  line-height: 1;
  white-space: nowrap;
}

/* Centring an icon font against text is not a box problem -- the boxes already
   line up. Measured at 6x, the glyphs' ink is centred on the *cap* band: from
   the baseline to the cap height.

   That means the answer depends on what is beside the icon:

   - **Digits** fill the cap band exactly, so a count is already centred
     against its icon. Nothing to correct, and nothing to keep in step if the
     type changes: `.is-count` gets no offset at all.
   - **Words** here are lowercase, so their mass sits lower -- but not by a
     fixed amount: "friends" reaches the cap band with its f and d, while
     "private" is all x-height with a descender. Those two want offsets almost
     a pixel apart, so no single value centres both; this splits them, which
     puts each within half a pixel. */
.meta-item [class^="icon-"] {
  display: inline-flex;
  align-items: center;
  line-height: 1;
  font-size: 1rem;
  position: relative;
  top: 0.03em;
}

/* Beside digits there is nothing to judge: digits fill one band, baseline to
   cap height, and so does the glyph -- they either line up or they do not.
   This is the offset that makes them, measured rather than guessed.
   `measure-meta.mjs` screenshots the row at ten times scale and compares the
   two ink centres; across the candidates 0, -0.0625, -0.09 and -0.12em the
   means came out +1.65, +0.65, -0.02 and -0.35 pixels. Re-run it after any
   change to the type or the icon font. */
.meta-item.is-count [class^="icon-"] { top: -0.09em; }

.meta-item.is-count sub {
  /* Digits, aligned as digits: no baseline shift, no superscript metrics --
     the box is the alignment. */
  position: static;
  vertical-align: baseline;
  display: inline-flex;
  align-items: center;
  line-height: 1;
}

.meta-item sub {
  position: static;
  font-size: 0.85em;
  font-weight: 700;
  color: var(--app-accent);
  line-height: 1;
}

.meta-dot { color: rgba(255, 255, 255, 0.25); line-height: 1; }

/* The whitespace between details. Its own element so it keeps the line's
   height and cannot be collapsed away by the flex box around it. Wide enough
   that the sets read as separate things rather than as one run of glyphs --
   the icons are the punctuation now, so the gaps have to do the work the
   middots used to. */
.meta-space {
  white-space: pre;
  line-height: 1;
  flex: 0 0 auto;
  width: 1.6em;
  overflow: hidden;
}

/* What the note carries -- its emblem, its glimpses, what hangs off it -- is
   white, so the counts read at a glance. The timestamp stays quiet and privacy
   keeps its own colour. */
.meta-item.is-count,
.meta-item.is-count sub { color: #fff; }

/* Privacy is colour-coded by how far the note reaches: private is as quiet as
   the "edited" text beside it, friends is the accent, public is warm. */
.meta-item.privacy-private { color: var(--app-faint); }
.meta-item.privacy-protected { color: var(--app-accent); }
.meta-item.privacy-public { color: #ff9f45; }

/* Three lines tall, so the picture squares off against the text beside it. */
.note-emblem {
  width: calc(var(--row-line) * 3);
  height: calc(var(--row-line) * 3);
  flex-shrink: 0;
  border-radius: 10px;
  object-fit: cover;
  border: 1px solid var(--app-border);
}

.note-emblem.small { width: 44px; height: 44px; }

.note-emblem.is-empty {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, 0.09);
  border-color: transparent;
  color: rgba(255, 255, 255, 0.24);
}

.note-emblem.is-empty [class^="icon-"] { font-size: 1.3rem; }
.note-emblem.small.is-empty [class^="icon-"] { font-size: 1.05rem; }

.avatar.mini {
  width: 20px;
  height: 20px;
  border-radius: 999px;
  object-fit: cover;
}

/* --------------------------------------------------------------- row tools */

/* Overlaid on the row's right edge rather than taking a column of their own,
   so a still page is just the notes. Focus counts as hover, or they would be
   unreachable from the keyboard. */
.row-tools {
  position: absolute;
  top: 50%;
  /* The same 8px on the right as inside, so the group sits off the edge by
     exactly the distance it holds its own icons away from its border. */
  right: 8px;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px;
  border-radius: 999px;
  background: rgba(20, 22, 27, 0.92);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.4);
  opacity: 0;
  pointer-events: none;
  /* Slow on purpose: the tools arrive after the pointer has settled on a row
     rather than flashing past every row it crosses on the way. The row being
     left drops its transition entirely (see .is-leaving), so one group never
     fades out behind another fading in. */
  transition: opacity 0.8s ease;
}

.note-card:hover .row-tools,
.row-tools:focus-within { opacity: 1; pointer-events: auto; }

/* The row the pointer just left. Its fade is cancelled rather than run: two
   groups dissolving past each other reads as lag, not as motion. */
.note-card.is-leaving .row-tools { transition: none; opacity: 0; }

/* A move is a drag across the whole list, and the overlay sits exactly where
   the pointer needs to be. It goes away until the move is settled. */
.note-tree.is-moving .row-tools { display: none; }

.row-tool {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: none;
  color: var(--app-muted);
  font-size: 1.1rem;
  cursor: pointer;
  text-decoration: none;
  /* On the way out only. Lighting up should feel like an answer, not like the
     page considering the question -- so the hover state itself has no
     transition, and this one runs when the pointer leaves. */
  transition: color 0.8s ease;
}

/* The icon itself lights up. A disc behind it read as a second button. */
.row-tool:hover,
.row-tool:focus-visible {
  background: none;
  color: var(--app-accent);
  text-decoration: none;
  transition: none;
}

/* ------------------------------------------------------------ moving a note */

.note-card.is-selected {
  border-color: var(--app-accent);
  background: rgba(0, 255, 255, 0.06);
  cursor: move;
}

.note-card.is-selected .note-card-link { cursor: move; }

/* A row with its children folded away. Dotted rather than solid: a solid line
   is a border between things, and this is the opposite -- it means something
   continues below that you cannot see. */
.note-card.has-hidden-children {
  border-bottom: 2px dotted rgba(0, 255, 255, 0.45);
}

/* Three marks for three drops, and each one says which. Landing *inside* a note
   lights the whole row, because that is what it becomes part of. Landing beside
   it draws a line at the edge it will take, offset to the depth it will sit at,
   because that is a place between two rows rather than a place within one. */
.note-card.is-drop-target {
  background: rgba(0, 255, 255, 0.1);
  box-shadow: inset 0 0 0 1px var(--app-accent);
}

.note-card.is-drop-above::before,
.note-card.is-drop-below::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: 2px;
  border-radius: 2px;
  background: var(--app-accent);
}

.note-card.is-drop-above::before { top: -1px; }
.note-card.is-drop-below::after { bottom: -1px; }

.move-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin: 0 var(--row-pad) var(--row-pad);
  padding: 8px 12px;
  border-radius: 9px;
  background: rgba(0, 0, 0, 0.42);
  color: var(--app-muted);
  font-size: 0.86rem;
}

.move-message { flex: 1; min-width: 180px; }
.move-actions { display: flex; gap: 8px; flex-shrink: 0; }
.move-bar .button.small { padding: 0.35rem 0.8rem; font-size: 0.84rem; }

/* ===========================================================================
 * The note editor
 *
 * The sponsor selector and the writing area are one surface, so where a note
 * sits and what it says read as one thing rather than as a field above a box.
 * ======================================================================== */

/* Sponsor, writing area and toolbar sit inside one 1px border -- none of them
   draws its own -- and that border is what answers to focus, so the whole
   surface lights up rather than a box inside it. */
.note-surface {
  border: 1px solid var(--app-border);
  border-radius: 12px;
  overflow: hidden;
  /* One colour behind the whole thing, sponsor line included: a band of a
     different shade above the writing made the line look like a separate
     control sitting on top of the editor. */
  background: #000;
  margin-bottom: 0;
  /* Leaving only. Clicking in should light the border at once -- see the
     `transition: none` on the focused state. */
  transition: border-color 0.5s ease;
}

.note-surface:focus-within,
.note-surface.is-focused {
  border-color: var(--app-accent);
  transition: none;
}

/* One column from the sponsor line down through the writing: no divider between
   them and no background separating them either. What marks the line as a
   choice is the choice itself, named in the accent. */
.note-surface .sponsor-select {
  border-bottom: 0;
  background: none;
}


/* The sponsor line is the top of the surface, so it wears the surface's
   corners. What must not round is anything *inside* it -- see the rule that
   squares the editor, which the skin insists on with !important. */

/* The statement and the search box are the same row in two states, so they are
   built to the same box: same padding, same height, same type. Swapping one for
   the other cannot move anything below it. */
.note-surface .sponsor-current,
.note-surface .sponsor-search {
  display: flex;
  align-items: center;
  width: 100%;
  min-height: 44px;
  padding: 0 14px;
  border: 0;
  border-radius: 0;
  background: none;
}

/* No cell behind it on hover: this is a line of text that happens to be
   pressable, not a button. The name and its icon go cyan instead, at once, and
   cool back over half a second. */
.note-surface .sponsor-current,
.note-surface .sponsor-current [class^="icon-"],
.note-surface .sponsor-current [data-sponsor-title] {
  color: #fff;
  transition: color 0.5s ease;
}

.note-surface .sponsor-current:hover { background: none; }

.note-surface .sponsor-current:hover,
.note-surface .sponsor-current:hover [class^="icon-"],
.note-surface .sponsor-current:hover [data-sponsor-title],
.note-surface .sponsor-current:focus-visible,
.note-surface .sponsor-current:focus-visible [class^="icon-"],
.note-surface .sponsor-current:focus-visible [data-sponsor-title] {
  color: var(--app-accent);
  transition: none;
}

/* Nothing around the search box: it is the same line of text as the statement
   it replaced, not a field dropped into the surface. */
.note-surface .sponsor-search {
  gap: 9px;
}

/* Same glyph, same place, whether the line is showing a choice or taking one. */
.sponsor-search-icon {
  flex: 0 0 auto;
  font-size: 1.15rem;
  color: var(--app-accent);
}

.note-surface .sponsor-search input[type="text"] {
  width: 100%;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  font-size: 0.98rem;
  line-height: 1.4;
  box-shadow: none;
}

.note-surface .sponsor-search input[type="text"]:focus { border: 0; box-shadow: none; }

.note-surface .sponsor-search input[type="text"]::placeholder { color: var(--app-faint); }

/* Left mid-search: the typing goes warm rather than the box gaining an outline
   the rest of the surface does not have. */
.note-surface.is-incomplete .sponsor-search input[type="text"],
.sponsor-select.is-incomplete .sponsor-search input[type="text"] {
  border: 0;
  box-shadow: none;
  color: #ff9f45;
  caret-color: #ff9f45;
}

/* The options hang straight off the bottom of the row -- no gap to fall
   through, and no corners anywhere: this is a list of rows, not a floating
   card, so the rows run edge to edge and a highlight fills one completely. */
.note-surface .combo-list {
  top: 100%;
  margin-top: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: #222;
}

/* While options are showing, the row and the list are one object -- so the row
   gives up its bottom corners rather than drawing a curve across the join. */
.sponsor-select.is-open .sponsor-search,
.sponsor-select.is-open .sponsor-search input[type="text"] {
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
}

.note-surface .combo-option {
  border-radius: 0;
  gap: 10px;
  /* No padding at all except on the right: the emblem starts at the left edge
     and runs the full height of the row, so there is nothing for it to be
     inset from. */
  padding: 0 14px 0 0;
  align-items: center;
  color: #777;
  min-height: 34px;
}

/* Alternating rows, so a long list is readable without rules between them. */
.note-surface .combo-option.is-even { background: #333; }
.note-surface .combo-option.is-odd { background: #222; }

/* The one being chosen. Cyan the whole width of the row -- no inset, no
   rounding -- with the text dark enough to read on it. */
.note-surface .combo-option.is-active,
.note-surface .combo-option.is-active:hover {
  background: var(--app-accent);
  color: #333;
}

.note-surface .combo-option:hover { color: #fff; }
.note-surface .combo-option.is-active .sponsor-flag,
.note-surface .combo-option.is-active [class^="icon-"] { color: #333; }

/* The note's face at the head of its row: flush to the left edge, square, and
   cropped from the middle, so every row gives it the same space whatever shape
   the picture happens to be. */
.combo-emblem {
  width: 34px;
  height: 34px;
  align-self: center;
  flex: 0 0 auto;
  border-radius: 0;
  object-fit: cover;
  object-position: center;
}

.combo-emblem.is-empty {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, 0.10);
  color: rgba(255, 255, 255, 0.5);
  font-size: 0.95rem;
}

/* The placeholder follows its row, a shade dimmer than the row behind it so it
   still reads as a gap rather than as a tile. */
.combo-option.is-even .combo-emblem.is-empty { background: #2a2a2a; }
.combo-option.is-odd .combo-emblem.is-empty { background: #1b1b1b; }
.combo-option.is-active .combo-emblem.is-empty { background: rgba(0, 0, 0, 0.18); color: rgba(0, 0, 0, 0.5); }
.combo-emblem.is-empty [class^="icon-"] { opacity: 0.5; }

/* The skin sets these with !important, so undoing them has to as well -- which
   is why the writing area kept a rounded top edge under the sponsor line long
   after the surface around it had squared off. The rounding belongs to the
   surface; nothing inside it draws a corner of its own. */
.note-surface .tox-tinymce,
.note-surface .tox .tox-sidebar-wrap,
.note-surface .tox .tox-edit-area,
.note-surface .tox .tox-edit-area__iframe {
  border: 0 !important;
  border-radius: 0 !important;
}

/* The name on the left, the root marker against the right edge. */
.sponsor-current { justify-content: flex-start; }
.sponsor-current [data-sponsor-title] { flex: 1; text-align: left; }

.sponsor-flag {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin-left: auto;
  opacity: 0.5;
  font-size: 0.82rem;
  white-space: nowrap;
}

.sponsor-flag [class^="icon-"] { font-size: 0.95rem; }

/* In the list the file icon is plain white until the row is the highlighted
   one, where everything goes cyan together. */
.combo-option .icon-file { color: #fff; }
.combo-option.is-active .icon-file { color: var(--app-accent); }
.combo-option .combo-option-label { flex: 1; text-align: left; }

/* ------------------------------------------------------------ TinyMCE chrome */

/* Controls under the writing. Black bar, no divider, no button chrome -- the
   glyphs sit directly on it. */
.tox .tox-editor-header,
.tox .tox-toolbar,
.tox .tox-toolbar__primary,
.tox .tox-toolbar-overlord {
  background: #000 !important;
  border: 0 !important;
  box-shadow: none !important;
}

.tox:not(.tox-tinymce-inline) .tox-editor-header { box-shadow: none !important; border: 0 !important; }
.tox .tox-sidebar-wrap { border-bottom: 0 !important; }

.tox .tox-tbtn,
.tox .tox-tbtn:hover,
.tox .tox-tbtn--enabled,
.tox .tox-tbtn--enabled:hover {
  background: transparent !important;
  border: 0 !important;
  box-shadow: none !important;
}

.tox .tox-tbtn {
  color: rgba(255, 255, 255, 0.85) !important;
  cursor: pointer;
  /* Both ways: it warms up under the pointer and cools down when it leaves,
     at the same speed, rather than snapping back. */
  transition: color 0.8s ease !important;
}

.tox .tox-tbtn:hover,
.tox .tox-tbtn:focus-visible {
  color: var(--app-accent) !important;
  transition: none !important;
}
.tox .tox-tbtn--enabled { color: var(--app-accent) !important; }

/* A disabled control does not light up, whatever the pointer does. */
.tox .tox-tbtn--disabled:hover,
.tox .tox-tbtn:disabled:hover,
.tox .tox-tbtn[aria-disabled="true"]:hover { color: rgba(255, 255, 255, 0.85) !important; }

/* The block-format selector is a control, so it says so under the pointer. The
   skin left it as a default cursor, which made the one dropdown on the toolbar
   look like a label. */
.tox .tox-tbtn--select,
.tox .tox-tbtn--select * { cursor: pointer !important; }

/* Privacy carries its word in the colour that word wears on a note row. It does
   not brighten under the pointer -- the colour is information here, not a
   state, and a control that changed colour on hover would be saying the setting
   had changed. */
.tox .tox-tbtn.tl-privacy-button,
.tox .tox-tbtn.tl-privacy-button:hover,
.tox .tox-tbtn.tl-privacy-button:focus-visible,
.tox .tox-tbtn.tl-privacy-button .tox-tbtn__select-label {
  cursor: pointer !important;
  transition: none !important;
}

.tox .tox-tbtn.tl-privacy-button[data-privacy="private"],
.tox .tox-tbtn.tl-privacy-button[data-privacy="private"]:hover,
.tox .tox-tbtn.tl-privacy-button[data-privacy="private"] .tox-tbtn__select-label {
  color: var(--app-faint) !important;
}

.tox .tox-tbtn.tl-privacy-button[data-privacy="protected"],
.tox .tox-tbtn.tl-privacy-button[data-privacy="protected"]:hover,
.tox .tox-tbtn.tl-privacy-button[data-privacy="protected"] .tox-tbtn__select-label {
  color: var(--app-accent) !important;
}

.tox .tox-tbtn.tl-privacy-button[data-privacy="public"],
.tox .tox-tbtn.tl-privacy-button[data-privacy="public"]:hover,
.tox .tox-tbtn.tl-privacy-button[data-privacy="public"] .tox-tbtn__select-label {
  color: #ff9f45 !important;
}

/* The white flash on click.
 *
 * The skin draws a 2px focus ring on a toolbar button the moment it takes
 * focus, which for a mouse click is the moment before the button does
 * anything -- so it blinked white and vanished. Focus is still visible for a
 * keyboard, through the accent colour the hover rule already gives it; what is
 * suppressed is only the ring, and only for a pointer.
 *
 * :focus-visible is the distinction the browser already makes between "this
 * was clicked" and "this was tabbed to", so it is the one used rather than a
 * timer or a mousedown handler. */
.tox .tox-tbtn:focus:not(:focus-visible),
.tox .tox-tbtn:focus:not(:focus-visible)::after,
.tox .tox-tbtn:active,
.tox .tox-tbtn:active::after,
.tox .tox-tbtn--select:focus:not(:focus-visible),
.tox .tox-collection__item:focus:not(:focus-visible) {
  outline: 0 !important;
  box-shadow: none !important;
  border-color: transparent !important;
}

/* TinyMCE's own tooltip is a black box that covers the toolbar. The names live
   in the caption under the editor instead. */
.tox-tooltip,
.tox .tox-tooltip { display: none !important; }

/* The caption line. Always present, always one line tall, so naming a control
   can never nudge the page. */
.editor-footline {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin: 8px 2px 14px;
  min-height: 1.4rem;
}

.editor-caption {
  margin: 0;
  color: var(--app-faint);
  font-size: 0.86rem;
  line-height: 1.4;
  text-align: left;
  /* Fades out when the pointer leaves. The fade is removed outright when a new
     name arrives, so one control's name never dissolves through the next. */
  opacity: 1;
  transition: opacity 0.8s ease;
}

.editor-caption.is-fading { opacity: 0; }

/* A control that can do something names itself in the accent; one that cannot
   stays as quiet as the line it sits on. */
.editor-caption.is-live { color: var(--app-accent); }

.editor-supreme {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  color: var(--app-accent);
  font-size: 0.86rem;
  white-space: nowrap;
}

.editor-supreme [class^="icon-"] { font-size: 1rem; }

/* Above the writing rather than under it: that a note is the root is a fact
   about the note, not about anything on the toolbar. */
.editor-topline {
  display: flex;
  justify-content: flex-end;
  margin: 0 2px 8px;
}

/* Save against the right edge of the surface it saves, with anything else on
   the row -- deleting the note -- at the other end. */
.commit-row { justify-content: flex-end; }
.commit-row .button.danger { margin-right: auto; }

/* Save is the counterpart of "New note" on the notes page, so it is built to
   the same size. The line-height is stated because a <button> does not inherit
   the chrome's own the way an <a> does, which is what made the two differ. */
.button.large {
  padding: 0.7rem 1.5rem;
  font-size: 0.95rem;
  line-height: 1.7;
}

/* A control that cannot do anything says so by fading, not by refusing the
   pointer -- a "no entry" cursor reads as an error rather than as "nothing to
   undo yet". */
.tox .tox-tbtn--disabled,
.tox .tox-tbtn--disabled:hover,
.tox .tox-tbtn:disabled,
.tox .tox-tbtn:disabled:hover {
  opacity: 0.5;
  cursor: default !important;
  background: transparent !important;
}

/* The "more" button is a way in, not a state. Every other more-button in the
   app greys towards white under the pointer, and this one was taking the
   accent because it shares a rule with the formatting controls -- where the
   accent means "this is on". Destructive items keep their red; that is a
   different thing being said. */
.tox .tox-tbtn:has(.icon-more):hover,
.tox .tox-tbtn:has(.icon-more):focus-visible,
.tox .tox-tbtn--enabled:has(.icon-more),
.tox .tox-tbtn--enabled:has(.icon-more):hover {
  color: #fff !important;
  background: rgba(255, 255, 255, 0.09) !important;
  border-radius: 8px !important;
}

/* The note menu -- the one holding Delete Note. Scoped by the glyph that item
   carries, so the block-format dropdown next to it is left alone: there, the
   highlight on opening marks the format you are already in, which is worth
   showing. Here nothing has been picked yet, and a lit first row said
   otherwise. */
.tox .tox-menu:has(.icon-shareNote) .tox-collection__item--active:not(:hover):not(:focus-visible) {
  background: transparent !important;
}

/* Deleting a note is the one destructive thing in here, and it says so in the
   same voice as Delete Account in the account menu: a light red that stays
   readable on a dark panel, going full red with white words under the pointer.
   rgba(255,0,0,0.7) on #14161b is a muddy maroon -- legible in a swatch, not in
   a menu. */
.tox .tox-collection__item:has(.icon-delete),
.tox .tox-collection__item:has(.icon-delete) .tox-collection__item-label {
  color: #ff8f8f !important;
}

.tox .tox-collection__item:has(.icon-delete):hover,
.tox .tox-collection__item:has(.icon-delete):hover .tox-collection__item-label,
.tox .tox-collection__item:has(.icon-delete):hover .tox-collection__item-icon {
  background: #f00 !important;
  color: #fff !important;
}

.tox .tox-collection__item:has(.icon-delete) .tox-collection__item-icon { color: inherit !important; }

/* The menu the toolbar opens is the same object as the one under the account
   picture, so it is built to the same measurements rather than to the skin's.
   The skin's own list padding is zeroed: it was adding a band above the first
   row and below the last that the account menu does not have. */
.tox .tox-menu {
  min-width: 190px !important;
  padding: 6px !important;
  border: 1px solid var(--app-border-hover) !important;
  border-radius: 10px !important;
  background: #14161b !important;
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.55) !important;
  /* Lifted clear of the icon. -14px rather than -6px: at -6 the panel sat close
     enough to the toolbar to read as attached to it, and the eight pixels are
     what separate a menu belonging to a button from a menu resting on one. */
  margin-top: -14px !important;
}

.tox .tox-menu .tox-collection__group {
  padding: 0 !important;
  margin: 0 !important;
  border: 0 !important;
}

.tox .tox-collection--list .tox-collection__group { padding: 0 !important; }

.tox .tox-collection__item {
  padding: 9px 12px !important;
  border-radius: 7px !important;
  font-size: 0.92rem !important;
  cursor: pointer !important;
}

/* An option that cannot do anything: quiet, and no promise of a highlight. */
.tox .tox-collection__item--state-disabled,
.tox .tox-collection__item[aria-disabled="true"] {
  color: var(--app-faint) !important;
  cursor: default !important;
}

.tox .tox-collection__item--state-disabled:hover,
.tox .tox-collection__item[aria-disabled="true"]:hover {
  background: transparent !important;
  color: var(--app-faint) !important;
}

/* The "more" menu is an icon, not a dropdown: the caret beside it belongs to a
   control that shows a current value, which this does not. The block-format
   picker keeps its own. */
.tox .tox-toolbar__primary > .tox-toolbar__group:last-child .tox-tbtn__select-chevron {
  display: none !important;
}

/* Privacy is the last group, pushed to the far end of the bar. */
.tox .tox-toolbar__primary > .tox-toolbar__group:last-child {
  margin-left: auto;
  border-left: 0 !important;
}

.tox .tox-toolbar__group { border: 0 !important; }

/* The writing surface itself. Black: the note is the only thing on it. */
.tox .tox-edit-area__iframe { background: #000 !important; }

/* The skin draws its own focus ring as a 2px ::before on the edit area, which
   landed as a white box inside the surface's own cyan border -- two rings
   saying the same thing, one of them the wrong colour. The surface border is
   the focus indicator here, so this one goes. */
.tox .tox-edit-area::before,
.tox .tox-edit-area::after { display: none !important; }

.choice-title [class^="icon-"] { margin-right: 7px; font-size: 1.1rem; vertical-align: -2px; }

/* A button that is present but has nothing behind it. Rendered as a span, so
   it cannot be clicked or tabbed to -- not merely painted to look that way. */
.button.is-disabled {
  opacity: 0.45;
  cursor: default;
  background: rgba(255, 255, 255, 0.12);
  color: rgba(255, 255, 255, 0.7);
}

.button.is-disabled:hover { opacity: 0.45; background: rgba(255, 255, 255, 0.12); }

.meta-item.is-supreme { color: var(--app-accent); }


/* ===========================================================================
 * Snapshots: a copy of a note, handed over, and the conversation on it
 * ======================================================================== */

/* The copy sits inside the panel that carries the conversation, indented the
   same amount as everything else in there -- it is one of the things in the
   panel rather than a slab the panel happens to contain. */
.snapshot-body { padding: 0 2px 4px; margin-bottom: 18px; }

/* Who handed it over, headed the way every other block on this screen is
   headed. `align-items: center` rather than the panel header's `baseline`,
   because a 44px circle beside one line of text has no baseline worth sharing
   -- lined up on their middles, the face and the sentence read as one thing. */
.panel-header.shared-by {
  align-items: center;
  justify-content: flex-start;
  gap: 12px;
}

.shared-by-line {
  font-size: 1rem;
  font-weight: 600;
  color: rgba(255, 255, 255, 0.92);
}

/* A second heading inside a panel that already has one. It needs the air above
   it that the gap between two panels used to provide, and a line to say the
   copy has ended and the talking has started. */
.panel-header.is-inner {
  margin-top: 22px;
  padding-top: 18px;
  border-top: 1px solid var(--app-border);
}

/* The one line under it: how far the copy went on the left, where the rest of
   them are on the right. Author's view only -- who else was handed a copy is
   not a recipient's business. */
.snapshot-meta {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 20px;
  font-size: 0.9rem;
}

.snapshot-meta-quiet { color: var(--app-faint); }

.comment-list {
  list-style: none;
  margin: 0 0 18px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 18px;
}

.comment {
  display: flex;
  align-items: flex-start;
  gap: 12px;
}

/* Answers sit under what they answered, indented past the picture above them
   so the eye follows the thread rather than reading a flat list in which one
   line happens to be about another. */
.comment-list.is-replies {
  margin: 14px 0 0;
  padding-left: 18px;
  border-left: 1px solid var(--app-border);
  gap: 14px;
}

/* Any comment can be answered, including one that was itself an answer -- so
   the nesting has no floor and the indent needs one. Past the third level the
   step shrinks rather than stopping: the rule still says "this answers the
   thing above it", but a long back-and-forth no longer marches off to a column
   one word wide. */
.comment-list.is-replies .comment-list.is-replies .comment-list.is-replies {
  padding-left: 10px;
}

/* A face at the size a face is worth reading, rather than the 20px dot it was.
   Same circle the message rows use, so a person looks like a person on every
   screen in this section. */
.comment-face {
  width: 44px;
  height: 44px;
  flex-shrink: 0;
  border-radius: 999px;
  object-fit: cover;
  border: 1px solid var(--app-border);
  background: rgba(255, 255, 255, 0.08);
}

/* Replies are a level down, so their faces are too. */
.comment-list.is-replies .comment-face { width: 34px; height: 34px; }

.comment-main { flex: 1; min-width: 0; }

.comment-who {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px;
  font-size: 0.86rem;
}

/* Arrives with the pointer, beside the time. A reply link on every comment at
   all times is a column of invitations; one on the comment being looked at is
   an offer. Focus counts, or it is unreachable from a keyboard. */
.comment-reply-hit {
  display: inline-flex;
  align-items: baseline;
  gap: 8px;
  opacity: 0;
  transition: opacity 140ms ease;
}

.comment:hover > .comment-main > .comment-who > .comment-reply-hit,
.comment-reply-hit:focus-within { opacity: 1; }

@media (hover: none) {
  .comment-reply-hit { opacity: 1; }
}

.comment-reply {
  padding: 0;
  border: 0;
  background: none;
  color: var(--app-accent);
  font: inherit;
  font-size: 0.82rem;
  font-weight: 600;
  cursor: pointer;
}

.comment-reply:hover { text-decoration: underline; text-underline-offset: 4px; }

/* Written where it will appear: under the comment it answers, inside it. */
.comment-reply-form { margin-top: 12px; }
.comment-reply-form[hidden] { display: none; }

.comment-reply-form textarea {
  width: 100%;
  box-sizing: border-box;
}

.comment-reply-form .actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 8px;
}

.comment-when,
.comment-where { color: var(--app-faint); font-size: 0.82rem; }

/* The author's own comments carry a mark rather than a different bubble: it is
   one conversation, and who wrote the note is a fact about the person. */
.comment-mark {
  padding: 1px 7px;
  border-radius: 999px;
  background: rgba(0, 255, 255, 0.14);
  color: var(--app-accent);
  font-size: 0.74rem;
}

.comment-body {
  margin: 4px 0 0;
  color: rgba(255, 255, 255, 0.92);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* Choosing who gets a copy. Rows rather than a wrapped run of chips, because
   the question is "which of these people", and a list is how that is read. */
.share-people {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-bottom: 16px;
  max-height: 240px;
  overflow-y: auto;
}

.share-person {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 10px;
  border-radius: 8px;
  cursor: pointer;
}

.share-person:hover { background: rgba(255, 255, 255, 0.06); }
.share-person input { accent-color: var(--app-accent); }

/* A share inside a message thread: the words are the message, and this is the
   thing they are about. */
.bubble-snapshot {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
  padding: 8px 10px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 9px;
  background: rgba(0, 0, 0, 0.25);
  color: #fff;
  text-decoration: none;
  transition: border-color 0.5s ease, color 0.5s ease;
}

.bubble-snapshot:hover {
  border-color: var(--app-accent);
  color: var(--app-accent);
  text-decoration: none;
  transition: none;
}

.bubble-snapshot [class^="icon-"] { font-size: 1.05rem; }

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}


/* The person at the top of a thread. It goes to their profile, so it keeps the
   pointer -- but it is a heading, and headings do not light up and underline
   themselves when the pointer crosses them. */
.thread-person,
.thread-person:hover,
.thread-person:focus-visible {
  color: inherit;
  text-decoration: none;
  cursor: pointer;
}

.thread-person:hover .page-title { color: inherit; }

/* A count in a panel header, said in words. A circle with a number in it makes
   the reader work out what is being counted; the sentence does not. */
.panel-count {
  color: var(--app-faint);
  font-size: 0.86rem;
  white-space: nowrap;
}

/* ===========================================================================
 * Heeders
 * ======================================================================== */

/* The mark for "people are listening to this". It is the public glyph turned a
   quarter turn -- the same shape that means "this reaches other people", aimed
   at the note rather than away from it -- so the set needs no new drawing and
   the two read as related, which they are. */
.icon-heeder::before {
  content: "\eb2a";
  display: inline-block;
  transform: rotate(-90deg);
}

.icon-heeder {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: "thotlink-icons" !important;
  font-style: normal;
  font-weight: normal;
  font-variant: normal;
  text-transform: none;
  line-height: 1;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}


/* ===========================================================================
 * Settings
 * ======================================================================== */

/* Same reason as the note form: the retro chrome styles a bare <form> as a
   card, and a card holding two panels is a box inside a box. */
.settings-form {
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
  backdrop-filter: none;
  animation: none;
  display: block;
  gap: 0;
}

.settings-form input[type="hidden"] { display: none !important; }
.settings-form .panel { margin-bottom: 18px; }

/* The sponsor selector, out of the editor and into a field. Same control, same
   rules about which notes may be chosen -- a second implementation would have
   been a second set of those rules. */
.sponsor-select.is-standalone {
  border: 1px solid var(--app-border-hover);
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.04);
  overflow: visible;
}

.sponsor-select.is-standalone .sponsor-current,
.sponsor-select.is-standalone .sponsor-search {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  min-height: 44px;
  padding: 0 14px;
  border: 0;
  border-radius: 10px;
  background: none;
  color: #fff;
  transition: color 0.5s ease;
}

.sponsor-select.is-standalone .sponsor-current:hover,
.sponsor-select.is-standalone .sponsor-current:hover [class^="icon-"] {
  color: var(--app-accent);
  transition: none;
}

/* No border, no corners, no background: the field is the line of text, and the
   box around it belongs to the selector. A rounded inner edge was clipping the
   caret at the start of the line, which is what made it look like the cursor
   had gone missing until something was typed. */
.sponsor-select.is-standalone .sponsor-search input[type="text"] {
  width: 100%;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  font-size: 0.98rem;
  box-shadow: none;
  outline: none;
}

.sponsor-select.is-standalone.is-open,
.sponsor-select.is-standalone.is-open .sponsor-search {
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
}

/* The list hangs off the selector's padding box, which sits a border-width
   inside the selector's own border -- so left/right of 0 draw it 2px narrow and
   its edges land just inside the edges above them. Pulled out by that border on
   each side, the two borders are one line. */
.sponsor-select.is-standalone .combo-list {
  top: 100%;
  left: -1px;
  right: -1px;
  margin-top: 0;
  padding: 0;
  border: 1px solid var(--app-border-hover);
  border-top: 0;
  border-radius: 0 0 10px 10px;
  background: #222;
  /* Clipped sideways, scrollable downwards. It used to be `overflow: hidden`,
     which was there to keep the rows inside the rounded bottom corners -- and
     which also switched scrolling off. The list still moved under the arrow
     keys, because those call scrollIntoView and that works on a clipped box,
     so the only thing broken was the wheel. Two values does both jobs:
     anything other than `visible` clips to the radius. */
  overflow: hidden auto;
}

.sponsor-select.is-standalone .combo-option {
  border-radius: 0;
  gap: 10px;
  padding: 0 14px 0 0;
  min-height: 34px;
  align-items: center;
  color: #777;
}

.sponsor-select.is-standalone .combo-option.is-even { background: #333; }
.sponsor-select.is-standalone .combo-option.is-odd { background: #222; }

.sponsor-select.is-standalone .combo-option.is-active {
  background: var(--app-accent);
  color: #333;
}

/* A heading that carries its own mark. Inline-flex rather than a bare glyph,
   so the icon sits on the text's middle instead of on its baseline. */
.panel-header h3 { display: inline-flex; align-items: center; gap: 9px; }
.panel-header h3 [class^="icon-"] { color: var(--app-accent); font-size: 1.05em; }

/* Two settings under one heading. The rule is what says they are two answers
   and not one setting with a long explanation attached. */
.setting-rule {
  height: 0;
  margin: 26px 0 22px;
  border: 0;
  border-top: 1px solid var(--app-border);
}

.setting-description {
  margin: 6px 0 0;
  color: var(--app-muted);
  font-size: 0.9rem;
  line-height: 1.5;
  max-width: 68ch;
}

/* Yes or No, rather than a box to tick.
 *
 * "Show my emblems to heeders" is a statement, not a question, and a checkbox
 * beside a statement makes the reader work out what a tick means. Two words
 * and a mark beside the true one says it outright.
 *
 * The radios are still radios -- they post, and they answer the arrow keys --
 * they are just not drawn. The mark has a slot of its own at a fixed width, so
 * moving it from one word to the other does not shift either of them. */
.yes-no {
  display: flex;
  align-items: center;
  gap: 30px;
  margin-top: 14px;
}

.yes-no-option {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: #fff;
  cursor: pointer;
  transition: color 120ms ease;
}

.yes-no-option:hover,
.yes-no-option:focus-within { color: var(--app-accent); }

/* Present in the form and in the accessibility tree, absent from the picture.
   Not display:none, which would take it out of both. */
.yes-no-option input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

/* Reserved on both, drawn on one. `visibility` rather than `display`, so the
   space is held whichever side the mark is on. */
.yes-no-mark {
  width: 1.05em;
  flex-shrink: 0;
  text-align: center;
  color: var(--app-accent);
  font-size: 0.95rem;
  visibility: hidden;
}

.yes-no-option.is-on .yes-no-mark { visibility: visible; }

/* A setting that is on or off, with the sentence that explains it attached --
   a checkbox with a label three words long is a checkbox nobody can answer. */
.switch-row {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 4px 0;
  cursor: pointer;
}

.switch-row input {
  margin-top: 3px;
  width: 17px;
  height: 17px;
  accent-color: var(--app-accent);
  flex-shrink: 0;
}

.switch-main { display: flex; flex-direction: column; gap: 4px; }
.switch-title { color: #fff; font-weight: 600; }
.switch-description { color: var(--app-muted); font-size: 0.9rem; line-height: 1.5; }

/* ===========================================================================
 * Images
 * ======================================================================== */

.image-grid {
  list-style: none;
  margin: 0;
  /* Room under the last row, and no ceiling on the grid: it is the page's
     content, not a box inside it. */
  padding: 0 0 10px;
  display: grid;
  gap: 14px;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
}

.image-tile {
  position: relative;
  border: 1px solid var(--app-border);
  border-radius: 12px;
  overflow: hidden;
  background: var(--app-surface);
  transition: border-color 0.5s ease;
}

.image-tile:hover { border-color: var(--app-accent); transition: none; }

/* The whole tile is the way back to the note: an image in here is never just an
   image, it is part of something you wrote. */
.image-tile > a {
  display: block;
  color: inherit;
  text-decoration: none;
}

.image-tile img {
  display: block;
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
  background: rgba(255, 255, 255, 0.05);
}

.image-tile-main {
  display: block;
  padding: 10px 12px 12px;
}

.image-tile-title {
  display: block;
  color: var(--app-accent);
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.image-tile:hover .image-tile-title { color: #7ff; }

.image-tile-meta {
  display: flex;
  align-items: center;
  margin-top: 4px;
  color: var(--app-faint);
  font-size: 0.82rem;
}

/* The emblem badge, in the same half-white circle the image manager uses -- the
   same mark for the same fact, in both places it is shown. */
.image-tile-badge {
  position: absolute;
  top: 8px;
  left: 8px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.5);
  color: #111;
  font-size: 0.95rem;
}


/* ===========================================================================
 * A list row that is not a note, drawn like one
 * ======================================================================== */

/* Messages and shared snapshots read as rows of the same kind as a note: a
   picture, a name, a line of preview, and the details underneath. No rules
   between them, no underline on hover, and the whole row is the target. */
.row-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: block;
}

.row-item {
  border: 0;
  border-radius: 10px;
  transition: background 0.25s ease;
}

.row-item:hover { background: rgba(255, 255, 255, 0.05); transition: none; }

/* Unread is a brighter row, the same idea as an unread notification. */
.row-item.is-unread { background: rgba(255, 255, 255, 0.06); }
.row-item.is-unread:hover { background: rgba(255, 255, 255, 0.10); }

.row-link {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 10px 12px;
  color: inherit;
  text-decoration: none;
}

.row-link:hover { text-decoration: none; color: inherit; }

/* The same square the note rows use, at the same height as three lines of
   text, so a list of messages and a list of notes line up with each other. */
.row-avatar {
  width: calc(var(--row-line) * 3);
  height: calc(var(--row-line) * 3);
  flex-shrink: 0;
  border-radius: 999px;
  object-fit: cover;
  border: 1px solid var(--app-border);
  background: rgba(255, 255, 255, 0.08);
}

.row-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0;
}

.row-title,
.row-preview {
  display: block;
  line-height: var(--row-line);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.row-title { font-weight: 600; color: var(--app-accent); }
.row-item:hover .row-title { color: #7ff; }
.row-preview { color: var(--app-muted); font-size: 0.92rem; }

.row-meta {
  display: flex;
  align-items: center;
  gap: 6px;
  height: var(--row-line);
  line-height: var(--row-line);
  color: var(--app-faint);
  font-size: 0.84rem;
}

.row-meta .meta-item.is-count { color: #fff; }

/* A row that is a row rather than a link: the pieces inside it are what you
   click, so the container must not pretend to be clickable itself. */
.row-link.is-static { cursor: default; }

/* What the row offers, arriving with the pointer. A button on every row is a
   wall of buttons; a button on the row being looked at is an offer. Focus
   counts as hover or it would be unreachable from the keyboard. */
.row-aside {
  flex-shrink: 0;
  opacity: 0;
  transition: opacity 160ms ease;
}

.row-item:hover .row-aside,
.row-aside:focus-within { opacity: 1; }

@media (hover: none) {
  .row-aside { opacity: 1; }
}

/* A panel whose whole body is one of these lists: the space under the last row
   should be the space beside it, so the block of rows sits in the panel the
   same distance from every edge. The list's own bottom margin goes, and so
   does the last row's -- the gap between rows is a gap between rows, not a
   thing the panel should inherit. */
.panel > .row-list:last-child,
.panel > .note-tree:last-child { margin-bottom: 0; }

.panel > .note-tree:last-child > li:last-child,
.panel > .row-list:last-child > li:last-child { margin-bottom: 0; }


/* The picture somebody clicked on the Images screen, found again inside the
   note's image manager. It fades out when the pointer reaches it -- which is
   the moment it has served its purpose -- rather than on a timer. */
.just-clicked-pill {
  position: absolute;
  right: 8px;
  bottom: 8px;
  padding: 3px 9px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.72);
  color: var(--app-accent);
  font-size: 0.72rem;
  letter-spacing: 0.02em;
  pointer-events: none;
  opacity: 1;
  transition: opacity 1s ease;
}

.image-cell.is-found .just-clicked-pill { opacity: 0; }

.image-cell.is-just-clicked { box-shadow: inset 0 0 0 2px var(--app-accent); }
.image-cell.is-found { box-shadow: none; transition: box-shadow 1s ease; }
