/* The colour variables and every `.note-content` rule live in the render kit's
   canonical stylesheet, shared verbatim with the embeddable render host in
   render/index.html (and thus with native clients — see tools/dist-renderer.sh)
   so the read view, the editor preview and a client WebView cannot drift.
   @import must precede all other rules. */
@import url("render/note.css");

* { box-sizing: border-box; }

html, body {
  height: 100%;
}

body {
  margin: 0;
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  background: var(--bg);
  color: var(--fg);
  line-height: 1.5;
  display: flex;
  flex-direction: column;
}

#app {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}

/* 1.1rem/600 is MyCal's `.brand` and MyMail's `.sidebar-header` verbatim. The
   font and the font size are a cross-repo contract — ../mysuite,
   spec/app-name-label.md §3.1 and §3.2 — so changing either is a change in all
   three repositories, or in none. Read the values there, not here.

   This label used to be 1rem, ruled smaller because this corner was crowded.
   The column was widened to carry the larger label instead (see .sidebar), so
   the ruling that made it small is spent; app-name-label.md §2.1 records the
   supersession, and the guard in e2e/tests/logo.spec.ts moved with it.

   What breaks it silently: normalising `1.1rem` to `17.6px` (identical at a
   16px root, frozen at every other) or to `1.1em` (identical at EVERY root
   today, divergent the moment an ancestor sets a size). The two are caught by
   different things, and only one of them by anything here: logo.spec.ts asserts
   a 16px and a 32px root, which catches the `px` freeze; no rendered assertion
   in THIS app can see the `em` form at any root, because every ancestor between
   the label and <html> computes to exactly the root size, so `em` and `rem`
   coincide everywhere (measured: `1.1em` still gives 17.6 / 22 / 26.4 / 35.2px
   at 16 / 20 / 24 / 32px roots). mysuite's tools/check-contract.py catches both — it
   compares this declaration's TEXT across the three repos (app-name-label.md
   §7.1, pinned STRONG). Verified by mutation, not assumed: `1.1em` here reports
   "found `font-size: 1.1em`, expected `1.1rem`". Nobody's CI runs that script,
   so it catches these only when somebody runs it.

   `font-family` and `line-height` are NOT authored here: the label inherits
   both from `body`. The contract pins the declared stack (app-name-label.md
   §3.1) and that declaration lives in body{} — editing it there restyles this
   label, and will not look like a contract change while you are doing it.
   logo.spec.ts asserts the resolved stack on the label for that reason, so the
   red arrives here rather than in the rule you edited.

   `color` is not a contract value, but this declaration is load-bearing anyway:
   `a { color: var(--link) }` also matches this anchor and takes over the moment
   it is deleted as redundant. No sibling repo can catch that; neither of their
   labels is inside a link (app-name-label.md §8.3). */
.brand {
  font-size: 1.1rem;
  font-weight: 600;
  color: var(--fg);
  text-decoration: none;
}

/* Header row shared by both sidebar tabs: brand on the left, the tab strip in
   the middle, the reload button on the right — all sitting on one baseline (the
   header's bottom border, which doubles as the tab strip's baseline). */
.sidebar-header {
  display: flex;
  align-items: stretch;
  gap: 0.6rem;
  flex: none;
  margin: 0 16px 0.75rem;
  border-bottom: 1px solid var(--border);
}

/* The badge sits INSIDE the anchor (see .brand-logo), so the anchor needs its
   own flex context to put the mark beside the label text — as a flex item of
   .sidebar-header it is otherwise blockified and the badge would stack.
   The 8px gap is the shared logo contract's, not a local spacing choice.

   `align-self: flex-start`, NOT `center`, and that is the whole vertical fix:
   centred, the brand's top was `(header content height − brand height) / 2`
   below the header — a leftover of `.sidebar-tab`'s typography (its font-size,
   its 0.5rem padding, its 2px border), so the badge moved whenever a tab did,
   with nothing to catch it. Top-aligned, the badge's y is `.sidebar`'s
   padding-top and nothing else: a chosen number, which is what the shared logo
   contract's position requires and how both sibling apps author theirs. */
.sidebar-brand {
  flex: none;
  align-self: flex-start;
  display: flex;
  align-items: center;
  gap: 8px;
}

/* The app mark (components/Logo.tsx, the favicon glyph) on a --primary square.
   Box, radius, glyph size and the gap above are the cross-repo logo contract —
   see "The app logo is governed from outside this repo" in web/AGENTS.md. The
   glyph carries no width/height of its own, so both are set here.

   The badge is decorative: this element carries aria-hidden (so does the <svg>
   inside it, which makes Logo decorative wherever it is used), and the anchor's
   accessible name is carried by the "MyNotes" text node beside it. */
.brand-logo {
  width: 28px;
  height: 28px;
  flex-shrink: 0;
  /* Takes the badge out of .sidebar-brand's own `align-items: center`, so its y
     is the sidebar's padding-top and nothing else. Without it the badge keeps a
     term of `(brand height − 28) / 2`, where the brand is as tall as its tallest
     child — zero only while the 28px badge beats the label's line box, i.e. only
     up to a ~16.97px root font (28 ÷ 1.65, the label's 1.1rem × the inherited
     line-height 1.5; it was ~18.67 while the label was 1rem). That is a
     remainder that happens to be zero, not an authored position, and it is what
     made the badge drift to y = 19.797 at a 24px root — measured with this
     declaration removed. `.sidebar-brand` is a row, so this is the vertical axis.
     The `align-items: center` below stays: it centres the GLYPH inside this box. */
  align-self: flex-start;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--primary);
  color: #fff;
  border-radius: 6px;
}

.brand-logo svg {
  width: 17px;
  height: 17px;
}

.app-body {
  display: flex;
  flex: 1;
  min-height: 0;
  overflow: hidden;
}

/* No horizontal padding of its own — the inset is applied by the header, the
   footer and .sidebar-content instead, so the notes list can cancel it (see
   .notes-list) and paint the active row's highlight edge to edge.

   540px, reached in three steps. The first two grew the column by exactly what
   was added to the header, keeping the tab strip's clearance at 15.63px:

     420 → 456   the brand badge (28px badge + the contract's 8px gap)
     456 → 464   the inset going 12px → 16px on both sides
     464 → 540   the label going 1rem → 1.1rem, plus tolerance (below)

   The third step is sized differently, and on purpose, because "grows by what
   was added" produced a column that fit **this machine's** font and nothing
   else. GitHub's runner resolves `system-ui` about 1.25× wider than this one,
   which was enough to overflow the strip in CI while every local run was green:
   the header had ~6px of slack, and the wider font wanted ~46px more.

   So the budget is stated rather than inferred. The header's content box is this
   column less its 1px border and 32px of side insets, and at a 16px root with
   the 1.1rem label the row inside it splits into 250px that does NOT scale with
   the font's width (badge, tab padding, action buttons, gaps) and 182px of text
   (the label and the three tab labels). A column of W therefore absorbs text up
   to (W − 33 − 250) / 182 times this machine's metrics:

     464px → 0.99×   overflows here too, once the label grew
     540px → 1.41×   this value

   1.41 is deliberately well past the 1.25 that was actually observed, because
   the next label, the next tab, or a translated string spends the same budget.
   MyCal sizes its own row the same way and says so ("26px spare, which absorbs a
   system-ui about a quarter wider than this machine's").

   That ratio is how to *think* about the column, but it is deliberately not what
   the suite asserts, and the difference matters if you go to change either. Its
   numerator is font-independent while its denominator is whatever the running
   machine renders, so the same healthy row reads 1.41 here and ~1.15 on CI — a
   floor under it would fail where the fonts are widest, which is the bug rather
   than the guard. e2e/tests/logo.spec.ts therefore pins the numerator directly:
   the px available *for* text, 257 here and 257 everywhere, against a floor of
   246 (1.35 × the 182px this machine renders). The leftover px are checked too,
   but only with a floor of 8, because a wider font is entitled to eat them —
   33px remain on CI's, 75px here.

   The second step is the shared logo contract's placement: the badge must sit
   16px from the sidebar's left edge, matching MyMail (and MyCal, which aimed at
   the same number deliberately — see its app.css comment). Moving only the
   header there would have put the badge 4px out of line with the note list
   below it, so the whole inset moved and the column paid for it.

   Widening rather than shrinking anything is the human's ruling. The value is
   app-local: mysuite's sidebar-footer.md §6.4 *records* this width, it does not
   pin it — only the **unit** is constrained, and the prohibition there is on
   converting px to rem (26.25rem reached 630px at a 24px root), never on the
   number moving. That is the durable form of the claim and it is what to rely
   on. §6.4 states it as "sidebar widths were never unified — 200 / 220 / 540
   are deliberately different"; the quotation is kept because it makes the claim
   checkable by grep, and it is expected to perish — it read 420 until MyNotes'
   own widening landed and mysuite updated §6.4 to match. If it no longer
   matches, the sentence above it is still true.

   Do not set this to the measured minimum. A column sitting on the threshold is
   one label change or one translated string away from failing again, and
   §"logo" in web/AGENTS.md explains why that failure is invisible — the tab
   strip shrinks below its content and paints under the buttons, moving nothing
   and clipping nothing. That is what the tolerance above buys.

   The main pane pays for it: 76px narrower at every viewport width. That is the
   human's ruling, taken with the alternative (a smaller label than the siblings')
   on the table.

   **The font-width axis is the one this covers**, and the root-font axis is only
   incidentally improved — do not read it as fixed. Measured needs, this machine:

     20px root → 535px   fits in 540, but at 1.02× — no margin at all
     24px root → 612px   still overflows, unchanged

   So the pre-existing WCAG 1.4.4 defect at Chrome's "Large" is *not* bought
   here. The 20px row happens to fit this machine's font and would overflow on
   CI's 1.25×-wider one, which is precisely the state this whole change exists to
   get out of. e2e/tests/logo.spec.ts therefore still asserts the fit at a 16px
   root only; extending it to 20px would pin a threshold, not a margin.

   **Viewport width is deliberately not addressed:** there is no @media anywhere
   in web/static/*.css, so this column stays 540px at every width and
   .app-body's overflow:hidden clips the main pane. Below ~546px of viewport the
   main pane was already at its floor before any of this and still is — 76px more
   of it is now unreachable, which is accepted, not overlooked. Mobile layout is
   a separate question from the badge. */
.sidebar {
  width: 540px;
  flex-shrink: 0;
  border-right: 1px solid var(--border);
  background: var(--surface);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  /* Top padding IS the badge's distance from the top of the viewport — 14px,
     the shared logo contract's placement, matching MyMail's own padding-top and
     the number MyCal's min-height was chosen to land on. It reads as ordinary
     spacing and is not: .sidebar-brand is top-aligned against it (see there), so
     changing this moves the badge out of contract.
     No bottom padding: .sidebar-footer is full-bleed and owns the 8px inset
     below its buttons (see there). */
  padding: 14px 0 0;
}

/* The header row stays put; this wrapper holds each tab's content below it. It
   does not scroll itself — the scroll region lives inside (the whole list for
   the tags tab, only the note rows for the notes tab) so the scrollbar never
   runs alongside the notes tab's fixed controls. */
.sidebar-content {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  padding: 0 16px;
}

/* Tags tab: the whole list is the scroll region within the sidebar content. */
.tag-list {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}

/* Notes tab: split the list into fixed controls + a scrolling rows region, so
   the search inputs, sort, tag filter and note count stay put while only the
   rows scroll — and the scrollbar sits beside the rows alone.
   The negative margin cancels .sidebar-content's inset so the list spans the
   panel edge to edge (clipping is at the padding box, so nothing is cut off);
   the inset is re-applied to the controls and to each row, leaving the active
   row's highlight and the row dividers full-width. */
.notes-list {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  margin: 0 -16px;
}

.notes-list-controls {
  flex: none;
  padding: 0 16px;
}

/* Padded so the loading/empty messages and the "Load more" button keep the
   sidebar's inset; the row list alone bleeds back out to the panel edge (each
   row carries the inset as padding instead — see .notes-list-scroll .note-row). */
.notes-list-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 0 16px;
}

.notes-list-scroll > ul {
  margin: 0 -16px;
}

/* Graph tab: the rendered diagram scrolls on both axes within the 540px sidebar
   (large graphs pan rather than shrink to fit). */
.sidebar-graph-wrap {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  position: relative;
}

/* Info affordance pinned to the top-right corner; hovering it reveals the
   graph's Mermaid source via the native title tooltip. */
.sidebar-graph-info {
  position: absolute;
  top: 0.35rem;
  right: 0.35rem;
  z-index: 1;
  display: inline-flex;
  color: var(--muted);
  cursor: help;
}

.sidebar-graph {
  flex: 1;
  min-height: 0;
  overflow: auto;
  padding: 0.5rem;
}

.sidebar-graph-msg {
  flex: none;
  padding: 0.5rem;
}

/* Nodes are clickable (navigate to the note); intrinsic diagram size, so wide
   graphs scroll instead of scaling down to illegibility. */
.sidebar-graph g.node {
  cursor: pointer;
}

.sidebar-graph .mermaid-diagram svg {
  height: auto;
}

/* Highlight the node for the note open in the main panel. Mermaid injects an
   id-scoped <style> for node shapes, so !important is needed to win over it. */
.sidebar-graph .graph-node-current rect,
.sidebar-graph .graph-node-current polygon,
.sidebar-graph .graph-node-current circle,
.sidebar-graph .graph-node-current path {
  stroke: var(--primary) !important;
  stroke-width: 3px !important;
}

/* Tag → color key for the node fills, pinned below the scrolling diagram. */
.sidebar-graph-legend {
  flex: none;
  list-style: none;
  margin: 0;
  padding: 0.4rem 0.5rem;
  border-top: 1px solid var(--border);
  display: flex;
  flex-wrap: wrap;
  gap: 0.15rem 0.6rem;
  font-size: 0.78rem;
  color: var(--muted);
  max-height: 30%;
  overflow-y: auto;
}

.sidebar-graph-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
}

.sidebar-graph-swatch {
  width: 0.8rem;
  height: 0.8rem;
  border-radius: 3px;
  border: 1px solid var(--border);
  flex: none;
}

/* Large graph in the main panel (the /graph route). main stops scrolling as a
   whole so the diagram's own scroll region owns both axes, edge to edge. */
main.graph-main {
  overflow: hidden;
  padding: 0;
}

.sidebar-graph-wrap--main {
  height: 100%;
}

/* Scale the diagram up to fill the wider main panel; larger-than-panel graphs
   still scroll (the wrap's overflow), so nothing shrinks to illegibility. */
.sidebar-graph--main .mermaid-diagram svg {
  width: 100%;
  max-width: 100%;
}

.sidebar .toolbar {
  flex-wrap: wrap;
}

.sidebar .toolbar input[type="search"] {
  flex-basis: 100%;
}

main {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 1.5rem;
  display: flex;
  flex-direction: column;
}

main.editor-main {
  padding: 0.5rem 1rem;
  overflow: hidden;
}

/* Overview list in the main panel: keep the "All notes"/tag heading fixed while
   the note rows scroll beneath it. main stops scrolling; the rows region does.
   main's own padding is dropped and re-applied to the heading and scroll region
   so the scrollbar sits at the panel's edge with a gap before the row content
   (otherwise it overlaps the rightmost delete button / tag chip). */
main.overview-main {
  overflow: hidden;
  padding: 0;
}

.overview-list {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.overview-list .overview-heading {
  flex: none;
  /* Bottom padding is the gap above the first note row. Set here (two classes)
     rather than via margin so it beats `.note-title { margin: 0 }`. */
  padding: 1.5rem 1.5rem 0.5rem;
}

.overview-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 0 1.5rem 1.5rem;
}

.note-row--active {
  background: var(--bg);
}

.select-prompt {
  margin: 2rem auto;
}

.toolbar {
  display: flex;
  gap: 0.5rem;
  margin-bottom: 1rem;
}

.toolbar input[type="search"] { flex: 1; }

input, textarea {
  padding: 0.5rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  color: var(--fg);
  font: inherit;
  width: 100%;
}

button {
  padding: 0.45rem 0.9rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--surface);
  color: var(--fg);
  font: inherit;
  cursor: pointer;
}

button.primary { background: var(--primary); color: #fff; border-color: var(--primary); }
button.danger { color: var(--danger); }
/* App-chrome anchors (note titles in the lists, backlinks, help links). Without
   this they fall back to the UA link colour, which is a near-black blue in the
   dark theme. `.note-content a` in note.css is more specific and still wins. */
a { color: var(--link); }

button.link { background: none; border: none; color: var(--link); padding: 0; cursor: pointer; }
button:disabled { opacity: 0.6; cursor: default; }

/* Compact ghost icon buttons — follows mycal action-icon convention */
button.btn-icon,
a.btn-icon {
    padding: 4px 8px;
    font-size: 0.9rem;
    line-height: 1.4;
    background: none;
    color: var(--muted);
}
a.btn-icon {
    display: inline-block;
    border: 1px solid var(--border);
    border-radius: 6px;
    text-decoration: none;
    cursor: pointer;
}
button.btn-icon:not(.active):hover:not(:disabled),
a.btn-icon:hover {
    background: var(--surface);
    color: var(--fg);
}
button.primary.btn-icon {
    background: var(--primary);
    color: #fff;
}
button.danger.btn-icon {
    color: var(--danger);
    border-color: var(--danger);
}
button.danger.btn-icon:hover:not(:disabled) {
    background: rgb(220 38 38 / 0.08);
    color: var(--danger);
}
/* Inline Lucide icons in icon-only buttons: center vertically so the SVG's
   baseline doesn't leave a descender gap under the glyph. */
.btn-icon .lucide,
.tag-chip-remove .lucide,
.toast-item button .lucide {
    vertical-align: middle;
}

.muted { color: var(--muted); }

ul { list-style: none; padding: 0; margin: 0; }

.item-list li {
  border-bottom: 1px solid var(--border);
}

.note-row {
  display: grid;
  grid-template-areas: "title" "date" "excerpt";
  grid-template-columns: 1fr;
  gap: 0.1rem;
  padding: 0.6rem 0;
  position: relative;
}

/* Sidebar rows carry the panel's horizontal inset themselves, so the active
   row's background and the dividers between rows span the panel's full width
   while the text stays aligned with the controls above. */
.notes-list-scroll .note-row {
  padding-left: 16px;
  padding-right: 16px;
}

.note-row .link { grid-area: title; font-size: 1rem; }
.note-row .note-date { grid-area: date; font-size: 0.8rem; white-space: nowrap; }
.note-row .note-excerpt { grid-area: excerpt; margin: 0; font-size: 0.875rem; line-height: 1.4; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* Drop the first row's top padding in the scrolling lists: it adds a gap above
   the list only at rest, since it scrolls out of view the moment the list moves,
   leaving the gap to the fixed header/controls above inconsistent otherwise. */
.notes-list-scroll > ul > li:first-child > .note-row,
.overview-scroll > ul > li:first-child > .note-row {
  padding-top: 0;
}

mark { background: #fef08a; color: var(--fg); border-radius: 2px; padding: 0 1px; }
:root[data-theme="dark"] mark { background: #713f12; }

.result-count { margin: 0 0 0.5rem; font-size: 0.875rem; }
.load-more { margin-top: 1rem; }

.item-form { display: flex; flex-direction: column; gap: 1rem; }
.item-form label { display: flex; flex-direction: column; gap: 0.3rem; font-weight: 500; }
.form-actions { display: flex; gap: 0.5rem; justify-content: flex-end; }

.note-view { display: flex; flex-direction: column; gap: 0.8rem; }

/* Single note view: keep the header (title, dates, tags, toolbar) fixed while
   the note content and backlinks scroll beneath it. main stops scrolling and
   its padding moves onto the header and scroll region (as in the overview) so
   the scrollbar sits at the panel edge with a gap before the content. */
main.note-view-main {
  overflow: hidden;
  padding: 0;
}

.note-view-main .note-view {
  flex: 1;
  min-height: 0;
}

.note-view-main .note-header {
  flex: none;
  padding: 1.5rem 1.25rem 0;
}

.note-view-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  /* 1.25rem horizontal gutter matches the exported HTML's body padding, so the
     rendered text column lines up with a downloaded/printed note. */
  padding: 0 1.25rem 1.5rem;
}

.note-backlinks {
  max-width: calc(65ch - 2.5rem); /* keep the column edge aligned with .note-content */
  margin-top: 0.5rem;
  padding-top: 1rem;
  border-top: 1px solid var(--border);
}

.note-backlinks h2 {
  margin: 0 0 0.5rem;
  font-size: 0.85rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
}

.note-backlinks ul { list-style: none; margin: 0; padding: 0; }
.note-backlinks li + li { margin-top: 0.3rem; }

.note-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
}

.note-header .toolbar { margin-bottom: 0; flex-shrink: 0; }

.note-header-left {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  min-width: 0;
}

.note-title {
  margin: 0;
  font-size: 1.5rem;
  font-weight: 600;
  line-height: 1.3;
}

.note-view-date { font-size: 0.85rem; }

/* Every `.note-content` rule lives in render/note.css (@imported at the top of
   this file), shared with the embeddable render host. */

.toast-container {
  position: fixed;
  bottom: 1rem;
  right: 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  z-index: 1000;
}

.toast-item {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  background: var(--fg);
  color: var(--bg);
  padding: 0.6rem 0.9rem;
  border-radius: 6px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}

.toast-btns { display: flex; gap: 0.3rem; }
.toast-item button { background: transparent; border: none; color: inherit; padding: 0.1rem 0.4rem; }

/* ── Editor (ItemForm) ─────────────────────────────────────────── */

.editor-page {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

/* Toolbar: layout toggles + dirty dot + cancel/save */
.editor-toolbar {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-shrink: 0;
}

.layout-btns { display: flex; }
.layout-btns button { border-radius: 0; }
.layout-btns button:first-child { border-radius: 6px 0 0 6px; }
.layout-btns button:last-child  { border-radius: 0 6px 6px 0; }
.layout-btns button + button    { border-left: none; }
.layout-btns button.active      { background: var(--primary); color: #fff; border-color: var(--primary); }

.toolbar-spacer { flex: 1; }

/* Format toolbar attached to the top of the editor pane */
.format-toolbar {
  display: flex;
  align-items: center;
  gap: 0;
  flex-shrink: 0;
  border-bottom: 1px solid var(--border);
  padding: 3px 6px;
  background: var(--bg);
}

.format-toolbar button.btn-icon {
  width: 24px;
  height: 24px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 3px;
  color: var(--fg);
  font-size: unset;
  line-height: unset;
}

.format-toolbar button.btn-icon:hover:not(:disabled) {
  background: rgba(0, 0, 0, 0.07);
  color: var(--fg);
}

:root[data-theme="dark"] .format-toolbar button.btn-icon:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.1);
  color: var(--fg);
}

.fmt-sep {
  width: 1px;
  height: 18px;
  background: var(--border);
  display: inline-block;
  margin: 0 5px;
  flex-shrink: 0;
}

.heading-menu {
  position: relative;
  display: inline-flex;
  margin-right: 2px;
}

.heading-select {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 24px;
  padding: 0 6px;
  background: none;
  border: 1px solid var(--border);
  border-radius: 3px;
  color: var(--fg);
  cursor: pointer;
  font-size: 0.85rem;
  line-height: 1;
  white-space: nowrap;
}

.heading-select:hover {
  background: rgba(0, 0, 0, 0.07);
}

:root[data-theme="dark"] .heading-select:hover {
  background: rgba(255, 255, 255, 0.1);
}

.heading-select-label {
  min-width: 62px;
  text-align: left;
}

.heading-select-chevron {
  width: 12px;
  height: 12px;
  flex-shrink: 0;
}

.heading-menu-dropdown {
  position: absolute;
  top: calc(100% + 3px);
  left: 0;
  z-index: 300;
  min-width: 170px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  padding: 4px 0;
}

.heading-menu-option {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  border-radius: 0;
  padding: 0.3rem 0.75rem;
  cursor: pointer;
  color: var(--fg);
  line-height: 1.3;
  white-space: nowrap;
}

.heading-menu-option:hover {
  background: rgba(0, 0, 0, 0.07);
}

.heading-menu-option.active {
  background: rgba(0, 0, 0, 0.05);
}

:root[data-theme="dark"] .heading-menu-option.active {
  background: rgba(255, 255, 255, 0.07);
}

:root[data-theme="dark"] .heading-menu-option:hover {
  background: rgba(255, 255, 255, 0.1);
}

/* Hand-drawn format-toolbar glyphs. They take their colour from the button's
   `color` (var(--fg)) rather than a fixed grey, so they follow the theme — a
   fixed dark grey is all but invisible on the dark theme's background. */
.fmt-stroke {
  fill: none;
  stroke: currentColor;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 2;
}

.fmt-stroke.fmt-thin {
  stroke-width: 1;
}

/* Solid shapes. Many glyphs (the strikethrough S, the table cells, the
   horizontal rule) are fill-only and carry no .fmt-stroke, so this must stand on
   its own rather than hang off `.fmt-stroke.fmt-fill` — without a rule they fall
   back to SVG's default black fill and vanish on the dark theme. Declared after
   .fmt-stroke so a shape carrying both classes is filled rather than hollow. */
.fmt-fill {
  fill: currentColor;
}

.fmt-even {
  fill-rule: evenodd;
}

.fmt-transparent {
  opacity: 0.4;
}

/* Meta bar: title + slug on one row */
.editor-meta {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  flex-shrink: 0;
  flex-wrap: wrap;
}

.meta-title {
  flex: 1;
  min-width: 180px;
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
  font-weight: 500;
  white-space: nowrap;
}
.meta-title input { flex: 1; min-width: 0; }

.meta-slug {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
  font-weight: 500;
  white-space: nowrap;
}
.meta-slug input { width: 200px; }
.meta-label-text { font-weight: 500; }

.slug-preview {
  font-family: ui-monospace, 'Cascadia Code', monospace;
  font-size: 0.85rem;
  color: var(--muted);
}
.slug-warning { font-size: 0.8rem; color: var(--danger); font-weight: normal; }

/* Split / editor / preview layout */
.editor-layout {
  flex: 1;
  min-height: 0;
  display: flex;
  gap: 0.75rem;
}

.editor-pane {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  border: 1px solid var(--border);
  border-radius: 6px;
  overflow: hidden;
}

/* CodeMirror mount point: fills the pane below the format toolbar */
.editor-cm {
  flex: 1;
  min-height: 0;
  overflow: hidden;
}

/* The preview pane is just the scroll box: it mirrors .editor-pane's box exactly
   (border, no horizontal padding) so the two panes split the split-view width
   evenly. Horizontal padding here would be added to the flex base size under
   border-box + flex-basis:0% and make this pane wider than the editor pane, so
   the text gutter lives on the inner .note-content instead (see below). */
.preview-pane {
  flex: 1;
  min-width: 0;
  overflow-y: auto;
  padding: 0.75rem 0;
  border: 1px solid var(--border);
  border-radius: 6px;
}

/* Inner rendered note: .note-content supplies the max-width cap (so the text
   wraps line-for-line like the read view and the export). The gutter is a margin
   rather than padding so it neither inflates the flex pane nor eats into the
   max-width cap. */
.preview-pane .note-content {
  margin: 0 0.75rem;
}

/* Hide the non-active pane (element stays in DOM so CM isn't destroyed) */
.editor-layout--preview .editor-pane  { display: none; }
.editor-layout--editor  .preview-pane { display: none; }

/* CodeMirror overrides */
.cm-editor {
  height: 100%;
  background: var(--bg);
  color: var(--fg);
}
.cm-editor.cm-focused { outline: none; }

.cm-scroller {
  overflow: auto;
  height: 100%;
  font-family: ui-monospace, 'Cascadia Code', 'Fira Code', 'JetBrains Mono', monospace;
  font-size: 0.9rem;
  line-height: 1.6;
}

.cm-content {
  padding: 0.75rem 1rem;
  min-height: 100%;
  max-width: 82ch; /* We want to wrap at column 80, for some reason we need to have 82ch for that to happen. */
}

/* CodeMirror's base theme pins the caret with `&light .cm-content { caret-color:
   black }` — a specificity a plain `.cm-content` rule here cannot beat, so on the
   dark theme the caret is black on a near-black background, i.e. invisible. (We
   never enable CM's own dark theme: styling lives in this file, not in JS themes
   — see web/ts/third_party/rebuild.sh, which deliberately omits EditorView.theme.) */
:root[data-theme="dark"] .cm-editor .cm-content {
  caret-color: var(--fg);
}

/* Markdown syntax highlighting in the editor.

   The editor runs @lezer/highlight's classHighlighter, which only tags spans
   with stable `tok-*` class names — every colour is chosen here, off the theme
   variables, so it follows the light/dark toggle. (CodeMirror's stock
   defaultHighlightStyle instead bakes fixed light-theme colours into generated
   class names app.css cannot reach; its markers came out at #404740, about
   1.5:1 against the dark background.)

   Markdown yields a small subset of the token vocabulary: tok-meta for every
   literal marker (#, **, `, >, -, a link's []() and — with the GFM extensions
   enabled — a table's |, a `~~` and a `[x]`'s brackets), tok-heading (also a
   table's header cells), tok-strong, tok-emphasis, tok-link, tok-url (also a
   bare autolinked URL/email), tok-labelName (a fence's language), tok-comment,
   tok-atom (a task marker's [ ]/[x] state) and tok-string (an emoji
   `:shortcode:`, a link title). tok-strikethrough is the one class
   classHighlighter does not emit; NoteEditor.tsx adds a second highlighter for
   it. Markers carry their construct's class too — `#` is
   `tok-heading tok-meta` — so tok-meta is declared last to win those ties and
   keep every marker one consistent colour. */
.cm-content .tok-heading {
  font-weight: 600;
  text-decoration: underline;
}
.cm-content .tok-strong { font-weight: 700; }
.cm-content .tok-emphasis { font-style: italic; }
.cm-content .tok-strikethrough { text-decoration: line-through; }
.cm-content .tok-subscript,
.cm-content .tok-superscript {
  font-size: 0.75em;
}
.cm-content .tok-subscript { vertical-align: sub; }
.cm-content .tok-superscript { vertical-align: super; }
.cm-content .tok-link,
.cm-content .tok-url { color: var(--link); }
.cm-content .tok-labelName { color: var(--muted); }
.cm-content .tok-string { color: var(--primary); }
.cm-content .tok-comment {
  color: var(--muted);
  font-style: italic;
}
.cm-content .tok-meta { color: var(--muted); }
/* The task marker is the whole `[x]`, brackets included, and carries only
   tok-atom — no tok-meta tie to lose, so its position here is free. --primary
   rather than --link: it is a state marker, not something to click. */
.cm-content .tok-atom {
  color: var(--primary);
  font-weight: 600;
}

/* Subtle cursor line highlight */
.cm-activeLine { background: transparent; }

/* Gutter (if any) picks up theme colors */
.cm-gutters {
  background: var(--surface);
  border-right: 1px solid var(--border);
  color: var(--muted);
}

/* Collapsed data: URL placeholder */
.cm-data-url-collapsed {
  background: var(--border);
  border-radius: 3px;
  padding: 0 3px;
  font-size: 0.85em;
  color: var(--muted);
  cursor: default;
}

/* ── Link picker modal ─────────────────────────────────────────── */

.link-picker-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.4);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding-top: 15vh;
  z-index: 500;
}

.link-picker {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
  width: min(480px, 90vw);
  max-height: 60vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.link-picker-input {
  border: none;
  border-bottom: 1px solid var(--border);
  border-radius: 8px 8px 0 0;
  padding: 0.75rem 1rem;
  font-size: 1rem;
  width: 100%;
}

.link-picker-list {
  overflow-y: auto;
  flex: 1;
}

.link-picker-list li { border-bottom: 1px solid var(--border); }
.link-picker-list li:last-child { border-bottom: none; }

.link-picker-item {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  border-radius: 0;
  padding: 0.6rem 1rem;
  cursor: pointer;
  color: var(--fg);
}

.link-picker-item:hover { background: var(--surface); }

.link-picker-empty { margin: 1rem; }

/* ── Emoji picker modal ────────────────────────────────────────── */

.emoji-picker-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.4);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding-top: 15vh;
  z-index: 500;
}

.emoji-picker {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
  width: min(420px, 92vw);
  max-height: 60vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.emoji-picker-input {
  border: none;
  border-bottom: 1px solid var(--border);
  border-radius: 8px 8px 0 0;
  padding: 0.75rem 1rem;
  font-size: 1rem;
  width: 100%;
  flex: none;
}

.emoji-picker-tabs {
  display: flex;
  gap: 0.15rem;
  padding: 0.35rem 0.5rem;
  border-bottom: 1px solid var(--border);
  overflow-x: auto;
  flex: none;
}

.emoji-picker-tab {
  flex: 0 0 auto;
  background: none;
  border: none;
  border-radius: 6px;
  padding: 0.25rem 0.4rem;
  font-size: 1.2rem;
  line-height: 1;
  cursor: pointer;
  opacity: 0.6;
}

.emoji-picker-tab:hover { background: var(--surface); opacity: 1; }
.emoji-picker-tab.active { background: var(--primary); opacity: 1; }

.emoji-picker-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(2.25rem, 1fr));
  gap: 0.1rem;
  padding: 0.5rem;
  overflow-y: auto;
  flex: 1;
  min-height: 0;
}

.emoji-picker-item {
  background: none;
  border: none;
  border-radius: 6px;
  padding: 0.25rem 0;
  font-size: 1.4rem;
  line-height: 1.2;
  cursor: pointer;
  aspect-ratio: 1;
}

.emoji-picker-item:hover { background: var(--surface); }

.emoji-picker-empty { margin: 1rem; }

/* ── Icon picker modal ─────────────────────────────────────────── */

.icon-picker-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.4);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding-top: 15vh;
  z-index: 500;
}

.icon-picker {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
  width: min(460px, 92vw);
  max-height: 60vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.icon-picker-input {
  border: none;
  border-bottom: 1px solid var(--border);
  border-radius: 8px 8px 0 0;
  padding: 0.75rem 1rem;
  font-size: 1rem;
  width: 100%;
  flex: none;
}

.icon-picker-tabs {
  display: flex;
  gap: 0.15rem;
  padding: 0.35rem 0.5rem;
  border-bottom: 1px solid var(--border);
  overflow-x: auto;
  flex: none;
}

.icon-picker-tab {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  border-radius: 6px;
  padding: 0.3rem;
  color: var(--fg);
  cursor: pointer;
  opacity: 0.6;
}

.icon-picker-tab:hover { background: var(--surface); opacity: 1; }
.icon-picker-tab.active { background: var(--primary); color: #fff; opacity: 1; }

.icon-picker-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(2.5rem, 1fr));
  gap: 0.1rem;
  padding: 0.5rem;
  overflow-y: auto;
  flex: 1;
  min-height: 0;
}

.icon-picker-item {
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  border-radius: 6px;
  padding: 0.4rem;
  color: var(--fg);
  cursor: pointer;
  aspect-ratio: 1;
}

.icon-picker-item:hover { background: var(--surface); }

.icon-picker-empty { margin: 1rem; }

.icon-picker-more {
  margin: 0;
  padding: 0.4rem 1rem;
  border-top: 1px solid var(--border);
  font-size: 0.85rem;
  text-align: center;
  flex: none;
}

/* ── Conflict dialog ────────────────────────────────────────────── */

.conflict-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.4);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding-top: 15vh;
  z-index: 600;
}

.conflict-dialog {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
  width: min(420px, 90vw);
  padding: 1.25rem;
}

.conflict-title {
  margin: 0 0 0.5rem;
  font-size: 1.1rem;
}

.conflict-body {
  margin: 0 0 1.25rem;
  color: var(--muted);
}

.conflict-actions {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.conflict-actions button:not(.link) {
  width: 100%;
}

/* ── Demo notice dialog ─────────────────────────────────────────── */

.demo-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.4);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding-top: 15vh;
  z-index: 600;
}

.demo-dialog {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
  width: min(460px, 90vw);
  padding: 1.25rem;
}

.demo-title {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0 0 0.75rem;
  font-size: 1.1rem;
}

.demo-body {
  margin: 0 0 1.25rem;
  color: var(--muted);
}

.demo-body p {
  margin: 0 0 0.75rem;
}

.demo-body p:last-child {
  margin-bottom: 0;
}

.demo-actions {
  display: flex;
  justify-content: flex-end;
}

.demo-actions button {
  min-width: 6rem;
}

/* ── Split dialog ───────────────────────────────────────────────── */

.split-overlay,
.email-overlay,
.publish-overlay,
.settings-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.4);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding-top: 15vh;
  z-index: 600;
}

.split-dialog,
.email-dialog,
.publish-dialog,
.settings-dialog {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
  width: min(420px, 90vw);
  padding: 1.25rem;
}

.split-title,
.email-title,
.publish-title,
.settings-title {
  margin: 0 0 0.5rem;
  font-size: 1.1rem;
}

.split-body,
.email-body,
.publish-body,
.settings-body {
  margin: 0 0 1rem;
  color: var(--muted);
}

.split-tag-selected {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  min-height: 2rem;
  align-items: center;
}

.split-actions,
.email-actions,
.publish-actions,
.settings-actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
  margin-top: 1.25rem;
}

.email-field {
  display: block;
  margin-bottom: 0.75rem;
}

.email-field > span {
  display: block;
  margin-bottom: 0.25rem;
  font-size: 0.85rem;
  color: var(--muted);
}

.email-field > input {
  width: 100%;
}

.email-error,
.publish-error {
  margin: 0.75rem 0 0;
  color: var(--danger);
}

/* ── Publish dialog ──────────────────────────────────────────────── */

.publish-link {
  display: block;
}

.publish-link > span {
  display: block;
  margin-bottom: 0.25rem;
  font-size: 0.85rem;
  color: var(--muted);
}

.publish-link > input {
  width: 100%;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.85rem;
}

/* A published note's toolbar button is tinted so the state is visible without
   opening the dialog — this is the only note state the toolbar carries. */
.btn-icon.is-published {
  color: var(--primary);
}

/* ── Settings dialog ─────────────────────────────────────────────── */

.settings-field {
  display: block;
}

.settings-field > span {
  display: block;
  margin-bottom: 0.25rem;
  font-size: 0.85rem;
  color: var(--muted);
}

.settings-field > input {
  width: 100%;
}

.settings-hint {
  margin: 0.4rem 0 0;
  font-size: 0.8rem;
  line-height: 1.4;
  color: var(--muted);
  /* The derived URL is interpolated here and may be one long token. */
  overflow-wrap: anywhere;
}

.settings-error {
  margin: 0.75rem 0 0;
  color: var(--danger);
}

/* ── Confirm / prompt dialog ─────────────────────────────────────── */

.dialog-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.4);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding-top: 15vh;
  /* Above every other overlay: these ask a question the rest of the UI is
     waiting on, and one can be raised from within another dialog's flow. */
  z-index: 700;
}

.dialog-box {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
  width: min(420px, 90vw);
  padding: 1.25rem;
}

/* Both carry interpolated note and tag titles — arbitrary user text, which may
   be one unbroken 200-character token. Break it rather than overflow the box. */
.dialog-title {
  margin: 0 0 0.5rem;
  font-size: 1.1rem;
  overflow-wrap: anywhere;
}

.dialog-body {
  margin: 0 0 1rem;
  color: var(--muted);
  overflow-wrap: anywhere;
}

.dialog-field {
  display: block;
}

.dialog-field > span {
  display: block;
  margin-bottom: 0.25rem;
  font-size: 0.85rem;
  color: var(--muted);
}

.dialog-field > input {
  width: 100%;
}

.dialog-actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
  margin-top: 1.25rem;
}

/* ── Markdown help popup ─────────────────────────────────────────── */

.markdown-help-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.4);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding-top: 4vh;
  z-index: 550;
}

.markdown-help {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
  width: min(560px, 92vw);
  max-height: 92vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.markdown-help-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.75rem 1rem;
  border-bottom: 1px solid var(--border);
  flex: none;
}

.markdown-help-title {
  margin: 0;
  font-size: 1.1rem;
}

.markdown-help-body {
  padding: 0.5rem 1rem 1rem;
  overflow-y: auto;
  flex: 1;
  min-height: 0;
}

.markdown-help-section {
  margin-top: 1rem;
}

.markdown-help-section-title {
  margin: 0 0 0.35rem;
  font-size: 0.85rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
}

.markdown-help-list {
  margin: 0;
  display: grid;
  grid-template-columns: minmax(10rem, auto) 1fr;
  gap: 0.3rem 1rem;
}

.markdown-help-row {
  display: contents;
}

.markdown-help-row dt {
  margin: 0;
}

.markdown-help-syntax {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 0.1rem 0.35rem;
  font-size: 0.85rem;
  white-space: pre-wrap;
}

.markdown-help-desc {
  margin: 0;
  align-self: center;
  color: var(--fg);
}

.markdown-help-note {
  margin: 0.5rem 0 0;
  font-size: 0.85rem;
  color: var(--muted);
}

/* ── Sidebar tabs ───────────────────────────────────────────────── */

.sidebar-tabs {
  display: flex;
  align-items: flex-end;
  gap: 0.25rem;
  flex: 1;
  min-width: 0;
}

/* Underline-style tabs: each reaches the header baseline and the active one
   paints a coloured underline over the header's 1px border (via the -1px
   margin), giving the classic "connected tab" look rather than plain buttons. */
.sidebar-tab {
  padding: 0.5rem 0.75rem;
  margin-bottom: -1px;
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  border-radius: 0;
  color: var(--muted);
  cursor: pointer;
  font-size: 0.9rem;
  font-weight: 500;
}

.sidebar-tab:hover:not(.active) {
  color: var(--fg);
}

.sidebar-tab.active {
  color: var(--link);
  border-bottom-color: var(--link);
}

.sidebar-actions {
  flex: none;
  align-self: center;
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.sidebar-reload {
  flex: none;
  align-self: center;
}

/* Sidebar footer: holds the global light/dark toggle, pinned below the scrolling
   sidebar content and above the sidebar's bottom edge, visible on every tab.
   Full-bleed, unlike the header and .sidebar-content which are inset by 0.75rem:
   the separator spans the whole sidebar and the MySuite spec's 8px inset to the
   buttons comes from this element's padding, never a margin. .sidebar therefore
   contributes no bottom padding — the 8px below the buttons is this padding-bottom. */
.sidebar-footer {
  flex: none;
  margin: 0.75rem 0 0;
  padding: 8px;
  border-top: 1px solid var(--border);
}

/* Demo builds only (mynotes -demo-server / -demo-bundle): a standing reminder
   that the notes live in this browser and nowhere else. */
.demo-badge {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin: 0 0 0.5rem;
  font-size: 0.75rem;
  line-height: 1.3;
  color: var(--muted);
}

.demo-badge svg {
  flex: none;
}

/* The footer's own buttons (theme toggle, Settings). Never wraps, and the budget
   is generous: the row has 523px of content box here (540px sidebar − 1px border
   − 16px of this footer's padding). The pair measured 174px when this was
   written, but that is a Chromium-on-Linux reading of text in whatever
   `system-ui` resolves to — re-measure it rather than trusting it if a label
   changes. See mysuite spec/sidebar-footer.md §2.4. */
.sidebar-footer-actions {
  display: flex;
  flex-wrap: nowrap;
  gap: 6px;
}

/* Theme toggle and Settings. Geometry, colours and interaction are fixed by the
   MySuite canonical spec — ../mysuite, spec/sidebar-footer.md, which is where
   these values are defined and where a change to them has to start.
   These two controls must render identically in MyCal,
   MyMail and MyNotes, so the numbers below are literals rather than the app's
   usual rem steps, and the computed outer height must stay 29.2px at the default
   16px root font size (1.5 × 0.80rem line box + 8px padding + 2px border).
   Restates every property the spec pins, rather than inheriting it from `button`
   or `.btn-icon`, so those can change without dragging the pinned appearance with
   them. `font-family` is the one exception, left as `inherit` by the spec itself.
   These two carry no `.btn-icon` — the block below overrides every property that
   class contributes, and keeping it would only mean out-specifying its hover rule
   here. */
.sidebar-footer-actions .settings-open {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  /* Content-sized, and it must STAY that way: a flex item shrinks before it
     overflows, and these are otherwise saved only by `min-width: auto` resolving
     to the nowrap content width. `min-width: 0` is a routine flex reset (this
     file uses it in eight places); if one ever reached these, the labels would
     shrink and clip with no ellipsis to show for it. Pinned so the only possible
     failure is a visible overflow. */
  flex-shrink: 0;
  padding: 4px 8px;
  /* These three already computed to exactly this before being written down: here
     they reach it by inheriting from `body` (the `button` rule above is
     `font: inherit`, so the UA's button font never applies), while the sibling
     apps arrive at the same values by other routes. Pinned because agreement by
     coincidence is not a contract — an ordinary `button { font-weight: 500 }` in
     any one repo would break the match silently. */
  font-family: inherit;
  font-weight: 400;
  font-style: normal;
  /* 0.80rem, not the 0.8rem used elsewhere in this file: written in the spec's
     two-decimal form so grepping the canonical value finds all three repos. */
  font-size: 0.80rem;
  line-height: 1.5;
  text-align: center;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: none;
  color: var(--muted);
  white-space: nowrap;
  cursor: pointer;
  transition: background 0.12s, color 0.12s, border-color 0.12s;
}

/* Both halves of this are pinned, separately, in mysuite spec/sidebar-footer.md:
   §5.1 pins the fill; §5.3 pins what it sits on, per app and theme, and the three
   are accepted to differ there. (Bare § below are that file; the one AGENTS.md
   reference is marked, because its §2.2 and the spec's are different rules.) Here
   the backdrop is --surface — #f9fafb light, #1f2937 dark — reached by the footer
   being transparent and .sidebar painting it: neither .sidebar-footer nor the
   resting rule above declares an opaque background, so the panel shows through.
   Measured against that backdrop, this fill is 1.053:1 light and 1.424:1 dark.
   The light figure is the weakest in the suite, and §5.1's floor is that figure —
   so this app does not merely satisfy the floor, it is the source of it. Changing
   either operand here would put this app below a floor it supplies, and the floor
   moves in the spec, never here (AGENTS.md §2.2). Note it forbids only getting
   worse than the worst: clearing it is not evidence the hover is visible, which
   §10.1 records as open for all three.
   Do not copy MyCal's light fill (#e5e7eb): that is its recorded deviation, for a
   backdrop that IS #f3f4f6.
   Hover stays clearly signalled regardless, since the border and text both change
   too, but "identical on hover" is not true across the suite — for a reason
   recorded in §5.1, not by accident. */
.sidebar-footer-actions .settings-open:hover:not(:disabled) {
  background: var(--hover-bg);
  color: var(--fg);
  border-color: var(--faint);
}

/* The offset is load-bearing, not decoration: it lifts the outline clear of the
   button's own 1px border so both of its adjacent colours are the sidebar's
   --surface, giving 4.9:1 (light) and 4.0:1 (dark) — WCAG 1.4.11 wants 3:1
   against adjacent colours. Sitting flush against --border instead, the dark
   outline would reach only 2.8:1, and no opacity could lift it — that is a
   ceiling of the two hues, which is why the suite settled on an opaque offset
   outline rather than a translucent ring. An outline is also painted under
   forced colours, where a box-shadow is not. Keep it at 2px — that is the
   minimum indicator area WCAG 2.4.13 asks for. */
.sidebar-footer-actions .settings-open:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}

/* ── Tag management ─────────────────────────────────────────────── */

.tag-manager-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0;
}

.tag-manager-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: left;
}

.tag-manager-count {
  flex: none;
  font-size: 0.8rem;
}

.tag-manager-delete {
  flex: none;
}

/* ── Tags ───────────────────────────────────────────────────────── */

.tag-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  margin-top: 0.15rem;
}

.tag-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0.1rem 0.6rem;
  font-size: 0.78rem;
  line-height: 1.5;
  color: var(--fg);
  text-decoration: none;
  white-space: nowrap;
}

a.tag-chip:hover { border-color: var(--link); color: var(--link); }

.tag-chip-remove {
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  font-size: 0.9rem;
  line-height: 1;
  color: var(--muted);
  cursor: pointer;
}
.tag-chip-remove:hover { color: var(--danger); }

.note-row {
  grid-template-areas: "title" "date" "excerpt" "tags";
}
.note-row .tag-chips { grid-area: tags; }

/* Overview rows carry the per-note action toolbar in a right-hand column,
   aligned to the title line to save vertical space (as in the read view). */
.note-row--actions {
  grid-template-areas:
    "title   actions"
    "date    actions"
    "excerpt actions";
  grid-template-columns: 1fr auto;
  column-gap: 0.5rem;
}
/* Right-hand column: the action toolbar with the note's tag chips stacked
   directly beneath it, both right-aligned. Keeping the chips here fills the
   space beside the note text rather than adding a full-width row below. */
.note-row-side {
  grid-area: actions;
  align-self: start;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.3rem;
}
.note-row-side .tag-chips {
  margin-top: 0;
  justify-content: flex-end;
}
.note-row-actions {
  margin-top: 0;
  margin-bottom: 0;
  flex-wrap: wrap;
  justify-content: flex-end;
}

.meta-tags {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
  font-weight: 500;
  flex: 1;
  min-width: 220px;
}

.tag-picker {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.3rem;
}

.tag-picker-input-wrap { position: relative; flex: 1; min-width: 160px; }

.tag-picker-input {
  width: 100%;
  min-width: 160px;
}

.tag-picker-dropdown {
  position: absolute;
  top: calc(100% + 2px);
  left: 0;
  right: 0;
  z-index: 300;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  max-height: 220px;
  overflow-y: auto;
}

.tag-picker-dropdown li { border-bottom: 1px solid var(--border); }
.tag-picker-dropdown li:last-child { border-bottom: none; }

.tag-picker-option,
.tag-picker-create {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  border-radius: 0;
  padding: 0.4rem 0.75rem;
  cursor: pointer;
  color: var(--fg);
  font-size: 0.9rem;
}

.tag-picker-option:hover,
.tag-picker-create:hover:not(:disabled) { background: var(--surface); }

.tag-picker-create {
  font-style: italic;
  color: var(--link);
}
.tag-picker-create:disabled { color: var(--muted); cursor: default; }

.tag-filter-row {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin-bottom: 0.5rem;
}

.tag-filter-label {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex: 1;
  min-width: 0;
  font-size: 0.85rem;
  font-weight: 500;
}

.tag-filter-name {
  flex: none;
  width: 2.5rem;
}

.tag-filter-select {
  flex: 1;
  font-size: 0.85rem;
  padding: 0.2rem 0.4rem;
}

/* Multi-tag filter: the "Tags" label and the add-tag picker share one row so
   the picker stays put; the active-filter chips wrap onto their own line below
   rather than shoving the picker down as they are added/removed. */
.tag-filter-tags {
  margin-bottom: 0.5rem;
}

.tag-filter-tags .tag-filter-row-tags {
  margin-bottom: 0;
}

/* Always rendered; reserve one chip row so height stays constant between zero
   and one active tag (0.78rem font × 1.5 line-height + 0.2rem padding + border). */
.tag-filter-tags .tag-chips {
  margin-top: 0.35rem;
  min-height: 1.5rem;
}

/* Token management and the separate device consent page. */
.settings-tabs { display: flex; gap: 8px; margin-bottom: 16px; }
.settings-tabs [aria-selected="true"] { background: var(--hover-bg); font-weight: 600; }
.settings-dialog { max-height: 90vh; overflow-y: auto; }
.device-review:disabled { background: var(--hover-bg); color: var(--muted); opacity: .55; cursor: not-allowed; }
.device-approval dl { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 8px 16px; }
.device-approval dd { margin: 0; overflow-wrap: anywhere; }
.device-approval dt { font-weight: 600; }
.device-actions { display: flex; gap: 8px; }
.api-token-list { padding-left: 20px; }
.api-token-list li { margin: 12px 0; overflow-wrap: anywhere; }
.device-page { margin: 0; padding: 24px; }
.device-page-content { max-width: 720px; margin: auto; }

/* Keyboard focus remains visible on links and the read pane. */
.note-row > a.link:focus-visible,
.tag-manager-name:focus-visible,
.note-view-scroll:focus-visible {
  outline: 2px solid var(--link);
  outline-offset: 2px;
}

/* Make room for the inset ring without shifting the title text. */
.note-row > a.link {
  padding-inline: 4px;
  margin-inline: -4px;
}

/* The first title is flush with the scrollport; keep its ring inside the
   link box so the top edge remains visible without adding a gap to the list. */
.note-row > a.link:focus-visible {
  outline-offset: -2px;
}

/* Tag buttons meet the scrollport's left edge. An inset ring and internal
   spacing keep the border visible and clear of the text. */
button.tag-manager-name {
  padding-inline: 4px;
  border-radius: 2px;
}
.tag-manager-name:focus-visible {
  outline-offset: -2px;
}
