/* ══════════════════════════════════════════════════════════════
   MODALS
══════════════════════════════════════════════════════════════ */
.modal-backdrop {
  /* Must outrank every full-screen overlay it can be triggered from — the
     admin dashboard (900) and loading screen (1000) in particular, since
     admin.js now shows these dialogs too — as well as the toast stack
     (1100), matching the 9999 the old admin-only dialog implementation used. */
  position: fixed; inset: 0; z-index: 1200;
  background: rgba(0,0,0,0.6); backdrop-filter: blur(2px);
  display: flex; align-items: center; justify-content: center; padding: 24px;
  opacity: 0; pointer-events: none; transition: opacity 0.18s ease;
}
.modal-backdrop.visible { opacity: 1; pointer-events: auto; }
.modal {
  background: var(--bg-surface); border: 1px solid var(--border);
  border-radius: var(--radius-xl); padding: 26px 22px;
  width: 100%; max-width: 400px; box-shadow: var(--shadow-lg);
  transform: scale(0.96) translateY(4px); transition: transform 0.18s ease;
}
.modal-backdrop.visible .modal { transform: scale(1) translateY(0); }
.modal h3 { font-size: 16px; font-weight: 600; margin-bottom: 6px; }
.modal p  { font-size: 14px; color: var(--text-secondary); margin-bottom: 18px; line-height: 1.5; }
.modal-actions { display: flex; gap: 10px; margin-top: 20px; flex-wrap: wrap; }
/* min-width: 0 overrides the flex default (min-width: auto), which would
   otherwise refuse to shrink a button below its label's intrinsic width and
   push the row wider than the modal for longer confirm/cancel labels. */
.modal-actions button { flex: 1 1 auto; min-width: 0; padding: 10px; border-radius: var(--radius-md); font-size: 14px; font-weight: 500; }
.modal-btn-cancel  { background: var(--bg-elevated); border: 1px solid var(--border); color: var(--text-primary); transition: background var(--transition); }
.modal-btn-cancel:hover  { background: var(--bg-base); }
.modal-btn-confirm { background: var(--accent); color: var(--text-inverse); transition: opacity var(--transition); }
.modal-btn-confirm:hover { opacity: 0.9; }
.modal-btn-danger  { background: var(--red); color: var(--text-inverse); transition: opacity var(--transition); }
.modal-btn-danger:hover  { opacity: 0.85; }
/* Confirm dialog specifics */
.confirm-modal-inner { max-width: 340px; width: 100%; }
.confirm-modal-message {
  font-size: 15px; color: var(--text-primary); line-height: 1.55;
  margin-bottom: 0; text-align: center;
  /* Long unbroken tokens (filenames, URLs) must wrap instead of overflowing
     the modal — e.g. Delete "some-long-filename-with-no-spaces.pdf"? */
  overflow-wrap: break-word;
  word-break: break-word;
  /* Preserves \n\n paragraph breaks in multi-line confirm messages. */
  white-space: pre-line;
}
.prompt-modal-input { margin: 12px 0 4px; }

/* Passcode modal — gives "set a passcode" the same visual weight as the
   #passcode-screen auth-gate card (icon, heading, sub-copy) instead of the
   bare single-field showPrompt() dialog it used to be, plus a confirm field
   with inline mismatch validation. */
.passcode-modal-inner { max-width: 360px; }
.passcode-modal-title { font-size: 18px; font-weight: 600; text-align: center; margin: 0 0 6px; }
.passcode-modal-sub { font-size: 13px; color: var(--text-secondary); text-align: center; margin: 0 0 18px; line-height: 1.5; }
.passcode-modal-input + .passcode-modal-input { margin-top: 10px; }
@media (max-width: 639px) {
  .passcode-modal-inner { padding: 22px 18px; }
}

/* ══════════════════════════════════════════════════════════════
   SHARE MODAL (card redesign)
══════════════════════════════════════════════════════════════ */
.share-modal { max-width: 480px; max-height: 92vh; overflow-y: auto; padding: 22px 20px; }
.share-modal h3 { margin-bottom: 6px; }
.share-modal-header { margin-bottom: 14px; }
.share-subtitle { margin: 0; font-size: 13px; color: var(--text-secondary); line-height: 1.5; }
.share-room-path { font-size: 13px; font-family: var(--font-mono); color: var(--text-primary); margin-bottom: 8px; word-break: break-all; }
.share-section-desc { font-size: 12px; color: var(--text-secondary); margin: 4px 0 10px; }
.share-section-title { font-size: 12px; font-weight: 600; color: var(--text-secondary); margin-bottom: 6px; }
.share-qr-download:disabled { opacity: 0.45; cursor: not-allowed; }
.share-qr { text-align: center; margin: 10px 0 8px; }
.share-qr canvas, .share-qr img { border-radius: var(--radius-md); }
.share-qr-download {
  width: 100%; padding: 7px; font-size: 12px;
  background: var(--bg-elevated); border: 1px solid var(--border);
  border-radius: var(--radius-sm); color: var(--text-secondary);
  display: flex; align-items: center; justify-content: center; gap: 6px;
  transition: background var(--transition), color var(--transition);
}
.share-qr-download svg { width: 13px; height: 13px; }
.share-qr-download:hover { background: var(--bg-base); color: var(--accent); }

/* ── Templates modal ────────────────────────────────────────── */
/* Flex-column layout prevents footer from overlapping the template grid. */
.templates-modal {
  max-width: 720px;
  max-height: min(90vh, 720px);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.templates-header {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 10px; flex-shrink: 0;
}
.templates-header h3 { margin: 0; }
.templates-close {
  width: 28px; height: 28px; border-radius: var(--radius-sm);
  display: flex; align-items: center; justify-content: center;
  color: var(--text-secondary); flex-shrink: 0;
}
.templates-close svg { width: 16px; height: 16px; }
.templates-close:hover { background: var(--bg-elevated); color: var(--text-primary); }
/* The .templates-tabs row is flex-shrink:0 so it never grows/shrinks */
.templates-tabs { flex-shrink: 0; }
/* The scrollable body fills all available height */
.templates-body {
  flex: 1; min-height: 0; overflow: hidden;
  display: flex; flex-direction: column;
}
/* Footer stays at the bottom, never overlaps content */
.modal-actions { flex-shrink: 0; margin-top: 14px; }

/* Search bar */
.tmpl-search-wrap { margin-bottom: 10px; flex-shrink: 0; }
.tmpl-search-input {
  width: 100%; padding: 8px 12px; font-size: 13px;
  background: var(--bg-input); border: 1px solid var(--border);
  border-radius: var(--radius-md); color: var(--text-primary); outline: none;
  transition: border-color var(--transition);
}
.tmpl-search-input:focus { border-color: var(--accent); }
.tmpl-search-input::placeholder { color: var(--text-muted); }
.tmpl-no-results {
  grid-column: 1 / -1; font-size: 13px; color: var(--text-muted);
  text-align: center; padding: 18px 0;
}

/* Two-column layout: list + preview. The preview track is a minmax(170,220)
   range rather than a hard 220px so it gives space back to the list column
   on narrower non-mobile widths (e.g. 640-680px, just above the mobile
   breakpoint below) instead of squeezing template cards to the point of
   overflow. */
.tmpl-two-col {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(170px, 220px); gap: 0;
  flex: 1; min-height: 0;
  border: 1px solid var(--border-soft); border-radius: var(--radius-md); overflow: hidden;
}
.tmpl-list-col { overflow-y: auto; min-width: 0; padding: 8px; }
.tmpl-preview-col {
  overflow-y: auto;
  border-left: 1px solid var(--border-soft);
  background: var(--bg-input); padding: 10px;
  display: flex; flex-direction: column; min-width: 0;
}
.tmpl-preview-hdr {
  font-size: 11px; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.07em; color: var(--accent);
  margin-bottom: 8px; flex-shrink: 0; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
.tmpl-preview-body {
  font-family: var(--font-mono); font-size: 11px;
  color: var(--text-secondary); line-height: 1.5;
  white-space: pre-wrap; word-break: break-word; margin: 0; flex: 1;
}

/* Template list — auto-fit/minmax instead of a hardcoded 2-column grid so
   card width adapts to whatever room the (also now-flexible) preview column
   leaves it, rather than staying pinned at exactly 2 columns even when
   that squeezes cards below a comfortable reading width. Below the 639px
   mobile breakpoint this is overridden back to a forced single column for
   touch target size (see the mobile media query). */
.templates-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 8px; }
.template-btn {
  display: flex; flex-direction: column; align-items: flex-start;
  padding: 10px 12px;
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: var(--radius-md);
  color: var(--text-primary); font-size: 13px; font-weight: 500;
  cursor: pointer; text-align: left; gap: 3px;
  transition: background var(--transition), border-color var(--transition);
}
.template-btn:hover  { background: var(--bg-base); border-color: var(--accent); }
.template-btn:hover .template-desc { color: var(--text-secondary); }
.template-btn:focus  { border-color: var(--accent); outline: none; }
.template-btn:active { transform: scale(0.97); }
.template-desc {
  font-size: 11px; font-weight: 400; color: var(--text-muted);
  line-height: 1.4; transition: color var(--transition);
}

/* Export/import bar in custom tab */
.tmpl-io-bar { display: flex; gap: 8px; margin-bottom: 14px; flex-wrap: wrap; }
.tmpl-io-btn {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 6px 12px; font-size: 12px; font-weight: 500;
  background: var(--bg-elevated); border: 1px solid var(--border);
  border-radius: var(--radius-md); color: var(--text-secondary);
  transition: background var(--transition), border-color var(--transition), color var(--transition);
}
.tmpl-io-btn:hover:not(:disabled) { border-color: var(--accent); color: var(--text-primary); }
.tmpl-io-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.template-choice-msg {
  font-size: 14px; font-weight: 600; color: var(--text-primary);
  margin: 6px 0 14px; line-height: 1.5;
}
.template-choice-actions { display: flex; flex-direction: column; gap: 8px; }
.template-choice-btn {
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
  padding: 11px 14px; border-radius: var(--radius-md);
  background: var(--bg-elevated); border: 1px solid var(--border); color: var(--text-primary);
  text-align: left;
  transition: background var(--transition), border-color var(--transition);
}
.template-choice-btn:hover { background: var(--bg-base); border-color: var(--accent); }
.template-choice-btn-label { font-size: 14px; font-weight: 500; }
.template-choice-btn-desc  { font-size: 11px; color: var(--text-muted); font-weight: 400; }
.template-choice-btn.primary { background: var(--accent); color: var(--text-inverse); border-color: var(--accent); }
.template-choice-btn.primary .template-choice-btn-desc { color: inherit; opacity: 0.72; }
.template-choice-btn.primary:hover { opacity: 0.9; }
.template-choice-btn.danger { background: var(--red); color: var(--text-inverse); border-color: var(--red); }
.template-choice-btn.danger .template-choice-btn-desc { color: inherit; opacity: 0.72; }
.template-choice-btn.danger:hover { opacity: 0.9; }
.template-choice-btn.cancel { opacity: 0.7; }
.template-choice-btn.cancel:hover { opacity: 1; }

/* Templates tab bar */
.templates-tabs {
  display: flex; gap: 2px; border-bottom: 1px solid var(--border);
  margin: 0 0 14px; padding-bottom: 0;
}
.tmpl-tab {
  padding: 7px 14px; font-size: 13px; font-weight: 500;
  color: var(--text-secondary); border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  border: 1px solid transparent; border-bottom: none; margin-bottom: -1px;
  transition: color var(--transition);
}
.tmpl-tab:hover  { color: var(--text-primary); }
.tmpl-tab.active { color: var(--accent); border-color: var(--border) var(--border) var(--bg-surface); background: var(--bg-surface); }

/* Templates group label */
.templates-group-label {
  grid-column: 1 / -1; font-size: 10px; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--text-muted); padding: 4px 0 2px;
}

/* Custom template manage list */
.custom-templates-list { display: flex; flex-direction: column; gap: 6px; }
.custom-template-item {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 12px; background: var(--bg-elevated); border: 1px solid var(--border);
  border-radius: var(--radius-md);
}
.custom-template-label { flex: 1; font-size: 13px; font-weight: 500; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.custom-template-actions { display: flex; gap: 4px; flex-shrink: 0; }
.custom-tmpl-btn {
  width: 28px; height: 28px; border-radius: var(--radius-sm);
  display: flex; align-items: center; justify-content: center;
  color: var(--text-secondary); border: 1px solid var(--border);
  background: transparent; transition: background var(--transition), color var(--transition);
}
.custom-tmpl-btn svg   { width: 13px; height: 13px; }
.custom-tmpl-btn:hover { background: var(--bg-base); color: var(--text-primary); }
.custom-tmpl-btn.danger:hover { background: var(--red); color: var(--text-inverse); border-color: var(--red); }

/* Save-as-template button in modal footer */
.modal-btn-secondary {
  display: flex; align-items: center; gap: 6px;
  padding: 8px 14px; border-radius: var(--radius-md); font-size: 12px;
  background: transparent; border: 1px solid var(--border);
  color: var(--text-secondary); transition: border-color var(--transition), color var(--transition);
  margin-right: auto;
}
.modal-btn-secondary:hover { border-color: var(--accent); color: var(--accent); }
.modal-btn-secondary svg   { flex-shrink: 0; }

/* ══════════════════════════════════════════════════════════════
   TOASTS
══════════════════════════════════════════════════════════════ */
#toast-container {
  position: fixed; bottom: calc(24px + var(--kb-inset, 0px)); left: 50%; transform: translateX(-50%);
  transition: bottom 0.15s ease;
  /* Must outrank every other fixed-position layer (.auth-screen: 900,
     #loading-screen: 1000) so toasts — including admin.js's own toasts,
     which share this container — are never hidden behind them. */
  z-index: 1100; display: flex; flex-direction: column-reverse;
  align-items: center; gap: 8px; pointer-events: none;
}
.toast {
  background: var(--bg-elevated); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: 10px 18px;
  font-size: 13px; color: var(--text-primary);
  box-shadow: var(--shadow-md); pointer-events: auto;
  animation: toast-in 0.2s ease forwards;
  /* Short status toasts ("Room cleared.") stay on one line naturally; longer
     ones (some error messages run a full sentence) must wrap instead of
     spilling silently past the pill's visible bounds. */
  max-width: min(calc(100vw - 48px), 420px);
  overflow-wrap: break-word; word-break: break-word; text-align: center;
}
.toast.success { border-color: rgba(74,222,128,0.35); color: var(--green); }
.toast.error   { border-color: rgba(248,113,113,0.35); color: var(--red); }
.toast.warning { border-color: rgba(251,191,36,0.35); color: var(--yellow); }
.toast.out     { animation: toast-out 0.2s ease forwards; }
@keyframes toast-in  { from{ opacity:0; transform:translateY(8px); } to{ opacity:1; transform:translateY(0); } }
@keyframes toast-out { from{ opacity:1; transform:translateY(0); } to{ opacity:0; transform:translateY(8px); } }

/* ══════════════════════════════════════════════════════════════
   FLOATING COMMENT COMPOSER — opens right at the current selection/caret
   (viewport-anchored), used by the add-comment FAB, the Ctrl+Shift+/
   shortcut, and the editor context menu's "Add comment" action.
   Submitting always persists a real anchored comment — see
   ui/collab.js's openFloatingCommentComposer(). Existing comments'
   own floating bubbles live in .comment-margin-layer instead (see
   editor.css) since they track a fixed note anchor, not a live caret.
══════════════════════════════════════════════════════════════ */
#comment-floating-layer {
  position: fixed; inset: 0; z-index: 55; pointer-events: none;
}
.comment-floating-composer {
  position: absolute; transform: translate(-8px, -100%);
  max-width: 220px; font-size: 12.5px; line-height: 1.35;
  pointer-events: auto; padding: 0;
  animation: comment-floating-in 0.15s ease forwards;
}
.comment-floating-composer input {
  width: 220px; font: inherit; font-size: 12.5px; color: var(--text-primary);
  background: var(--bg-elevated); border: 1px solid var(--accent);
  border-radius: var(--radius-md) var(--radius-md) var(--radius-md) 2px;
  padding: 6px 10px; box-shadow: var(--shadow-md); outline: none;
}
@keyframes comment-floating-in { from{ opacity:0; transform:translate(-8px, -94%); } to{ opacity:1; transform:translate(-8px, -100%); } }
/* Mobile: docks to the bottom above the keyboard instead of the caret-
   relative position above — see openFloatingCommentComposer()'s own
   comment for why a caret-anchored snapshot goes stale the moment the
   on-screen keyboard opens and shifts the visible viewport. A dedicated
   class (added by JS, replacing the inline left/top the desktop path
   sets) rather than a plain media-query override, since overriding those
   inline styles would need !important and fighting the shared entrance
   keyframe's own transform. Mirrors #search-panel's mobile treatment,
   including replacing .mobile-action-bar's footprint (rules further
   down) instead of stacking above it. */
@media (max-width: 639px) {
  .comment-floating-composer-dock {
    position: fixed; left: 8px; right: 8px; top: auto;
    bottom: calc(8px + var(--kb-inset, 0px));
    max-width: none; transform: none;
    transition: bottom 0.15s ease;
    animation: comment-floating-in-dock 0.15s ease forwards;
  }
  .comment-floating-composer-dock input { width: 100%; font-size: 16px; }
  @keyframes comment-floating-in-dock { from{ opacity:0; transform:translateY(10px); } to{ opacity:1; transform:translateY(0); } }
}

/* ══════════════════════════════════════════════════════════════
   MOBILE BOTTOM ACTION BAR
══════════════════════════════════════════════════════════════ */
.mobile-action-bar {
  display: none;
  position: fixed; left: 0; right: 0; z-index: 30;
  /* --kb-inset (src/keyboard-viewport.js) is 0px whenever the on-screen
     keyboard is closed, or on a platform where index.html's
     interactive-widget=resizes-content viewport hint already shrinks the
     layout viewport itself (so this bar's own bottom:0 is already correct
     without any extra push) — this only ever does something on the
     platforms neither of those covers. */
  bottom: var(--kb-inset, 0px);
  background: var(--bg-surface); border-top: 1px solid var(--border);
  padding: 8px 0 max(8px, env(safe-area-inset-bottom));
  justify-content: space-around; align-items: center;
  box-shadow: 0 -4px 16px rgba(0,0,0,0.25);
  transition: bottom 0.15s ease;
}
.mob-action-btn {
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  padding: 6px 10px; border-radius: var(--radius-sm);
  color: var(--text-secondary); min-width: 48px;
  transition: color var(--transition);
}
.mob-action-btn svg { width: 20px; height: 20px; }
.mob-action-btn .mob-label { font-size: 10px; line-height: 1; }
.mob-action-btn:hover, .mob-action-btn.active { color: var(--accent); }

/* ══════════════════════════════════════════════════════════════
   PWA / SW
══════════════════════════════════════════════════════════════ */
.pwa-bar {
  /* bottom stays a plain 0 (not --kb-inset) while hidden — translateY(100%)
     below only pushes the bar offscreen by its OWN height, measured from
     wherever `bottom` puts its resting position. If the hidden state
     inherited --kb-inset's raise too, a keyboard taller than the bar
     itself (routine — a keyboard is ~250-350px, the bar ~56-70px) would
     still leave part of the "hidden" bar, including its inactive Install
     button, visible above the true bottom edge. Only .visible below opts
     into tracking --kb-inset, once the bar is actually meant to be shown
     above the keyboard.
     z-index matches .sw-update-bar's own tier (app-shell.css) and for the
     same documented reason: beforeinstallprompt can fire on any screen,
     including the full-screen route containers (.auth-screen/.legal-screen/
     #landing-screen/#app-landing-screen: 900, #loading-screen: 1000) — a
     lower z-index left this bar rendered but genuinely unclickable
     (confirmed live: document.elementFromPoint() on its own buttons
     resolved to the landing screen underneath) whenever one of those was
     the active screen. */
  position: fixed; bottom: 0; left: 0; right: 0; z-index: 1100;
  background: var(--bg-elevated); border-top: 1px solid var(--border);
  padding: 12px 20px; display: flex; align-items: center; gap: 12px;
  transform: translateY(100%); transition: transform 0.3s ease, bottom 0.15s ease, box-shadow 0.3s ease;
}
/* Shadow only while visible — same fix as .side-panel (panels.css) and
   .admin-drawer (admin.css): an unconditional box-shadow on a
   translateY(100%)-hidden element bleeds past its edge regardless of the
   transform, showing as a persistent line at the bottom of the viewport
   even while this bar is fully off-screen. */
.pwa-bar.visible { transform: translateY(0); bottom: var(--kb-inset, 0px); box-shadow: var(--shadow-md); }
.pwa-bar-text { flex: 1; font-size: 13px; color: var(--text-secondary); }
.pwa-bar-text strong { color: var(--text-primary); }
.pwa-bar-btn { padding: 7px 16px; border-radius: var(--radius-sm); font-size: 13px; font-weight: 600; transition: opacity var(--transition); }
.pwa-bar-btn.install { background: var(--accent); color: var(--text-inverse); }
.pwa-bar-btn.install:hover { opacity: 0.9; }
.pwa-bar-btn.dismiss { color: var(--text-secondary); }

/* ══════════════════════════════════════════════════════════════
   READ-ONLY / LOCK MODE
══════════════════════════════════════════════════════════════ */
#note-editor.readonly { background: var(--bg-surface); cursor: default; }
body.read-only-mode #note-editor, body.lock-mode #note-editor { background: var(--bg-surface); }
body.read-only-mode .editor-wrap::after, body.lock-mode .editor-wrap::after {
  content: ''; position: absolute; top: 0; left: 0; right: 0;
  height: 2px; background: linear-gradient(90deg, transparent, var(--accent-glow), transparent);
  pointer-events: none;
}
body.read-only-mode [data-readonly-hide] { display: none !important; }
body.read-only-mode .note-preview li.md-task input[type="checkbox"],
body.lock-mode .note-preview li.md-task input[type="checkbox"] { cursor: not-allowed; }

/* ══════════════════════════════════════════════════════════════
   UTILITIES / SCROLLBAR
══════════════════════════════════════════════════════════════ */
html { scrollbar-width: thin; scrollbar-color: var(--border) transparent; }
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }
.hidden  { display: none !important; }
.sr-only { position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; border:0; }
.text-accent { color: var(--accent); }
.text-muted  { color: var(--text-muted); }

/* ══════════════════════════════════════════════════════════════
   MOBILE RESPONSIVE
══════════════════════════════════════════════════════════════ */
@media (max-width: 639px) {
  .mobile-action-bar { display: flex; }
  /* Must track .mobile-action-bar's own bottom offset (below) — otherwise
     the bar rises above the keyboard via --kb-inset while the editor's
     scrollable area doesn't reserve the extra space, leaving its last
     lines rendered underneath the now-raised bar. */
  #app-screen { padding-bottom: calc(60px + var(--kb-inset, 0px)); transition: padding-bottom 0.15s ease; }
  #toast-container { bottom: calc(72px + var(--kb-inset, 0px)); }
  .app-footer { display: none; }
  .app-header { padding: 9px 12px; }
  .header-room .room-name { max-width: 120px; }
  #note-editor { padding: 16px; font-size: 15px; }
  .note-preview { padding: 16px; font-size: 15px; }
  .note-live { padding: 16px; font-size: 15px; }
  .side-panel { width: 100vw; max-width: 100vw; }
  .auth-card { padding: 28px 20px; }
  .tools-grid { grid-template-columns: 1fr 1fr 1fr; }
  .templates-list { grid-template-columns: 1fr; }
  .tmpl-two-col { grid-template-columns: 1fr; min-height: 240px; }
  .tmpl-preview-col { display: none; }
  .share-modal { padding: 18px 16px; }
  .share-qr-download { min-height: 38px; }
  .remote-notice { left: 12px; right: 12px; transform: none; max-width: none; animation: none; }
  .remote-notice-actions { justify-content: stretch; }
  .remote-notice-btn { flex: 1; }
  .editor-toolbar { padding: 5px 12px; }
  /* Stack split-mode panes vertically on narrow viewports.
     Uses the current mode-split class (split-mode was the legacy alias). */
  .editor-wrap.mode-split { grid-template-columns: 1fr; grid-template-rows: minmax(0, 1fr) minmax(0, 1fr); }
  .editor-wrap.mode-split #note-editor { border-right: none; border-bottom: 1px solid var(--border); }
  .md-seg-btn[data-mode="split"] { display: none; }
  #more-dropdown { min-width: 200px; }
}

/* ══════════════════════════════════════════════════════════════
   FOCUS & MOTION
══════════════════════════════════════════════════════════════ */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
button:focus:not(:focus-visible) { outline: none; }
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
  .loading-spinner { animation: none; }
}

/* ══════════════════════════════════════════════════════════════
   REMOTE NOTICE (ENHANCED)
══════════════════════════════════════════════════════════════ */
.remote-notice-header { display: flex; align-items: baseline; gap: 6px; margin-bottom: 4px; }
.remote-notice-meta   { font-size: 11px; color: var(--text-muted); }
.remote-notice-counts {
  font-size: 11px; color: var(--text-secondary); margin-bottom: 8px;
  padding: 4px 8px; background: var(--bg-elevated); border-radius: 4px;
}
.remote-notice-btn.primary { background: var(--accent); color: var(--text-inverse); font-weight: 600; }
.remote-notice-btn.primary:hover { opacity: 0.88; }
.remote-notice-btn.subtle  { color: var(--text-muted); }
.remote-notice-btn.subtle:hover { color: var(--text-secondary); background: transparent; }

/* ══════════════════════════════════════════════════════════════
   SEARCH PANEL
══════════════════════════════════════════════════════════════ */
/* Find & Replace floats as a compact card near the top of the editor
   instead of the generic full-height drawer every other .side-panel uses.
   The whole point of Find/Replace is watching matches highlighted in the
   actual document as you step through them (panels.js's _jumpToMatch()
   already moves the real selection in the Write textarea or the Live CM6
   surface, not just this panel's own state) — a full-height drawer
   defeats that by hiding the document behind it, and on mobile the
   generic 100vw-wide mobile override hides it completely. openPanel()
   is also called with { dimBackdrop: false } for this panel specifically
   (see its call sites), so the shared dimming backdrop stays off and the
   editor underneath stays visible *and* interactive.
   An ID selector deliberately overrides .side-panel's positioning — ID
   specificity always wins over any number of classes, so this doesn't
   need !important or to care about source order relative to the base
   rules above. */
#search-panel {
  top: 64px; right: 16px; bottom: auto; left: auto;
  width: min(360px, calc(100vw - 32px));
  max-height: calc(100vh - 100px);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  transform: translateY(-10px) scale(0.98);
  opacity: 0;
  /* Unlike the base .side-panel rule (which relies on being translated
     fully off-screen while closed to stay out of the way), this panel
     stays in place — opacity alone would leave an invisible click-blocking
     rectangle floating over the editor while "closed". */
  pointer-events: none;
  transition: transform 0.2s cubic-bezier(0.4,0,0.2,1), opacity 0.2s ease;
}
#search-panel.open { transform: translateY(0) scale(1); opacity: 1; pointer-events: auto; }
/* Anchored to the bottom on mobile instead of the top — floats directly
   above the on-screen keyboard (same var(--kb-inset, 0px) tracking as
   .mobile-action-bar/.pwa-bar) rather than sitting under the header where
   it competes with whatever's above the keyboard for the same shrunken
   viewport. It also replaces .mobile-action-bar's footprint entirely
   instead of stacking above it — see the "FIND & REPLACE — MOBILE" rules
   further down, which hide the action bar and reclaim its reserved
   #app-screen padding while this panel is open. */
@media (max-width: 639px) {
  #search-panel {
    top: auto; right: 8px; left: 8px; bottom: calc(8px + var(--kb-inset, 0px));
    width: auto; max-width: none;
    max-height: calc(100vh - 100px - var(--kb-inset, 0px));
    transform: translateY(10px) scale(0.98);
    transition: transform 0.2s cubic-bezier(0.4,0,0.2,1), opacity 0.2s ease, bottom 0.15s ease;
  }
}
.search-bar {
  display: flex; align-items: center; gap: 6px;
  background: var(--bg-elevated); border: 1px solid var(--border);
  border-radius: 8px; padding: 6px 8px 6px 12px; margin-bottom: 6px;
}
.search-input {
  flex: 1; min-width: 0; background: transparent; border: none; outline: none;
  color: var(--text-primary); font-size: 14px; font-family: inherit;
}
.search-count { font-size: 12px; color: var(--text-muted); white-space: nowrap; }

/* Prev/Next live inline in the search bar itself (VS Code's/browser find
   bars' layout) rather than their own full-width button row below it — that
   extra row was most of what made this panel too tall to leave any note
   content visible on a mobile screen with the keyboard open. Icon-only,
   same 26px compact footprint as .search-case-btn below; the mobile
   override further down gives both a real touch target. */
.search-nav-btn {
  flex-shrink: 0; width: 26px; height: 26px;
  display: flex; align-items: center; justify-content: center;
  padding: 0; background: transparent; border: 1px solid transparent;
  border-radius: 6px; color: var(--text-secondary); cursor: pointer;
  transition: border-color 0.15s, color 0.15s, background 0.15s;
}
.search-nav-btn:hover { border-color: var(--border); background: var(--bg-base); color: var(--accent); }

/* Aa case-sensitive toggle inside the search bar */
.search-case-btn {
  flex-shrink: 0; background: transparent; border: 1px solid transparent;
  border-radius: 4px; padding: 2px 5px; color: var(--text-muted);
  font-size: 11px; font-weight: 700; letter-spacing: 0.03em; cursor: pointer;
  line-height: 1; transition: border-color 0.15s, color 0.15s, background 0.15s;
}
.search-case-btn:hover { border-color: var(--border); color: var(--text-secondary); }
.search-case-btn.is-active {
  border-color: var(--accent); color: var(--accent);
  background: rgba(var(--accent-rgb), 0.10);
}

/* Collapsed by default (see index.html's comment on #search-replace-toggle)
   — a plain link-style row, not a full button, since it's secondary to the
   search bar above it. */
.search-replace-toggle {
  display: flex; align-items: center; gap: 5px;
  padding: 6px 4px; margin-bottom: 4px;
  background: none; border: none; color: var(--text-secondary);
  font-size: 12.5px; font-weight: 500; cursor: pointer;
  transition: color 0.15s;
}
.search-replace-toggle:hover { color: var(--text-primary); }
.search-replace-toggle-chevron { transition: transform 0.15s ease; flex-shrink: 0; }
.search-replace-toggle[aria-expanded="true"] .search-replace-toggle-chevron { transform: rotate(90deg); }

.search-hint { font-size: 12px; color: var(--text-muted); margin: 0; }

/* Replace row (below the toggle, same panel) */
.replace-bar {
  display: flex; align-items: center; gap: 8px;
  background: var(--bg-elevated); border: 1px solid var(--border);
  border-radius: 8px; padding: 8px 12px; margin-bottom: 10px;
}
.replace-actions { display: flex; gap: 8px; margin-bottom: 14px; }
.replace-btn {
  flex: 1; padding: 7px 12px; background: var(--bg-elevated);
  border: 1px solid var(--border); border-radius: 6px;
  color: var(--text-secondary); font-size: 13px; cursor: pointer;
  transition: border-color 0.15s, color 0.15s, background 0.15s;
  font-family: inherit;
}
.replace-btn:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
.replace-btn--all {
  background: var(--accent-dim); border-color: var(--accent); color: var(--accent);
}
.replace-btn--all:hover:not(:disabled) { background: var(--accent); color: var(--text-inverse); }
.replace-btn:disabled { opacity: 0.38; cursor: not-allowed; }

/* Every control above was sized for a mouse (13-14px text, ~31px-tall
   buttons, a 29×17px "Aa" toggle) — well under the ~44px touch-target
   guidance real mobile UI follows elsewhere in this file (e.g.
   .share-qr-download's 38px in the main mobile block above). Placed here,
   after the base rules rather than in that earlier block, so it wins the
   cascade: a later same-specificity rule always overrides an earlier one
   regardless of which is inside a @media block. */
@media (max-width: 639px) {
  .search-bar, .replace-bar { padding: 8px 6px 8px 12px; }
  /* 16px, not the base rule's 14px — iOS Safari zooms the whole page in on
     focus for any input under its own 16px auto-zoom threshold. */
  .search-input { font-size: 16px; }
  .search-nav-btn, .search-case-btn {
    min-width: 38px; min-height: 38px; padding: 6px 8px; font-size: 13px;
  }
  .search-replace-toggle { padding: 10px 4px; font-size: 13.5px; }
  .replace-actions { gap: 10px; }
  .replace-btn { padding: 13px 12px; font-size: 14px; }
}

/* ══════════════════════════════════════════════════════════════
   EXPORT MODAL
══════════════════════════════════════════════════════════════ */
.export-modal { max-width: 360px; }
.export-options { display: flex; flex-direction: column; gap: 6px; margin: 16px 0; }
.export-btn {
  display: flex; align-items: center; gap: 10px; padding: 10px 14px;
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 8px;
  color: var(--text-primary); font-size: 13px; cursor: pointer; text-align: left;
  transition: border-color 0.15s, background 0.15s;
}
.export-btn:hover { border-color: var(--accent); background: var(--bg-surface); }
.export-btn svg   { flex-shrink: 0; color: var(--text-secondary); }
/* The label must never wrap (it's the primary, scannable text); the muted
   description is secondary and free to wrap or shrink instead — without
   this, a long description (e.g. "Copy rendered HTML to clipboard") could
   crowd its own row's label into wrapping while shorter rows stayed on one
   line, an inconsistent, lopsided-looking list. */
.export-label     { flex-shrink: 0; white-space: nowrap; }
.export-desc      { font-size: 11px; color: var(--text-muted); margin-left: auto; text-align: right; }
.export-separator { height: 1px; background: var(--border); margin: 4px 0; }

/* ══════════════════════════════════════════════════════════════
   KEYBOARD SHORTCUTS MODAL
══════════════════════════════════════════════════════════════ */
.shortcuts-modal { max-width: 400px; }
.shortcuts-list  { margin: 16px 0; }
.shortcuts-group { margin-bottom: 20px; }
.shortcuts-group-title {
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--text-muted); margin-bottom: 8px;
}
.shortcut-row {
  display: flex; align-items: center; gap: 6px; padding: 5px 0;
  border-bottom: 1px solid var(--bg-elevated); font-size: 13px;
  color: var(--text-secondary);
}
.shortcut-row span { margin-left: auto; color: var(--text-primary); }
kbd {
  display: inline-flex; align-items: center; padding: 2px 6px;
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 4px;
  font-size: 11px; font-family: var(--font-mono); color: var(--text-secondary);
  white-space: nowrap;
}
.shortcuts-note { font-size: 12px; color: var(--text-muted); margin: 12px 0 0; }


/* ══════════════════════════════════════════════════════════════
   COMMAND PALETTE
══════════════════════════════════════════════════════════════ */
/* Top-aligned rather than centered like other modals — a palette that jumps
   to vertical center on every open reads as an odd flash to a keyboard user
   who expects it to appear "at the top", the way every other app's does. */
.command-palette-backdrop { align-items: flex-start; padding-top: 12vh; }
.command-palette-modal {
  max-width: 560px; padding: 0; overflow: hidden;
  display: flex; flex-direction: column; max-height: 70vh;
}
.command-palette-search-row {
  display: flex; align-items: center; gap: 10px;
  padding: 14px 16px; border-bottom: 1px solid var(--border);
  color: var(--text-muted); flex-shrink: 0;
}
.command-palette-input {
  flex: 1; border: none; background: none; outline: none;
  font-size: 15px; color: var(--text-primary);
}
.command-palette-input::placeholder { color: var(--text-muted); }
.command-palette-esc-hint { color: var(--text-muted); flex-shrink: 0; }
.command-palette-list { overflow-y: auto; padding: 6px; }
.command-palette-item {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 9px 10px; border-radius: var(--radius-md);
  font-size: 13.5px; color: var(--text-primary); text-align: left;
  background: none; border: none;
}
.command-palette-item.active,
.command-palette-item:hover { background: color-mix(in srgb, var(--bg-elevated) 88%, var(--accent-dim)); }
.command-palette-item-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.command-palette-item-group { font-size: 11px; color: var(--text-muted); flex-shrink: 0; }
.command-palette-item-shortcut { flex-shrink: 0; }
.command-palette-empty { padding: 20px 10px; text-align: center; color: var(--text-muted); font-size: 13.5px; }

/* ══════════════════════════════════════════════════════════════
   SLASH-COMMAND QUICK-INSERT MENU
══════════════════════════════════════════════════════════════ */
.slash-menu {
  position: fixed; z-index: 1300;
  background: var(--bg-surface); border: 1px solid color-mix(in srgb, var(--border) 85%, transparent);
  border-radius: 10px; box-shadow: var(--shadow-lg);
  padding: 6px; width: 220px; max-height: 280px; overflow-y: auto;
  opacity: 0; transform: scale(0.96); pointer-events: none;
  transition: opacity 0.12s ease, transform 0.12s ease;
}
.slash-menu.visible { opacity: 1; transform: scale(1); pointer-events: auto; }
.slash-menu-list { display: flex; flex-direction: column; gap: 1px; }
.slash-menu-item {
  display: flex; align-items: center; justify-content: space-between; gap: 10px; width: 100%;
  padding: 7px 9px; border-radius: 7px;
  font-size: 13px; color: var(--text-primary); text-align: left;
}
.slash-menu-item:hover, .slash-menu-item.active { background: var(--bg-elevated); }
.slash-menu-item-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.slash-menu-item-hint { flex-shrink: 0; font-size: 11px; color: var(--text-muted); font-family: var(--font-mono); }
.slash-menu-empty { padding: 10px 9px; font-size: 13px; color: var(--text-muted); }


/* ══════════════════════════════════════════════════════════════
   PWA INSTALL BAR — push mobile action bar up when visible
══════════════════════════════════════════════════════════════ */
@media (max-width: 639px) {
  /* Raise the fixed action bar above the PWA install bar when it slides in,
     and increase app-screen bottom padding to match. */
  body:has(#pwa-install-bar.visible) .mobile-action-bar {
    bottom: calc(56px + var(--kb-inset, 0px)); /* 56px ≈ height of .pwa-bar */
  }
  body:has(#pwa-install-bar.visible) #app-screen {
    padding-bottom: calc(60px + 56px + var(--kb-inset, 0px));
  }
  body:has(#pwa-install-bar.visible) #toast-container {
    bottom: calc(72px + 56px + var(--kb-inset, 0px));
  }
}

/* ══════════════════════════════════════════════════════════════
   FIND & REPLACE — MOBILE: replaces the bottom action bar's
   footprint instead of stacking above it
══════════════════════════════════════════════════════════════ */
@media (max-width: 639px) {
  /* Nothing in .mobile-action-bar (Files/Tools/Devices/Settings) is useful
     mid-search, and leaving it visible underneath the now bottom-anchored
     search panel would either stack two bottom bars (wasting the vertical
     space this whole redesign is trying to reclaim) or need careful
     z-index layering for no real benefit — hiding it outright is simpler
     and gives the panel its footprint outright, the way a keyboard
     accessory bar replaces whatever else was docked at the bottom. */
  body:has(#search-panel.open) .mobile-action-bar { display: none; }
  /* #app-screen no longer needs to reserve the action bar's 60px while
     it's hidden — reclaim that space for the editor's scrollable area
     instead of leaving it as dead padding under the search panel. */
  body:has(#search-panel.open) #app-screen {
    padding-bottom: calc(8px + var(--kb-inset, 0px));
  }
  /* Toasts ("Replaced N matches.") are otherwise positioned to clear the
     36px action bar (72px, see the main mobile block above) — nowhere
     near enough to clear this panel, which is taller in either its
     collapsed (~193px) or Replace-expanded (~312px) state. 340px clears
     the taller of the two with room to spare; toasts are transient enough
     that a little extra headroom in the common (collapsed) case doesn't
     matter. */
  body:has(#search-panel.open) #toast-container {
    bottom: calc(340px + var(--kb-inset, 0px));
  }
  /* The add-comment FAB anchors to the editor pane's own bottom-right
     corner (editor.css), independent of --kb-inset — the bottom-anchored
     search panel would otherwise sit directly on top of it. */
  body:has(#search-panel.open) #btn-add-comment-fab { display: none; }
  /* .remote-notice ("new content available") is position:absolute within
     .editor-area, bottom:12px from *its* bottom edge — which, now that
     #app-screen's padding-bottom is reclaimed above, sits right where the
     search panel floats. Unlike the toast/FAB above, this one has real
     Apply/Keep/Copy actions a user might need mid-search, so it's bumped
     clear rather than hidden. 350px covers the same worst-case (expanded,
     ~312px) height as the toast, plus its own 8px bottom margin. */
  body:has(#search-panel.open) .remote-notice { bottom: 350px; }
}

/* ══════════════════════════════════════════════════════════════
   FLOATING COMMENT COMPOSER — MOBILE: same "replaces the bottom
   action bar's footprint" treatment as Find & Replace above — see
   that section's comments for the full reasoning.
══════════════════════════════════════════════════════════════ */
@media (max-width: 639px) {
  body:has(.comment-floating-composer-dock) .mobile-action-bar { display: none; }
  body:has(.comment-floating-composer-dock) #app-screen {
    padding-bottom: calc(8px + var(--kb-inset, 0px));
  }
  /* ~40px tall (16px input line-height + 12px padding + 8px bottom gap) —
     64px clears it with a little headroom. */
  body:has(.comment-floating-composer-dock) #toast-container {
    bottom: calc(64px + var(--kb-inset, 0px));
  }
  /* The add-comment FAB is what opened this composer — hide it while open,
     same as it's hidden for #search-panel above. */
  body:has(.comment-floating-composer-dock) #btn-add-comment-fab { display: none; }
  body:has(.comment-floating-composer-dock) .remote-notice { bottom: 64px; }
}

/* ══════════════════════════════════════════════════════════════
   KEYBOARD OPEN — MOBILE: reclaim the bottom action bar's
   footprint the moment ANY text surface is focused — typing in
   the note itself, not just a specific panel/composer — instead
   of merely repositioning the bar above the keyboard. The bar's
   own Files/Tools/Devices/Settings actions aren't useful mid-typing
   anyway (same reasoning as hiding it for Find & Replace above),
   and every note-taking app worth comparing to (Notion, Google
   Docs, Apple Notes) reclaims this same chrome while the keyboard
   is up rather than leaving a nav bar sandwiched between the
   content and the keyboard.

   Driven by body.keyboard-open (see keyboard-viewport.js), NOT
   --kb-inset directly: --kb-inset is 0px whenever the platform's
   own interactive-widget=resizes-content viewport hint already
   shrinks the layout viewport for the keyboard (Android Chrome and
   friends) — correct for POSITIONING (nothing needs to be pushed up
   there), but that leaves no geometric signal at all to detect
   "the keyboard is open" on exactly those platforms. Focus entering
   a text surface is a reliable proxy on every platform this matters
   on, independent of which viewport-resize mechanism is in play.

   No .remote-notice override needed here (unlike the search-panel/
   composer sections above): those also place a large floating
   element ON TOP of the reclaimed space, but this rule doesn't —
   #app-screen's shrunk padding-bottom alone lets .editor-area (and
   .remote-notice's bottom:12px position within it) grow into the
   freed space on its own. */
@media (max-width: 639px) {
  body.keyboard-open .mobile-action-bar { display: none; }
  body.keyboard-open #app-screen { padding-bottom: calc(8px + var(--kb-inset, 0px)); }
  body.keyboard-open #toast-container { bottom: calc(20px + var(--kb-inset, 0px)); }
}
