/* Noted - app chrome + rendered Markdown content styling.
   Theme is applied as data-theme="sakura"|"cherry"|"forest-brew"|"tea-mist"|
   "blueberry"|"kokoblu"|"dubai" on <html> (see src/theme.ts, a popover
   picker - seven themes don't fit a two-state cycle button). Cherry is the
   default for a first-time visitor (no stored preference) - see
   src/theme.ts's resolveInitialTheme.
   Two original themes:
   - Sakura (light): pink surfaces, burgundy as the darkest/text color.
   - Cherry (dark): charcoal grey-black surfaces, neon pink accent, glowing
     button hover.
   Five later additions, palettes supplied directly by the owner (only a
   handful of anchor colors given per theme - every other token below was
   derived from those following the same tint/shade relationships already
   established by Sakura/Cherry, e.g. -sunken/-surface/-overlay each a step
   lighter, -subtle text a blend between text and background, danger/
   warning/success picked to stay a distinct hue from the theme's own
   accent):
   - Forest Brew (dark): deep forest green surfaces, olive-lime accent/text.
   - Tea Mist (light): sage-green surfaces, dark forest-green accent/text.
   - Blueberry (dark): a blue-slate page background paired with a
     deliberately distinct deep-plum component surface (the owner's
     "Deep Surface" value - not a lightness step of the page background,
     a different hue entirely), periwinkle-blue accent. Note: the owner's
     exact text (#BABCD3) / background (#4B4F76) pair computes to ~4.2:1
     contrast - passes WCAG AA for large/bold text (3:1) but falls just
     short of the 4.5:1 body-text threshold. Shipped as given rather than
     silently altered; flagged here for visibility.
   - Kokoblu (dark, no glow): dark brown surfaces, steel-blue accent/text.
   - Dubai (dark, no glow): near-black brown surfaces, olive-lime
     accent/text.
   Kokoblu/Dubai deliberately never define --ds-glow (see below) and use a
   plain neutral elevation shadow rather than the brand-tinted one the
   other dark themes use, per the owner's explicit "no glow effect".
   The glow-on-hover treatment (`--ds-glow`, `.btn:hover` etc. further down)
   was Cherry-only; it's now theme-agnostic (falls back to `none` via
   `var(--ds-glow, none)`) so it naturally applies to every theme that
   defines `--ds-glow` (Cherry, Forest Brew, Tea Mist, Blueberry) and skips
   the ones that don't (Sakura, Kokoblu, Dubai).
   (Previously a three-way light/dark/sakura cycle - dark mode was retired,
   "light" was recolored and renamed to "Sakura", and the old "sakura" was
   renamed to "Cherry".)

   Restyled to the Atlassian Design System's visual language: its neutral/
   brand color scale (Blue 500/400, Neutral N-series light / DN-series
   dark), 8px-grid spacing, 3px component radius, and elevation shadows -
   applied here as plain CSS custom properties, since this app stays
   vanilla DOM/CSS with no framework (no @atlaskit/React dependency added -
   see the ADR recorded for that scope decision). Charlie Sans, Atlassian's
   actual typeface, isn't a freely embeddable web font, so the font stack
   below is ADS's own documented system-font fallback chain. */

/* "Noted" wordmark font - Erica One (SIL OFL 1.1, assets/fonts/OFL.txt),
   self-hosted rather than linked from Google Fonts' CDN: this app is
   local-first/offline-capable (PRODUCT-SPEC.md Section 4, and the actual
   offline-mode service worker - src/sw.ts) - a runtime CDN font request
   would silently break both once the page itself has loaded, and would be
   a network dependency this app otherwise has none of. Only the logo uses
   it; body text stays on the system-font stack above. */
@font-face {
  font-family: "Erica One";
  src: url("assets/fonts/EricaOne-Regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

:root {
  /* Sakura (default/base theme - no data-theme override needed since this
     is what applies whenever data-theme="sakura" is set). Light pink,
     burgundy as the darkest color. */

  /* Surfaces */
  --ds-surface: #fff0f5;
  --ds-surface-sunken: #ffe0eb;
  --ds-surface-overlay: #fff5f9;

  /* Text */
  --ds-text: #4a0e2e;
  --ds-text-subtle: #8a4a63;
  --ds-text-inverse: #fff0f5;

  /* Border */
  --ds-border: #f3c6d6;
  --ds-border-bold: #d98aa8;

  /* Brand / status */
  --ds-brand-bold: #c2185b;
  --ds-brand-bold-hover: #e64980;
  --ds-brand-subtle: #ffd6e5;
  --ds-danger: #de350b;
  --ds-danger-subtle: #ffebe6;
  --ds-warning: #ffab00;
  --ds-success: #36b37e;

  /* Code / drop zone */
  --ds-code-bg: #f8e1ea;
  --ds-drop-bg: #ffe4ec;

  /* Elevation (Atlassian's documented "raised" shadow) */
  --ds-shadow-raised: 0 1px 1px rgba(9, 30, 66, 0.25), 0 0 1px 1px rgba(9, 30, 66, 0.13);

  /* Shape / grid */
  --ds-radius: 3px;
  --ds-radius-large: 8px;
  --ds-focus-ring: #4c9aff;
  /* .content's custom scrollbar width (styles.css's ::-webkit-scrollbar
     rules) - a fixed value rather than each OS's variable default width,
     so page-markers.ts can size/position its dot overlay to exactly match
     without measuring it at runtime. */
  --ds-scrollbar-width: 12px;

  /* Back-compat aliases so any not-yet-migrated rule keeps working */
  --bg: var(--ds-surface-sunken);
  --surface: var(--ds-surface);
  --fg: var(--ds-text);
  --muted: var(--ds-text-subtle);
  --border: var(--ds-border);
  --accent: var(--ds-brand-bold);
  --code-bg: var(--ds-code-bg);
  --drop-bg: var(--ds-drop-bg);
  --danger: var(--ds-danger);
}

/* Cherry - charcoal grey-black surfaces, neon pink instead of blue/rose as
   the accent. Buttons additionally get a glowing pink hover effect - see
   `html[data-theme="cherry"] .btn:hover` etc. below. The logo also gets
   the brand-pink color here (see .app-title override further down)
   instead of the theme's own (light, low-contrast-looking-white) text
   color. */
html[data-theme="cherry"] {
  --ds-surface: #141316;
  --ds-surface-sunken: #0e0d10;
  --ds-surface-overlay: #1c1a1d;

  --ds-text: #ece7ea;
  --ds-text-subtle: #a99aa1;
  --ds-text-inverse: #141316;

  --ds-border: #2c262a;
  --ds-border-bold: #45383e;

  --ds-brand-bold: #ff2ea6;
  --ds-brand-bold-hover: #ff5ec2;
  --ds-brand-subtle: #3a1230;
  --ds-danger: #ff6b81;
  --ds-danger-subtle: #401019;
  --ds-warning: #e2b203;
  --ds-success: #4bce97;

  --ds-code-bg: #1c181c;
  --ds-drop-bg: #2a0f22;

  --ds-shadow-raised: 0 1px 1px rgba(0, 0, 0, 0.5), 0 0 1px 1px rgba(255, 46, 166, 0.16);
  --ds-glow: 0 0 0 1px rgba(255, 46, 166, 0.45), 0 0 14px rgba(255, 46, 166, 0.55);
}

/* Forest Brew - deep forest green surfaces, olive-lime accent (given as
   both "Text" and "Button" by the owner - deliberately the same color, not
   a derivation mistake). Dark theme: subtle tints (-brand-subtle etc.) skew
   dark, same convention Cherry established. */
html[data-theme="forest-brew"] {
  --ds-surface: #283624;
  --ds-surface-sunken: #212e1e;
  --ds-surface-overlay: #324230;

  --ds-text: #acc54e;
  --ds-text-subtle: #8fa968;
  --ds-text-inverse: #1a2416;

  --ds-border: #3a4a34;
  --ds-border-bold: #56684c;

  --ds-brand-bold: #acc54e;
  --ds-brand-bold-hover: #c3db6e;
  --ds-brand-subtle: #3c4a24;
  --ds-danger: #ff8a65;
  --ds-danger-subtle: #3d2018;
  --ds-warning: #e8c547;
  --ds-success: #5fbe8d;

  --ds-code-bg: #263424;
  --ds-drop-bg: #2c3a28;

  --ds-shadow-raised: 0 1px 1px rgba(0, 0, 0, 0.5), 0 0 1px 1px rgba(172, 197, 78, 0.16);
  --ds-glow: 0 0 0 1px rgba(172, 197, 78, 0.35), 0 0 14px rgba(172, 197, 78, 0.35);
}

/* Tea Mist - sage-green surfaces, dark forest-green accent (again given as
   both "Text" and "Button" by the owner). Light theme: subtle tints skew
   light and the elevation shadow stays the neutral Atlassian navy, same
   convention Sakura established, rather than Forest Brew/Blueberry's
   brand-tinted dark-theme shadow. */
html[data-theme="tea-mist"] {
  --ds-surface: #d6dcbc;
  --ds-surface-sunken: #cad1ab;
  --ds-surface-overlay: #e3e7ce;

  --ds-text: #242f21;
  --ds-text-subtle: #56604e;
  --ds-text-inverse: #cad1ab;

  --ds-border: #b9c293;
  --ds-border-bold: #9ca876;

  --ds-brand-bold: #242f21;
  --ds-brand-bold-hover: #3d4b37;
  --ds-brand-subtle: #dce2c4;
  --ds-danger: #c1502e;
  --ds-danger-subtle: #f3ddd3;
  --ds-warning: #b8860b;
  --ds-success: #3f7d4a;

  --ds-code-bg: #d2d8b7;
  --ds-drop-bg: #d8debd;

  --ds-shadow-raised: 0 1px 1px rgba(9, 30, 66, 0.25), 0 0 1px 1px rgba(9, 30, 66, 0.13);
  --ds-glow: 0 0 0 1px rgba(36, 47, 33, 0.25), 0 0 14px rgba(36, 47, 33, 0.25);
}

/* Blueberry - blue-slate page background (the owner's "Background") paired
   with a deliberately distinct deep-plum component surface (the owner's
   "Deep Surface" - a different hue, not a lightness step of the page
   background - see the file-header note on this theme's contrast too).
   Primary Accent is --ds-brand-bold; Secondary Accent (a lighter tint of
   it) maps to --ds-brand-bold-hover, same "hover is the lighter tint of
   the base accent" relationship every other theme already uses. */
html[data-theme="blueberry"] {
  --ds-surface: #411b32;
  --ds-surface-sunken: #4b4f76;
  --ds-surface-overlay: #5a2f49;

  --ds-text: #babcd3;
  --ds-text-subtle: #9799b5;
  --ds-text-inverse: #411b32;

  --ds-border: #5a5e82;
  --ds-border-bold: #757aa3;

  --ds-brand-bold: #647ebd;
  --ds-brand-bold-hover: #acbadb;
  --ds-brand-subtle: #2e3350;
  --ds-danger: #e0708a;
  --ds-danger-subtle: #3d1f2a;
  --ds-warning: #d9a441;
  --ds-success: #5fbe9a;

  --ds-code-bg: #3e2038;
  --ds-drop-bg: #453055;

  --ds-shadow-raised: 0 1px 1px rgba(0, 0, 0, 0.5), 0 0 1px 1px rgba(100, 126, 189, 0.16);
  --ds-glow: 0 0 0 1px rgba(100, 126, 189, 0.4), 0 0 14px rgba(100, 126, 189, 0.4);
}

/* Kokoblu - dark brown surfaces, steel-blue accent/text. Explicitly no
   glow: --ds-glow is never defined here, so `var(--ds-glow, none)`
   (the theme-agnostic hover rule further down) falls back to `none`, same
   as Sakura. The elevation shadow also stays a plain neutral black rather
   than the brand-tinted ambient shadow the glow-having dark themes use. */
html[data-theme="kokoblu"] {
  --ds-surface: #3a2924;
  --ds-surface-sunken: #31221d;
  --ds-surface-overlay: #453330;

  --ds-text: #a7bdd7;
  --ds-text-subtle: #8c97a3;
  --ds-text-inverse: #241812;

  --ds-border: #4a3a34;
  --ds-border-bold: #665048;

  --ds-brand-bold: #a7bdd7;
  --ds-brand-bold-hover: #c3d3e6;
  --ds-brand-subtle: #2e3a45;
  --ds-danger: #e08566;
  --ds-danger-subtle: #3d241c;
  --ds-warning: #d9b054;
  --ds-success: #6fbe8a;

  --ds-code-bg: #362621;
  --ds-drop-bg: #3c2b26;

  --ds-shadow-raised: 0 1px 1px rgba(0, 0, 0, 0.5), 0 0 1px 1px rgba(0, 0, 0, 0.3);
}

/* Dubai - near-black brown surfaces, olive-lime accent/text. Explicitly no
   glow, same reasoning as Kokoblu above. */
html[data-theme="dubai"] {
  --ds-surface: #33201c;
  --ds-surface-sunken: #2a1613;
  --ds-surface-overlay: #402a24;

  --ds-text: #abc44f;
  --ds-text-subtle: #8fa168;
  --ds-text-inverse: #1f110e;

  --ds-border: #43302a;
  --ds-border-bold: #5e463d;

  --ds-brand-bold: #abc44f;
  --ds-brand-bold-hover: #c3d876;
  --ds-brand-subtle: #3a431f;
  --ds-danger: #e2705a;
  --ds-danger-subtle: #3a2018;
  --ds-warning: #d9a544;
  --ds-success: #5fbe8d;

  --ds-code-bg: #301f1a;
  --ds-drop-bg: #362520;

  --ds-shadow-raised: 0 1px 1px rgba(0, 0, 0, 0.5), 0 0 1px 1px rgba(0, 0, 0, 0.3);
}

* {
  box-sizing: border-box;
}

html,
body {
  height: 100%;
}

body {
  margin: 0;
  background: var(--ds-surface-sunken);
  color: var(--ds-text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell,
    "Helvetica Neue", sans-serif;
  font-size: 14px;
  line-height: 1.4285714286;
  -webkit-font-smoothing: antialiased;
  /* The whole point of this layout: the browser window itself never
     scrolls - only .content (inside each pane) does, so a long document
     doesn't grow the page past the viewport. Everything from here down to
     .content needs `height: 100%`/`flex: 1` + `min-height: 0` at every
     level for that to actually hold - a flex/grid item's default
     min-height is `auto` (= its content's natural height), which silently
     overrides an explicit height and lets it grow past its container
     instead of clipping/scrolling - the bug this replaces. */
  overflow: hidden;
}

*:focus-visible {
  outline: 2px solid var(--ds-focus-ring);
  outline-offset: 1px;
}

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

.app-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 24px;
  border-bottom: 1px solid var(--ds-border);
  background: var(--ds-surface);
}

.app-footer {
  /* Auto-hide, taskbar-style: pulled out of #app's flex flow (so .panes
     fills the space it would've taken) and pinned over the bottom edge
     instead, mostly translated off-screen except for an 8px sliver -
     enough to show something's there and stay grabbable/hoverable, since a
     fully-offscreen element can't receive the hover that's meant to bring
     it back. :hover reveals it; JS (src/footer.ts) only toggles
     .footer-locked on click, so it stays open without hovering - unlocking
     it hands control back to plain :hover with no extra JS, so "click
     again while not hovering" auto-hides for free. */
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 20;
  transform: translateY(calc(100% - 8px));
  transition: transform 0.2s ease;
  cursor: pointer;
  text-align: center;
  padding: 6px;
  font-size: 12px;
  color: var(--ds-text-subtle);
  border-top: 1px solid var(--ds-border);
  background: var(--ds-surface);
}

.app-footer:hover,
.app-footer.footer-locked {
  transform: translateY(0);
}

.app-footer a {
  color: var(--ds-brand-bold);
  text-decoration: none;
}

.app-footer a:hover {
  text-decoration: underline;
}

.app-title {
  font-family: "Erica One", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-size: 22px;
  font-weight: 400; /* Erica One only ships a single (regular) weight */
  color: var(--ds-text);
  margin: 0;
}

/* Every theme but Sakura: the theme's own accent color instead of its
   plain text color for the logo (Sakura keeps its own burgundy text color
   here). For Forest Brew/Tea Mist this is a no-op in practice - their
   accent and text colors are the same given value - but it's applied
   uniformly rather than special-cased per theme. */
html[data-theme="cherry"] .app-title,
html[data-theme="forest-brew"] .app-title,
html[data-theme="tea-mist"] .app-title,
html[data-theme="blueberry"] .app-title,
html[data-theme="kokoblu"] .app-title,
html[data-theme="dubai"] .app-title {
  color: var(--ds-brand-bold);
}

.header-actions {
  display: flex;
  gap: 8px;
}

.btn {
  font: inherit;
  font-weight: 500;
  padding: 6px 12px;
  border-radius: var(--ds-radius);
  border: none;
  background: var(--ds-surface-sunken);
  color: var(--ds-text);
  cursor: pointer;
  transition: background-color 0.1s ease, box-shadow 0.15s ease;
}

.btn:hover:not(:disabled) {
  background: var(--ds-border);
}

.btn:active:not(:disabled) {
  background: var(--ds-border-bold);
}

/* Theme-agnostic glow-on-hover: applies whenever the active theme defines
   --ds-glow (Cherry, Forest Brew, Tea Mist, Blueberry), and is a no-op
   (falls back to `none`) for the ones that don't (Sakura, Kokoblu, Dubai).
   Originally Cherry-only via an explicit selector list; generalized so
   adding a themed variant is just "define --ds-glow or don't" rather than
   also updating this rule every time. */
.btn:hover:not(:disabled),
.tab-btn:hover,
.fmt-btn:hover {
  box-shadow: var(--ds-glow, none);
}

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

/* Icon-only header buttons (theme picker toggle, dual-window, offline) -
   square, centered icon, no text. title/aria-label carry the accessible
   name. */
.btn-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  padding: 0;
}

/* Theme picker - same popover shape as .paragraph-style-group/-popover
   (vertical list, one row per option), right-aligned instead of left since
   it hangs off the header's right-most icon button rather than a
   left-anchored toolbar button. */
.theme-picker {
  position: relative; /* anchors .theme-popover below */
  display: inline-flex;
}

#theme-toggle[aria-expanded="true"] {
  background: var(--ds-border);
}

.theme-popover {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 5;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 6px;
  min-width: 9rem;
  background: var(--ds-surface-overlay);
  border: 1px solid var(--ds-border);
  border-radius: var(--ds-radius);
  box-shadow: var(--ds-shadow-raised);
}
/* Same fix as .paragraph-style-popover[hidden] etc above: an explicit
   `display` on this class ties in specificity with the browser's plain
   `[hidden] { display: none }` rule. */
.theme-popover[hidden] {
  display: none;
}

.theme-option {
  display: flex;
  align-items: center;
  gap: 8px;
  font: inherit;
  text-align: left;
  padding: 6px 8px;
  border: none;
  border-radius: var(--ds-radius);
  background: transparent;
  color: var(--ds-text);
  cursor: pointer;
}

.theme-option:hover {
  background: var(--ds-surface-sunken);
}

.theme-option svg {
  flex-shrink: 0; /* keep the icon from being squeezed if a label ever wraps */
}

/* The currently-active theme's row - a persistent highlight, not just a
   hover state, so re-opening the popover shows at a glance which theme is
   already selected. */
.theme-option[aria-checked="true"] {
  background: var(--ds-brand-subtle);
  color: var(--ds-brand-bold);
  font-weight: 500;
}

.panes {
  flex: 1;
  min-height: 0;
  display: grid;
  grid-auto-rows: 1fr;
  gap: 16px;
  padding: 16px;
  overflow: hidden;
}

.panes-single {
  grid-template-columns: 1fr;
}

.panes-dual {
  grid-template-columns: 1fr 1fr;
}

@media (max-width: 720px) {
  .panes-dual {
    grid-template-columns: 1fr;
  }
}

.pane {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--ds-border);
  border-radius: var(--ds-radius-large);
  background: var(--ds-surface);
  box-shadow: var(--ds-shadow-raised);
  overflow: hidden;
  min-height: 20rem;
  position: relative; /* anchors .page-markers (page-markers.ts) */
}

.pane-toolbar-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px;
  padding: 10px 12px;
  border-bottom: 1px solid var(--ds-border);
}

.file-controls {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

/* Fixed width, sized to fit the longer of "Open file…" / "Clear file" -
   without this, the button visibly resized/shifted its neighbors every
   time its label swapped on hover (pane.ts's mouseenter/mouseleave). */
.browse-btn {
  min-width: 6.5rem;
  text-align: center;
}

.file-name {
  color: var(--ds-text-subtle);
  font-size: 12px;
  max-width: 14rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  cursor: pointer; /* click anywhere (base or locked extension) to rename the base - pane.ts - harmless no-op on "No file loaded" */
  border-radius: var(--ds-radius);
  padding: 2px 4px;
  margin: -2px -4px; /* padding without shifting neighboring layout */
}

/* Only .file-name-base ever becomes contentEditable - .file-name-ext
   (the locked extension, e.g. ".md") never does, so it can't be edited
   even if a click happens to land directly on it. */
.file-name-base[contenteditable="true"] {
  cursor: text;
  background: var(--ds-surface-sunken);
  outline: 1px solid var(--ds-brand-bold);
  padding: 0 2px;
  border-radius: var(--ds-radius);
}

.file-name-ext {
  opacity: 0.75; /* visually reads as "fixed," not part of what you just clicked into */
}

.tabs {
  display: flex;
  gap: 4px;
  background: var(--ds-surface-sunken);
  padding: 2px;
  border-radius: var(--ds-radius);
}

.tab-btn {
  font: inherit;
  font-weight: 500;
  padding: 4px 14px;
  border-radius: var(--ds-radius);
  border: none;
  background: transparent;
  color: var(--ds-text-subtle);
  cursor: pointer;
  transition: color 0.1s ease, box-shadow 0.15s ease;
}

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

.tab-btn.active {
  background: var(--ds-surface);
  color: var(--ds-brand-bold);
  box-shadow: var(--ds-shadow-raised);
}

.edit-toolbar {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 8px 12px;
  border-bottom: 1px solid var(--ds-border);
  background: var(--ds-surface-sunken);
}
/* `display: flex` above beats the browser's default `[hidden] { display:
   none }` rule (author styles win over the UA stylesheet), so without this
   the toolbar stays visible even when Pane.setMode("view") sets the
   `hidden` attribute. This restores hidden's effect for this element. */
.edit-toolbar[hidden] {
  display: none;
}

.fmt-btn {
  width: 2rem;
  height: 2rem;
  border-radius: var(--ds-radius);
  border: none;
  background: var(--ds-surface);
  color: var(--ds-text);
  cursor: pointer;
  transition: background-color 0.1s ease, box-shadow 0.15s ease;
}

.fmt-btn:hover {
  background: var(--ds-border);
}

.highlight-group {
  position: relative; /* anchors .highlight-popover below */
  display: flex;
  align-items: center;
  margin-left: 8px;
  padding-left: 8px;
  border-left: 1px solid var(--ds-border);
}

.highlight-toggle[aria-expanded="true"] {
  background: var(--ds-border);
}

/* 18 colors (+ remove) don't fit inline in the toolbar row - a popover
   grid, opened by .highlight-toggle (pane.ts), closed on selecting a
   color or clicking anywhere outside it. */
.highlight-popover {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: 5;
  display: grid;
  grid-template-columns: repeat(6, 1.25rem);
  gap: 6px;
  padding: 10px;
  background: var(--ds-surface-overlay);
  border: 1px solid var(--ds-border);
  border-radius: var(--ds-radius);
  box-shadow: var(--ds-shadow-raised);
}
/* Same fix as .edit-toolbar[hidden] above: an explicit `display` on this
   class ties in specificity with the browser's plain `[hidden] { display:
   none }` rule, and being later in the cascade would otherwise win -
   keeping the popover visible even when `hidden` is set. */
.highlight-popover[hidden] {
  display: none;
}

.highlight-swatch {
  width: 1.25rem;
  height: 1.25rem;
  border-radius: 50%;
  border: 1px solid rgba(9, 30, 66, 0.13);
  cursor: pointer;
  padding: 0;
}

.highlight-swatch:hover {
  transform: scale(1.15);
}

.highlight-none {
  grid-column: 1 / -1; /* full-width row of its own, set apart from the color grid */
  width: auto;
  height: 1.5rem;
  border-radius: var(--ds-radius);
  background: var(--ds-surface-sunken);
  color: var(--ds-text-subtle);
  font-size: 0.85rem;
  line-height: 1;
}

.drop-zone {
  margin: 16px;
  padding: 2rem 1rem;
  border: 2px dashed var(--ds-border-bold);
  border-radius: var(--ds-radius-large);
  text-align: center;
  color: var(--ds-text-subtle);
  background: var(--ds-drop-bg);
  cursor: pointer; /* also click-to-create-new-file (pane.ts), not just a drop target */
}

.drop-zone:hover,
.drop-zone:focus-visible {
  border-color: var(--ds-brand-bold);
  color: var(--ds-text);
}

.drop-zone.drag-over {
  border-color: var(--ds-brand-bold);
  color: var(--ds-text);
}

.content {
  flex: 1;
  min-height: 0; /* let it actually scroll instead of growing past the pane */
  padding: 16px 20px;
  overflow: auto;
  outline: none;
  scrollbar-color: var(--ds-border-bold) transparent; /* Firefox */
  scrollbar-width: thin;
}

.content[contenteditable="true"] {
  background: var(--ds-surface-sunken);
}

/* Chromium-only (this app's target, ADR-0003) scrollbar styling - a
   consistent, slim, rounded, theme-colored scrollbar rather than each OS's
   default chrome, so the page-marker dots (page-markers.ts) read as part
   of one deliberate scrollbar rather than an overlay on top of a mismatched
   native one. */
.content::-webkit-scrollbar {
  width: var(--ds-scrollbar-width);
}
.content::-webkit-scrollbar-track {
  background: var(--ds-surface-sunken);
}
.content::-webkit-scrollbar-thumb {
  background: var(--ds-border-bold);
  border-radius: var(--ds-radius-large);
  border: 3px solid var(--ds-surface-sunken);
}
.content::-webkit-scrollbar-thumb:hover {
  background: var(--ds-brand-bold);
}

/* A4-page markers (page-markers.ts) - a thin overlay positioned exactly
   over .content's scrollbar track (same width as --ds-scrollbar-width, same
   right edge), so the dots read as embedded in the scrollbar rather than a
   separate strip next to it. pointer-events:none on the track itself so it
   never blocks scrolling/dragging the real thumb underneath; individual
   dots re-enable pointer-events so they're still hoverable. */
.page-markers {
  position: absolute;
  right: 0;
  width: var(--ds-scrollbar-width);
  pointer-events: none;
  z-index: 1;
}

.page-marker-dot {
  position: absolute;
  left: 50%;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--ds-brand-bold);
  border: 1px solid var(--ds-surface-sunken);
  transform: translate(-50%, -50%);
  pointer-events: auto;
  cursor: default;
}

.export-controls {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  border-bottom: 1px solid var(--ds-border);
  background: var(--ds-surface-sunken);
}

.export-controls .copy-btn {
  margin-left: auto;
}

/* Export is a popover, same pattern as .highlight-group/.highlight-popover
   (see those rules) - .html/.pdf/.docx/.json used to be four always-visible
   buttons; now a single "Export" toggle (styled like the Copy button next
   to it, not uppercased - plain sentence case) opens a small menu. */
.export-group {
  position: relative; /* anchors .export-popover below */
}

.export-popover {
  position: absolute;
  top: 50%;
  left: calc(100% + 6px); /* beside the toggle button, not underneath it (was blocking the content below) */
  transform: translateY(-50%); /* vertically centered on the button, not top-aligned (read as slightly low/offset) */
  z-index: 5;
  display: flex;
  flex-direction: row; /* horizontal row of formats */
  gap: 4px;
  padding: 6px;
  background: var(--ds-surface-overlay);
  border: 1px solid var(--ds-border);
  border-radius: var(--ds-radius);
  box-shadow: var(--ds-shadow-raised);
  white-space: nowrap;
}
/* Same fix as .edit-toolbar[hidden]/.highlight-popover[hidden] above: an
   explicit `display` on this class ties in specificity with the browser's
   plain `[hidden] { display: none }` rule. */
.export-popover[hidden] {
  display: none;
}

/* ---- Paragraph style (H1-H6 / Body) popover, edit toolbar - Obsidian's
   font-size/paragraph-style setting. A vertical dropdown list (unlike the
   export popover's horizontal row) since each entry is a differently-sized
   label read top-to-bottom, same shape as Obsidian's own dropdown. */
.paragraph-style-group {
  position: relative; /* anchors .paragraph-style-popover below */
  display: flex;
  align-items: center;
}

.paragraph-style-toggle[aria-expanded="true"] {
  background: var(--ds-border);
}

.paragraph-style-popover {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: 5;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 6px;
  min-width: 6rem;
  background: var(--ds-surface-overlay);
  border: 1px solid var(--ds-border);
  border-radius: var(--ds-radius);
  box-shadow: var(--ds-shadow-raised);
}
/* Same fix as .edit-toolbar[hidden] etc above: an explicit `display` on
   this class ties in specificity with the browser's plain `[hidden] {
   display: none }` rule. */
.paragraph-style-popover[hidden] {
  display: none;
}

.paragraph-style-btn {
  font: inherit;
  text-align: left;
  padding: 4px 8px;
  border: none;
  border-radius: var(--ds-radius);
  background: transparent;
  color: var(--ds-text);
  cursor: pointer;
}

.paragraph-style-btn:hover {
  background: var(--ds-surface-sunken);
}

/* ---- Rendered Markdown content styling (both themes via variables) ---- */

.content h1,
.content h2,
.content h3,
.content h4,
.content h5,
.content h6 {
  font-weight: 600;
  line-height: 1.25;
  margin-top: 1.4em;
  margin-bottom: 0.5em;
  color: var(--ds-text);
}

.content h1:first-child,
.content h2:first-child,
.content h3:first-child {
  margin-top: 0;
}

.content a {
  color: var(--ds-brand-bold);
  text-decoration: none;
}

.content a:hover {
  text-decoration: underline;
}

.content blockquote {
  margin: 1rem 0;
  padding: 0.25rem 1rem;
  border-left: 4px solid var(--ds-border-bold);
  color: var(--ds-text-subtle);
}

.content code {
  background: var(--ds-code-bg);
  padding: 0.15em 0.35em;
  border-radius: var(--ds-radius);
  font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
  font-size: 0.9em;
}

.content pre {
  background: var(--ds-code-bg);
  padding: 1rem;
  border-radius: var(--ds-radius);
  overflow-x: auto;
}

.content pre code {
  background: none;
  padding: 0;
}

.content table {
  border-collapse: collapse;
  width: 100%;
  margin: 1rem 0;
}

.content th {
  background: var(--ds-surface-sunken);
  font-weight: 600;
}

.content th,
.content td {
  border: 1px solid var(--ds-border);
  padding: 0.4rem 0.6rem;
  text-align: left;
}

.content hr {
  border: none;
  border-top: 1px solid var(--ds-border);
  margin: 1.5rem 0;
}

.content img {
  max-width: 100%;
}

/* ---- Print (.pdf export) -----------------------------------------
   Hidden by default; only takes effect while document.body has
   .noted-printing, set right before window.print() by Pane.printPane(). */

@media print {
  /* A real @page margin (not a zero-margin-plus-manual-padding trick,
     which some browsers' print/Save-as-PDF pipeline doesn't render
     reliably) - Chromium (this app's print target, ADR-0003) applies this
     directly as the page's margin, same as picking a margin size in the
     print dialog, so it's dependable. Browsers strip background colors
     when printing by default (an ink-saving default) - print-color-adjust:
     exact below overrides that.

     The margin area itself (the strip @page margin creates around the
     content) is a separate paint region from the page's content box -
     html/body's background only fills the content box, not the margin
     strip, so it stayed white regardless of theme even after the fix
     below. Setting background directly on @page paints that margin strip
     too - Chromium supports @page background (unlike most @page margin-box
     pseudo-elements, which it doesn't). */
  @page {
    margin: 0.6in;
    background: var(--ds-surface);
  }

  html,
  body {
    background: var(--ds-surface);
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }

  body.noted-printing > #app > .app-header,
  body.noted-printing > #app > .app-footer,
  body.noted-printing .pane:not(.noted-print-target) {
    display: none !important;
  }

  body.noted-printing .noted-print-target {
    border: none;
    box-shadow: none;
    min-height: 0;
  }

  body.noted-printing .noted-print-target .pane-toolbar-row,
  body.noted-printing .noted-print-target .edit-toolbar,
  body.noted-printing .noted-print-target .drop-zone,
  body.noted-printing .noted-print-target .export-controls {
    display: none !important;
  }

  body.noted-printing .panes {
    display: block;
    padding: 0;
  }

  body.noted-printing .noted-print-target .content {
    overflow: visible;
    /* The page margin above already provides the space - no extra padding
       needed here (double-padding was the earlier version of this rule). */
    padding: 0;
  }

  /* Don't let the page break land in the middle of one of these - push the
     whole element to the next page instead of splitting it. A paragraph/
     item/etc. taller than one full page still has to break somewhere
     (there's no page left to push it onto), but that's the rare case;
     everything else now breaks cleanly between elements, not inside one. */
  body.noted-printing .noted-print-target .content h1,
  body.noted-printing .noted-print-target .content h2,
  body.noted-printing .noted-print-target .content h3,
  body.noted-printing .noted-print-target .content h4,
  body.noted-printing .noted-print-target .content h5,
  body.noted-printing .noted-print-target .content h6,
  body.noted-printing .noted-print-target .content p,
  body.noted-printing .noted-print-target .content li,
  body.noted-printing .noted-print-target .content blockquote,
  body.noted-printing .noted-print-target .content pre,
  body.noted-printing .noted-print-target .content table,
  body.noted-printing .noted-print-target .content img {
    break-inside: avoid;
    page-break-inside: avoid; /* older alias - broader engine support, harmless where break-inside is already respected */
  }

  /* A heading stranded alone at the bottom of a page, with its own content
     starting on the next page, is worse than pushing the heading down with
     it - keep a heading glued to whatever follows it. */
  body.noted-printing .noted-print-target .content h1,
  body.noted-printing .noted-print-target .content h2,
  body.noted-printing .noted-print-target .content h3,
  body.noted-printing .noted-print-target .content h4,
  body.noted-printing .noted-print-target .content h5,
  body.noted-printing .noted-print-target .content h6 {
    break-after: avoid;
    page-break-after: avoid;
  }

  /* A table's header row shouldn't repeat awkwardly or get separated from
     its first body row across a break. */
  body.noted-printing .noted-print-target .content thead {
    display: table-header-group;
  }
}

/* ---- Logo confetti easter egg (src/confetti.ts) -------------------- */

.app-title {
  user-select: none; /* click-and-hold shouldn't select the wordmark's text */
}

.confetti-overlay {
  position: fixed;
  inset: 0;
  pointer-events: none; /* never blocks clicking the logo again mid-shower, or anything else on the page */
  overflow: hidden;
  z-index: 9999;
}

.confetti-burst {
  position: absolute;
  inset: 0;
}

.confetti-piece {
  position: absolute;
  top: -20px;
  border-radius: 2px;
  animation-name: confetti-fall;
  animation-timing-function: ease-in;
  animation-fill-mode: forwards;
}

@keyframes confetti-fall {
  0% {
    transform: translate(0, 0) rotate(0deg);
    opacity: 1;
  }
  80% {
    opacity: 1;
  }
  100% {
    transform: translate(var(--confetti-drift, 0px), 110vh) rotate(var(--confetti-rotate, 360deg));
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .confetti-piece {
    animation: none;
    display: none;
  }
}

/* ---- Typing effects: Echo + Sublime + Warp (src/typing-effects.ts) -- */

.typing-fx-overlay {
  position: fixed;
  inset: 0;
  pointer-events: none; /* never intercepts clicks/typing in the pane beneath */
  overflow: visible;
  z-index: 9998; /* above normal content, below the confetti overlay (9999) */
}

.echo-glyph {
  position: fixed;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0;
  padding: 0;
  white-space: pre;
  pointer-events: none;
  transform-origin: center;
  text-shadow: 0 0 6px currentColor; /* soft glow - color is the theme accent, set inline per-glyph */
  animation: echo-pulse 180ms ease-out forwards;
}

@keyframes echo-pulse {
  0% {
    transform: scale(1);
    opacity: 0.85;
  }
  100% {
    transform: scale(1.35);
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .echo-glyph {
    animation: none;
    display: none;
  }
}

/* Sublime: top-down decay on delete. Two clipped halves of the same
   character, fading/blurring/lifting on staggered timing (see the two
   animation-duration + animation-delay values below) so the top visibly
   dissolves well before the bottom does, rather than both vanishing at
   once - "the weight of shatter, without the gravity, scatter, or noise". */

.sublime-decay {
  /* Pure grouping node for one-shot removal (spawnSublimeDecay) - all its
     children are position: fixed, so this wrapper's own box is irrelevant
     to layout or paint. */
  display: contents;
}

.sublime-half {
  position: fixed;
  overflow: hidden;
  pointer-events: none;
  margin: 0;
  padding: 0;
}

.sublime-half-inner {
  display: flex;
  align-items: center;
  justify-content: center;
  white-space: pre;
  transform-origin: center;
}

.sublime-half-top .sublime-half-inner {
  animation: sublime-fade-up 500ms ease-in forwards;
}

.sublime-half-bottom .sublime-half-inner {
  animation: sublime-fade-up 650ms ease-in forwards;
  animation-delay: 320ms;
}

@keyframes sublime-fade-up {
  0% {
    opacity: 1;
    filter: blur(0px);
    transform: translateY(0);
  }
  100% {
    opacity: 0;
    filter: blur(2px);
    transform: translateY(-6px);
  }
}

.sublime-mote {
  position: fixed;
  width: 2px;
  height: 2px;
  border-radius: 50%;
  pointer-events: none;
  animation: sublime-mote-drift 900ms ease-out forwards;
}

@keyframes sublime-mote-drift {
  0% {
    opacity: 0.8;
    transform: translate(0, 0);
  }
  100% {
    opacity: 0;
    transform: translate(var(--mote-drift-x, 0px), var(--mote-drift-y, -16px));
  }
}

@media (prefers-reduced-motion: reduce) {
  .sublime-half-inner,
  .sublime-mote {
    animation: none;
    display: none;
  }
}

/* Sublime, whole-selection variant: deleting a selected range (not just one
   character) reuses the same sublime-fade-up keyframe at word granularity -
   one word per element, each with its own animation-delay (set inline,
   src/typing-effects.ts's spawnParagraphDissolve) so the LAST word starts
   fading first and the FIRST word fades last - a reverse wave sweeping back
   through the selection. Before its delay elapses, a word has no animation
   running yet and simply renders at its plain, fully-opaque state. */
.sublime-word {
  position: fixed;
  display: flex;
  align-items: center;
  pointer-events: none;
  margin: 0;
  padding: 0;
  white-space: pre;
  animation: sublime-fade-up 500ms ease-in forwards;
}

@media (prefers-reduced-motion: reduce) {
  .sublime-word {
    animation: none;
    display: none;
  }
}

/* Warp: liquid caret stretch between same-line positions. One persistent
   quad per pane (src/typing-effects.ts's createWarpCaretController), its
   left/width/top/height driven per-frame by JS (the actual stretch - two
   independently-eased edges can't be expressed as a single CSS
   transition), opacity handled declaratively here so it fades in/out
   smoothly around the JS-driven motion rather than snapping. */

.warp-quad {
  position: fixed;
  pointer-events: none;
  background: var(--accent); /* tracks live theme changes - no inline color set in JS */
  border-radius: 1px;
  opacity: 0;
  transition: opacity 150ms ease-out;
}

@media (prefers-reduced-motion: reduce) {
  .warp-quad {
    display: none;
  }
}
