/* ==========================================================================
   Luogu Markdown + KaTeX Editor - Windows & Web Stylesheet
   ========================================================================== */

:root {
  /* Luogu Light Theme Colors */
  --bg-primary: #ffffff;
  --bg-secondary: #f8f9fa;
  --bg-tertiary: #f1f3f5;
  --bg-editor: #ffffff;
  --bg-preview: #ffffff;
  --text-primary: #2c3e50;
  --text-secondary: #6c757d;
  --text-muted: #adb5bd;
  --border-color: #e9ecef;
  --border-focus: #3498db;
  
  /* Brand & Accent Colors */
  --luogu-blue: #3498db;
  --luogu-blue-dark: #2980b9;
  --luogu-blue-light: #ebf5fb;
  --luogu-green: #2ecc71;
  --luogu-green-dark: #27ae60;
  --luogu-green-light: #eafaf1;
  --luogu-orange: #e67e22;
  --luogu-orange-light: #fef5e7;
  --luogu-red: #e74c3c;
  --luogu-red-light: #fdedec;
  --luogu-purple: #9b59b6;
  
  /* UI Dimensions & Shadows */
  --header-height: 48px;
  --toolbar-height: 44px;
  --statusbar-height: 28px;
  --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.05);
  --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
  --shadow-lg: 0 10px 25px rgba(0, 0, 0, 0.15), 0 4px 10px rgba(0, 0, 0, 0.08);
  --radius-sm: 4px;
  --radius-md: 6px;
  --radius-lg: 10px;
  
  /* Fonts */
  --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;
  --font-mono: "Cascadia Code", "Fira Code", "Consolas", "Courier New", monospace;
  --font-math: "KaTeX_Main", "Times New Roman", serif;
  --editor-font-size: 15px;
  --preview-font-size: 16px;

  /* File explorer (desktop panel). Kind colours follow the familiar editor idiom:
     loud for source you edit, quiet for data you only look at. Declared here rather
     than in the panel block so the dark theme can retune just the greys. */
  --ws-folder: #dcb67a;
  --ws-md: #519aba;
  --ws-txt: #8a8f98;
  --ws-js: #e2b93d;
  --ws-html: #e37933;
  --ws-css: #42a5f5;
  --ws-cpp: #9c6ade;
  --ws-py: #4b8bbe;
  --ws-java: #cc7832;
  --ws-rust: #d08770;
  --ws-go: #4ec9b0;
  --ws-shell: #89e051;
  --ws-config: #9aa0a6;
  --ws-img: #a074c4;
  --ws-pdf: #e05c5c;
  --ws-zip: #c9a227;
  --ws-bin: #7f8c8d;
  --ws-plain: #9aa0a6;
  --ws-indent: rgba(110, 110, 110, .30);
  --ws-selected-bg: rgba(52, 152, 219, .18);
  --ws-row-h: 22px;
}

/* Dark Theme */
[data-theme="dark"] {
  /* Tell the browser this page is dark so it paints UA-drawn widgets to match.
     Without it an unchecked <input type="checkbox"> stays a solid white square —
     glaring on a dark preview, and carried straight into the dark PDF. */
  color-scheme: dark;
  --bg-primary: #1e1e1e;
  --bg-secondary: #252526;
  --bg-tertiary: #2d2d30;
  --bg-editor: #1e1e1e;
  --bg-preview: #1e1e1e;
  --text-primary: #d4d4d4;
  --text-secondary: #9da5b4;
  --text-muted: #5c6370;
  --border-color: #333333;
  --border-focus: #007acc;
  
  --luogu-blue: #007acc;
  --luogu-blue-dark: #005999;
  --luogu-blue-light: #1a2f42;
  --luogu-green: #388e3c;
  --luogu-green-light: #1e3323;
  --luogu-orange: #d97706;
  --luogu-orange-light: #3d2b14;
  --luogu-red: #d32f2f;
  --luogu-red-light: #3b1b1b;

  /* Retune the explorer greys for a dark panel; the kind colours are mid-tones
     that already read well on both backgrounds. */
  --ws-plain: #8b949e;
  --ws-txt: #8b949e;
  --ws-config: #9aa0a6;
  --ws-indent: rgba(255, 255, 255, .13);
  --ws-selected-bg: rgba(0, 122, 204, .32);
  --ws-pdf: #e87979;
  --ws-zip: #d4b04a;
}

/* Academic White Theme */
[data-theme="academic"] {
  --bg-primary: #fafafa;
  --bg-secondary: #f4f4f5;
  --bg-tertiary: #e4e4e7;
  --bg-editor: #ffffff;
  --bg-preview: #ffffff;
  --text-primary: #18181b;
  --text-secondary: #52525b;
  --text-muted: #a1a1aa;
  --border-color: #e4e4e7;
  --border-focus: #18181b;
  --luogu-blue: #2563eb;
  --luogu-blue-light: #eff6ff;
}

/* Reset & Base */
*, *::before, *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html, body {
  width: 100%;
  height: 100%;
  overflow: hidden;
  font-family: var(--font-sans);
  font-size: 14px;
  color: var(--text-primary);
  background-color: var(--bg-primary);
  -webkit-font-smoothing: antialiased;
}

/* App Container Layout */
#app {
  display: flex;
  flex-direction: column;
  width: 100vw;
  height: 100vh;
  overflow: hidden;
}

/* Header & Titlebar */
.app-header {
  height: var(--header-height);
  background: var(--bg-secondary);
  border-bottom: 1px solid var(--border-color);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 16px;
  user-select: none;
  z-index: 100;
  position: relative;
}

.brand-section {
  display: flex;
  align-items: center;
  gap: 8px;
}

.app-title-group {
  display: flex;
  flex-direction: column;
}

.app-title {
  font-size: 15px;
  font-weight: 600;
  color: var(--text-primary);
  display: flex;
  align-items: center;
  gap: 8px;
}

.app-badge {
  font-size: 10px;
  padding: 2px 7px;
  background: var(--luogu-blue-light);
  color: var(--luogu-blue);
  border-radius: 10px;
  font-weight: 500;
  letter-spacing: 0.3px;
}

.header-center {
  display: flex;
  align-items: center;
  gap: 8px;
}

.doc-name-input {
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  padding: 4px 8px;
  font-size: 13px;
  font-weight: 500;
  color: var(--text-primary);
  text-align: center;
  transition: all 0.2s;
  min-width: 180px;
}

.doc-name-input:hover {
  background: var(--bg-tertiary);
}

.doc-name-input:focus {
  background: var(--bg-primary);
  border-color: var(--border-focus);
  outline: none;
}

.header-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  position: relative;
}

/* Button Styles */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 5px 12px;
  font-size: 12px;
  font-weight: 500;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-color);
  background: var(--bg-primary);
  color: var(--text-primary);
  cursor: pointer;
  transition: all 0.15s ease;
  user-select: none;
  white-space: nowrap;
}

.btn:hover {
  background: var(--bg-tertiary);
  border-color: var(--text-muted);
}

.btn-primary {
  background: var(--luogu-blue);
  color: #ffffff;
  border-color: var(--luogu-blue);
}

.btn-primary:hover {
  background: var(--luogu-blue-dark);
  border-color: var(--luogu-blue-dark);
}

.btn-success {
  background: var(--luogu-green);
  color: #ffffff;
  border-color: var(--luogu-green);
}

.btn-success:hover {
  background: var(--luogu-green-dark);
  border-color: var(--luogu-green-dark);
}

.btn-icon-only {
  padding: 5px 8px;
}

.btn svg {
  width: 14px;
  height: 14px;
}

/* Toolbar */
.toolbar-container {
  height: var(--toolbar-height);
  background: var(--bg-secondary);
  border-bottom: 1px solid var(--border-color);
  display: flex;
  align-items: center;
  padding: 0 12px;
  gap: 4px;
  overflow: visible;
  user-select: none;
  z-index: 50;
  position: relative;
}

.toolbar-group {
  display: flex;
  align-items: center;
  gap: 2px;
  position: relative;
}

.toolbar-divider {
  width: 1px;
  height: 20px;
  background: var(--border-color);
  margin: 0 6px;
  flex-shrink: 0;
}

.tool-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 30px;
  min-width: 30px;
  padding: 0 6px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  color: var(--text-primary);
  cursor: pointer;
  white-space: nowrap;
  font-size: 12px;
  transition: background-color 0.1s, border-color 0.1s;
}

.tool-btn:hover {
  background: var(--bg-tertiary);
  border-color: var(--border-color);
}

.tool-btn.active {
  background: var(--luogu-blue-light);
  color: var(--luogu-blue);
  border-color: var(--luogu-blue);
}

.tool-btn svg {
  width: 16px;
  height: 16px;
}

/* Dropdown Tool */
.tool-dropdown {
  position: relative;
  display: inline-flex;
}

.dropdown-menu {
  display: none;
  position: absolute;
  top: 100%;
  left: 0;
  margin-top: 0;
  background: var(--bg-primary);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  box-shadow: 0 10px 25px rgba(0, 0, 0, 0.18), 0 3px 8px rgba(0, 0, 0, 0.08);
  min-width: 190px;
  max-width: 340px;
  z-index: 9999;
  padding: 6px;
}

/* Invisible hover bridge */
.dropdown-menu::before {
  content: "";
  position: absolute;
  top: -12px;
  left: 0;
  right: 0;
  height: 12px;
  background: transparent;
}

/* Right-side dropdown alignment */
.header-actions .dropdown-menu,
.dropdown-menu-right,
.toolbar-container > div:last-child .dropdown-menu {
  left: auto !important;
  right: 0 !important;
}

.tool-dropdown:hover .dropdown-menu,
.tool-dropdown.open .dropdown-menu {
  display: block;
}

.dropdown-item {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 7px 10px;
  font-size: 12px;
  color: var(--text-primary);
  background: transparent;
  border: none;
  border-radius: var(--radius-sm);
  text-align: left;
  cursor: pointer;
  white-space: nowrap;
}

.dropdown-item:hover {
  background: var(--bg-tertiary);
  color: var(--luogu-blue);
}

.dropdown-item svg {
  width: 14px;
  height: 14px;
}

/* Main Workspace Split View */
.main-workspace {
  flex: 1;
  display: flex;
  width: 100%;
  height: calc(100vh - var(--header-height) - var(--toolbar-height) - var(--statusbar-height));
  overflow: hidden;
  position: relative;
  z-index: 1;
}

/* Editor Pane */
.editor-pane {
  flex: 1;
  height: 100%;
  display: flex;
  flex-direction: column;
  background: var(--bg-editor);
  border-right: 1px solid var(--border-color);
  position: relative;
  min-width: 250px;
}

.pane-header {
  height: 28px;
  background: var(--bg-secondary);
  border-bottom: 1px solid var(--border-color);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 12px;
  font-size: 11px;
  font-weight: 600;
  color: var(--text-secondary);
  user-select: none;
}

.editor-wrapper {
  flex: 1;
  display: flex;
  position: relative;
  overflow: hidden;
}

.line-numbers-gutter {
  position: relative;
  width: 52px;
  padding: 12px 10px 12px 8px;
  text-align: right;
  font-family: var(--font-mono);
  font-size: var(--editor-font-size, 15px);
  line-height: 1.6;
  color: var(--text-muted);
  background: transparent;
  user-select: none;
  overflow: visible;
  will-change: transform;
  /* Numbers are absolutely positioned at measured offsets because soft-wrapped
     lines have varying heights. */
  white-space: nowrap;
}

.gutter-num-abs {
  position: absolute;
  right: 10px;
}

/* Each gutter row must occupy exactly one text line so the numbers stay aligned
   with the textarea (which uses white-space: pre, i.e. one row per logical line). */
/* Stacking context for the line-number column. */
.gutter-stack {
  position: relative;
  flex: 0 0 auto;
  display: flex;
  overflow: hidden;
  /* The number layer is translated to follow the textarea's scroll, so this
     container clips it. */
  background: var(--bg-secondary);
  border-right: 1px solid var(--border-color);
}



/* Holds the highlight layer and the textarea in the same coordinate space.
   Carries the editor background, since the textarea itself is now transparent. */
.editor-stack {
  flex: 1;
  position: relative;
  min-width: 0;
  display: flex;
  background: var(--bg-editor);
}

/* Must match .editor-textarea metric for metric, or the painted boxes drift away
   from the glyphs they are meant to sit behind. */
.find-highlights {
  position: absolute;
  inset: 0;
  padding: 12px 16px;
  font-family: var(--font-mono);
  font-size: var(--editor-font-size, 15px);
  line-height: 1.6;
  tab-size: 4;
  white-space: pre-wrap;
  overflow-wrap: break-word;
  word-break: break-word;
  overflow: hidden;
  color: transparent;
  pointer-events: none;
  user-select: none;
  z-index: 0;
}
.find-highlights mark {
  background: rgba(241, 196, 15, .40);
  color: transparent;
  border-radius: 2px;
}
.find-highlights mark.is-current {
  background: rgba(243, 156, 18, .75);
  box-shadow: 0 0 0 1px rgba(211, 84, 0, .9);
}
.find-highlights mark.latex-match-pair {
  background: rgba(52, 152, 219, .24);
  box-shadow: inset 0 0 0 1px rgba(41, 128, 185, .85);
  border-radius: 2px;
}
.find-highlights mark.is-current.latex-match-pair {
  background: rgba(243, 156, 18, .75);
  box-shadow: 0 0 0 1px rgba(211, 84, 0, .9), inset 0 0 0 1px rgba(41, 128, 185, .85);
}
[data-theme="dark"] .find-highlights mark {
  background: rgba(241, 196, 15, .28);
}
[data-theme="dark"] .find-highlights mark.is-current {
  background: rgba(243, 156, 18, .62);
}
[data-theme="dark"] .find-highlights mark.latex-match-pair {
  background: rgba(52, 152, 219, .35);
  box-shadow: inset 0 0 0 1px rgba(83, 170, 235, .9);
}

.editor-textarea {
  flex: 1;
  height: 100%;
  /* Transparent so the highlight layer shows through; the caret keeps its own
     colour via caret-color. */
  position: relative;
  z-index: 1;
  background-color: transparent;
  padding: 12px 16px;
  border: none;
  outline: none;
  resize: none;
  font-family: var(--font-mono);
  font-size: var(--editor-font-size, 15px);
  line-height: 1.6;
  color: var(--text-primary);
  caret-color: var(--text-primary);
  tab-size: 4;
  /* Soft-wrap long lines so nothing hides off-screen horizontally. A logical line may
     now span several visual rows, so the gutter can no longer assume one row per line;
     it measures each line's real height instead (see measureLineTops). */
  white-space: pre-wrap;
  overflow-wrap: break-word;
  word-break: break-word;
  overflow-x: hidden;
  overflow-y: auto;
}

.latex-completion-popup {
  position: absolute;
  z-index: 6;
  width: min(360px, calc(100% - 16px));
  max-height: 250px;
  overflow: hidden;
  background: var(--bg-primary);
  color: var(--text-primary);
  border: 1px solid var(--border-color);
  border-radius: 8px;
  box-shadow: var(--shadow-lg);
  font: 13px/1.4 var(--font-sans);
}
.latex-completion-popup[hidden] { display: none !important; }
.latex-caret-mirror {
  position: absolute;
  top: 0;
  left: 0;
  visibility: hidden;
  pointer-events: none;
  z-index: -1;
  box-sizing: border-box;
  white-space: pre-wrap;
  overflow-wrap: break-word;
  word-break: break-word;
}
.latex-completion-items {
  max-height: 206px;
  overflow-y: auto;
  padding: 4px;
}
.latex-completion-item {
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  padding: 6px 9px;
  border: 0;
  border-radius: 5px;
  background: transparent;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.latex-completion-item:hover,
.latex-completion-item.is-active {
  background: var(--luogu-blue-light);
  color: var(--luogu-blue-dark, var(--luogu-blue));
}
.latex-completion-command {
  flex: 0 0 auto;
  font: 13px/1.4 var(--font-mono);
  font-weight: 600;
}
.latex-completion-preview {
  overflow: hidden;
  color: var(--text-secondary);
  text-overflow: ellipsis;
  white-space: nowrap;
}
.latex-completion-footer {
  padding: 5px 9px;
  border-top: 1px solid var(--border-color);
  color: var(--text-muted);
  font-size: 11px;
}

/* Resizer Splitter */
.split-resizer {
  /* 必须显式写 flex：两侧面板被拖成 `0 0 NN%` 之后，两者的 basis 相加就等于容器
     宽度，分割条作为唯一还能收缩的伸缩项会被压成 0px —— 拖一次就"消失"了。 */
  flex: 0 0 6px;
  width: 6px;
  height: 100%;
  background: var(--border-color);
  cursor: col-resize;
  transition: background 0.15s;
  z-index: 5;
}

.split-resizer:hover, .split-resizer.resizing {
  background: var(--luogu-blue);
}

/* Preview Pane */
.preview-pane {
  flex: 1;
  height: 100%;
  display: flex;
  flex-direction: column;
  background: var(--bg-preview);
  min-width: 250px;
  overflow: hidden;
}

.preview-content {
  flex: 1;
  padding: 24px 32px;
  overflow-y: auto;
  font-size: var(--preview-font-size, 16px);
  /* Positioning context for Typora's overlay editors, which are placed in the
     scroll container's own coordinate space. */
  position: relative;
  line-height: 1.75;
  color: var(--text-primary);
  word-break: break-word;
}

/* View Mode States */
.mode-editor-only .preview-pane,
.mode-editor-only .split-resizer {
  display: none !important;
}

.mode-preview-only .editor-pane,
.mode-preview-only .split-resizer {
  display: none !important;
}

/* Typora mode: the preview pane becomes the whole workspace and doubles as the
   editor. The source textarea stays in the DOM (it remains the canonical document
   and every other feature reads from it) but is hidden along with the resizer. */
.mode-typora .editor-pane,
.mode-typora .split-resizer {
  display: none !important;
}

/* 分割条拖动会把内联 flex 写在两侧面板上；只剩一栏时必须中和掉，否则可见的那一栏
   仍按拖动比例占宽，右侧留一片空白（!important 的样式表规则能压过内联样式）。 */
.mode-editor-only .editor-pane,
.mode-preview-only .preview-pane,
.mode-typora .preview-pane {
  flex: 1 1 auto !important;
}

.mode-typora .preview-pane {
  /* Centre the column the way a document editor does, rather than letting text run
     the full width of a maximised window. Tables and code blocks in solutions are
     wide, so this is deliberately roomier than a prose-only measure. */
  --typora-measure: 1000px;
}

.mode-typora .preview-content {
  max-width: var(--typora-measure);
  margin: 0 auto;
  padding: 24px 32px 45vh;
  /* Generous bottom padding keeps the last block reachable above the fold once it
     expands into a textarea. It doubles as the click target for "start a new
     paragraph at the end", so it must stay tall even for an empty document. */
  min-height: 100%;
  box-sizing: border-box;
  cursor: text;
}

/* Hint that blocks are clickable, without the noise of a permanent outline. */
.preview-content.typora-mode > * {
  border-radius: 4px;
  transition: background-color 0.12s ease;
}

.preview-content.typora-mode > *:hover {
  cursor: text;
}

/* Only tint blocks that have no surface of their own. A code block / table / bilibili
   card / callout paints its own background, and washing --bg-secondary over it made
   the light theme show pale syntax colours on white. */
.preview-content.typora-mode > *:hover:not(.luogu-code-block-wrapper):not(.luogu-code-block-wrap):not(.luogu-table-wrapper):not(.luogu-bilibili-container):not(details.luogu-callout):not(ul):not(ol) {
  background-color: var(--bg-secondary);
}

/* A list is edited one item at a time, so the hover affordance must point at the
   item under the pointer rather than washing over the whole list. Innermost wins:
   on a nested list only the deepest <li> containing the pointer lights up. */
.preview-content.typora-mode li:hover {
  cursor: text;
}
.preview-content.typora-mode li:hover > :not(ul):not(ol),
.preview-content.typora-mode li:hover {
  background-color: var(--bg-secondary);
  border-radius: 4px;
}
.preview-content.typora-mode li:hover ul,
.preview-content.typora-mode li:hover ol,
.preview-content.typora-mode li:has(li:hover) {
  background-color: transparent;
}

/* Blocks that paint their own background must not be tinted by the generic hover
   above: a code block has a dark surface of its own, and washing --bg-secondary over
   it (or over a table's zebra striping) produced visibly wrong colours. Highlight
   them with a ring instead, which reads on both light and dark themes. */
.preview-content.typora-mode > .luogu-code-block-wrapper:hover,
.preview-content.typora-mode > .luogu-code-block-wrap:hover,
.preview-content.typora-mode > .luogu-table-wrapper:hover,
.preview-content.typora-mode > .luogu-bilibili-container:hover,
.preview-content.typora-mode > details.luogu-callout:hover {
  /* Deliberately does NOT touch background-color. These blocks paint their own
     surface (a code block stays dark even under the light theme); an earlier version
     forced `transparent` here, which dropped that background and left pale syntax
     colours on white. The ring alone is enough of a hover affordance. */
  box-shadow: 0 0 0 2px var(--luogu-blue-light, rgba(52, 152, 219, 0.35));
}

/* Inside those blocks, highlight the actual sub-target instead. */
.preview-content.typora-mode .luogu-table td:hover,
.preview-content.typora-mode .luogu-table th:hover {
  box-shadow: inset 0 0 0 2px var(--luogu-blue);
  cursor: text;
}

/* Only the title text is editable, so it is the only part of the header that gets a
   text cursor and an edit affordance; the rest of the summary still toggles. */
.preview-content.typora-mode .luogu-callout-title:hover {
  cursor: text;
  text-decoration: underline dashed;
  text-underline-offset: 3px;
  background: rgba(52, 152, 219, 0.12);
  border-radius: 3px;
  box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.12);
}

.preview-content.typora-mode .luogu-callout-summary {
  cursor: pointer;
}

.preview-content.typora-mode .luogu-callout-icon:hover {
  cursor: pointer;
  filter: brightness(1.25);
}

.preview-content.typora-mode pre[data-code-body]:hover {
  cursor: text;
}

/* Sub-block editors sit inside an existing block, so they must not inherit the
   full-width block spacing the top-level editor uses. */
.typora-partial {
  margin: 0;
}


.typora-code-input {
  border-radius: 0;
  border-color: var(--luogu-blue);
  background: var(--code-bg, #2d2d2d);
  color: var(--code-fg, #ccc);
  tab-size: 4;
}

/* A cell / title editor should read as inline text, not as a block form field. */
.typora-inline-input {
  min-height: 0;
  padding: 2px 6px;
  font-family: inherit;
  font-size: inherit;
  line-height: inherit;
}

.preview-content.typora-mode:focus {
  outline: none;
}

/* The in-place source editor swapped in for the block being edited. */
.typora-editing {
  margin: 4px 0;
}

.typora-block-input {
  display: block;
  width: 100%;
  box-sizing: border-box;
  min-height: 2.4em;
  padding: 10px 12px;
  border: 1px solid var(--luogu-blue);
  border-radius: 4px;
  background: var(--bg-primary);
  color: var(--text-primary);
  font-family: 'JetBrains Mono', 'Fira Code', Consolas, 'Courier New', monospace;
  font-size: var(--editor-font-size, 15px);
  line-height: 1.7;
  resize: none;
  overflow: hidden;
  outline: none;
  white-space: pre-wrap;
  word-break: break-word;
  tab-size: 4;
}

.typora-block-input:focus {
  box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.18);
}

.typora-block-input::placeholder {
  color: var(--text-secondary);
  opacity: 0.6;
}

/* A brand-new paragraph is dashed until it has content, so it reads as a draft
   rather than as a block that already exists in the document. */
.typora-inserting .typora-block-input {
  border-style: dashed;
}

/* Status Bar */
.app-statusbar {
  height: var(--statusbar-height);
  background: var(--bg-secondary);
  border-top: 1px solid var(--border-color);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 16px;
  font-size: 11px;
  color: var(--text-secondary);
  user-select: none;
  z-index: 10;
  position: relative;
}

.status-left, .status-right {
  display: flex;
  align-items: center;
  gap: 16px;
}

.status-item {
  display: flex;
  align-items: center;
  gap: 4px;
}

.status-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--luogu-green);
}

.status-score-badge {
  padding: 1px 6px;
  border-radius: 10px;
  font-weight: 600;
  font-size: 10px;
}

.status-score-good {
  background: var(--luogu-green-light);
  color: var(--luogu-green-dark);
}

.status-score-warn {
  background: var(--luogu-orange-light);
  color: var(--luogu-orange);
}

/* ==========================================================================
   Luogu Markdown Rendered Content Styles (预览与排版样式)
   ========================================================================== */

.preview-content h1,
.preview-content h2,
.preview-content h3,
.preview-content h4,
.preview-content h5,
.preview-content h6 {
  margin-top: 1.5em;
  margin-bottom: 0.6em;
  font-weight: 600;
  color: var(--text-primary);
  line-height: 1.35;
}

.preview-content h1 { font-size: 1.85em; border-bottom: 1px solid var(--border-color); padding-bottom: 0.3em; }
.preview-content h2 { font-size: 1.5em; border-bottom: 1px solid var(--border-color); padding-bottom: 0.25em; }
.preview-content h3 { font-size: 1.25em; }
.preview-content h4 { font-size: 1.1em; }
.preview-content h5 { font-size: 1em; }
.preview-content h6 { font-size: 0.9em; color: var(--text-secondary); }

.preview-content p {
  margin-bottom: 1em;
}

/* Emphasis & Inline Elements */
.preview-content strong {
  font-weight: 600;
  color: var(--text-primary);
}

.preview-content em {
  font-style: italic;
}

.preview-content del {
  text-decoration: line-through;
  color: var(--text-muted);
}

/* GFM footnotes. Rendered as a trailing <section> separated from the body, with
   small superscript references in the text. Mirrors remark-gfm's structure so the
   result matches what Luogu produces. */
.luogu-footnote-ref a {
  text-decoration: none;
  padding: 0 1px;
}

.luogu-footnotes {
  margin-top: 32px;
  padding-top: 12px;
  border-top: 1px solid var(--border-color, #d0d7de);
  font-size: 0.9em;
}

.luogu-footnotes-title {
  font-size: 1em;
  font-weight: 600;
  margin: 0 0 8px;
  color: var(--text-secondary, #57606a);
}

.luogu-footnotes-list {
  margin: 0;
  padding-left: 24px;
}

.luogu-footnote-item {
  margin: 4px 0;
}

.luogu-footnote-item p {
  margin: 0;
}

.luogu-footnote-back {
  text-decoration: none;
  margin-left: 4px;
}

.luogu-inline-code {
  font-family: var(--font-mono);
  font-size: 0.88em;
  padding: 2px 6px;
  background: var(--bg-tertiary);
  color: #e06c75;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-color);
}

.preview-content hr.luogu-hr {
  height: 2px;
  border: none;
  background: var(--border-color);
  margin: 1.5em 0;
}

.preview-content blockquote.luogu-blockquote {
  margin: 1em 0;
  padding: 8px 16px;
  border-left: 4px solid var(--luogu-blue);
  background: var(--bg-secondary);
  color: var(--text-secondary);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}

.preview-content blockquote blockquote {
  margin: 0.5em 0;
  border-left-color: var(--luogu-purple);
}

/* Links & Images */
.preview-content a.luogu-link {
  color: var(--luogu-blue);
  text-decoration: none;
  transition: color 0.15s;
}

.preview-content a.luogu-link:hover {
  text-decoration: underline;
  color: var(--luogu-blue-dark);
}

.luogu-img-wrapper {
  display: inline-block;
  max-width: 100%;
  margin: 0.5em 0;
}

.luogu-img {
  max-width: 100%;
  height: auto;
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-sm);
}

.luogu-img-error {
  border: 1px dashed var(--luogu-red);
  padding: 10px;
  color: var(--luogu-red);
  background: var(--luogu-red-light);
}

/* Lists & Tasks */
.preview-content ul.luogu-list,
.preview-content ol.luogu-list {
  padding-left: 24px;
  margin-bottom: 1em;
}

.preview-content li {
  margin-bottom: 0.35em;
}

.preview-content ul.luogu-task-list {
  list-style-type: none;
  padding-left: 4px;
}

.luogu-task-item {
  display: block;
  margin-bottom: 6px;
}
.luogu-task-item > .luogu-checkbox-label {
  display: flex;
  align-items: center;
}

.luogu-checkbox-label {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
}

.luogu-task-checkbox {
  width: 15px;
  height: 15px;
  accent-color: var(--luogu-blue);
  cursor: pointer;
  flex: none;
}

/* The checkbox is the only place that toggles; everything else on the row edits the
   item. Make that split visible: a pointer cursor on the box, a text caret on the
   label text (which would otherwise inherit the label's pointer). */
.preview-content.typora-mode .luogu-checkbox-label { cursor: text; }
.preview-content.typora-mode .luogu-task-checkbox { cursor: pointer; }
.preview-content.typora-mode .luogu-task-checkbox:hover {
  outline: 2px solid var(--luogu-blue-light, rgba(52, 152, 219, 0.45));
  outline-offset: 1px;
}

/* Luogu Code Blocks - with Auto-Wrapping in Preview */
.luogu-code-block-wrapper {
  margin: 1.2em 0;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-color);
  background: #282c34;
  color: #abb2bf;
  box-shadow: var(--shadow-sm);
  overflow: hidden;
  width: 100%;
  box-sizing: border-box;
}

.luogu-code-pre,
.luogu-code-pre code,
.luogu-code-pre code[class*="language-"],
.luogu-code-pre pre[class*="language-"],
pre[class*="language-"],
code[class*="language-"] {
  white-space: pre-wrap !important;
  word-break: break-all !important;
  word-wrap: break-word !important;
  overflow-wrap: anywhere !important;
}

.luogu-code-pre code,
.luogu-code-pre code[class*="language-"] {
  display: block !important;
  /* Grow to the widest line rather than to the visible width, so that when the
     block is scrolled right the highlight backgrounds keep covering the row
     instead of ending at the original fold. min-width keeps short blocks full
     width so a highlight still spans the pane. */
  width: max-content !important;
  min-width: 100% !important;
  box-sizing: border-box !important;
}

.luogu-code-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 6px 14px;
  background: #21252b;
  border-bottom: 1px solid #181a1f;
  font-size: 11px;
  font-weight: 600;
  user-select: none;
  gap: 8px;
}

.luogu-code-lang {
  color: #61afef;
  letter-spacing: 0.5px;
  flex-shrink: 0;
}

.luogu-code-actions {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  white-space: nowrap !important;
}

.luogu-code-copy-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 3px 10px;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: var(--radius-sm);
  color: #abb2bf;
  font-size: 11px;
  cursor: pointer;
  transition: all 0.15s;
  white-space: nowrap !important;
  flex-shrink: 0 !important;
  min-width: 64px;
  height: 24px;
  line-height: 1;
}

.luogu-code-copy-btn:hover {
  background: rgba(255, 255, 255, 0.15);
  color: #ffffff;
}

.luogu-code-copy-btn .copy-text {
  white-space: nowrap !important;
  display: inline-block;
}

.luogu-code-copy-btn .copy-icon {
  width: 12px;
  height: 12px;
  flex-shrink: 0;
  display: inline-block;
}

.luogu-code-pre {
  margin: 0;
  padding: 12px 0;
  font-family: var(--font-mono);
  font-size: 0.8125em;
  line-height: 1.6;
  /* The whole block scrolls horizontally, so a single scrollbar sits at the very
     bottom of the code block. Scrolling per line instead gave every overflowing
     line its own scrollbar, wedged between that line and the next — it ate into
     the line's height and covered the line below it. The gutter numbers stay put
     because they are position:sticky against this scroll container. */
  overflow-x: auto !important;
  width: 100%;
  box-sizing: border-box;
  display: block !important;
}

.code-line {
  /* Lines do not scroll individually — see .luogu-code-pre. Each row must be at
     least as wide as the widest line so that highlight backgrounds and the gutter
     tint extend across the full scrolled width instead of stopping at the fold;
     `width: max-content` with `min-width: 100%` achieves both. */
  display: block !important;
  white-space: pre !important;
  padding: 0 16px;
  /* Inherit the <code> width (widest line) so every row — highlighted or not —
     paints across the whole scrollable width. */
  min-width: 100%;
  box-sizing: border-box;
}

.code-line-number {
  display: inline-block !important;
  position: sticky;
  left: 0;
  width: 36px;
  min-width: 36px;
  padding-right: 14px;
  margin-right: 10px;
  text-align: right;
  color: #5c6370;
  background: #282c34;
  border-right: 1px solid #3e4451;
  user-select: none;
  vertical-align: top;
  z-index: 2;
}

.code-line-text {
  /* Code text beside the gutter, kept on a single row (no wrapping) so it aligns
     to its own line number; long lines scroll horizontally within the line. */
  display: inline !important;
  vertical-align: top;
  white-space: pre !important;
  word-break: normal !important;
  word-wrap: normal !important;
  overflow-wrap: normal !important;
}

.code-line-highlighted {
  background: rgba(229, 192, 123, 0.15);
  border-left: 3px solid #e5c07b;
  padding-left: 13px;
}

/* When a line is highlighted, the sticky gutter cell must composite the same
   translucent highlight over the code background — otherwise the gutter shows a
   plain dark block that sits "one layer above" the highlight. (The linear-gradient
   layer + opaque base keeps it opaque so it still masks horizontally-scrolled code.) */
.code-line-highlighted .code-line-number {
  background: linear-gradient(rgba(229, 192, 123, 0.15), rgba(229, 192, 123, 0.15)), #282c34;
}

/* Tables & Merged Cells */
.luogu-table-wrapper {
  width: 100%;
  overflow-x: auto;
  margin: 1.2em 0;
}

.luogu-table-center-wrapper {
  display: flex;
  justify-content: center;
  margin: 1.4em auto;
}

.luogu-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 1em;
  border: 1px solid var(--border-color);
  background: var(--bg-primary);
}

.luogu-table-center-wrapper .luogu-table {
  width: auto;
  max-width: 100%;
}

.luogu-table th,
.luogu-table td {
  padding: 8px 14px;
  border: 1px solid var(--border-color);
}

.luogu-table th {
  background: var(--bg-secondary);
  font-weight: 600;
  color: var(--text-primary);
}

.luogu-table tr:nth-child(even) td {
  background: var(--bg-secondary);
}

/* Tuack / Cute-Table Style (洛谷 Tuack 竞赛风格表格) */
.luogu-tuack-table {
  border: 2px solid #3498db;
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-sm);
}

.luogu-tuack-table th {
  background: #3498db;
  color: #ffffff;
  border: 1px solid #2980b9;
  font-weight: 600;
  text-align: center;
}

.luogu-tuack-table td {
  border: 1px solid #d4e6f1;
}

.luogu-tuack-table tr:nth-child(even) td {
  background: #f4f9fd;
}

.luogu-tuack-table tr:hover td {
  background: #eaf2f8;
}

/* Dark mode: make the Tuack (contest) table use a dark-appropriate palette
   instead of the hardcoded light blue. */
[data-theme="dark"] .luogu-tuack-table {
  border-color: #1f6feb;
}
[data-theme="dark"] .luogu-tuack-table th {
  background: #1f6feb;
  border-color: #1759c4;
}
[data-theme="dark"] .luogu-tuack-table td {
  border-color: #2d3a4a;
}
[data-theme="dark"] .luogu-tuack-table tr:nth-child(even) td {
  background: #1c2530;
}
[data-theme="dark"] .luogu-tuack-table tr:hover td {
  background: #24303e;
}

.luogu-cute-centered-table {
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
}

.luogu-cute-centered-table th {
  background: var(--luogu-blue-light);
  color: #1f618d;
  font-weight: 600;
  text-align: center;
}

/* Align Blocks (居中 & 居右排版) */
.luogu-align-center {
  text-align: center;
  margin: 1em 0;
}

.luogu-align-right {
  text-align: right;
  margin: 1em 0;
}

/* Typora mode: alignment containers are otherwise invisible — centred text looks
   the same whether it came from `:::align{center}` or a lone short line — so the
   editor marks their extent with a dashed rule and a corner label, the same idea as
   a callout's coloured edge. Screen/print/export are untouched. */
.preview-content.typora-mode [class*="luogu-align-"] {
  position: relative;
  border: 1px dashed var(--border-color);
  border-radius: 4px;
  padding: 10px 12px;
  margin-top: 1.5em;
}

.preview-content.typora-mode [class*="luogu-align-"]::before {
  position: absolute;
  top: -0.75em;
  left: 10px;
  padding: 0 6px;
  border-radius: 3px;
  background: var(--bg-primary);
  color: var(--text-secondary);
  font-family: var(--font-mono, monospace);
  font-size: 11px;
  line-height: 1.4;
  letter-spacing: 0.02em;
  pointer-events: none;
}

.preview-content.typora-mode .luogu-align-left::before { content: "居左"; }
.preview-content.typora-mode .luogu-align-center::before { content: "居中"; }
.preview-content.typora-mode .luogu-align-right::before { content: "居右"; }

/* The badge is the control that switches left/center/right, so make it read as
   clickable rather than as a passive label. */
.preview-content.typora-mode [class*="luogu-align-"]::before { cursor: pointer; }
.preview-content.typora-mode [class*="luogu-align-"]:hover::before {
  background: var(--luogu-blue, #3498db);
  color: #fff;
}

.preview-content.typora-mode [class*="luogu-align-"]:hover {
  border-color: var(--luogu-blue);
}

.preview-content.typora-mode .luogu-epigraph {
  border: 1px dashed var(--border-color);
  border-radius: 4px;
  margin-top: 1.6em;
}

.preview-content.typora-mode .luogu-epigraph::after {
  content: "引言";
  position: absolute;
  top: -0.75em;
  left: 10px;
  padding: 0 6px;
  border-radius: 3px;
  background: var(--bg-primary);
  color: var(--text-secondary);
  font-family: var(--font-mono, monospace);
  font-size: 11px;
  pointer-events: none;
}

/* Epigraph (引言) */
.luogu-epigraph {
  position: relative;
  margin: 1.5em 0;
  padding: 16px 20px 16px 48px;
  background: var(--bg-secondary);
  border-left: 4px solid var(--luogu-blue);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
}

.luogu-epigraph-quote-mark {
  position: absolute;
  top: 6px;
  left: 14px;
  font-size: 38px;
  line-height: 1;
  font-family: Georgia, serif;
  color: var(--luogu-blue);
  opacity: 0.5;
  user-select: none;
}

.luogu-epigraph-body {
  font-style: italic;
  font-size: 1em;
  color: var(--text-primary);
  margin-bottom: 6px;
}

.luogu-epigraph-author {
  text-align: right;
  font-size: 0.86em;
  font-weight: 500;
  color: var(--text-secondary);
}

/* Foldable Callouts (折叠框: info, success, warning, error) */
.luogu-callout {
  margin: 1.2em 0;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-color);
  background: var(--bg-primary);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
  transition: all 0.2s ease;
}

.luogu-callout-summary {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  font-weight: 600;
  font-size: 0.8125em;
  cursor: pointer;
  user-select: none;
  list-style: none;
  transition: background 0.15s;
}

.luogu-callout-summary::-webkit-details-marker {
  display: none;
}

.luogu-callout-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  flex-shrink: 0;
}

.callout-icon-svg {
  width: 18px;
  height: 18px;
}

.luogu-callout-title {
  /* Only as wide as the text, with `margin-right:auto` keeping the arrow pinned
     right. It used to be `flex:1`, which stretched the span across the whole
     summary — so in Typora mode a click anywhere on the header row landed on the
     title and opened the rename editor instead of expanding the box. */
  flex: 0 1 auto;
  margin-right: auto;
  min-width: 0;
}

.luogu-callout-arrow {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  transition: transform 0.2s ease;
}

.arrow-svg {
  width: 14px;
  height: 14px;
}

.luogu-callout[open] > .luogu-callout-summary .luogu-callout-arrow {
  transform: rotate(180deg);
}

.luogu-callout-content {
  padding: 12px 16px;
  border-top: 1px solid var(--border-color);
  background: var(--bg-primary);
  font-size: 1em;
}

/* Callout Color Schemes */
.luogu-callout-info {
  border-left: 4px solid var(--luogu-blue);
}
.luogu-callout-info > .luogu-callout-summary {
  background: var(--luogu-blue-light);
  color: #1f618d;
}

.luogu-callout-success {
  border-left: 4px solid var(--luogu-green);
}
.luogu-callout-success > .luogu-callout-summary {
  background: var(--luogu-green-light);
  color: #196f3d;
}

.luogu-callout-warning {
  border-left: 4px solid var(--luogu-orange);
}
.luogu-callout-warning > .luogu-callout-summary {
  background: var(--luogu-orange-light);
  color: #b9770e;
}

.luogu-callout-error {
  border-left: 4px solid var(--luogu-red);
}
.luogu-callout-error > .luogu-callout-summary {
  background: var(--luogu-red-light);
  color: #943126;
}

/* Bilibili Video Container */
.luogu-bilibili-container {
  margin: 1.5em 0;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-color);
  overflow: hidden;
  background: #000000;
  box-shadow: var(--shadow-md);
}

.luogu-bilibili-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 6px 12px;
  background: #232323;
  color: #ffffff;
  font-size: 11px;
}

.luogu-bilibili-badge {
  background: #fb7299;
  color: #ffffff;
  padding: 1px 6px;
  border-radius: var(--radius-sm);
  font-weight: bold;
}

.luogu-bilibili-link {
  display: flex;
  align-items: center;
  gap: 4px;
  color: #00a1d6;
  text-decoration: none;
}

.luogu-bilibili-link:hover {
  text-decoration: underline;
}

.ext-icon {
  width: 12px;
  height: 12px;
}

.luogu-bilibili-player-wrapper {
  position: relative;
  width: 100%;
  padding-top: 56.25%; /* 16:9 ratio */
}

.luogu-bilibili-player-wrapper iframe {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  border: none;
}

/* Click-to-load placeholder shown in place of the Bilibili iframe. Keeps the app
   fully offline (and free of third-party trackers) until the user opts in. */
.luogu-bilibili-facade {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  border: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  cursor: pointer;
  background: #111827;
  color: #e5e7eb;
  font-size: 13px;
  font-family: inherit;
  transition: background 0.15s;
}

.luogu-bilibili-facade:hover {
  background: #1f2937;
}

.luogu-bilibili-facade:focus-visible {
  outline: 2px solid #38bdf8;
  outline-offset: -2px;
}

.luogu-bilibili-play-icon {
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: #fb7299;
  color: #ffffff;
  display: flex;
  align-items: center;
  justify-content: center;
}

.luogu-bilibili-play-icon svg {
  width: 28px;
  height: 28px;
  margin-left: 3px;
}

/* KaTeX Formulas */
.luogu-math-display {
  margin: 1.2em 0;
  overflow-x: auto;
  overflow-y: hidden;
  text-align: center;
  padding: 6px 0;
}

.luogu-math-inline {
  display: inline-block;
  padding: 0 2px;
}

.katex-error {
  color: var(--luogu-red);
  background: var(--luogu-red-light);
  padding: 2px 4px;
  border-radius: var(--radius-sm);
  font-family: var(--font-mono);
  font-size: 0.9em;
}

/* ==========================================================================
   Modals & Drawers (模态框、公式抽屉、表格生成器)
   ========================================================================== */

.modal-overlay {
  display: none;
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  /* Solid dim only — backdrop-filter:blur forces a full-viewport backdrop
     repaint on every hover style change inside the dialog (esp. KaTeX cards). */
  background: rgba(0, 0, 0, 0.48);
  z-index: 10000;
  align-items: center;
  justify-content: center;
}

.modal-overlay.active {
  display: flex;
}

.modal-dialog {
  background: var(--bg-primary);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  width: 90%;
  max-width: 580px;
  max-height: 88vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  animation: modalFadeIn 0.2s ease;
}

.modal-dialog-large {
  max-width: 960px;
  width: 94%;
}

@keyframes modalFadeIn {
  from { opacity: 0; transform: translateY(-10px) scale(0.98); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}

.modal-header {
  height: 48px;
  padding: 0 20px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-bottom: 1px solid var(--border-color);
  background: var(--bg-secondary);
  flex-shrink: 0;
}

.modal-title {
  font-size: 15px;
  font-weight: 600;
}

.modal-close-btn {
  background: transparent;
  border: none;
  font-size: 20px;
  cursor: pointer;
  color: var(--text-secondary);
  padding: 4px;
  line-height: 1;
}

.modal-close-btn:hover {
  color: var(--text-primary);
}

.modal-body {
  padding: 20px;
  overflow-y: auto;
  flex: 1;
}

.modal-footer {
  padding: 12px 20px;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 10px;
  border-top: 1px solid var(--border-color);
  background: var(--bg-secondary);
  flex-shrink: 0;
}

/* 设置弹窗：一行一个设置项，左边是名字、右边是控件。
   用 grid 而不是 flex 是为了让所有行的控件左边缘对齐 —— 名字长短不一时，
   右对齐的控件列看起来才不晃。 */
.settings-body {
  display: flex;
  flex-direction: column;
  gap: 22px;
}

.settings-section + .settings-section {
  padding-top: 18px;
  border-top: 1px solid var(--border-color);
}

.settings-section-title {
  margin: 0 0 10px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--luogu-blue);
  text-transform: uppercase;
}

.settings-row {
  display: grid;
  grid-template-columns: 160px minmax(0, 1fr);
  align-items: center;
  gap: 12px 16px;
  padding: 7px 0;
  font-size: 13px;
}

.settings-label {
  font-weight: 600;
  color: var(--text-primary);
}

.settings-control {
  justify-self: end;
  width: auto;
  min-width: 190px;
  max-width: 260px;
}

/* 开关那一列：勾选框在上、说明在下；说明跟着勾选框走，不然一行里两段文字会
   在垂直方向错开，看起来像两个设置项。 */
.settings-switch {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  cursor: pointer;
  justify-self: end;
  max-width: 380px;
}

.settings-switch input[type="checkbox"] {
  flex: 0 0 auto;
  margin-top: 2px;
  width: 15px;
  height: 15px;
  accent-color: var(--luogu-blue);
  cursor: pointer;
}

.settings-switch .settings-hint {
  font-size: 12px;
  line-height: 1.45;
  color: var(--text-secondary);
}

/* 窄屏上把两列叠起来，否则 160px 的名字列会把控件挤到看不见。 */
@media (max-width: 560px) {
  .settings-row {
    grid-template-columns: 1fr;
    gap: 6px;
  }

  .settings-control,
  .settings-switch {
    justify-self: stretch;
    max-width: none;
  }
}

/* Form Controls in Modal */
.form-group {
  margin-bottom: 16px;
}

.form-label {
  display: block;
  font-size: 12px;
  font-weight: 600;
  margin-bottom: 6px;
  color: var(--text-primary);
}

.form-input, .form-select {
  width: 100%;
  padding: 8px 12px;
  font-size: 13px;
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  background: var(--bg-primary);
  color: var(--text-primary);
  outline: none;
  transition: border-color 0.15s;
}

.form-input:focus, .form-select:focus {
  border-color: var(--border-focus);
}

.form-hint {
  font-size: 11px;
  color: var(--text-muted);
  margin-top: 4px;
}

/* Formula Drawer Grid
   Hover must stay GPU-cheap: KaTeX trees are deep, so never change the card's
   own background/border on :hover (that invalidates paint for every nested
   span). Instead, fade a single ::before overlay via opacity only. */
.math-tabs {
  display: flex;
  gap: 6px;
  border-bottom: 1px solid var(--border-color);
  margin-bottom: 16px;
  overflow-x: auto;
  padding-bottom: 6px;
  /* Isolate tab strip repaints from the formula grid below. */
  contain: layout style;
}

.math-tab-btn {
  padding: 6px 12px;
  border: none;
  background: transparent;
  font-size: 12px;
  font-weight: 500;
  color: var(--text-secondary);
  border-radius: var(--radius-sm);
  cursor: pointer;
  white-space: nowrap;
  transition: none;
}

.math-tab-btn.active {
  background: var(--luogu-blue-light);
  color: var(--luogu-blue);
  font-weight: 600;
}

/* Panels stay mounted; only [hidden] toggles. Avoids re-parsing KaTeX HTML. */
.math-tab-panel {
  display: block;
}

.math-tab-panel[hidden] {
  display: none !important;
}

.math-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
  gap: 8px;
  padding: 2px;
}

.math-grid-wide {
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
}

.math-item-card {
  /* <button> reset — cards are real buttons for a11y + no inline onclick */
  appearance: none;
  -webkit-appearance: none;
  font: inherit;
  color: inherit;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 8px 6px;
  background: var(--bg-secondary);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  cursor: pointer;
  min-height: 66px;
  max-width: 100%;
  overflow: hidden;
  box-sizing: border-box;
  position: relative;
  /* Isolate each card so hover compositing never dirties siblings. */
  contain: layout style paint;
  transition: none !important;
}

/* Hover highlight is a separate overlay layer — only its opacity changes. */
.math-item-card::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  /* 1px inset ring + fill without touching the card's own border/background */
  box-shadow: inset 0 0 0 1px var(--luogu-blue);
  background: var(--luogu-blue-light);
  opacity: 0;
  pointer-events: none;
  z-index: 0;
  /* opacity-only = compositor-friendly, no layout/paint of KaTeX children */
  transition: opacity 0.06s linear;
}

.math-item-card:hover::before,
.math-item-card:focus-visible::before {
  opacity: 0.92;
}

.math-item-card:focus {
  outline: none;
}

.math-item-card:focus-visible {
  outline: 2px solid var(--luogu-blue);
  outline-offset: 1px;
}

.math-item-card.card-wide {
  grid-column: span 2;
}

/* IMPORTANT: do NOT change background/border on the card itself on :hover.
   That path is what made KaTeX hover feel sticky. */

.math-item-preview {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  max-width: 100%;
  /* overflow:auto creates a scrollable layer that reflows on every hover paint;
     clip keeps glyphs inside without a scrollport. */
  overflow: hidden;
  font-size: 13px;
  margin-bottom: 4px;
  padding: 2px 0;
  pointer-events: none !important;
  /* Keep KaTeX subtree from participating in hit-testing / hover invalidation. */
  contain: layout style paint;
}

.math-item-preview * {
  pointer-events: none !important;
}

.math-item-preview .katex {
  /* Prevent KaTeX from expanding the card and causing sibling reflow */
  max-width: 100%;
  font-size: 1.05em;
}

.math-item-preview .katex-display {
  margin: 0 !important;
  font-size: 0.9em;
}

.math-item-label {
  position: relative;
  z-index: 1;
  font-size: 11px;
  color: var(--text-secondary);
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  width: 100%;
  pointer-events: none !important;
}

/* Table Generator Grid */
.table-builder-grid-preview {
  margin: 14px 0;
  overflow-x: auto;
}

.grid-table-editor {
  border-collapse: collapse;
  width: 100%;
}

.grid-table-editor td, .grid-table-editor th {
  border: 1px solid var(--border-color);
  padding: 6px 8px;
  position: relative;
}

.grid-cell-input {
  width: 100%;
  border: none;
  outline: none;
  background: transparent;
  font-size: 12px;
  text-align: center;
}

.grid-cell-tools {
  display: flex;
  gap: 2px;
  justify-content: center;
  margin-top: 4px;
}

.btn-mini {
  padding: 1px 4px;
  font-size: 10px;
  border-radius: 2px;
  border: 1px solid var(--border-color);
  background: var(--bg-secondary);
  cursor: pointer;
}

/* Toast Notifications */
.toast-container {
  position: fixed;
  bottom: 38px;
  right: 20px;
  z-index: 20000;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.toast {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 14px;
  background: #2c3e50;
  color: #ffffff;
  border-radius: var(--radius-md);
  font-size: 12px;
  box-shadow: var(--shadow-lg);
  animation: toastSlideIn 0.2s ease;
}

.toast-success { background: #27ae60; }
.toast-error { background: #e74c3c; }
.toast-info { background: #2980b9; }

@keyframes toastSlideIn {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: translateY(0); }
}

/* ==========================================================================
   Print & PDF Stylesheet (100% 解决所有 PDF 导出与打印 Bug)
   ========================================================================== */
@media print {
  /* Force a light (white) palette for the printed PDF, regardless of the editor's
     current theme. Without this, dark-mode variables leak into content elements
     (inline code, tables, callouts, blockquotes, epigraph) and print as dark
     blocks on a white page. Sources are the LIGHT palette values. */
  .print-light:root,
  .print-light[data-theme="light"],
  .print-light[data-theme="dark"],
  .print-light[data-theme="academic"] {
    --bg-primary: #ffffff;
    --bg-secondary: #f6f8fa;
    --bg-tertiary: #eef1f4;
    --bg-editor: #ffffff;
    --bg-preview: #ffffff;
    --text-primary: #24292e;
    --text-secondary: #57606a;
    --text-muted: #6e7781;
    --border-color: #d0d7de;
    --luogu-blue: #3498db;
    --luogu-blue-light: #eff6fb;
    --luogu-green: #2eaf5e;
    --luogu-green-light: #eafaf1;
    --luogu-orange: #e67e22;
    --luogu-orange-light: #fef5e7;
    --luogu-red: #e74c3c;
    --luogu-red-light: #fdecea;
  }

  /* Tuack table: its palette is hardcoded (light) so ensure dark-mode overrides
     don't leak into the printed PDF. */
  .luogu-tuack-table {
    border-color: #3498db !important;
  }
  .luogu-tuack-table th {
    background: #3498db !important;
    border-color: #2980b9 !important;
  }
  .luogu-tuack-table td {
    border-color: #d4e6f1 !important;
  }
  .luogu-tuack-table tr:nth-child(even) td {
    background: #f4f9fd !important;
  }
  .luogu-tuack-table tr:hover td {
    background: #eaf2f8 !important;
  }

  @page {
    size: A4 portrait;
    margin: 15mm 15mm 15mm 15mm;
  }
  
  *, *::before, *::after {
    -webkit-print-color-adjust: exact !important;
    print-color-adjust: exact !important;
    color-adjust: exact !important;
  }
  
  html, body {
    width: 100% !important;
    height: auto !important;
    overflow: visible !important;
    font-size: 11pt !important;
    line-height: 1.6 !important;
  }

  /* Page colours follow the chosen print mode instead of being hard-forced to white,
     which previously left dark-mode PDFs with near-black headings on a dark page. */
  .print-light, .print-light body {
    background: #ffffff !important;
    color: #24292e !important;
  }
  .print-dark, .print-dark body {
    background: #1e1e1e !important;
    color: #e6e6e6 !important;
  }
  
  #app, .main-workspace, .preview-pane, .preview-content {
    display: block !important;
    width: 100% !important;
    height: auto !important;
    overflow: visible !important;
    padding: 0 !important;
    margin: 0 !important;
    border: none !important;
    box-shadow: none !important;
  }
  .print-light #app, .print-light .main-workspace,
  .print-light .preview-pane, .print-light .preview-content {
    background: #ffffff !important;
  }
  .print-dark #app, .print-dark .main-workspace,
  .print-dark .preview-pane, .print-dark .preview-content {
    background: #1e1e1e !important;
    color: #e6e6e6 !important;
  }
  
  /* Hide interactive UI elements */
  .app-header,
  .toolbar-container,
  .editor-pane,
  .split-resizer,
  .app-statusbar,
  .modal-overlay,
  .toast-container,
  .pane-header,
  .luogu-code-actions,
  .luogu-code-copy-btn,
  button.luogu-code-copy-btn,
  .copy-icon,
  .copy-text {
    display: none !important;
    visibility: hidden !important;
    opacity: 0 !important;
  }
  
  /* Prevent awkward page breaks */
  h1, h2, h3, h4, h5, h6 {
    page-break-after: avoid !important;
    break-after: avoid !important;
    page-break-inside: avoid !important;
    break-inside: avoid !important;
  }
  .print-light h1, .print-light h2, .print-light h3,
  .print-light h4, .print-light h5, .print-light h6 {
    color: #1a1a1a !important;
  }
  .print-dark h1, .print-dark h2, .print-dark h3,
  .print-dark h4, .print-dark h5, .print-dark h6 {
    color: #f0f0f0 !important;
  }
  
  p, blockquote, .luogu-p {
    orphans: 3;
    widows: 3;
  }
  
  pre, code, .luogu-code-block-wrapper, .luogu-callout, .luogu-epigraph, .luogu-table-wrapper, table {
    page-break-inside: avoid !important;
    break-inside: avoid !important;
  }
  
  tr {
    page-break-inside: avoid !important;
    break-inside: avoid !important;
  }
  
  /* Prism ships a DARK theme (Tomorrow Night): #999 comments, #ccc punctuation,
     #7ec699 strings — all chosen to sit on a near-black background. Printing forces
     the code block to a light background, but nothing was re-colouring the tokens,
     so every one of them landed under 3:1 contrast on white (punctuation was 1.51).
     That is the "code colours are wrong in the PDF" report: pale text on pale paper.

     Re-map the whole palette to light-background equivalents, keeping the hue so the
     printed code still reads like the on-screen code. Values are the GitHub light
     syntax palette, all >= 4.5:1 against #f6f8fa. */
  .print-light .luogu-code-block-wrapper .token.comment,
  .print-light .luogu-code-block-wrapper .token.block-comment,
  .print-light .luogu-code-block-wrapper .token.prolog,
  .print-light .luogu-code-block-wrapper .token.doctype,
  .print-light .luogu-code-block-wrapper .token.cdata {
    color: #6a737d !important;
  }
  .print-light .luogu-code-block-wrapper .token.punctuation,
  .print-light .luogu-code-block-wrapper .token.double-colon {
    color: #24292e !important;
  }
  .print-light .luogu-code-block-wrapper .token.tag,
  .print-light .luogu-code-block-wrapper .token.attr-name,
  .print-light .luogu-code-block-wrapper .token.namespace,
  .print-light .luogu-code-block-wrapper .token.deleted {
    color: #b31d28 !important;
  }
  .print-light .luogu-code-block-wrapper .token.function-name,
  .print-light .luogu-code-block-wrapper .token.function {
    color: #6f42c1 !important;
  }
  .print-light .luogu-code-block-wrapper .token.boolean,
  .print-light .luogu-code-block-wrapper .token.number,
  .print-light .luogu-code-block-wrapper .token.constant,
  .print-light .luogu-code-block-wrapper .token.symbol {
    color: #005cc5 !important;
  }
  .print-light .luogu-code-block-wrapper .token.class-name,
  .print-light .luogu-code-block-wrapper .token.property,
  .print-light .luogu-code-block-wrapper .token.macro,
  .print-light .luogu-code-block-wrapper .token.directive-hash {
    color: #a04100 !important;
  }
  .print-light .luogu-code-block-wrapper .token.atrule,
  .print-light .luogu-code-block-wrapper .token.builtin,
  .print-light .luogu-code-block-wrapper .token.keyword,
  .print-light .luogu-code-block-wrapper .token.directive,
  .print-light .luogu-code-block-wrapper .token.selector,
  .print-light .luogu-code-block-wrapper .token.important {
    /* #d73a49 is 4.30:1; nudged darker for AA. */
    color: #b31d28 !important;
  }
  .print-light .luogu-code-block-wrapper .token.string,
  .print-light .luogu-code-block-wrapper .token.attr-value,
  .print-light .luogu-code-block-wrapper .token.char,
  .print-light .luogu-code-block-wrapper .token.regex,
  .print-light .luogu-code-block-wrapper .token.variable {
    color: #032f62 !important;
  }
  .print-light .luogu-code-block-wrapper .token.operator,
  .print-light .luogu-code-block-wrapper .token.entity,
  .print-light .luogu-code-block-wrapper .token.url {
    color: #b31d28 !important;
  }
  .print-light .luogu-code-block-wrapper .token.inserted {
    color: #22863a !important;
  }

  /* Printing in dark mode. The default is print-light (see printDocument): a reader
     usually wants ink-on-paper. But when the user explicitly keeps the dark theme we
     honour it, and then the PAGE itself must be dark as well — otherwise dark code
     blocks and callouts float on white paper, which looks broken and wastes toner.
     Prism's own dark palette is already correct here, so tokens are left alone. */
  /* Tables in dark PDF. The generic .luogu-table rules are themed for the screen,
     so a tuack/three table kept its light-theme rules (#d4e6f1 borders, white cell
     backgrounds) on the dark page and came out nearly invisible. */
  .print-dark .luogu-table,
  .print-dark .luogu-tuack-table,
  .print-dark .luogu-three-table {
    background: transparent !important;
    color: #d4d4d4 !important;
  }

  .print-dark .luogu-table th,
  .print-dark .luogu-table td {
    border-color: #4b5563 !important;
    color: #d4d4d4 !important;
  }

  .print-dark .luogu-table tr:nth-child(even) td {
    background: rgba(255, 255, 255, 0.04) !important;
  }

  .print-dark .luogu-tuack-table th,
  .print-dark .luogu-three-table thead th {
    background: transparent !important;
    color: #f3f4f6 !important;
  }

  /* The rules that define these two styles must stay visible: tuack's row lines and
     column divider, and the three-line table's three horizontal rules. */
  .print-dark .luogu-tuack-table th,
  .print-dark .luogu-tuack-table td {
    border-top-color: #9ca3af !important;
    border-bottom-color: #9ca3af !important;
  }

  .print-dark table.luogu-tuack-table[data-tuack-col] th,
  .print-dark table.luogu-tuack-table[data-tuack-col] td {
    border-left-color: #d4d4d4 !important;
  }

  .print-dark table.luogu-three-table th,
  .print-dark table.luogu-three-table td {
    border-color: #d4d4d4 !important;
  }

  .print-dark .luogu-code-block-wrapper {
    color: #abb2bf !important;
  }

  .print-dark body,
  .print-dark #app,
  .print-dark .main-workspace,
  .print-dark .preview-pane,
  .print-dark .preview-content {
    background: var(--bg-primary) !important;
    color: var(--text-primary) !important;
  }

  /* Code blocks in print: force 100% clean multi-line wrapping */
  .print-light .luogu-code-block-wrapper {
    background: #f6f8fa !important;
    color: #24292e !important;
    border: 1px solid #d1d5da !important;
    border-radius: 6px !important;
    box-shadow: none !important;
    margin: 14px 0 !important;
    page-break-inside: avoid !important;
    break-inside: avoid !important;
    display: block !important;
    width: 100% !important;
    max-width: 100% !important;
    box-sizing: border-box !important;
  }
  
  .print-light .luogu-code-header {
    background: #eaecef !important;
    color: #444d56 !important;
    border-bottom: 1px solid #d1d5da !important;
    padding: 6px 12px !important;
    font-size: 9pt !important;
    display: flex !important;
    justify-content: space-between !important;
  }
  
  .luogu-code-pre,
  .luogu-code-pre code,
  .code-line,
  .code-line-text,
  pre, code {
    white-space: pre-wrap !important;
    word-break: break-all !important;
    overflow-wrap: anywhere !important;
  }

  .luogu-code-pre {
    background: transparent !important;
    color: #24292e !important;
    padding: 8px 0 !important;
    margin: 0 !important;
    font-family: Consolas, "Courier New", monospace !important;
    font-size: 9pt !important;
    line-height: 1.45 !important;
    overflow: visible !important;
    display: block !important;
    width: 100% !important;
    max-width: 100% !important;
    box-sizing: border-box !important;
  }
  
  .luogu-code-pre code {
    display: block !important;
    width: 100% !important;
    box-sizing: border-box !important;
  }

  .code-line {
    display: flex !important;
    flex-direction: row !important;
    align-items: flex-start !important;
    width: 100% !important;
    max-width: 100% !important;
    min-height: 14pt !important;
    padding: 1px 6px !important;
    box-sizing: border-box !important;
  }
  
  .code-line-number {
    display: inline-block !important;
    width: 30px !important;
    min-width: 30px !important;
    max-width: 30px !important;
    flex: 0 0 30px !important;
    padding-right: 6px !important;
    margin-right: 6px !important;
    text-align: right !important;
    user-select: none !important;
    font-size: 8.5pt !important;
    line-height: 1.45 !important;
  }

  /* The gutter must take its colours from the print mode. The screen rules paint it
     #282c34 with a #5c6370 number, and the print block only overrode the text colour
     — so a light PDF got a black gutter stripe running down the side of an otherwise
     white code block. `position: sticky` is a screen-scrolling affordance and is
     equally wrong on paper. */
  .code-line-number {
    position: static !important;
  }
  .print-light .code-line-number {
    background: #f6f8fa !important;
    /* #8c959f is 2.85:1 on this background; darkened to clear WCAG AA while staying
       visibly subordinate to the code itself. */
    color: #6e7781 !important;
    border-right: 1px solid #d0d7de !important;
  }
  .print-dark .code-line-number {
    background: #282c34 !important;
    color: #7f8896 !important;
    border-right: 1px solid #3e4451 !important;
  }
  /* Highlighted rows tint their own gutter cell to match the row. */
  .print-light .code-line-highlighted .code-line-number {
    background: #fff8c5 !important;
    color: #6a5b16 !important;
  }
  .print-dark .code-line-highlighted .code-line-number {
    background: linear-gradient(rgba(229, 192, 123, 0.15), rgba(229, 192, 123, 0.15)), #282c34 !important;
  }
  
  .code-line-text {
    display: block !important;
    flex: 1 1 0% !important;
    min-width: 0 !important;
    width: 100% !important;
    max-width: calc(100% - 36px) !important;
    box-sizing: border-box !important;
    font-size: 9pt !important;
    line-height: 1.45 !important;
  }

  /* Unhighlighted code text. Split by print mode: the flat #24292e used to apply in
     both, so a dark-mode PDF printed near-black text onto a near-black code block. */
  .print-light .code-line-text { color: #24292e !important; }
  .print-dark .code-line-text { color: #abb2bf !important; }
  
  /* A highlighted row gains a 3px left border, so its left padding must drop by
     exactly 3px to keep the code text on the same x as every other row. The base row
     uses 6px, so this must be 3px — it said 5px, which pushed highlighted lines 2px
     right, and the dark mode had no rule at all (a full 3px shift). */
  .print-light .code-line-highlighted,
  .print-dark .code-line-highlighted {
    border-left: 3px solid #d4a72c !important;
    padding-left: 3px !important;
  }
  .print-light .code-line-highlighted {
    background: #fff8c5 !important;
  }
  .print-dark .code-line-highlighted {
    background: linear-gradient(rgba(229, 192, 123, 0.15), rgba(229, 192, 123, 0.15)), #282c34 !important;
  }
  
  /* Callouts in print: always forced fully open */
  .luogu-callout {
    display: block !important;
    box-shadow: none !important;
    page-break-inside: avoid !important;
    break-inside: avoid !important;
    margin: 12px 0 !important;
    border: 1px solid #d0d7de !important;
  }
  
  .luogu-callout > summary {
    pointer-events: none !important;
    list-style: none !important;
    padding: 8px 12px !important;
  }

  .luogu-callout > summary::-webkit-details-marker {
    display: none !important;
  }
  
  .luogu-callout-content {
    display: block !important;
    visibility: visible !important;
    opacity: 1 !important;
    height: auto !important;
    max-height: none !important;
    padding: 10px 14px !important;
    border-top: 1px solid #d0d7de !important;
  }
  
  .luogu-callout-arrow {
    display: none !important;
  }

  /* Bilibili Video in Print / PDF - Clean printable link banner */
  .luogu-bilibili-container {
    display: block !important;
    background: #f6f8fa !important;
    border: 1px solid #d0d7de !important;
    border-radius: 6px !important;
    box-shadow: none !important;
    margin: 12px 0 !important;
    page-break-inside: avoid !important;
  }

  .luogu-bilibili-header {
    background: #f6f8fa !important;
    color: #24292e !important;
    padding: 8px 12px !important;
    display: flex !important;
    justify-content: space-between !important;
    align-items: center !important;
    border: none !important;
  }

  .luogu-bilibili-badge {
    background: #fb7299 !important;
    color: #ffffff !important;
    padding: 2px 6px !important;
    border-radius: 4px !important;
    font-size: 8.5pt !important;
  }

  .luogu-bilibili-link {
    display: inline-flex !important;
    align-items: center !important;
    gap: 4px !important;
    color: #0969da !important;
    font-size: 9pt !important;
    text-decoration: underline !important;
  }

  .luogu-bilibili-player-wrapper {
    display: none !important;
  }

  /* Math display */
  .luogu-math-display {
    page-break-inside: avoid !important;
    break-inside: avoid !important;
  }
}

/* ==========================================================================
   Auto-save failure indicator
   ========================================================================== */
.save-failed {
  color: #dc2626 !important;
  font-weight: 600;
}

/* ==========================================================================
   Responsive / mobile layout
   --------------------------------------------------------------------------
   The stylesheet previously contained no width breakpoints at all, so the
   fixed two-column workspace and the 6px drag handle were unusable on a phone
   — a real scenario for reading problems and touching up solutions on the go.
   On narrow screens the panes stack and the existing view-mode classes
   (.mode-editor-only / .mode-preview-only) become the primary way to switch.
   ========================================================================== */
@media (max-width: 900px) {
  .main-workspace {
    flex-direction: column;
    height: auto;
    min-height: calc(100vh - var(--header-height) - var(--toolbar-height) - var(--statusbar-height));
  }

  /* Inline flex percentages are set by the splitter drag; neutralise them. */
  .editor-pane,
  .preview-pane {
    flex: 1 1 auto !important;
    width: 100% !important;
    min-width: 0 !important;
    height: 50vh;
  }

  /* Dragging a vertical divider makes no sense in a stacked layout. */
  .split-resizer {
    display: none !important;
  }

  /* When a single pane is selected, let it use the full viewport. */
  .mode-editor-only .editor-pane,
  .mode-preview-only .preview-pane {
    height: calc(100vh - var(--header-height) - var(--toolbar-height) - var(--statusbar-height));
  }

  .toolbar-container {
    flex-wrap: wrap;
    height: auto;
    row-gap: 4px;
    padding-top: 6px;
    padding-bottom: 6px;
  }

  .app-header {
    height: auto;
    flex-wrap: wrap;
    row-gap: 4px;
  }
}

@media (max-width: 600px) {
  /* Reclaim vertical space: on a phone the header + toolbar otherwise consumed
     over a third of the viewport before any content was visible. */
  .app-header {
    padding: 4px 8px;
  }

  .toolbar-container {
    padding: 2px 6px;
    row-gap: 2px;
    column-gap: 2px;
  }

  .toolbar-group {
    gap: 2px;
  }

  .editor-pane,
  .preview-pane {
    height: 50vh;
  }

  /* Comfortable touch targets while staying compact. */
  .toolbar-container button,
  .pane-header button {
    min-width: 36px;
    min-height: 36px;
    padding: 4px 6px;
  }

  /* The editor is the primary surface on a phone; keep the app header from
     wrapping into several tall rows. */
  .app-header .header-title-group {
    flex: 1 1 100%;
  }

  /* Secondary chrome costs too much room on a phone. */
  .toolbar-divider {
    display: none;
  }

  .luogu-table-wrapper {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }
}

/* Respect users who ask for reduced motion. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* A container name the renderer does not know (e.g. a typo like ":::s"). Shown as
   literal text rather than being coerced into an info box, with a hint that it is
   probably a mistake. */
.luogu-unknown-container {
  font-family: var(--font-mono);
  color: var(--text-muted);
  background: var(--bg-secondary);
  border-left: 3px solid var(--border-color);
  padding: 4px 10px;
  border-radius: 4px;
}

.luogu-unclosed-warning {
  color: #b26a00;
  background: rgba(255, 170, 0, 0.1);
  border-left: 3px solid #ffaa00;
  padding: 6px 10px;
  border-radius: 4px;
  font-size: 0.92em;
}

.luogu-unclosed-warning code {
  background: rgba(0, 0, 0, 0.08);
  padding: 1px 4px;
  border-radius: 3px;
}

/* Typora mode: hide the element that has been swapped out for an editor.
   This must come last in the stylesheet — `.luogu-code-pre code` is also
   `display:block !important`, and between two !important declarations of equal
   specificity the later one wins. */
/* ::cute-table{three} — booktabs 三线表: only three horizontal rules (top, under the
   header, bottom) and no vertical rules at all. Selectors are qualified with the
   table class so they outrank the generic `.luogu-table th/td` borders. */
table.luogu-three-table {
  border-collapse: collapse;
  border: 0 !important;
  background: transparent;
  box-shadow: none;
}

table.luogu-three-table th,
table.luogu-three-table td {
  border: 0 !important;
  background: transparent !important;
  padding: 8px 18px;
  text-align: center;
}

table.luogu-three-table thead th {
  border-top: 2px solid var(--text-primary) !important;
  border-bottom: 1px solid var(--text-primary) !important;
  font-weight: 600;
}

table.luogu-three-table tbody tr:last-child td {
  border-bottom: 2px solid var(--text-primary) !important;
}

/* ::cute-table{tuack=N} — thicker divider on the left edge of column N, marking the
   boundary between two logical halves of the table. */
table.luogu-tuack-table[data-tuack-col="2"] th:nth-child(2),
table.luogu-tuack-table[data-tuack-col="2"] td:nth-child(2),
table.luogu-tuack-table[data-tuack-col="3"] th:nth-child(3),
table.luogu-tuack-table[data-tuack-col="3"] td:nth-child(3),
table.luogu-tuack-table[data-tuack-col="4"] th:nth-child(4),
table.luogu-tuack-table[data-tuack-col="4"] td:nth-child(4),
table.luogu-tuack-table[data-tuack-col="5"] th:nth-child(5),
table.luogu-tuack-table[data-tuack-col="5"] td:nth-child(5),
table.luogu-tuack-table[data-tuack-col="6"] th:nth-child(6),
table.luogu-tuack-table[data-tuack-col="6"] td:nth-child(6),
table.luogu-tuack-table[data-tuack-col="7"] th:nth-child(7),
table.luogu-tuack-table[data-tuack-col="7"] td:nth-child(7),
table.luogu-tuack-table[data-tuack-col="8"] th:nth-child(8),
table.luogu-tuack-table[data-tuack-col="8"] td:nth-child(8) {
  border-left: 2.5px solid var(--text-primary) !important;
}

/* Merged-cell hover: the cell is temporarily un-merged, and the positions it had
   swallowed come back as real cells showing their own source text. Dashed borders
   and a tinted background mark them as a transient preview rather than content. */
.preview-content.typora-mode td.typora-unmerged-origin,
.preview-content.typora-mode th.typora-unmerged-origin {
  outline: 2px solid var(--luogu-blue);
  outline-offset: -2px;
}

.preview-content.typora-mode td.typora-unmerged-cell,
.preview-content.typora-mode th.typora-unmerged-cell {
  border: 1px dashed var(--luogu-blue) !important;
  background: rgba(52, 152, 219, 0.08) !important;
  color: var(--text-secondary);
  font-family: var(--font-mono, monospace);
  text-align: center;
  /* The revealed markers are metadata, not prose: keep them from being selected or
     dragged while the pointer is just passing over the table. */
  user-select: none;
}

/* An editor pinned over the element it edits, leaving the document's own layout
   untouched. A merged table cell cannot host an inline editor without the table
   reflowing around it, so the cell editor floats instead. */
/* Cell-local editing: the marker text is replaced by the input inside the same
   <td>, so the un-merged skeleton around it keeps its own markers on screen.
   （这里原本漏了一个 `{`：选择器与注释粘在一起，于是下面这条规则从未生效过，
   单元格的 padding / 字号都没被归零。） */
.preview-content.typora-mode td.typora-host-editing,
.preview-content.typora-mode th.typora-host-editing {
  padding: 0 !important;
  /* Collapse the cell's own text nodes: the editor replaces the marker, so the
     original glyph must not linger above the input. */
  font-size: 0;
  line-height: 0;
}
.preview-content.typora-mode .typora-host-editing .typora-editing {
  display: block;
  font-size: var(--preview-font-size, 16px);
}
.preview-content.typora-mode .typora-host-editing .typora-block-input {
  width: 100%;
  /* Ignore the textarea's `cols` intrinsic width so it cannot widen the column. */
  min-inline-size: 0;
  field-sizing: content;
  /* No min-width in em: a wide editor forces the whole column wider and squeezes
     the neighbouring marker cell down to nothing. The cell already reserves the
     space it had before the edit. */
  min-width: 0;
  box-sizing: border-box;
  border: 2px solid var(--luogu-blue, #3498db);
  border-radius: 3px;
  padding: 2px 4px;
  text-align: center;
  /* 曾经写死 #fff：暗色主题下点开 `^` / `<` 这类合并标记格，编辑框是个刺眼的白块。 */
  background: var(--bg-primary);
}

.typora-overlay {
  position: absolute;
  z-index: 40;
  margin: 0;
  padding: 0;
}

.preview-content.typora-mode .typora-editing.typora-overlay .typora-block-input {
  width: 100%;
  height: 100%;
  min-height: 0;
  margin: 0;
  padding: 2px 6px;
  border: 2px solid var(--luogu-blue);
  border-radius: 3px;
  background: var(--bg-primary);
  text-align: center;
  resize: none;
}

.preview-content .typora-hidden,
.preview-content .typora-hidden code,
.preview-content .typora-hidden pre {
  display: none !important;
}

/* Callout type picker (Typora mode). Appended to <body> so it is never clipped by
   the preview pane's overflow. */
.typora-type-menu {
  position: fixed;
  z-index: 9999;
  min-width: 128px;
  padding: 4px;
  background: var(--bg-primary);
  border: 1px solid var(--border-color);
  border-radius: 6px;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.18);
}

.typora-type-item {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 7px 10px;
  border: 0;
  border-radius: 4px;
  background: transparent;
  color: var(--text-primary);
  font-size: 13px;
  text-align: left;
  cursor: pointer;
}

.typora-type-item:hover {
  background: var(--bg-secondary);
}

.typora-type-item.is-current {
  font-weight: 600;
}

.typora-type-item.is-current::after {
  content: "✓";
  margin-left: auto;
  color: var(--luogu-blue);
}

.typora-type-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  flex: none;
}

/* Scroll-sync toggle: dim + strike through when off, so the state is readable at a
   glance rather than only from the tooltip. */
.tool-btn.scroll-sync-btn { position: relative; }
.tool-btn.scroll-sync-btn:not(.active) { opacity: .45; }
.tool-btn.scroll-sync-btn:not(.active)::after {
  content: '';
  position: absolute;
  left: 15%;
  right: 15%;
  top: 50%;
  height: 2px;
  background: currentColor;
  transform: rotate(-45deg);
  border-radius: 2px;
}

/* ---- Find & replace bar -----------------------------------------------------
   Docked above the editor rather than shown as a modal: replacing text is an
   iterative task, and a modal would cover the very matches being stepped through. */
.find-bar {
  flex: none;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 8px 10px;
  background: var(--bg-secondary);
  border-bottom: 1px solid var(--border-color);
}
.find-bar[hidden] { display: none; }
.find-row {
  display: flex;
  align-items: center;
  gap: 6px;
}
.find-input {
  flex: 1 1 auto;
  min-width: 0;
  padding: 5px 8px;
  font-size: 13px;
  font-family: var(--font-mono);
  color: var(--text-primary);
  background: var(--bg-primary);
  border: 1px solid var(--border-color);
  border-radius: 4px;
}
.find-input:focus {
  outline: none;
  border-color: var(--luogu-blue);
  box-shadow: 0 0 0 2px var(--luogu-blue-light, rgba(52, 152, 219, .25));
}
.find-count {
  flex: none;
  min-width: 52px;
  text-align: center;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
}
.find-btn {
  flex: none;
  min-width: 28px;
  height: 28px;
  padding: 0 8px;
  font-size: 13px;
  line-height: 1;
  color: var(--text-primary);
  background: var(--bg-primary);
  border: 1px solid var(--border-color);
  border-radius: 4px;
  cursor: pointer;
}
.find-btn:hover { border-color: var(--luogu-blue); color: var(--luogu-blue); }
.find-btn-wide { min-width: 46px; }
.find-close { font-size: 18px; }
.find-opts { flex-wrap: wrap; gap: 12px; }
.find-opt {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 12px;
  color: var(--text-muted);
  cursor: pointer;
  user-select: none;
}
.find-opt input { cursor: pointer; }
.find-error {
  font-size: 12px;
  color: var(--luogu-red, #e74c3c);
}

/* "Replace all" armed for confirmation: it rewrites the whole document, so the
   second click is deliberate rather than a repeat of the first. */
.find-btn.is-armed {
  min-width: 0;
  padding: 0 10px;
  color: #fff;
  background: var(--luogu-red, #e74c3c);
  border-color: var(--luogu-red, #e74c3c);
  font-weight: 600;
}
.find-btn.is-armed:hover { color: #fff; filter: brightness(1.08); }

/* ---- Paging markers ---------------------------------------------------------
   `:::Pagination`, `:::Header[..]` and `:::Footer[..]` produce no article content —
   they instruct the paged outputs. On screen they are drawn as small chips so the
   author can see where a page will break and what the running head will say; in
   print the chips are hidden, because the text is emitted into the @page margin
   boxes instead and would otherwise appear twice. */
.luogu-page-break {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 22px 0;
  color: var(--text-muted);
  font-size: 12px;
  user-select: none;
}
.luogu-page-break::before,
.luogu-page-break::after {
  content: '';
  flex: 1;
  border-top: 2px dashed var(--border-color);
}
.luogu-page-break > span {
  flex: none;
  padding: 2px 10px;
  border: 1px solid var(--border-color);
  border-radius: 999px;
  background: var(--bg-secondary);
  letter-spacing: .1em;
}

.luogu-page-meta {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin: 6px 0;
  padding: 4px 10px;
  border-left: 3px solid var(--luogu-blue, #3498db);
  background: rgba(52, 152, 219, .07);
  border-radius: 0 4px 4px 0;
  font-size: 12px;
  color: var(--text-muted);
}
.luogu-page-meta-tag {
  flex: none;
  font-weight: 600;
  color: var(--luogu-blue, #3498db);
}
.luogu-page-meta-text {
  color: var(--text-primary);
  word-break: break-word;
}

/* The chips are editing aids, never part of the output. */
@media print {
  .luogu-page-break,
  .luogu-page-meta { display: none !important; }
  /* A section owns its page; never let one leak a stray blank page at the end. */
  .luogu-page-section > *:last-child { margin-bottom: 0 !important; }
}
.print-light .luogu-page-break, .print-light .luogu-page-meta,
.print-dark .luogu-page-break, .print-dark .luogu-page-meta { display: none !important; }

/* ---- NOI / tuack style print theme -----------------------------------------
   Modelled on official NOI problem statements: serif throughout, light sample
   boxes rather than a dark editor theme, thin black table grids, and a running
   head plus "第 N 页 共 M 页" foot. Applied only when printing with this theme
   chosen, so the default PDF is untouched. */
.print-noi .preview-content,
.print-noi .preview-content * {
  /* CJK serif first; the Latin faces are listed ahead so digits and formulas fall
     to Times rather than to a CJK face's half-width glyphs. */
  font-family: "Times New Roman", Times, "SimSun", "Songti SC",
    "Source Han Serif SC", "Noto Serif CJK SC", "STSong", serif !important;
}
/* Maths and code must keep their own faces. */
.print-noi .preview-content .katex,
.print-noi .preview-content .katex * { font-family: KaTeX_Main, "Times New Roman", serif !important; }
.print-noi .preview-content code,
.print-noi .preview-content pre,
.print-noi .preview-content pre * {
  font-family: Consolas, "Courier New", "DejaVu Sans Mono", monospace !important;
}

@media print {
  .print-noi html, .print-noi body,
  html.print-noi, body.print-noi { font-size: 10.5pt !important; }

  .print-noi .preview-content { line-height: 1.5 !important; }

  /* Headings: plain bold serif, no rules or colour — NOI statements use 【…】
     section titles with nothing decorative around them. */
  .print-noi .preview-content h1,
  .print-noi .preview-content h2,
  .print-noi .preview-content h3,
  .print-noi .preview-content h4 {
    border: none !important;
    color: #000 !important;
    font-weight: bold !important;
    padding: 0 !important;
    margin: 1.1em 0 .5em !important;
    page-break-after: avoid !important;
  }
  .print-noi .preview-content h1 { font-size: 15pt !important; text-align: center !important; }
  .print-noi .preview-content h2 { font-size: 12.5pt !important; }
  .print-noi .preview-content h3 { font-size: 11.5pt !important; }

  /* Sample I/O reads as a bordered light box, not the editor's dark theme. */
  .print-noi .preview-content .luogu-code-block-wrapper,
  .print-noi .preview-content pre,
  .print-noi .preview-content .luogu-code-pre {
    background: #fff !important;
    color: #000 !important;
    border: 1px solid #000 !important;
    border-radius: 0 !important;
    box-shadow: none !important;
  }
  .print-noi .preview-content pre *,
  .print-noi .preview-content code { color: #000 !important; background: transparent !important; }
  /* Inline code sits in prose as plain monospace in a printed statement —
     the on-screen pill (rounded tinted box) looks like a UI widget on paper. */
  .print-noi .preview-content :not(pre) > code {
    border: none !important;
    border-radius: 0 !important;
    padding: 0 !important;
    background: transparent !important;
  }
  .print-noi .preview-content .luogu-code-header { display: none !important; }

  /* Thin black grid, the way statement tables are set. The table element carries
     its own border in the tuack / three-line styles, and the tuack divider column
     is coloured separately — both need overriding or a blue frame survives on an
     otherwise monochrome page. */
  .print-noi .preview-content table,
  .print-noi .preview-content table.luogu-tuack-table,
  .print-noi .preview-content table.luogu-three-table,
  .print-noi .preview-content table.luogu-cute-centered-table {
    border-collapse: collapse !important;
    border-color: #000 !important;
  }
  .print-noi .preview-content table.luogu-tuack-table[data-tuack-col] th,
  .print-noi .preview-content table.luogu-tuack-table[data-tuack-col] td,
  .print-noi .preview-content .luogu-tuack-table th,
  .print-noi .preview-content .luogu-tuack-table td,
  .print-noi .preview-content .luogu-three-table th,
  .print-noi .preview-content .luogu-three-table td {
    border-color: #000 !important;
  }
  .print-noi .preview-content th,
  .print-noi .preview-content td {
    border: .6pt solid #000 !important;
    background: #fff !important;
    color: #000 !important;
    padding: 3pt 6pt !important;
  }
  .print-noi .preview-content thead th { font-weight: bold !important; }
  .print-noi .preview-content tr:nth-child(even) td { background: #fff !important; }

  /* Callouts flatten to a plain ruled block; coloured panels look wrong on paper. */
  .print-noi .preview-content details.luogu-callout {
    border: none !important;
    border-left: 1.5pt solid #000 !important;
    background: #fff !important;
    box-shadow: none !important;
    border-radius: 0 !important;
  }
  .print-noi .preview-content .luogu-callout-summary {
    background: #fff !important;
    color: #000 !important;
    border: none !important;
  }
  .print-noi .preview-content .luogu-callout-arrow,
  .print-noi .preview-content .luogu-callout-icon { display: none !important; }

  .print-noi .preview-content blockquote {
    border-left: 1.5pt solid #000 !important;
    background: #fff !important;
    color: #000 !important;
  }
  .print-noi .preview-content a { color: #000 !important; text-decoration: none !important; }
  .print-noi .preview-content hr { border-top: .6pt solid #000 !important; }
}

/* The print context does not inherit the page's colour scheme, so the dark PDF
   needs it declared again — otherwise the checkbox reverts to a white square. */
@media print {
  html.print-dark, .print-dark body { color-scheme: dark; }
}

/* ---- Workspace panel & file explorer (desktop only) --------------------------
   Hidden unless a native filesystem is present; `has-workspace` is set by
   LuoguWorkspace.mount(). The tree aims to read like a sidebar you already know:
   22px rows, a chevron that rotates, kind-coloured icons, indent guides. */
.workspace-panel { display: none; }
.has-workspace .workspace-panel {
  display: flex;
  flex-direction: column;
  flex: 0 0 240px;
  min-width: 180px;
  max-width: 480px;
  border-right: 1px solid var(--border-color);
  background: var(--bg-secondary);
  overflow: hidden;
  font-size: 12px;
}
/* Grab handle: a flex item with negative margins, so it is 5px of pointer target
   straddling the border while costing zero layout width. */
.ws-resizer {
  flex: 0 0 5px;
  width: 5px;
  margin: 0 -2.5px;
  z-index: 6;
  cursor: col-resize;
  background: transparent;
}
.ws-resizer:hover { background: var(--border-focus); opacity: .5; }
body.ws-resizing { user-select: none; }
body.ws-resizing,
body.ws-resizing * { cursor: col-resize !important; }

.ws-head {
  display: flex;
  align-items: center;
  gap: 1px;
  padding: 5px 6px;
  border-bottom: 1px solid var(--border-color);
}
.ws-title {
  flex: 1;
  padding-left: 2px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .04em;
  color: var(--text-secondary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ws-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  padding: 0;
  border: none;
  border-radius: 4px;
  background: transparent;
  color: var(--text-secondary);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
}
.ws-btn:hover { background: var(--bg-tertiary); color: var(--text-primary); }
.ws-btn:active { transform: translateY(1px); }

.ws-filter-row { padding: 6px 6px 2px; }
.ws-filter {
  width: 100%;
  height: 22px;
  padding: 0 6px;
  font: inherit;
  font-size: 12px;
  color: var(--text-primary);
  background: var(--bg-primary);
  border: 1px solid var(--border-color);
  border-radius: 3px;
  outline: none;
}
.ws-filter:focus { border-color: var(--border-focus); }
.ws-filter::placeholder { color: var(--text-muted); }

.ws-sec {
  padding: 5px 8px;
  border-top: 1px solid var(--border-color);
  color: var(--text-muted);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .04em;
}
.ws-tree {
  flex: 1 1 auto;
  overflow: auto;
  padding: 4px 4px 8px;
  outline: none;
  user-select: none;
}
.ws-recent { flex: 0 0 auto; max-height: 132px; overflow: auto; padding: 2px 4px 6px; }
.ws-empty { padding: 6px 8px; color: var(--text-muted); }
.ws-empty-open {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  padding: 10px 8px;
}
.ws-empty-hint { color: var(--text-muted); }
.ws-empty-btn {
  padding: 3px 10px;
  border: 1px solid var(--border-color);
  border-radius: 4px;
  background: var(--bg-tertiary);
  color: var(--text-primary);
  font: inherit;
  font-size: 11px;
  cursor: pointer;
}
.ws-empty-btn:hover { border-color: var(--border-focus); color: var(--border-focus); }
.ws-empty-btn-quiet { background: transparent; }

/* --- tree rows --- */
.ws-node {
  position: relative;
  display: flex;
  align-items: center;
  gap: 5px;
  user-select: none;   /* 指针拖动时不要顺手选中一排文件名 */
  -webkit-user-select: none;
  height: var(--ws-row-h);
  padding: 0 6px;
  border-radius: 4px;
  color: var(--text-primary);
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
}
.ws-node:hover { background: var(--bg-tertiary); }
/* Indent guides: one 1px line per ancestor level, painted as the row's background
   so there is no per-level element to keep in sync with the DOM. */
.ws-node.has-guides {
  background-image: repeating-linear-gradient(
    to right,
    var(--ws-indent) 0 1px,
    transparent 1px 14px);
  background-repeat: no-repeat;
  background-position: 6px 0;
  background-size: calc(var(--ws-depth, 1) * 14px) 100%;
}
.ws-node.is-root > .ws-node-name { font-weight: 600; }
.ws-node.is-selected { background: var(--ws-selected-bg); }
/* The focus ring only while the tree actually has focus — otherwise it lingers as a
   stray blue box after you click into the editor. */
#wsTree:focus-within .ws-node.is-focused { box-shadow: inset 0 0 0 1px var(--border-focus); }
/* The file open in the editor and the selected row are different things and can
   both be true; the open one gets colour, the selection gets the fill. */
.ws-node.is-open > .ws-node-name { color: var(--luogu-blue); font-weight: 600; }
.ws-node.is-dirty > .ws-node-name::after {
  content: '●';
  margin-left: 5px;
  font-size: 9px;
  color: var(--luogu-orange);
  vertical-align: 1px;
}
.ws-node.is-dragging { opacity: .45; }
.ws-node.is-drop-target {
  background: var(--luogu-blue-light);
  box-shadow: inset 0 0 0 1px var(--border-focus);
}
.ws-node-name { overflow: hidden; text-overflow: ellipsis; }
.ws-input-row { cursor: default; }

.ws-chev {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  color: var(--text-muted);
}
.ws-svg-chev { transition: transform .12s ease; }
.ws-node.is-expanded > .ws-chev .ws-svg-chev { transform: rotate(90deg); }
.ws-icon { flex: none; display: inline-flex; width: 16px; height: 16px; }

/* --- icons (inline SVG, see luogu-workspace.js for why not a font) --- */
.ws-svg {
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.15;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.ws-svg-btn { stroke-width: 1.25; }
.ws-svg-folder { color: var(--ws-folder); }
.ws-folder-back { opacity: .55; }
.ws-svg[data-slot="md"] { color: var(--ws-md); }
.ws-svg[data-slot="txt"] { color: var(--ws-txt); }
.ws-svg[data-slot="js"] { color: var(--ws-js); }
.ws-svg[data-slot="html"] { color: var(--ws-html); }
.ws-svg[data-slot="css"] { color: var(--ws-css); }
.ws-svg[data-slot="cpp"] { color: var(--ws-cpp); }
.ws-svg[data-slot="py"] { color: var(--ws-py); }
.ws-svg[data-slot="java"] { color: var(--ws-java); }
.ws-svg[data-slot="rust"] { color: var(--ws-rust); }
.ws-svg[data-slot="go"] { color: var(--ws-go); }
.ws-svg[data-slot="shell"] { color: var(--ws-shell); }
.ws-svg[data-slot="config"] { color: var(--ws-config); }
.ws-svg[data-slot="img"] { color: var(--ws-img); }
.ws-svg[data-slot="pdf"] { color: var(--ws-pdf); }
.ws-svg[data-slot="zip"] { color: var(--ws-zip); }
.ws-svg[data-slot="bin"] { color: var(--ws-bin); }
.ws-svg[data-slot="plain"] { color: var(--ws-plain); }

/* --- inline rename / new-entry input --- */
.ws-inline-input {
  flex: 1;
  min-width: 0;
  height: 18px;
  padding: 0 5px;
  font: inherit;
  font-size: 12px;
  color: var(--text-primary);
  background: var(--bg-primary);
  border: 1px solid var(--border-focus);
  border-radius: 3px;
  outline: none;
}

/* --- context menu --- */
.ws-menu {
  position: fixed;
  z-index: 2000;
  min-width: 190px;
  padding: 4px;
  background: var(--bg-primary);
  border: 1px solid var(--border-color);
  border-radius: 6px;
  box-shadow: 0 6px 20px rgba(0, 0, 0, .18);
  font-size: 12px;
}
.ws-menu-item {
  display: flex;
  align-items: center;
  gap: 12px;
  height: 24px;
  padding: 0 8px;
  border-radius: 4px;
  color: var(--text-primary);
  cursor: pointer;
  outline: none;
}
.ws-menu-item > span:first-child { flex: 1; }
.ws-menu-item:hover,
.ws-menu-item:focus { background: var(--bg-tertiary); }
.ws-menu-hint { color: var(--text-muted); font-size: 11px; }
.ws-menu-item.is-danger { color: var(--luogu-red); }
.ws-menu-item.is-danger:hover,
.ws-menu-item.is-danger:focus { background: var(--luogu-red-light); }
.ws-menu-sep { height: 1px; margin: 4px 6px; background: var(--border-color); }

/* 只在桌面版（有文件树）出现的控件。 */
/* 只在桌面版出现的控件：写回磁盘、自动保存、保存时排版。网页版现在也有标签页
   （has-workspace 对它同样成立），但这些功能在那儿没有依据，所以按 ws-desktop 判。 */
.ws-desktop-only { display: none !important; }
/* 每种宿主元素要显式写回它自己的 display —— 用 revert 会退到浏览器默认值，
   网格行的两列布局就没了。 */
html.ws-desktop .dropdown-item.ws-desktop-only { display: flex !important; }
html.ws-desktop .status-item.ws-desktop-only { display: flex !important; }
html.ws-desktop .settings-row.ws-desktop-only { display: grid !important; }
html.ws-desktop #themeShortcut { display: block !important; }
.settings-row.ws-web-only { display: grid; }
html.ws-desktop .ws-web-only { display: none !important; }

/* "是否保存更改？" 三选一对話框：保存 / 不保存 / 取消。
   宿主自带的 confirm 只有两个按钮，缺的正是最常用的那个"保存"。 */
.ws-ask-dialog {
  width: min(420px, 92vw);
  padding: 20px 22px 16px;
  border-radius: 10px;
  box-shadow: 0 10px 34px rgba(0, 0, 0, .28);
}
.ws-ask-title { margin: 0 0 8px; font-size: 15px; color: var(--text-primary); }
.ws-ask-body { margin: 0 0 18px; font-size: 13px; line-height: 1.7; color: var(--text-secondary); }
.ws-ask-buttons { display: flex; gap: 8px; justify-content: flex-end; }
.ws-ask-btn {
  padding: 6px 16px;
  font: inherit;
  font-size: 13px;
  color: var(--text-primary);
  background: var(--bg-tertiary);
  border: 1px solid var(--border-color);
  border-radius: 6px;
  cursor: pointer;
}
.ws-ask-btn:hover { border-color: var(--border-focus); }
.ws-ask-btn.is-primary {
  color: #fff;
  background: var(--luogu-blue);
  border-color: var(--luogu-blue);
}
.ws-ask-btn.is-primary:hover { background: var(--luogu-blue-dark, var(--luogu-blue)); }
.ws-ask-btn.is-danger { color: var(--luogu-red); }
.ws-ask-btn.is-danger:hover { border-color: var(--luogu-red); background: var(--luogu-red-light); }
.ws-ask-btn:focus-visible { outline: 2px solid var(--border-focus); outline-offset: 1px; }


/* --- 收起后的侧栏 ---
   宽度由 JS 直接写在 style 上（宽度本来就是内联设置的，类选择器压不过它），
   这里只负责把面板里的东西藏起来、把轨道露出来。 */
.workspace-panel.is-collapsed {
  /* min-width 会把 flex-basis 顶回去（宽度取两者中更大的），收起时必须让开。 */
  padding: 0;
  min-width: 0;
  max-width: none;
}
.workspace-panel.is-collapsed .ws-head,
.workspace-panel.is-collapsed .ws-filter-row,
.workspace-panel.is-collapsed .ws-tree,
.workspace-panel.is-collapsed .ws-sec,
.workspace-panel.is-collapsed .ws-recent { display: none; }
.ws-rail { display: none; }
.workspace-panel.is-collapsed .ws-rail {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding-top: 6px;
  width: 100%;
}

/* --- 拖动时跟着指针的小标签 ---
   fixed + transform：拖动过程中只改 transform，不触发布局。 */
.ws-drag-ghost {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 9000;
  pointer-events: none;
  max-width: 240px;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  padding: 3px 9px;
  border-radius: 5px;
  font-size: 12px;
  color: var(--text-primary);
  background: var(--bg-secondary);
  border: 1px solid var(--border-focus);
  box-shadow: 0 4px 14px rgba(0, 0, 0, .28);
}
body.ws-dragging { user-select: none; }
body.ws-dragging,
body.ws-dragging * { cursor: grabbing !important; }

/* --- 把文件拖到窗口上时的提示层（桌面版的原生拖放） --- */
.ws-drop-hint {
  position: absolute;
  inset: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  background: rgba(127, 127, 127, .16);
  border: 2px dashed var(--luogu-blue);
  border-radius: 6px;
}
/* [hidden] 只是 UA 样式表里的 display:none，压不过上面写死的 display:flex。 */
.ws-drop-hint[hidden],
.ws-drag-ghost[hidden] { display: none !important; }
.ws-drop-hint-box {
  padding: 9px 16px;
  border-radius: 8px;
  font-size: 13px;
  color: var(--text-primary);
  background: var(--bg-secondary);
  border: 1px solid var(--border-color);
  box-shadow: 0 6px 20px rgba(0, 0, 0, .25);
}

/* --- empty state: no file open ------------------------------------------------
   Shown when the last tab is closed. The textarea is made read-only at the same
   time (LuoguWorkspace._applyEmptyState) — with no tab there is nowhere to save
   what you type, so the typing is blocked rather than silently dropped. */
.ws-watermark {
  position: absolute;
  inset: 0;
  z-index: 30;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 20px;
  background: var(--bg-editor);
  color: var(--text-secondary);
  text-align: center;
}
/* `display: flex` above would otherwise beat the [hidden] attribute. */
.ws-watermark[hidden] { display: none; }
.ws-watermark-icon { color: var(--text-muted); opacity: .55; }
.ws-svg-watermark { width: 44px; height: 44px; stroke-width: 1.1; }
.ws-watermark-title { font-size: 14px; font-weight: 600; color: var(--text-primary); }
.ws-watermark-actions { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; }
.ws-watermark-btn {
  padding: 5px 14px;
  font: inherit;
  font-size: 12px;
  color: var(--text-primary);
  background: var(--bg-tertiary);
  border: 1px solid var(--border-color);
  border-radius: 5px;
  cursor: pointer;
}
.ws-watermark-btn:hover { border-color: var(--border-focus); color: var(--border-focus); }
.ws-watermark-hint { font-size: 11px; color: var(--text-muted); max-width: 340px; line-height: 1.7; }
.ws-watermark-hint kbd {
  padding: 1px 5px;
  font-family: var(--font-mono, monospace);
  font-size: 10px;
  color: var(--text-secondary);
  background: var(--bg-tertiary);
  border: 1px solid var(--border-color);
  border-bottom-width: 2px;
  border-radius: 3px;
}
/* Read-only editor: dimmed just enough to read as "nothing to edit here". */
.ws-no-docs .editor-textarea { opacity: .5; cursor: default; }
.ws-no-docs .line-numbers-gutter { opacity: .35; }

/* Document tabs, above the editor. */
.ws-tabs { display: none; }
.has-workspace .ws-tabs {
  display: flex;
  flex: none;
  gap: 1px;
  overflow-x: auto;
  background: var(--bg-secondary);
  border-bottom: 1px solid var(--border-color);
}
.ws-tab {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 5px 8px 5px 12px;
  font-size: 12px;
  cursor: pointer;
  border-right: 1px solid var(--border-color);
  color: var(--text-muted);
  white-space: nowrap;
  max-width: 200px;
}
.ws-tab:hover { background: var(--bg-tertiary); }
.ws-tab.is-active {
  background: var(--bg-editor);
  color: var(--text-primary);
  box-shadow: inset 0 2px 0 var(--luogu-blue);
}
.ws-tab.is-dirty .ws-tab-name { font-style: italic; }
.ws-tab.is-dirty .ws-tab-close { color: var(--luogu-orange); }
.ws-tab-name { overflow: hidden; text-overflow: ellipsis; }
.ws-tab-close {
  border: none;
  background: transparent;
  cursor: pointer;
  color: inherit;
  font-size: 12px;
  line-height: 1;
  padding: 1px 3px;
  border-radius: 3px;
}
.ws-tab-close:hover { background: var(--luogu-red, #e74c3c); color: #fff; }
@media print { .workspace-panel, .ws-tabs, .ws-menu { display: none !important; } }
