:root {
  color-scheme: light;
  font-family: system-ui, sans-serif;

  --bg: #f7f8fa;
  --text: #1a1a1a;
  --text-muted: #666666;
  --border: #dddddd;
  --border-strong: #bbbbbb;
  --control-bg: #eceef2;
  --control-bg-hover: #e1e4e9;
  --error: #b00020;
  --success: #1b8a3c;
  --field-bg: #ffffff;

  --title-math: #2979ff;
  --title-link: #ffd600;
  --title-morph: #ff00c8;

  --field-border: rgba(41, 121, 255, 0.45);
  --field-border-focus: #2979ff;
  --field-glow: rgba(41, 121, 255, 0.45);
  --field-selection-bg: rgba(41, 121, 255, 0.35);
  --field-contains-bg: rgba(41, 121, 255, 0.18);

  /* Light mode gets a slightly lighter header bar than dark mode, so it reads less harshly against the white canvas. */
  --header-bg: #2b2f38;
  --header-border: #3a3f49;
  --header-text: #e8e8ea;
  --header-control-bg: #343842;
  --header-control-bg-hover: #3f4450;
}

:root[data-theme='dark'] {
  color-scheme: dark;

  --bg: #14161a;
  --text: #e8e8ea;
  --text-muted: #9aa0a6;
  --border: #333942;
  --border-strong: #454c56;
  --control-bg: #262a33;
  --control-bg-hover: #323844;
  --error: #ff6b6b;
  --success: #5fd988;
  --field-bg: #1b1e24;

  --header-bg: #1c1f26;
  --header-border: #333942;
  --header-control-bg: #262a33;
  --header-control-bg-hover: #323844;
}

body {
  margin: 0;
  color: var(--text);
  background: var(--bg);
}

/* Form controls don't inherit the page's font by default - browsers give them their own
   "control font" instead (Chrome's default is a fixed 13.3333px Arial, regardless of what the
   page itself uses), so every text input/select/textarea in the app was rendering noticeably
   smaller than its surrounding text. Below 16px, iOS Safari also auto-zooms the whole page on
   focus to keep the text legible - explaining why opening Manage Snippets or Save as Snippet
   (both of which focus a text input as soon as they open) zoomed in on iPhones specifically.
   touch-action: manipulation alone (see the menu items below) was never going to fix that, since
   the zoom wasn't about a double-tap gesture at all. */
input,
select,
textarea,
button {
  font: inherit;
}

header {
  background: var(--header-bg);
  border-bottom: 1px solid var(--header-border);
}

header .header-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem;
}

.title-group {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.title-group .logo {
  flex: none;
  display: block;
  position: relative;
  top: 5px;
}

header h1 {
  margin: 0;
  font-size: 1.9rem;
  font-weight: 800;
  letter-spacing: 0.02em;
  filter: drop-shadow(0 0 6px rgba(255, 0, 200, 0.35));
}

/* Wide desktop viewports get a slightly larger logo/title - narrower screens (where header
   space is already tight) keep the original sizing above untouched. */
@media (min-width: 1024px) {
  .title-group .logo {
    width: 68px;
    height: 68px;
  }

  header h1 {
    font-size: 2.4rem;
  }
}

.t-math {
  color: var(--title-math);
}

.t-link {
  color: var(--title-link);
}

.t-morph {
  color: var(--title-morph);
}

.header-menu-item {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  width: 100%;
  box-sizing: border-box;
  padding: 0.5rem 0.9rem;
  border: none;
  border-radius: 0.4rem;
  background: none;
  color: var(--header-text);
  font-size: 0.9rem;
  text-align: left;
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
  touch-action: manipulation;
}

.header-menu-item:hover {
  background: var(--header-control-bg-hover);
}

.header-menu-icon {
  flex: none;
  width: 1.1rem;
  height: 1.1rem;
  color: var(--header-text);
}

/* Overrides the icon's inherited colour (rather than the whole menu item's text) so only the
   little account glyph itself signals sign-in status at a glance. */
.auth-icon-signed-out {
  color: var(--error);
}

.auth-icon-signed-in {
  color: var(--success);
}

.header-menu-divider {
  height: 1px;
  margin: 0.3rem 0.2rem;
  background: var(--header-border);
}

.morph-menu {
  position: fixed;
  z-index: 1000;
  min-width: 10rem;
  max-height: calc(100vh - 16px);
  overflow-y: auto;
  margin: 0;
  padding: 0.35rem;
  list-style: none;
  border: 1px solid var(--border-strong);
  border-radius: 0.5rem;
  background: var(--field-bg);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
  box-sizing: border-box;
}

.morph-menu[hidden] {
  display: none;
}

.morph-menu-divider {
  height: 1px;
  margin: 0.3rem 0.2rem;
  background: var(--border-strong);
}

.morph-menu-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  padding: 0.4rem 0.8rem;
  border: none;
  border-radius: 0.4rem;
  background: none;
  color: var(--text);
  font-size: 0.9rem;
  text-align: left;
  cursor: pointer;
  /* Same reasoning as .header-menu-toggle above - this menu is opened by a right-click/long-press
     and then a tap on one of these items, and without this iOS Safari can mistake that two-tap
     sequence for its own double-tap-to-zoom gesture. */
  touch-action: manipulation;
}

.morph-menu-item:hover {
  background: var(--field-border-focus);
  color: #ffffff;
}

/* A row for an assignable Morph operation (see buildAssignableMenuRow in app.js) is two
   independent buttons side by side, not one - each gets its own hover highlight, rather than
   the row highlighting as a whole, so it's visually obvious before you even click that the
   shortcut side is a separate target from the label side. */
.morph-menu-item-row {
  display: flex;
  align-items: stretch;
  gap: 2px;
}

.morph-menu-item-trigger {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex: 1;
  min-width: 0;
  padding: 0.4rem 0.8rem;
  border: none;
  border-radius: 0.4rem;
  background: none;
  color: var(--text);
  font-size: 0.9rem;
  text-align: left;
  cursor: pointer;
  touch-action: manipulation;
}

.morph-menu-item-trigger:hover {
  background: var(--field-border-focus);
  color: #ffffff;
}

.morph-menu-shortcut-btn {
  flex: none;
  padding: 0.4rem 0.6rem;
  border: none;
  border-radius: 0.4rem;
  background: none;
  color: var(--text-muted);
  font-size: 0.78rem;
  white-space: nowrap;
  cursor: pointer;
  touch-action: manipulation;
}

.morph-menu-shortcut-btn:hover {
  background: var(--control-bg-hover);
  color: var(--text);
}

/* "Add shortcut" (nothing assigned yet) reads as an invitation rather than a fixed label, so
   it's styled slightly differently from an actual assigned combo. */
.morph-menu-shortcut-btn-unassigned {
  font-style: italic;
}

/* Reserved before every row's label (see createMenuIconSlot in app.js) so every row lines up at
   the same indent, whether or not it actually has an icon in it. */
.morph-menu-icon-slot {
  display: flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 1.1rem;
  height: 1.1rem;
}

.morph-menu-icon {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* The "Morph" entry's own icon keeps the same pink used for "morph" in the header title, even
   on hover, rather than inheriting the plain hover color every other menu icon gets. */
.morph-menu-icon-morph,
.morph-menu-item:hover .morph-menu-icon-morph,
.morph-menu-item-trigger:hover .morph-menu-icon-morph {
  color: var(--title-morph);
}

.morph-submenu-toggle {
  position: relative;
  padding-right: 1.6rem;
}

.morph-submenu-toggle::after {
  content: '\203a';
  position: absolute;
  right: 0.8rem;
  top: 50%;
  transform: translateY(-50%);
  font-size: 1rem;
}

.morph-submenu-toggle[aria-expanded='true']::after {
  content: '\2039';
}

.morph-submenu {
  list-style: none;
  margin: 0;
  padding: 0 0 0 0.9rem;
}

.morph-submenu[hidden] {
  display: none;
}

.morph-submenu-checkable .morph-menu-item {
  position: relative;
  padding-left: 1.7rem;
}

.morph-submenu-checkable .morph-menu-item-checked::before {
  content: '\2713';
  position: absolute;
  left: 0.6rem;
  top: 50%;
  transform: translateY(-50%);
}

.morph-submenu-heading {
  padding: 0.4rem 0.8rem 0.1rem;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
}

/* Spacing for the math-template previews reused from MathLive's own "Insert" menu (see
   mathlive-static.css, linked in index.html, for the KaTeX classes rendering the previews
   themselves) - these two classes are the only pieces of that layout it doesn't already cover. */
.ML__insert-template {
  display: inline-block;
  min-width: 2.5rem;
  margin-right: 0.6rem;
}

.ML__insert-label {
  color: var(--text-muted);
}

.morph-menu-item:hover .ML__insert-label {
  color: #ffffff;
}

.matrix-picker-label {
  padding: 0.3rem 0.8rem;
  font-size: 0.85rem;
  color: var(--text-muted);
}

.matrix-picker-grid {
  display: grid;
  grid-template-columns: repeat(5, 1.1rem);
  grid-template-rows: repeat(5, 1.1rem);
  gap: 2px;
  padding: 0.2rem 0.8rem 0.4rem;
}

.matrix-picker-cell {
  border: 1px solid var(--border-strong);
  border-radius: 0.15rem;
  background: var(--control-bg);
  cursor: pointer;
}

.matrix-picker-cell.active {
  background: var(--field-border-focus);
  border-color: var(--field-border-focus);
}

.header-menu {
  position: relative;
}

.header-menu-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  padding: 0;
  border: 1px solid var(--header-border);
  border-radius: 0.4rem;
  background: var(--header-control-bg);
  color: var(--header-text);
  cursor: pointer;
  /* Without this, iOS Safari can mistake the two quick taps needed to open this menu and then
     pick an item inside it (e.g. tapping the hamburger, then "Manage snippets...") for the
     start of its own double-tap-to-zoom gesture, and zoom the whole page in instead of just
     actioning the tap. manipulation tells it these taps are for direct interaction, not zooming. */
  touch-action: manipulation;
}

.header-menu-toggle:hover {
  background: var(--header-control-bg-hover);
}

.header-menu-dropdown {
  position: absolute;
  top: calc(100% + 0.4rem);
  right: 0;
  z-index: 10;
  min-width: 10rem;
  margin: 0;
  padding: 0.35rem;
  list-style: none;
  border: 1px solid var(--header-border);
  border-radius: 0.5rem;
  background: var(--header-bg);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
}

.header-menu-dropdown[hidden] {
  display: none;
}

/* Wide desktop viewports have plenty of room, so the dropdown can grow wider and use a larger
   font without needing to wrap any of its entries onto a second line - narrower screens keep
   the original compact sizing above untouched. */
@media (min-width: 1024px) {
  .header-menu-dropdown {
    min-width: 14rem;
  }

  .header-menu-item {
    padding: 0.6rem 1.1rem;
    font-size: 1.05rem;
  }

  .header-menu-icon {
    width: 1.3rem;
    height: 1.3rem;
  }
}

main {
  max-width: 90rem;
  margin: 0 auto;
  padding: 2rem 2rem;
  box-sizing: border-box;
}

.hint {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  color: var(--text-muted);
  font-size: 1.1rem;
  line-height: 1.5;
  margin-top: 0;
}

/* `.hint`'s own "display: flex" above is an author-stylesheet rule, which (regardless of
   specificity) always wins the cascade over the browser's built-in "[hidden] { display: none }"
   UA rule - without this, setting the "hidden" attribute on a .hint element (see examples-hint)
   would have no visible effect at all. */
.hint[hidden] {
  display: none;
}

.hint-icon {
  flex: none;
  width: 1.5em;
  height: 1.5em;
}

.hint-morph {
  color: var(--title-morph);
}

/* A small icon sized and aligned to sit inline within a sentence (unlike .hint-icon above,
   which is its own flex item beside the text) - used in the touch wording to show the actual
   glyph on the field's own menu button (three horizontal lines, like a hamburger menu) rather
   than describing it in words alone. */
.hint-inline-icon {
  width: 1em;
  height: 1em;
  vertical-align: -0.15em;
}

/* Exactly one of these is ever shown - which one is decided by the same media query that
   shows/hides the per-field menu button itself (see .field-menu-btn) - a mouse/trackpad
   device gets the right-click wording, a touch-only device (which never fires a contextmenu
   event) gets the tap-the-menu-button wording instead. The leading mouse icon (see .hint-icon)
   only makes sense for the right-click wording, so it's hidden alongside it on touch too. */
.hint-pointer-coarse {
  display: none;
}

@media (hover: none) and (pointer: coarse) {
  .hint-icon,
  .hint-pointer-fine {
    display: none;
  }

  .hint-pointer-coarse {
    display: inline;
  }
}

.hint-examples {
  font-size: 0.95rem;
  font-style: italic;
  /* No icon of its own - indent past the primary hint's icon + gap so the two lines of text
     still line up. */
  padding-left: calc(1.5em + 0.5rem);
  margin-top: 0.3rem;
}

math-field {
  font-size: 1.4rem;
  /* Padding lives on ::part(container) below, not here - see its comment for why. */
  padding: 0;
  border: 1px solid var(--field-border);
  border-radius: 0.5rem;
  box-sizing: border-box;
  background: var(--field-bg);
  color: var(--text);
  outline: none;
  max-width: 100%;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
  --selection-background-color: var(--field-selection-bg);
  --selection-color: var(--text);
  --contains-highlight-background-color: var(--field-contains-bg);
  --primary: var(--field-border-focus);
}

/* MathLive's inner container (the part that actually renders the formula and handles clicks)
   only ever sizes itself to its content, top-aligned - on a short equation in a row stretched
   taller than that content (see .doc-line's align-items: stretch, which makes every field match
   the height of the stacked delete/board-mode/keyboard-toggle buttons beside it), that leaves
   dead space below the formula that's visually inside the field but outside this container, so
   clicking there doesn't focus the field, and the formula itself sits nearer the top of the
   field than the middle. Stretching the container to fill the field (height/width: 100%) and
   centering its content fixes both: the container - not the field's own box - is what's actually
   clickable, so it needs to cover the whole field for clicks anywhere in it to land on the
   formula. Padding moves here from the field itself so the container's edges still line up with
   the field's, rather than leaving a slim unclickable strip around the border.
   .board-content math-field below undoes this padding for the no-chrome board-mode display.
   !important is needed on every property here: MathLive's own shadow-DOM stylesheet also
   targets this element directly (as .ML__container), and that internal rule otherwise wins the
   cascade over this ::part() rule from outside the shadow tree despite matching specificity. */
math-field::part(container) {
  height: 100% !important;
  width: 100% !important;
  box-sizing: border-box !important;
  align-items: center !important;
  padding: 0.75rem !important;
}

math-field:focus-within {
  border: 1.5px solid var(--field-border-focus);
  box-shadow: 0 0 0 2px rgba(41, 121, 255, 0.25), 0 0 8px rgba(41, 121, 255, 0.3);
}

math-field::part(virtual-keyboard-toggle),
math-field::part(menu-toggle) {
  color: var(--field-border-focus);
}

/* Our own right-click menu fully replaces MathLive's own field menu (see openFieldMenu in
   app.js), and our own keyboard-toggle-btn/field-menu-btn (below .doc-line) fully replace its
   virtual keyboard toggle - hide both of MathLive's now-unused in-field buttons. */
math-field::part(menu-toggle),
math-field::part(virtual-keyboard-toggle) {
  display: none;
}

math-field::part(menu-item):hover {
  background: var(--field-border-focus);
  color: #ffffff;
}

.document {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 1rem;
  border: 1px solid var(--border);
  border-radius: 0.6rem;
  background: var(--control-bg);
}

.doc-line {
  display: flex;
  align-items: stretch;
  gap: 0.5rem;
}

.doc-line math-field {
  flex: 1;
  min-width: 0;
}

/* Delete (top), Board mode (middle), and the keyboard/menu toggle (bottom) are spread across
   the field's full height - exactly 3 items are ever visible at once (keyboard-toggle-btn and
   field-menu-btn are mutually exclusive via the media query below), so space-between lands the
   middle one dead center regardless of row height. */
.doc-line-actions {
  flex: none;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 0.35rem;
}

.doc-line .delete-line,
.doc-line .keyboard-toggle-btn,
.doc-line .field-menu-btn,
.doc-line .board-mode-btn {
  flex: none;
  width: 1.75rem;
  height: 1.75rem;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border-strong);
  border-radius: 0.4rem;
  background: var(--field-bg);
  color: var(--text-muted);
  cursor: pointer;
}

.doc-line .delete-line svg,
.doc-line .keyboard-toggle-btn svg,
.doc-line .field-menu-btn svg,
.doc-line .board-mode-btn svg {
  width: 65%;
  height: 65%;
}

.doc-line .delete-line:hover {
  background: var(--error);
  border-color: var(--error);
  color: #ffffff;
}

.doc-line .keyboard-toggle-btn:hover,
.doc-line .field-menu-btn:hover,
.doc-line .board-mode-btn:hover {
  background: var(--field-border-focus);
  border-color: var(--field-border-focus);
  color: #ffffff;
}

/* Our own replacements for MathLive's in-field buttons: the keyboard toggle for desktop/mouse
   use, and the menu button as a touch-device fallback for opening the field menu (see
   createLine in app.js) - right-click already does that job on desktop. */
.doc-line .field-menu-btn {
  display: none;
}

@media (hover: none) and (pointer: coarse) {
  .doc-line .keyboard-toggle-btn {
    display: none;
  }

  .doc-line .field-menu-btn {
    display: inline-flex;
  }
}

.document-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.5rem;
}

.add-line {
  align-self: flex-start;
  padding: 0.4rem 0.8rem;
  border: 1px dashed var(--border-strong);
  border-radius: 0.4rem;
  background: none;
  color: var(--text-muted);
  cursor: pointer;
}

.add-line:hover {
  background: var(--control-bg);
}

.app-dialog {
  padding: 1.25rem;
  border: 1px solid var(--border-strong);
  border-radius: 0.5rem;
  background: var(--field-bg);
  color: var(--text);
  min-width: 16rem;
}

.app-dialog::backdrop {
  background: rgba(0, 0, 0, 0.5);
}

.app-dialog h2 {
  margin: 0 0 1rem;
  font-size: 1.05rem;
}

.app-dialog-field {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 0.75rem;
}

.app-dialog-field input {
  width: 5rem;
  padding: 0.3rem;
  border: 1px solid var(--border-strong);
  border-radius: 0.3rem;
  text-align: center;
  color: var(--text);
  background: var(--control-bg);
}

.app-dialog-field input:focus {
  outline: none;
  border-color: var(--field-border-focus);
  box-shadow: 0 0 0 2px rgba(41, 121, 255, 0.25);
}

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

.app-dialog-actions button {
  padding: 0.4rem 0.9rem;
  border: 1px solid var(--border-strong);
  border-radius: 0.4rem;
  background: none;
  color: var(--text);
  cursor: pointer;
}

.app-dialog-actions button:hover {
  background: var(--control-bg);
}

.app-dialog-actions button:focus {
  outline: none;
  border-color: var(--field-border-focus);
  box-shadow: 0 0 0 2px rgba(41, 121, 255, 0.25);
}

.app-dialog-actions .app-dialog-primary {
  border-color: var(--field-border-focus);
  background: var(--field-border-focus);
  color: #ffffff;
}

/* .app-dialog-actions button:hover (above) is more specific than .app-dialog-primary alone, so
   without this it would win on hover and swap the primary button's background to the plain
   --control-bg while its white label text stayed as-is - unreadable in light mode. */
.app-dialog-actions .app-dialog-primary:hover {
  background: var(--field-border-focus);
  filter: brightness(0.88);
}

/* A snippet's name can be much longer than a single variable/number, so (unlike Series' fields)
   it gets its own full-width row instead of the compact label-left/input-right layout. */
.snippet-dialog {
  min-width: 22rem;
}

.snippet-dialog .app-dialog-field {
  flex-direction: column;
  align-items: stretch;
}

.snippet-dialog .app-dialog-field input,
.snippet-dialog .app-dialog-field select {
  box-sizing: border-box;
  width: 100%;
  text-align: left;
}

.snippet-new-category-input {
  margin-top: 0.4rem;
}

.app-dialog-field-optional {
  font-weight: normal;
  color: var(--text-muted);
}

.shortcut-dialog {
  min-width: 20rem;
}

.shortcut-dialog-hint {
  margin: 0 0 0.75rem;
  font-size: 0.85rem;
  color: var(--text-muted);
}

/* The actual capture target - focused automatically when the dialog opens, and styled to look
   distinctly "waiting for input" (dashed border) rather than like a normal text field, since
   typing into it does nothing: only real keydown events are read (see
   handleShortcutCaptureKeydown in app.js). */
.shortcut-capture-box {
  padding: 0.75rem;
  border: 1px dashed var(--border-strong);
  border-radius: 0.4rem;
  background: var(--control-bg);
  color: var(--text);
  font-size: 0.95rem;
  text-align: center;
  cursor: default;
}

.shortcut-capture-box:focus {
  outline: none;
  border-style: solid;
  border-color: var(--field-border-focus);
  box-shadow: 0 0 0 2px rgba(41, 121, 255, 0.25);
}

.shortcut-capture-box-empty {
  color: var(--text-muted);
  font-style: italic;
}

.shortcut-dialog-warning {
  margin: 0.75rem 0 0;
  font-size: 0.85rem;
  color: var(--text-muted);
}

.shortcut-dialog-warning-blocking {
  color: var(--error);
}

/* Shared by every plain dropdown in the app (the category filter below and the Save as snippet
   dialog's category field) so they all look identical - deliberately not a native
   `<input list=...>` combo, whose unstylable native suggestions popup looks nothing like the
   rest of the app. */
.themed-select {
  padding: 0.35rem 0.5rem;
  border: 1px solid var(--border-strong);
  border-radius: 0.3rem;
  color: var(--text);
  background: var(--control-bg);
}

.themed-select:focus {
  outline: none;
  border-color: var(--field-border-focus);
  box-shadow: 0 0 0 2px rgba(41, 121, 255, 0.25);
}

.manage-snippets-dialog {
  width: 32rem;
  max-width: 90vw;
  /* A fixed height (not just a cap) rather than one that follows the content - a <dialog> is
     re-centered in the viewport every time its size changes, so if the list's height varied
     with how many snippets the current search/category filter matched, picking a category
     with fewer (or more) results would shift the whole dialog - including the dropdown the
     mouse is still sitting over - right out from under the pointer. */
  height: min(80vh, 34rem);
  /* <dialog> defaults to overflow: auto - every child here already fits within the fixed
     height above (the snippet list is the only part meant to scroll, via its own
     overflow-y further down), but Chromium's scrollHeight calculation for a scroll container
     whose flex child is itself a flex-basis:0 scroll container can still pick up that child's
     full unclipped content height, giving the dialog a second, redundant scrollbar of its own
     right alongside the list's. Disabling overflow here rules that out entirely. */
  overflow: hidden;
}

.manage-snippets-dialog[open] {
  display: flex;
  flex-direction: column;
}

.manage-snippets-filters {
  display: flex;
  gap: 0.5rem;
  margin-bottom: 0.75rem;
}

.manage-snippets-search {
  flex: 1;
  min-width: 0;
  padding: 0.35rem 0.5rem;
  border: 1px solid var(--border-strong);
  border-radius: 0.3rem;
  color: var(--text);
  background: var(--control-bg);
}

.manage-snippets-category-filter {
  flex: none;
  max-width: 10rem;
}

.manage-snippets-search:focus {
  outline: none;
  border-color: var(--field-border-focus);
  box-shadow: 0 0 0 2px rgba(41, 121, 255, 0.25);
}

.manage-snippets-list {
  overflow-y: auto;
  flex: 1;
  margin: 0 -0.25rem;
  padding: 0 0.25rem;
}

.manage-snippets-empty {
  color: var(--text-muted);
  margin: 0;
  padding: 1rem 0;
}

.manage-snippets-group-heading {
  margin: 0.9rem 0 0.35rem;
  font-size: 0.78rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
}

.manage-snippets-group-heading:first-child {
  margin-top: 0;
}

.manage-snippet-row {
  display: flex;
  align-items: stretch;
  gap: 0.6rem;
  /* The right padding keeps a visible gap between the delete button and the scrollbar that
     appears in the list above once there are enough snippets to need scrolling - without it,
     the delete button sits flush against the scrollbar track with no breathing room at all. */
  padding: 0.5rem 0.6rem 0.5rem 0;
  border-bottom: 1px solid var(--border);
}

.manage-snippet-row:last-child {
  border-bottom: none;
}

/* Stretched (via the row's align-items above) to match the combined height of the stacked
   name + category fields alongside it, rather than sitting shorter than them - see the inert
   attribute set on this element in app.js for why it's no longer deliberately kept small-font.
   A long equation can easily be wider (or, with a fraction/sum/integral, taller) than this
   fixed-size box - overflow: hidden clips it rather than spilling into the name/category fields
   next to it. It's only a preview, not the full editable snippet, so losing the odd edge or
   descender off a very long equation here is an acceptable trade-off. */
.manage-snippet-preview {
  flex: none;
  width: 9rem;
  display: flex;
  align-items: center;
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: 0.3rem;
  background: none;
  font-size: 1rem;
  pointer-events: none;
}

/* Overrides the general math-field::part(container) padding above with this preview's own
   (formerly-on-the-host) tighter padding - see that rule's comment for why padding now lives on
   ::part(container) rather than the host at all. */
.manage-snippet-preview::part(container) {
  padding: 0.3rem 0.4rem !important;
}

.manage-snippet-preview:focus-within {
  border: 1px solid var(--border);
  box-shadow: none;
}

.manage-snippet-preview::part(virtual-keyboard-toggle) {
  display: none;
}

.manage-snippet-fields {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 0.3rem;
}

/* box-sizing: border-box is what makes `width: 100%` actually fit the fields column above -
   without it, each input's own padding and border add on top of that 100%, overflowing past
   the column's right edge and into the delete button next to it. */
.manage-snippet-name {
  box-sizing: border-box;
  width: 100%;
  padding: 0.35rem 0.5rem;
  border: 1px solid var(--border-strong);
  border-radius: 0.3rem;
  color: var(--text);
  background: var(--control-bg);
}

.manage-snippet-name:focus {
  outline: none;
  border-color: var(--field-border-focus);
  box-shadow: 0 0 0 2px rgba(41, 121, 255, 0.25);
}

/* A notch smaller and more muted than the name field above it, so the category reads as
   secondary metadata rather than competing with the snippet's own name. */
.manage-snippet-category {
  box-sizing: border-box;
  width: 100%;
  padding: 0.3rem 0.5rem;
  border: 1px solid var(--border-strong);
  border-radius: 0.3rem;
  font-size: 0.85rem;
  color: var(--text-muted);
  background: var(--control-bg);
}

.manage-snippet-category:focus {
  outline: none;
  border-color: var(--field-border-focus);
  box-shadow: 0 0 0 2px rgba(41, 121, 255, 0.25);
  color: var(--text);
}

/* Identical in size and style to .doc-line's delete-line button in the scratchpad above, so
   every "delete this" icon button in the app looks and behaves the same way. */
.manage-snippet-delete {
  align-self: center;
  flex: none;
  width: 1.75rem;
  height: 1.75rem;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border-strong);
  border-radius: 0.4rem;
  background: var(--field-bg);
  color: var(--text-muted);
  cursor: pointer;
}

.manage-snippet-delete svg {
  width: 65%;
  height: 65%;
}

.manage-snippet-delete:hover {
  background: var(--error);
  border-color: var(--error);
  color: #ffffff;
}

/* Wider on desktop-sized viewports (same breakpoint as the header logo/title) so the equation
   previews get correspondingly more room too - placed after every other Manage Snippets rule
   above (rather than alongside the header's own min-width: 1024px block near the top of this
   file) so it actually wins the cascade: a later same-specificity rule always beats an earlier
   one, matching or not, and .manage-snippets-dialog/.manage-snippet-preview's own unconditional
   width rules live down here. */
@media (min-width: 1024px) {
  .manage-snippets-dialog {
    width: 56rem;
  }

  .manage-snippet-preview {
    width: 20rem;
  }
}

.about-dialog {
  max-width: 24rem;
}

.about-header {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-bottom: 1rem;
}

.about-logo {
  flex: none;
  width: 2.5rem;
  height: 2.5rem;
}

.about-header h2 {
  margin: 0;
  font-size: 1.4rem;
  font-weight: 800;
  letter-spacing: 0.02em;
}

.about-description {
  margin: 0 0 1rem;
  color: var(--text);
  line-height: 1.5;
}

.about-copyright {
  margin: 0 0 0.25rem;
  color: #ffffff;
  font-size: 0.85rem;
  font-weight: bold;
}

.about-link {
  margin: 0 0 1rem;
  font-size: 0.85rem;
}

.about-link a {
  color: var(--field-border-focus);
}

.about-startup-check {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.9rem;
  color: var(--text-muted);
  cursor: pointer;
}

.about-startup-check input {
  margin: 0;
  accent-color: var(--field-border-focus);
  cursor: pointer;
}

.help-dialog {
  max-width: 28rem;
}

/* Unlike the other content-hugging dialogs above, the privacy policy's text is long enough to
   need its own scroll area capped to a share of the viewport height - the dialog itself picks
   up flex layout (see the [open] rule below) so the Close button stays pinned below the
   scrollable body rather than scrolling away with it. */
.privacy-dialog {
  width: 34rem;
  max-width: 90vw;
  max-height: 85vh;
}

.privacy-dialog[open] {
  display: flex;
  flex-direction: column;
}

.privacy-dialog-body {
  overflow-y: auto;
  padding-right: 0.5rem;
}

.privacy-dialog-body h2 {
  margin: 0 0 0.5rem;
  font-size: 1.25rem;
}

.privacy-dialog-body h3 {
  margin: 1.25rem 0 0.4rem;
  font-size: 1rem;
  color: var(--text);
}

.privacy-dialog-body p,
.privacy-dialog-body ul {
  margin: 0 0 0.75rem;
  color: var(--text);
  line-height: 1.5;
}

.privacy-dialog-body ul {
  padding-left: 1.25rem;
}

.privacy-dialog-body a {
  color: var(--field-border-focus);
}

.privacy-effective-date {
  color: var(--text-muted);
}

.privacy-footer-note {
  margin-top: 1rem;
  font-size: 0.85rem;
  color: var(--text-muted);
}

.help-shortcuts {
  list-style: none;
  margin: 0 0 1rem;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
}

.help-shortcuts li {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
}

.help-shortcuts code {
  flex: none;
  padding: 0.15rem 0.45rem;
  border: 1px solid var(--border-strong);
  border-radius: 0.3rem;
  background: var(--control-bg);
  font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
  font-size: 0.8rem;
  white-space: nowrap;
  color: var(--text);
}

.help-shortcuts span {
  color: var(--text-muted);
  font-size: 0.9rem;
  line-height: 1.4;
}

.help-dialog-link {
  margin: 0 0 1rem;
  font-size: 0.85rem;
  color: var(--text-muted);
}

.help-dialog-link a {
  color: var(--field-border-focus);
}

/* Same reasoning (and the same cascade-ordering caveat) as the Manage Snippets rule above - on
   desktop these content-hugging dialogs just use their own max-width unchanged, which looks
   noticeably narrow/cramped on a wide monitor (long paragraphs wrapping tightly in About; the
   Help dialog's two-column shortcut list barely has room for its own descriptions). Each still
   only grows as wide as its own content wants, up to the larger cap. */
@media (min-width: 1024px) {
  .about-dialog {
    max-width: 32rem;
  }

  .help-dialog {
    max-width: 40rem;
  }

  .privacy-dialog {
    width: 40rem;
  }
}

/* Fills as much of the viewport as reasonably possible (rather than .app-dialog's small
   content-hugging min-width) so the equation inside can be scaled up to the largest size that
   still fits without clipping - see fitBoardField in app.js. */
.board-dialog {
  position: relative;
  box-sizing: border-box;
  width: 96vw;
  height: 92vh;
  max-width: 96vw;
  max-height: 92vh;
  padding: 1rem;
}

/* display must stay scoped to [open] - setting it unconditionally on .board-dialog would
   override the browser's own default `dialog:not([open]) { display: none }` rule (author
   styles always win over UA styles, regardless of selector specificity), making the dialog
   visible even while closed. */
.board-dialog[open] {
  display: flex;
  flex-direction: column;
}

.board-dialog-close {
  position: absolute;
  top: 0.75rem;
  right: 0.75rem;
  z-index: 1;
  width: 2.5rem;
  height: 2.5rem;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border-strong);
  border-radius: 0.4rem;
  background: var(--field-bg);
  color: var(--text-muted);
  cursor: pointer;
}

.board-dialog-close svg {
  width: 60%;
  height: 60%;
}

.board-dialog-close:hover {
  background: var(--error);
  border-color: var(--error);
  color: #ffffff;
}

.board-content {
  flex: 1;
  min-height: 0;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

/* Read-only and non-interactive - this is purely a big display, not an editable field, so it
   gets none of the usual math-field chrome (border/background/padding/focus ring). */
.board-content math-field {
  pointer-events: none;
  border: none;
  box-shadow: none;
  padding: 0;
  background: none;
}

.board-content math-field::part(container) {
  padding: 0 !important;
}

.board-content math-field:focus-within {
  border: none;
  box-shadow: none;
}

#status {
  min-height: 1.2em;
  color: var(--text-muted);
}

#status.error {
  color: var(--error);
}

.app-footer {
  max-width: 90rem;
  margin: 0 auto;
  padding: 1.5rem 2rem 2rem;
  box-sizing: border-box;
  text-align: center;
  color: var(--text-muted);
  font-size: 0.9rem;
  line-height: 1.5;
}

.app-footer p {
  margin: 0;
}

.app-footer-copyright {
  color: var(--text);
  font-weight: bold;
}

:root[data-theme='dark'] .app-footer-copyright {
  color: #ffffff;
}

.app-footer a {
  color: var(--field-border-focus);
  text-decoration: none;
}

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