:root {
    --bg: #0f0f0f;
    --panel: #111111;
    --border: #262626;
    --divider: #1b1b1b;
    --hover: #171717;
    --text: #f2f2f2;
    --muted: #8b8b8b;
    --dim: #6e6e6e;
    --accent: #f0a726;
    --modal-bg: #161616;
}

* {
    box-sizing: border-box;
}

html, body {
    height: 100%;
}

body {
    margin: 0;
    background: var(--bg);
    color: var(--text);
    font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    font-size: 14px;
    -webkit-font-smoothing: antialiased;
}

/* --- layout --- */

.editor-page {
    display: flex;
    height: 100vh;
}

.file-explorer {
    display: flex;
    flex-direction: column;
    width: 280px;
    flex-shrink: 0;
    border-right: 1px solid var(--border);
    background: var(--panel);
}

.editor-main {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-width: 0;
}

/* --- file explorer: search + new file --- */

.explorer-header {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 18px 8px;
    border-bottom: 1px solid var(--border);
}

.search {
    position: relative;
}

.search svg {
    position: absolute;
    left: 12px;
    top: 50%;
    transform: translateY(-50%);
    width: 14px;
    height: 14px;
    color: var(--dim);
    pointer-events: none;
}

.search input {
    width: 100%;
    height: 36px;
    padding: 0 12px 0 34px;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: #141414;
    color: var(--text);
    font: inherit;
    font-size: 12.5px;
    outline: none;
    transition: border-color 0.15s, background 0.15s;
}

.search input::placeholder {
    color: var(--dim);
}

.search input:focus {
    border-color: #3a3a3a;
    background: #181818;
}

.search input::-webkit-search-cancel-button {
    -webkit-appearance: none;
}

.new-file-btn,
.upload-file-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    padding: 11px 12px;
    border-radius: 10px;
    font: inherit;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    transition: background 0.15s;
}

.new-file-btn {
    border: 1px solid #EC4899;
    background: rgba(236, 72, 153, 0.12);
    color: #EC4899;
}

.new-file-btn:hover {
    background: rgba(236, 72, 153, 0.22);
}

.upload-file-btn {
    border: 1px solid #3b82f6;
    background: rgba(59, 130, 246, 0.12);
    color: #3b82f6;
}

.upload-file-btn:hover {
    background: rgba(59, 130, 246, 0.22);
}

.new-file-btn svg,
.upload-file-btn svg {
    width: 14px;
    height: 14px;
}

/* --- file list --- */

.file-list {
    flex: 1;
    overflow-y: auto;
    padding: 8px;
    padding-top: 18px;
}

.file-list-empty {
    padding: 30px 12px;
    text-align: center;
    font-size: 12.5px;
    color: var(--dim);
}

.file-item {
    position: relative;
    display: flex;
    align-items: center;
    width: 100%;
    margin-bottom: 4px;
    border: 1px solid rgba(128, 128, 128, 0.288);
    border-radius: 10px;
    transition: background 0.12s, border-color 0.12s;
}

.file-item:hover {
    background: var(--hover);
}

.file-item.active {
    border-color: #80c715;
    background: rgba(128, 199, 21, 0.1);
}

.file-item-open {
    flex: 1;
    min-width: 0;
    padding: 11px 6px 11px 12px;
    border: 0;
    border-radius: 10px;
    background: none;
    color: inherit;
    font: inherit;
    font-size: 13px;
    font-weight: 600;
    text-align: left;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    cursor: pointer;
}

.file-item-rename-input {
    flex: 1;
    min-width: 0;
    margin: 6px 8px 6px 12px;
    padding: 5px 8px;
    border: 1px solid var(--accent);
    border-radius: 7px;
    background: #141414;
    color: var(--text);
    font: inherit;
    font-size: 13px;
    font-weight: 600;
    outline: none;
}

/* --- file item kebab menu --- */

.file-item-menu-wrap {
    position: relative;
    flex-shrink: 0;
    padding-right: 6px;
}

.file-item-menu-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    border: 0;
    border-radius: 7px;
    background: none;
    color: var(--muted);
    opacity: 0;
    pointer-events: none;
    cursor: pointer;
    transition: background 0.12s, color 0.12s, opacity 0.12s;
}

.file-item:hover .file-item-menu-btn,
.file-item:focus-within .file-item-menu-btn,
.file-item.menu-open .file-item-menu-btn {
    opacity: 1;
    pointer-events: auto;
}

.file-item-menu-btn:hover {
    background: #262626;
    color: var(--text);
}

.file-item-menu-btn svg {
    width: 15px;
    height: 15px;
}

.file-item-menu {
    position: absolute;
    top: calc(100% + 4px);
    right: 0;
    min-width: 152px;
    padding: 6px;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--modal-bg);
    box-shadow: 0 12px 30px rgba(0, 0, 0, 0.45);
    z-index: 20;
}

.file-item-menu-option {
    display: flex;
    align-items: center;
    width: 100%;
    height: 32px;
    padding: 0 10px;
    border: 0;
    border-radius: 7px;
    background: none;
    color: var(--text);
    font: inherit;
    font-size: 12.5px;
    text-align: left;
    cursor: pointer;
    transition: background 0.12s;
}

.file-item-menu-option:hover {
    background: var(--hover);
}

.file-item-menu-option.danger {
    color: #ef4444;
}

/* --- editor toolbar --- */

.editor-toolbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    height: 68px;
    flex-shrink: 0;
    padding: 0 20px;
    border-bottom: 1px solid var(--border);
}

.editor-toolbar-actions,
.editor-toolbar-left {
    display: flex;
    align-items: center;
    gap: 10px;
}

.toolbar-divider {
    width: 1px;
    height: 24px;
    background: var(--border);
}

.format-btn {
    font-size: 15px;
    font-weight: 700;
}

.format-btn-italic {
    font-style: italic;
}

.format-btn-heading {
    font-size: 12px;
}

.format-btn-script {
    font-size: 13px;
}

.format-btn:not(:disabled) {
    border-color: #565656;
    color: #e8e8e8;
}

.font-size-control {
    display: flex;
    align-items: stretch;
    height: 32px;
    border-radius: 8px;
    border: 1.5px solid #3a3a3a;
    background: rgba(255, 255, 255, 0.04);
    transition: background-color 0.15s, border-color 0.15s;
}

.font-size-control:hover {
    background: rgba(255, 255, 255, 0.08);
    border-color: #565656;
}

.font-size-input {
    width: 40px;
    border: none;
    background: transparent;
    color: #e8e8e8;
    font-size: 13px;
    font-family: inherit;
    text-align: center;
    /* The native up/down spinner is hidden - fontSizeCaretBtn's own popover is the "dropdown" for presets;
       typing a value directly still works exactly like any other number input. */
    -moz-appearance: textfield;
}

.font-size-input::-webkit-inner-spin-button,
.font-size-input::-webkit-outer-spin-button {
    -webkit-appearance: none;
    margin: 0;
}

.font-size-input:focus {
    outline: none;
}

.font-size-caret-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    border: none;
    border-left: 1px solid #3a3a3a;
    background: transparent;
    color: #cfcfcf;
    cursor: pointer;
}

.font-size-caret-btn svg {
    width: 11px;
    height: 11px;
    flex-shrink: 0;
}

.font-size-control:has(:disabled) {
    opacity: 0.35;
}

.font-size-input:disabled,
.font-size-caret-btn:disabled {
    cursor: not-allowed;
}

.font-size-popover {
    position: fixed;
    z-index: 200;
    display: flex;
    flex-direction: column;
    gap: 2px;
    max-height: 260px;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: 6px;
    border: 1px solid var(--border);
    border-radius: 12px;
    background: var(--modal-bg);
    box-shadow: 0 12px 30px rgba(0, 0, 0, 0.45);
}

.font-size-option {
    padding: 6px 16px;
    border: none;
    border-radius: 6px;
    background: transparent;
    color: #e8e8e8;
    font-size: 13px;
    font-family: inherit;
    text-align: left;
    cursor: pointer;
}

.font-size-option:hover {
    background: rgba(255, 255, 255, 0.08);
}

.font-size-option.selected {
    color: #eab308;
    background: rgba(234, 179, 8, 0.18);
}

.format-btn.active:not(:disabled) {
    border-color: #eab308;
    color: #eab308;
    background: rgba(234, 179, 8, 0.18);
}

.text-color-btn {
    flex-direction: column;
    gap: 2px;
    padding-top: 3px;
}

.text-color-letter {
    font-size: 14px;
    font-weight: 700;
    line-height: 1;
}

.text-color-bar {
    width: 18px;
    height: 3px;
    border-radius: 2px;
    border: 1px solid rgba(255, 255, 255, 0.25);
    background: #ffffff;
}

.icon-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    border: 1.5px solid #3a3a3a;
    background: rgba(255, 255, 255, 0.04);
    color: #cfcfcf;
    cursor: pointer;
    transition: background-color 0.15s, transform 0.15s, border-color 0.15s, color 0.15s;
}

.icon-btn svg {
    width: 17px;
    height: 17px;
}

.icon-btn:hover:not(:disabled) {
    background: rgba(255, 255, 255, 0.08);
    border-color: #565656;
    transform: scale(1.06);
}

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

.save-btn:not(:disabled) {
    border-color: #f97316;
    color: #f97316;
    background: rgba(249, 115, 22, 0.12);
}

.save-btn.saved:not(:disabled) {
    border-color: #22c55e;
    color: #22c55e;
    background: rgba(34, 197, 94, 0.12);
}

.download-btn:not(:disabled) {
    border-color: #4fa8ff;
    color: #4fa8ff;
    background: rgba(79, 168, 255, 0.12);
}

/* --- editor surface --- */

/* .editor-toolbar stays a full-width sibling above this row (see .editor-main) - only the row below it splits
   into the sources sidebar/viewer and the actual editing surface, so the sidebar never pushes the toolbar
   itself out of the way. */
.editor-content-row {
    display: flex;
    flex: 1;
    min-height: 0;
}

.editor-surface {
    position: relative;
    flex: 1;
    min-width: 0;
    min-height: 0;
    background: var(--bg);
}

/* While the enlarged source viewer is open, the sidebar (see updateSourcesUI) is display:none entirely, so
   these two flex-grow ratios (3 and 2, i.e. 3/5 and 2/5 of the row) are the only things sharing the row's
   width - exactly the split described in the request that shaped this. */
.editor-content-row.viewer-open .editor-surface {
    flex: 3;
}

/* --- sources sidebar ("Quellen") ----------------------------------------------------------------------- */

.sources-sidebar {
    display: flex;
    flex-direction: column;
    width: 260px;
    flex-shrink: 0;
    border-right: 1px solid var(--border);
    background: var(--panel);
    transition: width 0.15s;
}

.sources-sidebar.collapsed {
    width: 44px;
}

.sources-sidebar-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    height: 44px;
    flex-shrink: 0;
    padding: 0 6px 0 14px;
    border-bottom: 1px solid var(--border);
}

.sources-sidebar-title {
    font-size: 13px;
    font-weight: 700;
    white-space: nowrap;
    overflow: hidden;
}

.sources-sidebar.collapsed .sources-sidebar-title {
    display: none;
}

.sources-collapse-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    flex-shrink: 0;
    border: 0;
    border-radius: 7px;
    background: none;
    color: var(--muted);
    cursor: pointer;
    transition: background 0.12s, color 0.12s, transform 0.15s;
}

.sources-collapse-btn:hover {
    background: var(--hover);
    color: var(--text);
}

.sources-collapse-btn svg {
    width: 15px;
    height: 15px;
}

.sources-sidebar.collapsed .sources-collapse-btn svg {
    transform: rotate(180deg);
}

.sources-add-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    margin: 10px;
    padding: 9px 10px;
    border: 1px solid #3b82f6;
    border-radius: 10px;
    background: rgba(59, 130, 246, 0.12);
    color: #3b82f6;
    font: inherit;
    font-size: 12.5px;
    font-weight: 600;
    cursor: pointer;
    transition: background 0.15s;
}

.sources-add-btn:hover {
    background: rgba(59, 130, 246, 0.22);
}

.sources-add-btn svg {
    width: 14px;
    height: 14px;
    flex-shrink: 0;
}

.sources-sidebar.collapsed .sources-add-btn span {
    display: none;
}

.sources-sidebar.collapsed .sources-add-btn {
    margin: 10px 6px;
    padding: 9px;
}

.sources-list {
    flex: 1;
    overflow-y: auto;
    padding: 4px 10px 10px;
}

.sources-sidebar.collapsed .sources-list {
    display: none;
}

.source-group {
    margin-bottom: 14px;
}

.source-group-pinned {
    padding-bottom: 14px;
    margin-bottom: 14px;
    border-bottom: 1px solid var(--border);
}

.source-group-label {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 6px;
    margin-bottom: 6px;
    color: var(--muted);
    font-size: 11.5px;
    font-weight: 600;
}

/* The ellipsis truncation used to sit on .source-group-label itself, but overflow:hidden on a FLEX CONTAINER
   clips its children's boxes wholesale rather than just the overflowing text - a long enough name pushed the
   rename/remove buttons (real flex siblings, not text) outside the container's own bounds and then clipped them
   away entirely, silently making them unreachable instead of just shortening the name. Only the name's own span
   truncates now - flex:1 1 auto lets it fill the remaining space and actually SHRINK there (flex items default
   to min-width:auto, which is exactly wide enough to never truncate; min-width:0 overrides that) - so the
   buttons, both flex-shrink:0, are guaranteed to stay visible regardless of how long the name is. */
.source-group-name {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.source-group-rename-input {
    flex: 1 1 auto;
    min-width: 0;
    padding: 3px 6px;
    border: 1px solid var(--accent);
    border-radius: 5px;
    background: #141414;
    color: var(--text);
    font: inherit;
    font-size: 11.5px;
    font-weight: 600;
}

.source-group-rename,
.source-group-remove {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    flex-shrink: 0;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: none;
    color: var(--dim);
    cursor: pointer;
    transition: background 0.12s, color 0.12s;
}

.source-group-rename:hover {
    background: rgba(255, 255, 255, 0.1);
    color: var(--text);
}

.source-group-remove:hover {
    background: rgba(239, 68, 68, 0.18);
    color: #ef4444;
}

.source-group-rename svg,
.source-group-remove svg {
    width: 12px;
    height: 12px;
}

.source-pages {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(64px, 1fr));
    gap: 6px;
}

.source-thumb {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    aspect-ratio: 3 / 4;
    padding: 0;
    border: 1px solid var(--border);
    border-radius: 8px;
    overflow: hidden;
    background: #141414;
    cursor: pointer;
    transition: border-color 0.12s, transform 0.12s;
}

.source-thumb:hover {
    border-color: #565656;
    transform: scale(1.03);
}

.source-thumb img,
.source-thumb canvas {
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
}

.source-thumb-page {
    position: absolute;
    bottom: 2px;
    right: 3px;
    padding: 0 4px;
    border-radius: 4px;
    background: rgba(0, 0, 0, 0.65);
    color: #fff;
    font-size: 9.5px;
    font-weight: 600;
    line-height: 1.5;
}

.source-page-loading {
    grid-column: 1 / -1;
    padding: 10px 0;
    color: var(--dim);
    font-size: 11.5px;
    text-align: center;
}

/* A generic file's thumb - same box as .source-thumb, but plain (not a <button>: there's nothing to open),
   so no pointer cursor or hover state. */
.source-thumb-file {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 4px;
    aspect-ratio: 3 / 4;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: #141414;
    color: var(--dim);
}

.source-thumb-file svg {
    width: 26px;
    height: 26px;
}

.source-thumb-file span {
    font-size: 9px;
    font-weight: 700;
    letter-spacing: 0.03em;
}

/* --- source viewer (enlarged, 2/5 of the row) ---------------------------------------------------------- */

.source-viewer {
    display: flex;
    flex-direction: column;
    flex: 2;
    min-width: 0;
    /* Sits in the sidebar's own spot (left of the editor-surface, see editor.html) while open - bordered on
       the right, the same edge the sidebar itself borders when it's showing instead. */
    border-right: 1px solid var(--border);
    background: var(--panel);
}

.source-viewer-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    height: 44px;
    flex-shrink: 0;
    padding: 0 10px 0 16px;
    border-bottom: 1px solid var(--border);
}

.source-viewer-title {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 13px;
    font-weight: 700;
}

.source-viewer-body {
    flex: 1;
    min-height: 0;
    display: flex;
    /* Top-anchored (not centered) so whatever's shown - image, PDF page, or docx page - always starts flush
       under the header instead of floating in the middle of the panel with empty space above it. Anything
       shorter than the panel just leaves its own extra space below instead, which also matches how a document
       taller than the panel (docx, a multi-page PDF) needs to open at its actual beginning, not its middle. */
    align-items: flex-start;
    justify-content: center;
    padding: 20px;
    overflow: auto;
    /* Reserves scrollbar space UNCONDITIONALLY, whether or not anything currently overflows - without this, a
       classic (non-overlay) scrollbar appearing/disappearing as Ctrl+scroll zoom (see the wheel listener
       below) grows or shrinks the content changes this element's own clientWidth/clientHeight mid-gesture,
       which is exactly what was throwing off the zoom-to-cursor math: a correction computed against "the box
       as it was a moment ago" no longer matches "the box as it now is" once a scrollbar has silently eaten a
       few pixels out from under it. Reserving the gutter up front keeps this box's own available size
       constant through the whole gesture, so that can't happen. */
    scrollbar-gutter: stable;
}

/* Middle-click ("mouse wheel click") drag panning - see sourceViewerBodyEl's own mousedown listener. The `*`
   override (with !important) is what keeps the hand cursor showing even while dragging happens to pass over a
   PDF's own text layer, which otherwise sets its own `cursor: text` on every span it contains. */
.source-viewer-body.dragging,
.source-viewer-body.dragging * {
    cursor: grabbing !important;
}

/* Sized to an explicit pixel width/height in JS (see renderSourceViewer), same reasoning as
   .source-viewer-page-wrap just below - NOT max-width/max-height:100%, which resolve against
   .source-viewer-body's own content-box width/height and would silently reclamp this the moment Ctrl+scroll
   zoom grows the image enough to trigger a scrollbar on the OTHER axis (shrinking the available space for
   this one), breaking uniform scaling right in the middle of a zoom gesture. */
.source-viewer-image {
    display: block;
    border-radius: 4px;
    box-shadow: 0 12px 30px rgba(0, 0, 0, 0.45);
}

/* Sized to the exact pixel dimensions renderSourceViewer picked the page's own viewport at (see
   PDF_VIEWER_MAX_SCALE) - never CSS-scaled afterward as part of the initial render, since the .textLayer below
   has to land its selectable text spans in the exact same pixel grid the canvas was actually rendered at;
   scaling one without the other would leave the selectable text visually offset from the page it's supposed to
   sit on top of. Ctrl+scroll (see sourceViewerBodyEl's own wheel listener) DOES scale this afterward - scaling
   the whole wrap as one unit keeps the canvas and its text layer in proportion to each other at any zoom. */
.source-viewer-page-wrap {
    position: relative;
    flex-shrink: 0;
    border-radius: 4px;
    overflow: hidden;
    box-shadow: 0 12px 30px rgba(0, 0, 0, 0.45);
}

/* transform-origin 0 0 (rather than the default 50% 50%) is what makes the zoom-to-cursor math in
   sourceViewerBodyEl's wheel listener work out to a single scrollLeft/scrollTop correction - it's simpler to
   reason about a scale anchored at a fixed, known corner than one anchored at a center point that itself
   moves every time the box's size changes. */
.source-viewer-image,
.source-viewer-page-wrap {
    transform-origin: 0 0;
}

.source-viewer-canvas {
    display: block;
}

/* pdf.js's own text-layer contract (see its renderTextLayer/setLayerDimensions): an invisible grid of <span>s
 * exactly overlaid on the canvas above, one per piece of extracted text, real enough for the browser's native
 * selection/copy to work on but never actually painted - only ::selection gets a visible highlight. Adapted
 * from pdf.js's own web/pdf_viewer.css (same class names/structure, trimmed to just what this needs). */
.textLayer {
    position: absolute;
    inset: 0;
    overflow: hidden;
    line-height: 1;
    text-align: initial;
    text-size-adjust: none;
    forced-color-adjust: none;
    transform-origin: 0 0;
}

.textLayer span,
.textLayer br {
    color: transparent;
    position: absolute;
    white-space: pre;
    cursor: text;
    transform-origin: 0% 0%;
}

.textLayer ::selection {
    background: rgba(240, 167, 38, 0.35);
}

.textLayer br::selection {
    background: transparent;
}

.textLayer .endOfContent {
    display: block;
    position: absolute;
    inset: 100% 0 0;
    z-index: -1;
    cursor: default;
    user-select: none;
}

.textLayer .endOfContent.active {
    top: 0;
}

/* Only shown for a multi-page PDF (see renderSourceViewer) - an image or a single-page PDF has nothing to
   page through. */
.source-viewer-footer {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 16px;
    height: 44px;
    flex-shrink: 0;
    border-top: 1px solid var(--border);
}

.source-viewer-page-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    border: 0;
    border-radius: 50%;
    background: none;
    color: var(--text);
    cursor: pointer;
    transition: background 0.12s;
}

.source-viewer-page-btn:hover:not(:disabled) {
    background: var(--hover);
}

.source-viewer-page-btn:disabled {
    color: var(--dim);
    cursor: not-allowed;
}

.source-viewer-page-btn svg {
    width: 15px;
    height: 15px;
}

.source-viewer-page-indicator {
    min-width: 64px;
    text-align: center;
    font-size: 12.5px;
    color: var(--muted);
}

/* --- "add source" modal -------------------------------------------------------------------------------- */

.source-modal-body {
    max-height: 70vh;
    overflow-y: auto;
}

.source-modal-pending {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.source-modal-pending:not(:empty) {
    margin-bottom: 14px;
}

.source-pending-card {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: #141414;
}

.source-pending-preview {
    display: flex;
    align-items: center;
    gap: 3px;
    width: 72px;
    height: 72px;
    flex-shrink: 0;
    padding: 3px;
    border-radius: 7px;
    background: #0c0c0c;
    overflow: hidden;
}

.source-pending-preview img,
.source-pending-preview canvas {
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
}

.source-pending-preview .source-page-loading {
    padding: 0;
    font-size: 10px;
}

.source-pending-file-icon {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 4px;
    width: 100%;
    height: 100%;
    color: var(--dim);
}

.source-pending-file-icon svg {
    width: 26px;
    height: 26px;
}

.source-pending-file-icon span {
    font-size: 9px;
    font-weight: 700;
    letter-spacing: 0.03em;
}

.source-pending-name-input {
    flex: 1;
    min-width: 0;
    height: 36px;
    padding: 0 10px;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: #0c0c0c;
    color: var(--text);
    font: inherit;
    font-size: 13px;
    outline: none;
}

.source-pending-name-input:focus {
    border-color: var(--accent);
}

.source-pending-remove {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    flex-shrink: 0;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: none;
    color: var(--dim);
    cursor: pointer;
    transition: background 0.12s, color 0.12s;
}

.source-pending-remove:hover {
    background: rgba(239, 68, 68, 0.18);
    color: #ef4444;
}

.source-pending-remove svg {
    width: 13px;
    height: 13px;
}

.img-modal-dropzone.drag-over {
    border-color: var(--accent);
    background: rgba(240, 167, 38, 0.08);
}

/* --- Word document (.docx) preview content ------------------------------------------------------------- */
/* Base typography for mammoth.js's converted HTML - plain semantic tags (h1-h6, p, ul/ol/li, table, strong,
   em, img), styled generically here rather than reusing .doc-preview's own rules, which target THIS app's own
   document schema (classes like .doc-list/.h2-underline that mammoth's output never has). A white "page"
   background (unlike the dark app chrome around it) matches the same visual metaphor a PDF page's own white
   canvas already uses. */
.docx-page {
    background: #fff;
    color: #1a1a1a;
    font-family: Georgia, "Times New Roman", serif;
    font-size: 14px;
    line-height: 1.6;
    padding: 32px 40px;
    text-align: left;
}

.docx-page h1, .docx-page h2, .docx-page h3, .docx-page h4 {
    margin: 0 0 12px;
    font-family: inherit;
    font-weight: 700;
    color: #111;
}

.docx-page h1 { font-size: 23px; }
.docx-page h2 { font-size: 19px; }
.docx-page h3 { font-size: 16px; }
.docx-page h4 { font-size: 14px; }

.docx-page p {
    margin: 0 0 12px;
}

.docx-page ul, .docx-page ol {
    margin: 0 0 12px;
    padding-left: 26px;
}

.docx-page img {
    max-width: 100%;
}

.docx-page table {
    border-collapse: collapse;
    margin: 0 0 12px;
}

.docx-page table td, .docx-page table th {
    border: 1px solid #ccc;
    padding: 4px 8px;
}

/* The sidebar/pending-preview shrink target - see buildDocxPreviewPage/scaleDocxPreviewToFit. A plain
   (non-flex) box filling its parent (.source-thumb or .source-pending-preview) completely - both of those are
   flex+align-items:center, exactly right for an image/PDF canvas's own object-fit-based centering, but wrong
   for a page usually much TALLER than the box even after shrinking: centering it would crop to the MIDDLE of
   the document instead of a peek at its top. A 100%-sized plain wrapper is never itself centered (there's no
   extra space to center within), so .docx-page just sits at its natural top-left position inside it instead -
   the same "just a peek at the top of the document" treatment a PDF's own first-page thumbnail gives for
   free. */
.source-thumb-docx-crop {
    width: 100%;
    height: 100%;
    overflow: hidden;
}

.source-thumb-docx-crop .docx-page {
    transform-origin: 0 0;
}

/* The enlarged view - a comfortable reading width, scrolled vertically like any normal long document, NOT the
   fixed-then-shrunk thumbnail treatment above. */
.source-viewer-docx-page {
    width: min(700px, 100%);
    flex-shrink: 0;
    border-radius: 4px;
    box-shadow: 0 12px 30px rgba(0, 0, 0, 0.45);
}


.json-editor {
    display: block;
    position: absolute;
    inset: 0;
    z-index: 2;
    width: 100%;
    height: 100%;
    /* Same centered reading column as before, plus extra left room for the line-numbers gutter stacked
       underneath it (see .json-highlight below) - see the code block's own padding comment for why the
       gutter's own width (3.8em) is hardcoded here rather than left to the plugin's own rule. */
    padding: 30px max(24px, calc(50% - 360px)) 30px calc(max(24px, calc(50% - 360px)) + 3.8em);
    border: 0;
    outline: none;
    resize: none;
    /* No wrapping (see the code block's own comment on this) - a long line scrolls horizontally instead of
       drifting the line-numbers gutter out of sync with the text. */
    white-space: pre;
    overflow-x: auto;
    overflow-y: auto;
    background: transparent;
    /* Actual editing happens here, invisibly - the visible, colored text is .json-highlight stacked exactly
       on top of it (position/inset/padding kept identical between the two on purpose). */
    color: transparent;
    caret-color: var(--text);
    -webkit-text-fill-color: transparent;
    font-family: "SF Mono", "Fira Code", Consolas, "Courier New", monospace;
    font-size: 13px;
    line-height: 1.6;
    tab-size: 2;
}

/* The highlighted, line-numbered copy shown through the (invisible) #jsonEditor textarea stacked on top of
   it - same overlay trick as a code block's .code-editor-textarea/.code-editor-highlight pair (it reuses
   that class for its token colors/line-numbers-rows styling), just positioned relative to .editor-surface
   instead of a per-block .code-editor-body, and padded to match #jsonEditor's own centered column exactly. */
.editor-surface pre.json-highlight.line-numbers {
    position: absolute;
    inset: 0;
    z-index: 1;
    padding: 30px max(24px, calc(50% - 360px)) 30px calc(max(24px, calc(50% - 360px)) + 3.8em);
    pointer-events: none;
    overflow: hidden;
    color: #d8d8d8;
}

.editor-placeholder {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0 24px;
    text-align: center;
    font-size: 13.5px;
    color: var(--dim);
}

.editor-textarea {
    width: 100%;
    height: 100%;
    padding: 30px max(24px, calc(50% - 360px));
    border: 0;
    outline: none;
    overflow-y: auto;
    background: transparent;
    color: var(--text);
    font-family: inherit;
    font-size: 15px;
    line-height: 1.7;
    /* Inherited by every block below - the default "normal" collapses runs of whitespace to a single space
       and strips leading/trailing spaces, so typing more than one space (or leading spaces) silently had no
       visible effect even though it was stored correctly in the block's runs all along. Matches .doc's own
       white-space in studymaterial.css, so the editor shows exactly what the published document will. */
    white-space: pre-wrap;
}

.editor-textarea strong,
.editor-textarea b {
    font-weight: 700;
}

.editor-textarea em,
.editor-textarea i {
    font-style: italic;
}

/* Matches the studymaterial viewer's .doc h1/h2/h3 exactly, so a heading looks in the editor - and in the
   preview below - the way it will actually render. */
.editor-textarea h1,
.doc-preview h1 {
    margin: 0 0 8px;
    padding-bottom: 0px;
    border-bottom: 1px solid var(--border);
    font-size: 32px;
    font-weight: 700;
    letter-spacing: -0.01em;
}

.editor-textarea h2,
.doc-preview h2 {
    display: inline-block;
    max-width: 100%;
    font-size: 22px;
    font-weight: 700;
    letter-spacing: -0.005em;
}

.doc-preview h2 {
    margin: 36px 0 12px;
}

/* Editor-only: h2 sits inside a full-width .h2-row wrapper (see editor.js) so its color picker can sit at the
   far right of the whole column instead of hugging the end of the text. The wrapper - not the h2 itself -
   carries the margin that used to live on the bare h2, so the wrapper's own box (button's positioning
   context) ends exactly at the heading/underline's bottom edge, with no left-over margin inside it. */
.editor-textarea h2 {
    margin: 0;
}

.h2-row {
    position: relative;
    margin: 36px 0 12px;
}

.h2-color-btn {
    position: absolute;
    right: 0;
    bottom: 0;
    width: 16px;
    height: 16px;
    padding: 0;
    border: 1.5px solid rgba(255, 255, 255, 0.4);
    border-radius: 50%;
    cursor: pointer;
    transition: transform 0.15s;
}

.h2-color-btn:hover {
    transform: scale(1.15);
}

.h2-color-btn.no-color {
    background: var(--panel);
    background-image: linear-gradient(135deg, transparent 47%, #ef4444 49%, #ef4444 51%, transparent 53%);
}

.h2-color-popover {
    top: 100%;
    right: 0;
    margin-top: 6px;
}

.editor-textarea h2.h2-underline::after,
.doc-preview h2.h2-underline::after {
    content: "";
    display: block;
    width: 100%;
    height: 3px;
    margin-top: 4px;
    border-radius: 2px;
    background: var(--h2-color);
}

.editor-textarea h3,
.doc-preview h3 {
    margin: 28px 0 10px;
    font-size: 18px;
    font-weight: 700;
    letter-spacing: -0.005em;
}

/* --- shared block-content types (math/code/img/table/list/callout) ---
   Copied from studymaterial.css's .doc-scoped rules (minus its own :root, which the editor already has its
   own conflicting version of) so a block type the editor can't edit yet still renders - both inline in the
   live surface as a read-only island, and in Vorschau - pixel-identical to the real viewer. */

.editor-textarea .doc-list,
.doc-preview .doc-list {
    margin: 0 0 14px;
    padding-left: 24px;
    font-size: 15px;
    line-height: 1.7;
    color: #d8d8d8;
}

.editor-textarea .doc-list:last-child,
.doc-preview .doc-list:last-child {
    margin-bottom: 0;
}

.editor-textarea .doc-list li,
.doc-preview .doc-list li {
    margin-bottom: 6px;
}

.editor-textarea .doc-list li:last-child,
.doc-preview .doc-list li:last-child {
    margin-bottom: 0;
}

.editor-textarea ul.doc-list,
.doc-preview ul.doc-list {
    list-style: disc;
}

.editor-textarea ol.doc-list,
.doc-preview ol.doc-list {
    list-style: decimal;
}

.editor-textarea .math-block,
.doc-preview .math-block {
    margin: 16px 0;
    /* Not overflow-x:auto/overflow-y:hidden (as this used to be) - KaTeX's own layout (e.g. an integral's
       stacked limits) can genuinely render taller than the block's own flow height accounts for, and
       clipping either axis cuts that content off; overflow-x can't be "visible" while overflow-y isn't (or
       vice versa - the CSS spec forces the "visible" one back to "auto" otherwise), so both have to be. A
       very wide formula now simply overflows past the column instead of scrolling. */
    overflow: visible;
    text-align: left;
    font-size: 14px;
}

.editor-textarea .math-block .katex-display,
.doc-preview .math-block .katex-display {
    margin: 0 !important;
    text-align: left !important;
}

.editor-textarea .math-block .katex-display > .katex,
.doc-preview .math-block .katex-display > .katex {
    text-align: left !important;
}

/* Only applied when the math block's own "centered" attribute is set (default false - the original/only look
   before that attribute existed, matching the two rules just above). Higher specificity (one more class) than
   both of those, so it wins outright regardless of source order, rather than needing a third !important tier
   to fight them. */
.editor-textarea .math-block.math-centered .katex-display,
.doc-preview .math-block.math-centered .katex-display {
    text-align: center !important;
}

.editor-textarea .math-block.math-centered .katex-display > .katex,
.doc-preview .math-block.math-centered .katex-display > .katex {
    text-align: center !important;
}

/* Only applied when the math block's "normal_font" attribute is true (the default, and the original/only
   look before that attribute existed) - overrides KaTeX's own math typefaces with the site's normal UI font.
   Set to false, none of this applies and KaTeX renders with its own conventional math fonts instead. Also
   applied to the math modal's own live preview, so it always shows exactly what committing will look like. */
.editor-textarea .math-block.math-normal-font .katex,
.editor-textarea .math-block.math-normal-font .katex *,
.doc-preview .math-block.math-normal-font .katex,
.doc-preview .math-block.math-normal-font .katex *,
.math-modal-preview.math-normal-font .katex,
.math-modal-preview.math-normal-font .katex * {
    font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif !important;
    font-weight: 700 !important;
    font-style: normal !important;
}

.editor-textarea .math-fallback,
.doc-preview .math-fallback {
    font-family: "Courier New", monospace;
    color: var(--muted);
    font-size: 14px;
}

.editor-textarea .code-block,
.doc-preview .code-block {
    position: relative;
    margin: 20px 0;
    border: 1px solid var(--border);
    border-radius: 14px;
    background: #0a0a0a;
    overflow: hidden;
}

.editor-textarea .code-lang,
.doc-preview .code-lang {
    display: block;
    padding: 9px 16px;
    border-bottom: 1px solid var(--border);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--dim);
}

.editor-textarea .code-block pre,
.doc-preview .code-block pre {
    margin: 0;
    /* Left padding reserves room for the line-numbers plugin's gutter - hardcoded to match its own
       "left: -3.8em" positioning rather than relying on its same-specificity padding-left rule to win the
       cascade against this rule's other sides, which it doesn't reliably do. */
    padding: 16px 16px 16px 3.8em;
    /* The line-numbers plugin assumes exactly one visual row per source line - wrapping breaks that (a
       wrapped line still only gets ONE numbered gutter row, so every number after it drifts down). A long
       line scrolling horizontally instead of wrapping is what keeps the numbers aligned with their lines. */
    overflow-x: auto;
    /* <code> is display:inline, so each of ITS visual lines is sized by the line box "strut" - a hypothetical
       inline box using the BLOCK CONTAINER's (this <pre>'s) own font/line-height, not just its content's.
       Without this, <pre> falls back to the browser's UA-default monospace size/line-height (~15px/25.5px
       here), taller than the 13px/20.8px actually used below - every real code line then renders slightly
       taller than a numbered gutter row (whose rows sit in normal block flow, unaffected by that strut),
       drifting further out of sync with every line. Matching them here is what keeps the two in lockstep. */
    font-family: "SF Mono", "Fira Code", Consolas, "Courier New", monospace;
    font-size: 13px;
    line-height: 1.6;
}

.editor-textarea .code-block code,
.doc-preview .code-block code {
    color: #d8d8d8;
    white-space: pre;
}

.editor-textarea .code-block .line-numbers-rows,
.doc-preview .code-block .line-numbers-rows {
    border-right-color: var(--border);
}

.editor-textarea .code-block .line-numbers-rows > span:before,
.doc-preview .code-block .line-numbers-rows > span:before {
    color: var(--dim);
}

.editor-textarea .code-block .token.comment,
.doc-preview .code-block .token.comment,
.editor-textarea .code-block .token.prolog,
.doc-preview .code-block .token.prolog,
.editor-textarea .code-block .token.doctype,
.doc-preview .code-block .token.doctype,
.editor-textarea .code-block .token.cdata,
.doc-preview .code-block .token.cdata {
    color: var(--dim);
    font-style: italic;
}

.editor-textarea .code-block .token.punctuation,
.doc-preview .code-block .token.punctuation {
    color: #a8a8a8;
}

.editor-textarea .code-block .token.property,
.doc-preview .code-block .token.property,
.editor-textarea .code-block .token.tag,
.doc-preview .code-block .token.tag,
.editor-textarea .code-block .token.boolean,
.doc-preview .code-block .token.boolean,
.editor-textarea .code-block .token.number,
.doc-preview .code-block .token.number,
.editor-textarea .code-block .token.constant,
.doc-preview .code-block .token.constant,
.editor-textarea .code-block .token.symbol,
.doc-preview .code-block .token.symbol,
.editor-textarea .code-block .token.deleted,
.doc-preview .code-block .token.deleted {
    color: #F97316;
}

.editor-textarea .code-block .token.selector,
.doc-preview .code-block .token.selector,
.editor-textarea .code-block .token.attr-name,
.doc-preview .code-block .token.attr-name,
.editor-textarea .code-block .token.string,
.doc-preview .code-block .token.string,
.editor-textarea .code-block .token.char,
.doc-preview .code-block .token.char,
.editor-textarea .code-block .token.builtin,
.doc-preview .code-block .token.builtin,
.editor-textarea .code-block .token.inserted,
.doc-preview .code-block .token.inserted {
    color: #84CC16;
}

.editor-textarea .code-block .token.operator,
.doc-preview .code-block .token.operator,
.editor-textarea .code-block .token.entity,
.doc-preview .code-block .token.entity,
.editor-textarea .code-block .token.url,
.doc-preview .code-block .token.url {
    color: #d8d8d8;
}

.editor-textarea .code-block .token.atrule,
.doc-preview .code-block .token.atrule,
.editor-textarea .code-block .token.attr-value,
.doc-preview .code-block .token.attr-value,
.editor-textarea .code-block .token.keyword,
.doc-preview .code-block .token.keyword {
    color: #EC4899;
}

.editor-textarea .code-block .token.function,
.doc-preview .code-block .token.function,
.editor-textarea .code-block .token.class-name,
.doc-preview .code-block .token.class-name {
    color: #3B82F6;
}

.editor-textarea .code-block .token.regex,
.doc-preview .code-block .token.regex,
.editor-textarea .code-block .token.important,
.doc-preview .code-block .token.important,
.editor-textarea .code-block .token.variable,
.doc-preview .code-block .token.variable {
    color: #EAB308;
}

/* --- live code block (editor only) --------------------------------------------------------------------
   A code block is never edited through the shared contenteditable/runs system at all (same reasoning as
   img: it's a self-contained island, contentEditable="false" at the wrapper) - it's a plain <textarea> with
   a syntax-highlighted <pre> stacked exactly on top of it. The textarea's own text is made transparent
   (kept only for genuine accessibility/selection/caret purposes) so the highlighted copy shows through;
   every keystroke re-renders that copy via Prism. The textarea is the only element actually in flow - it
   drives the container's height (auto-grown to scrollHeight on input), and the highlighted <pre> is
   absolutely positioned to exactly overlay it, so the two can never drift out of sync. */

.code-block-editor {
    position: relative;
    margin: 20px 0;
    border: 1px solid var(--border);
    border-radius: 14px;
    background: #0a0a0a;
    overflow: hidden;
}

.code-lang-select {
    display: block;
    width: 100%;
    padding: 9px 16px;
    border: none;
    border-bottom: 1px solid var(--border);
    background: #0a0a0a;
    font-family: inherit;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--dim);
    cursor: pointer;
}

.code-lang-select:focus {
    outline: none;
    color: var(--text);
}

.code-editor-body {
    position: relative;
}

.code-editor-textarea,
.code-editor-highlight {
    margin: 0;
    /* Left padding reserves room for the line-numbers gutter (see .code-editor-highlight .line-numbers-rows
       below) - the textarea gets it too, even though it never shows numbers itself, purely so its (invisible)
       text and caret line up with the highlighted copy stacked on top of it. */
    padding: 16px 16px 16px 3.8em;
    font-family: "SF Mono", "Fira Code", Consolas, "Courier New", monospace;
    font-size: 13px;
    line-height: 1.6;
    /* The line-numbers plugin assumes exactly one visual row per source line - wrapping breaks that (a
       wrapped line still only gets ONE numbered gutter row, so every number after it drifts down). A long
       line scrolling horizontally instead of wrapping is what keeps the numbers aligned with their lines -
       see the textarea's own scroll listener below, which keeps the highlighted copy scrolled in lockstep. */
    white-space: pre;
    tab-size: 4;
}

.code-editor-textarea {
    position: relative;
    z-index: 2;
    display: block;
    width: 100%;
    border: none;
    background: transparent;
    color: transparent;
    caret-color: var(--text);
    -webkit-text-fill-color: transparent;
    resize: none;
    overflow-x: auto;
    overflow-y: hidden;
}

.code-editor-textarea:focus {
    outline: none;
}

/* The line-numbers plugin's own CSS ships "pre[class*='language-'].line-numbers { position: relative; ... }"
   at (0,2,1) specificity - higher than a bare ".code-editor-highlight" rule (0,1,0), so it would silently
   win and turn this back into a normal in-flow, non-overlaying element regardless of load order. Matching
   its specificity (and adding the .line-numbers class here too, both to be safe and to stay correct if that
   plugin selector ever changes) is what actually keeps this absolutely positioned over the textarea. */
.code-editor-body pre.code-editor-highlight.line-numbers {
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    overflow: hidden;
    color: #d8d8d8;
}

.code-editor-highlight code {
    font-family: inherit;
    font-size: inherit;
    line-height: inherit;
    white-space: inherit;
}

.code-editor-highlight .line-numbers-rows {
    border-right-color: var(--border);
}

.code-editor-highlight .line-numbers-rows > span:before {
    color: var(--dim);
}

.code-editor-highlight .token.comment,
.code-editor-highlight .token.prolog,
.code-editor-highlight .token.doctype,
.code-editor-highlight .token.cdata {
    color: var(--dim);
    font-style: italic;
}

.code-editor-highlight .token.punctuation {
    color: #a8a8a8;
}

.code-editor-highlight .token.property,
.code-editor-highlight .token.tag,
.code-editor-highlight .token.boolean,
.code-editor-highlight .token.number,
.code-editor-highlight .token.constant,
.code-editor-highlight .token.symbol,
.code-editor-highlight .token.deleted {
    color: #F97316;
}

.code-editor-highlight .token.selector,
.code-editor-highlight .token.attr-name,
.code-editor-highlight .token.string,
.code-editor-highlight .token.char,
.code-editor-highlight .token.builtin,
.code-editor-highlight .token.inserted {
    color: #84CC16;
}

.code-editor-highlight .token.operator,
.code-editor-highlight .token.entity,
.code-editor-highlight .token.url {
    color: #d8d8d8;
}

.code-editor-highlight .token.atrule,
.code-editor-highlight .token.attr-value,
.code-editor-highlight .token.keyword {
    color: #EC4899;
}

.code-editor-highlight .token.function,
.code-editor-highlight .token.class-name {
    color: #3B82F6;
}

.code-editor-highlight .token.regex,
.code-editor-highlight .token.important,
.code-editor-highlight .token.variable {
    color: #EAB308;
}

.editor-textarea .img-block,
.doc-preview .img-block {
    margin: 22px auto;
    max-width: 100%;
    border-radius: 12px;
    border: 1px solid var(--border);
    overflow: hidden;
}

.editor-textarea .img-block img,
.doc-preview .img-block img {
    display: block;
    width: 100%;
    max-width: 100%;
    object-fit: cover;
}

.editor-textarea .img-row,
.doc-preview .img-row {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    margin: 22px auto;
    max-width: 100%;
}

.editor-textarea .img-row-item,
.doc-preview .img-row-item {
    flex-shrink: 0;
    border-radius: 12px;
    border: 1px solid var(--border);
}

/* Vorschau only, deliberately NOT .editor-textarea - see attachImageClickToZoom in renderer.js (the click is
   already skipped there for the live editable surface; this keeps the cursor/hover from suggesting it's
   clickable there too, where an image is only ever managed through its own pencil-button modal). */
.doc-preview .clickable-img {
    cursor: pointer;
    transition: opacity 0.15s;
}

.doc-preview .clickable-img:hover {
    opacity: 0.88;
}

.editor-textarea .img-text-row,
.doc-preview .img-text-row {
    display: flex;
    align-items: flex-start;
    gap: 16px;
    margin: 22px auto;
    max-width: 100%;
}

.editor-textarea .img-text-group,
.doc-preview .img-text-group {
    flex: 0 0 50%;
}

.editor-textarea .img-text-caption,
.doc-preview .img-text-caption {
    flex: 1;
    min-width: 0;
    margin: 0;
    font-size: 15px;
    line-height: 1.7;
}

.doc-preview .img-text-caption {
    color: #d8d8d8;
}

/* --- graph (JSXGraph coordinate system) ------------------------------------------------------------------ */

.editor-textarea .graph-block,
.doc-preview .graph-block {
    margin: 22px auto;
    max-width: 100%;
}

.editor-textarea .graph-board-wrap,
.doc-preview .graph-board-wrap {
    width: 100%;
    border: 1px solid var(--border);
    border-radius: 12px;
    overflow: hidden;
    /* JSXGraph renders onto a plain white board (like a real sheet of graph paper) regardless of the app's
       own dark theme - deliberately not restyled to match, since recoloring axes/grid/labels would mean
       fighting JSXGraph's own default styling throughout, for a page element that's meant to look like a
       graph first and an app UI element second. */
    background: #fff;
}

.editor-textarea .graph-board,
.doc-preview .graph-board {
    width: 100%;
}

/* Middle-click drag panning - see setupGraphZoomPan in renderer.js. */
.editor-textarea .graph-board.graph-dragging,
.editor-textarea .graph-board.graph-dragging *,
.doc-preview .graph-board.graph-dragging,
.doc-preview .graph-board.graph-dragging * {
    cursor: grabbing !important;
}

.editor-textarea .graph-legend,
.doc-preview .graph-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 14px;
    margin-top: 8px;
    font-size: 12.5px;
    color: var(--muted);
}

.editor-textarea .graph-legend-item,
.doc-preview .graph-legend-item {
    display: flex;
    align-items: center;
    gap: 6px;
}

.editor-textarea .graph-legend-swatch,
.doc-preview .graph-legend-swatch {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    flex-shrink: 0;
}

/* --- bar / pie charts (plain SVG, see makeBarChart/makePieChart in renderer.js) --- */

.editor-textarea .chart-block,
.doc-preview .chart-block {
    margin: 22px auto;
    max-width: 100%;
}

.editor-textarea .chart-svg-wrap,
.doc-preview .chart-svg-wrap {
    width: 100%;
    border: 1px solid var(--border);
    border-radius: 12px;
    overflow: hidden;
    background: var(--panel);
    padding: 12px;
    box-sizing: border-box;
}

.editor-textarea .chart-svg-wrap-pie,
.doc-preview .chart-svg-wrap-pie {
    display: flex;
    justify-content: center;
}

.editor-textarea .chart-svg,
.doc-preview .chart-svg {
    width: 100%;
    height: auto;
    display: block;
}

.editor-textarea .pie-chart-svg,
.doc-preview .pie-chart-svg {
    width: auto;
    max-width: 100%;
}

.editor-textarea .chart-axis-line,
.doc-preview .chart-axis-line {
    stroke: var(--border);
    stroke-width: 1.5;
}

.editor-textarea .chart-value-label,
.doc-preview .chart-value-label {
    font-size: 13px;
    fill: var(--text);
}

.editor-textarea .chart-category-label,
.doc-preview .chart-category-label {
    font-size: 13px;
    fill: var(--muted);
}

.editor-textarea .chart-pie-label,
.doc-preview .chart-pie-label {
    font-size: 14px;
    font-weight: 600;
    fill: #fff;
}

.editor-textarea .chart-block {
    position: relative;
}

/* Editor-only: a live image/formula needs a positioning context for its edit button - Vorschau/the real
   viewer never get one, so they're untouched. A formula has no card background of its own (unlike img/code),
   so it only gets padding-right to keep the button clear of the rendered LaTeX, the same treatment a
   passthrough info box's color button gets. */
.editor-textarea .img-block,
.editor-textarea .img-row,
.editor-textarea .img-text-row,
.editor-textarea .graph-block {
    position: relative;
}

.editor-textarea .math-block {
    position: relative;
    padding-right: 44px;
    /* The edit button sits at top:10px and is 30px tall (see .math-edit-btn below) and, being absolutely
       positioned, doesn't contribute to this block's own auto-height at all - without this, a short/flat
       formula (e.g. "E = mc^2", shorter than 40px) renders a block shorter than the button itself, clipping
       its bottom edge off. */
    min-height: 48px;
}

.img-edit-btn,
.math-edit-btn,
.graph-edit-btn {
    position: absolute;
    top: 10px;
    right: 10px;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    padding: 0;
    border: 1.5px solid var(--accent);
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.8);
    color: var(--accent);
    cursor: pointer;
    transition: transform 0.15s;
}

.img-edit-btn svg,
.math-edit-btn svg,
.graph-edit-btn svg {
    width: 14px;
    height: 14px;
}

.img-edit-btn:hover,
.math-edit-btn:hover,
.graph-edit-btn:hover {
    background: rgba(240, 167, 38, 0.32);
    transform: scale(1.08);
}

/* The table's own edit button lives in a plain toolbar row above the <table> (see createTableBlockEl for
   why it's not a floated corner button like img/math's) - a small always-visible bar, not hover-revealed,
   since it's never competing with a cell's own content for the same space. */
.table-block-toolbar {
    display: flex;
    justify-content: flex-end;
    padding: 8px 8px 8px;
}

.table-edit-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    padding: 0;
    border: 1.5px solid var(--accent);
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.8);
    color: var(--accent);
    cursor: pointer;
    transition: transform 0.15s;
}

.table-edit-btn svg {
    width: 13px;
    height: 13px;
}

.table-edit-btn:hover {
    background: rgba(240, 167, 38, 0.32);
    transform: scale(1.08);
}

.editor-textarea .table-wrap,
.doc-preview .table-wrap {
    margin: 20px 0;
    border: 1px solid var(--border);
    border-radius: 14px;
    overflow: auto;
}

/* Editor-only: a live table needs a positioning context for its own edit button, same reasoning as img/math
   above - Vorschau/the real viewer never get one, so they're untouched. */
.editor-textarea .table-wrap {
    position: relative;
}

.editor-textarea .doc-table,
.doc-preview .doc-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 14px;
}

.doc-table td {
    position: relative;
}

.editor-textarea .doc-table td,
.doc-preview .doc-table td {
    padding: 10px 16px;
    border-bottom: 1px solid var(--border);
    border-right: 1px solid var(--border);
    color: #d8d8d8;
    vertical-align: top;
}

.editor-textarea .doc-table tr:last-child td,
.doc-preview .doc-table tr:last-child td {
    border-bottom: 0;
}

.editor-textarea .doc-table td:last-child,
.doc-preview .doc-table td:last-child {
    border-right: 0;
}

.editor-textarea .doc-table td > *:first-child,
.doc-preview .doc-table td > *:first-child {
    margin-top: 0;
}

.editor-textarea .doc-table td > *:last-child,
.doc-preview .doc-table td > *:last-child {
    margin-bottom: 0;
}

/* --- live table cells (editor only) --------------------------------------------------------------------
   A table's overall shape only ever changes through the table modal (like img/math's own edit button), but
   each cell's CONTENT is directly, independently editable in place - ".table-cell-content" is the actual
   contenteditable="true" text host (see createTableBlockEl), kept separate from the <td> itself so recoloring
   a cell (which replaces the <td>'s children via updateBlockEl) can never collide with renderRunsInto's own
   innerHTML wipe of just the text host. */

.table-cell-content {
    min-height: 1.4em;
    outline: none;
}

.table-cell-content[contenteditable="false"] {
    cursor: default;
}

/* A cell can stack any number of items (text paragraphs, images, formulas, in any mix and order - see
   createCellItemEl) - these tighten their normally document-column-sized spacing (a bare <p>'s default UA
   margin; img/math's own ~16-22px margins meant for a 720px-wide page) down to something that fits a table
   cell's own much smaller footprint, and drop the leading/trailing margin so the first/last item sits flush
   with the cell's own padding, same reasoning as the shared ".doc-table td > *:first-child/:last-child" rule
   above (which only reaches the ".table-cell-content" wrapper itself, not what's nested another level in). */
.table-cell-content > *:first-child {
    margin-top: 0;
}

.table-cell-content > *:last-child {
    margin-bottom: 0;
}

.table-cell-content p {
    margin: 0 0 6px;
}

.table-cell-content .img-block,
.table-cell-content .img-row,
.table-cell-content .img-text-row {
    margin: 8px auto;
}

.table-cell-content .math-block {
    margin: 8px 0;
    /* Reserves just enough room for THIS item's own small edit button (22px at top:4/right:4 - see
       ".doc-table td .math-edit-btn" above) instead of the much larger space the top-level math block's own
       30px button needs. */
    padding-right: 30px;
    min-height: 30px;
}

/* The per-cell color swatch (attachColorPickerFixed's own .callout-color-btn, reused as-is) and an img/math
   cell's edit button both sit in a cell's corners - hidden until hover so a table full of cells doesn't look
   cluttered at rest, same "reveal on hover" treatment the file list's own kebab menu already uses. The color
   swatch moves to the top-LEFT here (its default top-right would otherwise collide with an img/math cell's
   edit button) and shrinks to fit a cell's own modest padding. Its popover is fixed-positioned straight onto
   <body> (see attachColorPickerFixed) rather than nested/positioned within the cell, so a table wider or
   taller than the editor's own scroll box - .table-wrap's overflow-x:auto, or the editor surface's own
   overflow-y - can never clip it. */
.editor-textarea .doc-table td .callout-color-btn {
    top: 6px;
    left: 6px;
    right: auto;
    width: 16px;
    height: 16px;
    opacity: 0;
    transition: opacity 0.15s, transform 0.15s;
}

.editor-textarea .doc-table td:hover .callout-color-btn {
    opacity: 1;
}

.editor-textarea .doc-table td .img-edit-btn,
.editor-textarea .doc-table td .math-edit-btn {
    top: 4px;
    right: 4px;
    width: 22px;
    height: 22px;
    opacity: 0;
}

.editor-textarea .doc-table td:hover .img-edit-btn,
.editor-textarea .doc-table td:hover .math-edit-btn {
    opacity: 1;
}

.editor-textarea .doc-table td .img-edit-btn svg,
.editor-textarea .doc-table td .math-edit-btn svg {
    width: 11px;
    height: 11px;
}

.editor-textarea .callout,
.doc-preview .callout {
    margin: 20px 0;
    padding: 16px 20px;
    border: 1px solid;
    border-radius: 14px;
}

/* Editor-only: Vorschau mimics the real read-only viewer, which has no color picker at all. */
.editor-textarea .callout {
    position: relative;
    padding-right: 44px;
}

.callout-color-btn {
    position: absolute;
    top: 10px;
    right: 10px;
    width: 20px;
    height: 20px;
    padding: 0;
    border: 1.5px solid rgba(255, 255, 255, 0.4);
    border-radius: 50%;
    cursor: pointer;
    transition: transform 0.15s;
}

.callout-color-btn:hover {
    transform: scale(1.12);
}

.callout-color-btn.no-color,
.callout-swatch.no-color {
    background: var(--panel);
    background-image: linear-gradient(135deg, transparent 47%, #ef4444 49%, #ef4444 51%, transparent 53%);
}

.callout-color-popover {
    position: absolute;
    top: 36px;
    right: 10px;
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    width: 118px;
    padding: 8px;
    border: 1px solid var(--border);
    border-radius: 12px;
    background: var(--modal-bg);
    box-shadow: 0 12px 30px rgba(0, 0, 0, 0.45);
    z-index: 30;
}

.callout-swatch {
    position: relative;
    width: 20px;
    height: 20px;
    padding: 0;
    border: 1.5px solid rgba(255, 255, 255, 0.15);
    border-radius: 50%;
    cursor: pointer;
    transition: transform 0.15s;
}

.callout-swatch:hover {
    transform: scale(1.12);
}

/* The currently-picked swatch (see buildColorPopover's own opts.currentColor) - a plain checkmark, not a
   border ring, per the request this was built from. White with a dark drop-shadow so it stays legible
   whether the swatch underneath is dark (most of SITE_COLORS) or light (e.g. the "no color"/white default
   one) - a single fixed color could disappear against either. */
.callout-swatch.selected::after {
    content: "";
    position: absolute;
    inset: 0;
    margin: auto;
    width: 7px;
    height: 4px;
    border-left: 2px solid #fff;
    border-bottom: 2px solid #fff;
    transform: rotate(-45deg) translateY(-1px);
    filter: drop-shadow(0 0 1.5px rgba(0, 0, 0, 0.9));
}

.callout-color-input {
    flex: 1 1 100%;
    min-width: 0;
    margin-top: 4px;
    height: 30px;
    padding: 0 10px;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: #141414;
    color: var(--text);
    font: inherit;
    font-size: 12.5px;
    outline: none;
    transition: border-color 0.15s, background 0.15s;
}

.callout-color-input:focus {
    border-color: #3a3a3a;
    background: #181818;
}

.callout-color-input::placeholder {
    color: var(--dim);
}

.editor-textarea .callout-body > *:first-child,
.doc-preview .callout-body > *:first-child {
    margin-top: 0;
}

.editor-textarea .callout-body > h2:first-child,
.doc-preview .callout-body > h2:first-child {
    margin-top: 0;
}

.editor-textarea .callout-body > *:last-child,
.doc-preview .callout-body > *:last-child {
    margin-bottom: 0;
}

.editor-textarea .unknown-block,
.doc-preview .unknown-block {
    margin: 16px 0;
    padding: 10px 14px;
    border: 1px dashed #4a4a4a;
    border-radius: 10px;
    font-size: 12.5px;
    color: var(--dim);
}

/* Block types the editor doesn't have editing UI for yet render as a read-only (contenteditable="false")
   island right in the live surface - dimmed and with a "not editable" cursor so that's obvious at a glance. */
.editor-textarea .editor-readonly-block {
    cursor: default;
    opacity: 0.75;
}

/* --- read-only preview (matches the studymaterial viewer's .doc exactly) --- */

.doc-preview {
    width: 100%;
    height: 100%;
    padding: 40px max(24px, calc(50% - 360px));
    overflow-y: auto;
    color: var(--text);
    font-size: 15px;
    line-height: 1.7;
    white-space: pre-wrap;
}

.doc-preview p {
    margin: 0 0 14px;
    font-size: 15px;
    line-height: 1.7;
    color: #d8d8d8;
}

.doc-preview p:last-child {
    margin-bottom: 0;
}

.doc-preview strong {
    font-weight: 700;
    color: var(--text);
}

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

/* --- editor/json/preview switch --- */

.view-toggle {
    display: flex;
    align-items: center;
    gap: 2px;
    padding: 3px;
    border-radius: 999px;
    background: #141414;
    border: 1px solid var(--border);
}

.view-toggle-btn {
    padding: 6px 12px;
    border: 0;
    border-radius: 999px;
    background: none;
    color: var(--dim);
    font: inherit;
    font-size: 11.5px;
    font-weight: 600;
    letter-spacing: 0.02em;
    cursor: pointer;
    transition: background 0.15s, color 0.15s;
}

.view-toggle-btn:disabled {
    opacity: 0.35;
    cursor: not-allowed;
}

.view-toggle-btn.active:not(:disabled) {
    background: #8b5cf6;
    color: #0d0d0d;
}

/* --- create-file modal --- */

.modal-backdrop {
    position: fixed;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20px;
    background: rgba(0, 0, 0, 0.6);
    backdrop-filter: blur(2px);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.15s;
    z-index: 100;
}

.modal-backdrop.open {
    opacity: 1;
    pointer-events: auto;
}

.modal {
    width: 100%;
    max-width: 420px;
    max-height: calc(100vh - 40px);
    display: flex;
    flex-direction: column;
    background: var(--modal-bg);
    border: 1px solid #262626;
    border-radius: 18px;
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.5);
    transform: scale(0.97) translateY(6px);
    transition: transform 0.15s;
}

.modal-backdrop.open .modal {
    transform: none;
}

.modal-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 14px;
    padding: 18px 20px;
}

.modal-title {
    margin: 0;
    font-size: 16.5px;
    font-weight: 700;
}

.modal-close {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    border: 0;
    border-radius: 50%;
    background: none;
    color: var(--dim);
    cursor: pointer;
    flex-shrink: 0;
    transition: background 0.15s, color 0.15s;
}

.modal-close:hover {
    background: #232323;
    color: var(--text);
}

.modal-close svg {
    width: 16px;
    height: 16px;
}

.modal-divider {
    height: 1px;
    background: var(--border);
    flex-shrink: 0;
}

.modal-body {
    display: flex;
    flex-direction: column;
    gap: 14px;
    padding: 20px;
    overflow-y: auto;
}

.modal-field {
    display: flex;
    align-items: baseline;
    gap: 16px;
}

.modal-label {
    width: 92px;
    flex-shrink: 0;
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: #7d7d7d;
}

.modal-value {
    font-size: 13.5px;
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.modal-text {
    margin: 0;
    font-size: 13.5px;
    line-height: 1.6;
    color: #cfcfcf;
}

.field {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.field-label {
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: #7d7d7d;
}

.field-input {
    height: 38px;
    padding: 0 12px;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: #141414;
    color: var(--text);
    font: inherit;
    font-size: 13.5px;
    outline: none;
    transition: border-color 0.15s, background 0.15s;
}

.field-input:focus {
    border-color: #3a3a3a;
    background: #181818;
}

/* Every plain number input across the modals (axis min/max, row/column counts, point/chart-row values, column
   max-width) - the native up/down spinner is removed everywhere, same treatment .font-size-input already had
   on its own before this existed. Firefox honors `appearance` alone; Chrome/Edge/Safari only drop theirs via
   these pseudo-elements, which is why a plain `appearance: textfield` alone (as the old, now-redundant
   .table-modal-number/.graph-modal-number rules had) left them showing there. */
input[type="number"] {
    appearance: textfield;
    -moz-appearance: textfield;
}

input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
    -webkit-appearance: none;
    margin: 0;
}

.modal-footer {
    display: flex;
    gap: 10px;
    padding: 18px 20px 20px;
    flex-shrink: 0;
}

.modal-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    flex: 1;
    height: 42px;
    border-radius: 999px;
    font: inherit;
    font-size: 13.5px;
    font-weight: 600;
    cursor: pointer;
    transition: filter 0.15s, border-color 0.15s, color 0.15s, background 0.15s;
}

.modal-open-btn {
    border: 1.5px solid var(--accent);
    background: rgba(240, 167, 38, 0.12);
    color: var(--accent);
}

.modal-open-btn:hover {
    filter: brightness(1.15);
}

.modal-close-btn {
    border: 1.5px solid #333;
    background: transparent;
    color: #cfcfcf;
}

.modal-close-btn:hover {
    border-color: #4a4a4a;
    color: var(--text);
}

/* --- img modal --- */

.img-modal {
    max-width: 520px;
}

.img-modal-body {
    gap: 16px;
}

.img-modal-thumbs {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
}

.img-modal-thumb {
    position: relative;
    width: 92px;
    height: 92px;
    border-radius: 10px;
    overflow: hidden;
    border: 1px solid var(--border);
    cursor: grab;
}

.img-modal-thumb:active {
    cursor: grabbing;
}

.img-modal-thumb img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    pointer-events: none;
}

.img-modal-thumb-delete {
    position: absolute;
    top: 4px;
    right: 4px;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    padding: 0;
    border: 1.5px solid #ef4444;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.75);
    color: #ef4444;
    cursor: pointer;
    transition: transform 0.15s;
}

.img-modal-thumb-delete svg {
    width: 11px;
    height: 11px;
}

.img-modal-thumb-delete:hover {
    transform: scale(1.1);
}

.img-modal-dropzone {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 18px;
    border: 1.5px dashed #3a3a3a;
    border-radius: 12px;
    background: #141414;
    color: var(--dim);
    font: inherit;
    font-size: 12.5px;
    cursor: pointer;
    transition: border-color 0.15s, color 0.15s, background 0.15s;
}

.img-modal-dropzone svg {
    width: 15px;
    height: 15px;
    flex-shrink: 0;
}

.img-modal-dropzone:hover:not(:disabled) {
    border-color: var(--accent);
    color: var(--accent);
}

.img-modal-dropzone:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

.img-modal-caption {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.img-modal-textarea {
    height: auto;
    padding: 10px 12px;
    resize: vertical;
    font-family: inherit;
    line-height: 1.5;
}

.img-modal-side-toggle {
    display: flex;
    gap: 8px;
}

.img-modal-side-btn {
    flex: 1;
    height: 36px;
    border: 1px solid var(--border);
    border-radius: 9px;
    background: #141414;
    color: var(--dim);
    font: inherit;
    font-size: 12.5px;
    font-weight: 600;
    cursor: pointer;
    transition: border-color 0.15s, color 0.15s, background 0.15s;
}

.img-modal-side-btn:hover {
    color: var(--text);
}

.img-modal-side-btn.active {
    border-color: var(--accent);
    background: rgba(240, 167, 38, 0.12);
    color: var(--accent);
}

.img-modal-width {
    display: flex;
    align-items: center;
    gap: 14px;
}

.img-modal-width .field-label {
    flex-shrink: 0;
}

.img-modal-width input[type="range"] {
    flex: 1;
    accent-color: var(--accent);
}

/* --- math modal --- */

.math-modal {
    max-width: 480px;
}

.math-modal-body {
    gap: 16px;
}

.math-modal-textarea {
    height: auto;
    padding: 10px 12px;
    resize: vertical;
    font-family: "SF Mono", "Fira Code", Consolas, "Courier New", monospace;
    font-size: 13px;
    line-height: 1.5;
}

.math-modal-switch-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    cursor: pointer;
}

.math-modal-switch {
    position: relative;
    display: inline-flex;
    flex-shrink: 0;
    width: 40px;
    height: 22px;
}

.math-modal-switch input {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    opacity: 0;
    cursor: pointer;
}

.math-modal-switch-track {
    position: absolute;
    inset: 0;
    border-radius: 999px;
    background: #141414;
    border: 1px solid var(--border);
    transition: background-color 0.15s, border-color 0.15s;
}

.math-modal-switch-thumb {
    position: absolute;
    top: 2px;
    left: 2px;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: var(--dim);
    transition: transform 0.15s, background-color 0.15s;
}

.math-modal-switch input:checked + .math-modal-switch-track {
    background: rgba(240, 167, 38, 0.2);
    border-color: var(--accent);
}

.math-modal-switch input:checked + .math-modal-switch-track .math-modal-switch-thumb {
    transform: translateX(18px);
    background: var(--accent);
}

.math-modal-fontsize-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
}

.math-modal-fontsize-row .field-input {
    width: 80px;
    flex-shrink: 0;
    text-align: center;
}

.math-modal-preview-wrap {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.math-modal-preview {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 64px;
    padding: 16px;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: #0a0a0a;
    overflow-x: auto;
    color: var(--text);
}

.math-modal-preview:empty::before {
    content: "Vorschau erscheint hier";
    color: var(--dim);
    font-size: 12.5px;
}

.math-modal-preview .katex-display {
    margin: 0 !important;
}

.math-modal-preview .katex-error {
    color: #ef4444;
    font-family: "SF Mono", "Fira Code", Consolas, "Courier New", monospace;
    font-size: 12.5px;
    white-space: pre-wrap;
}

/* --- table modal --- */

.table-modal {
    max-width: 480px;
}

.table-modal-body {
    gap: 16px;
}

.table-modal-size-row {
    display: flex;
    gap: 12px;
}

.table-modal-size-row .field {
    flex: 1;
}

/* Spinner-arrow removal for this now lives in the global input[type="number"] rule near .field-input. */

.table-modal-header-color-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
}

/* Just big enough to hold the (top-left, 20px) color swatch attachColorPickerFixed appends. */
.table-color-picker {
    position: relative;
    width: 30px;
    height: 30px;
}

.table-color-picker .callout-color-btn {
    top: 5px;
    left: 5px;
    right: auto;
}

.table-modal-preview-wrap {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.table-modal-preview {
    padding: 16px;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: #0a0a0a;
    overflow-x: auto;
}

/* The preview reuses the exact .table-wrap/.doc-table markup and shared sizing (padding, borders, font) the
   live document renders a table with (see the shared ".editor-textarea .doc-table td, .doc-preview .doc-table
   td" rule etc. above) - this just extends those same rules to also apply inside the modal's own preview box,
   so what you see here really is what the inserted table will look like, not a simplified stand-in. */
.table-modal-preview .table-wrap {
    margin: 0;
}

.table-modal-preview .doc-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 14px;
}

.table-modal-preview .doc-table td {
    padding: 10px 16px;
    border-bottom: 1px solid var(--border);
    border-right: 1px solid var(--border);
    color: #d8d8d8;
    vertical-align: top;
}

.table-modal-preview .doc-table tr:last-child td {
    border-bottom: 0;
}

.table-modal-preview .doc-table td:last-child {
    border-right: 0;
}

/* --- graph modal --------------------------------------------------------------------------------------- */

.graph-modal {
    max-width: 560px;
}

.graph-modal-body {
    gap: 16px;
}

.graph-modal-axis-row {
    display: flex;
    gap: 10px;
}

.graph-modal-axis-row .field {
    flex: 1;
    min-width: 0;
}

/* Spinner-arrow removal for this now lives in the global input[type="number"] rule near .field-input. */

.graph-modal-grid-row {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 13px;
    color: var(--text);
    cursor: pointer;
}

.graph-modal-grid-row input {
    accent-color: var(--accent);
    cursor: pointer;
}

.graph-modal-elements {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.graph-element-row {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 14px 44px 14px 14px;
    border: 1px solid var(--border);
    border-radius: 12px;
    background: #141414;
}

.graph-element-remove {
    position: absolute;
    top: 10px;
    right: 10px;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: none;
    color: var(--dim);
    cursor: pointer;
    transition: background 0.12s, color 0.12s;
}

.graph-element-remove:hover {
    background: rgba(239, 68, 68, 0.18);
    color: #ef4444;
}

.graph-element-remove svg {
    width: 13px;
    height: 13px;
}

.graph-element-top-row {
    display: flex;
    align-items: center;
    gap: 10px;
}

.graph-element-name-input {
    flex: 1;
    min-width: 0;
    height: 34px;
    padding: 0 10px;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: #0c0c0c;
    color: var(--text);
    font: inherit;
    font-size: 13px;
}

/* Just big enough to hold the (top-left, 20px) color swatch attachColorPickerFixed appends - same sizing
   .table-color-picker already uses one level up. */
.graph-color-picker {
    position: relative;
    width: 30px;
    height: 30px;
    flex-shrink: 0;
}

.graph-color-picker .callout-color-btn {
    top: 5px;
    left: 5px;
    right: auto;
}

.graph-element-expression-input {
    width: 100%;
    height: 34px;
    padding: 0 10px;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: #0c0c0c;
    color: var(--text);
    font: 13px "SF Mono", "Fira Code", Consolas, "Courier New", monospace;
}

.graph-element-hint {
    font-size: 11px;
    color: var(--dim);
}

.graph-points-count-row {
    display: flex;
    align-items: center;
    gap: 10px;
}

.graph-points-count-row .field-input {
    width: 80px;
    flex-shrink: 0;
}

.graph-points-rows {
    display: flex;
    flex-direction: column;
    gap: 6px;
    max-height: 220px;
    overflow-y: auto;
    padding-right: 4px;
}

.graph-points-row {
    display: flex;
    align-items: center;
    gap: 8px;
}

.graph-points-row-label {
    width: 20px;
    flex-shrink: 0;
    font-size: 11px;
    color: var(--dim);
    text-align: right;
}

.graph-points-row input {
    flex: 1;
    min-width: 0;
    height: 32px;
    padding: 0 8px;
    border: 1px solid var(--border);
    border-radius: 7px;
    background: #0c0c0c;
    color: var(--text);
    font: inherit;
    font-size: 13px;
}

.graph-element-options-row {
    display: flex;
    gap: 16px;
}

.graph-element-options-row label {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 12.5px;
    color: var(--muted);
    cursor: pointer;
}

.graph-element-options-row input {
    accent-color: var(--accent);
    cursor: pointer;
}

.graph-modal-add-row {
    display: flex;
    gap: 10px;
}

.graph-modal-add-btn {
    display: flex;
    flex: 1;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 9px 10px;
    border: 1px solid #3b82f6;
    border-radius: 10px;
    background: rgba(59, 130, 246, 0.12);
    color: #3b82f6;
    font: inherit;
    font-size: 12.5px;
    font-weight: 600;
    cursor: pointer;
    transition: background 0.15s;
}

.graph-modal-add-btn:hover {
    background: rgba(59, 130, 246, 0.22);
}

.graph-modal-add-btn svg {
    width: 14px;
    height: 14px;
    flex-shrink: 0;
}

/* --- diagram type chooser (shown before the graph/bar/pie modal for a brand new diagram) --- */

.diagram-chooser-modal {
    max-width: 420px;
}

.diagram-chooser-body {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
}

.diagram-chooser-btn {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 10px;
    padding: 22px 12px;
    border: 1px solid var(--border);
    border-radius: 14px;
    background: #141414;
    color: var(--text);
    font: inherit;
    font-size: 13px;
    font-weight: 600;
    text-align: center;
    cursor: pointer;
    transition: border-color 0.15s, background 0.15s;
}

.diagram-chooser-btn:hover {
    border-color: var(--accent);
    background: rgba(240, 167, 38, 0.08);
}

.diagram-chooser-btn svg {
    width: 32px;
    height: 32px;
    color: var(--accent);
    flex-shrink: 0;
}

/* --- bar / pie chart modals (share this row layout: index, label text, numeric value, color swatch) --- */

.chart-modal {
    max-width: 480px;
}

.chart-modal-count-row {
    display: flex;
    align-items: center;
    gap: 10px;
}

.chart-modal-count-row .field-input {
    width: 80px;
    flex-shrink: 0;
}

.chart-modal-rows {
    display: flex;
    flex-direction: column;
    gap: 8px;
    max-height: 320px;
    overflow-y: auto;
    padding-right: 4px;
}

.chart-modal-row {
    display: flex;
    align-items: center;
    gap: 8px;
}

.chart-modal-row-label {
    width: 20px;
    flex-shrink: 0;
    font-size: 11px;
    color: var(--dim);
    text-align: right;
}

.chart-modal-row-text,
.chart-modal-row-value {
    height: 32px;
    padding: 0 8px;
    border: 1px solid var(--border);
    border-radius: 7px;
    background: #0c0c0c;
    color: var(--text);
    font: inherit;
    font-size: 13px;
}

.chart-modal-row-text {
    flex: 1;
    min-width: 0;
}

.chart-modal-row-value {
    width: 90px;
    flex-shrink: 0;
}

/* A plain <tr> of its own (see renderTableModal) above the real header row - one <td> per column, each
   holding just that column's max-width input (see buildMaxWidthInput), nothing else. Every other cell-level
   rule above (padding/borders/color/vertical-align) already applies to these <td>s too, being real table
   cells in the same .doc-table - this only trims the padding down for a row that's a control, not data. */
.table-modal-preview .doc-table tr.table-modal-maxwidth-row td {
    padding: 8px;
}

.table-modal-maxwidth-input {
    display: block;
    width: 100%;
    height: 26px;
    padding: 0 8px;
    border: 1px solid var(--border);
    border-radius: 6px;
    background: #141414;
    color: var(--text);
    font: inherit;
    font-size: 11.5px;
    outline: none;
    transition: border-color 0.15s, background 0.15s;
    /* Standard property (Chromium honors it directly) plus the Firefox-specific one - both needed to drop
       the native up/down spinner buttons; Chromium/Safari additionally need the pseudo-elements below. */
    appearance: textfield;
    -moz-appearance: textfield;
}

.table-modal-maxwidth-input::-webkit-outer-spin-button,
.table-modal-maxwidth-input::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}

.table-modal-maxwidth-input:focus {
    border-color: #3a3a3a;
    background: #181818;
}

.table-modal-maxwidth-input::placeholder {
    color: var(--dim);
}

/* attachColorPickerFixed's popover is position:fixed, appended to <body> - never clipped by this scrollable
   preview box or the modal itself, and always painted above the modal backdrop (z-index 100). */
.callout-color-popover-fixed {
    position: fixed;
    top: auto;
    right: auto;
    z-index: 200;
}

.danger-btn {
    border-color: rgba(239, 68, 68, 0.5);
    color: #ef4444;
}

.danger-btn:hover {
    border-color: #ef4444;
    background: rgba(239, 68, 68, 0.12);
}

@media (max-width: 760px) {
    .file-explorer {
        width: 220px;
    }

    .editor-textarea {
        padding: 28px 20px;
    }
}
