/* Skalant.RichTextEditor — content styles. Link this from the host page:
   <link href="_content/Skalant.RichTextEditor/richTextEditor.css" rel="stylesheet" />
   The toolbar itself uses Tailwind utility classes in the component markup (Tailwind is a
   required peer dependency — see the README). These rules restore semantics the host's CSS
   reset (e.g. Tailwind Preflight) tends to strip, expose the re-themeable design tokens, and
   style the floating-text-box / image chrome (previously assigned inline from JS). */

/* ---------------------------------------------------------------------------
   Theme tokens (design tokens)
   ---------------------------------------------------------------------------
   Override any of these from the host — on :root, on .rich-text-editor, or on a
   wrapping element — to re-theme the toolbar and editor chrome WITHOUT editing the
   component markup, e.g.:

       .rich-text-editor { --rte-toolbar-bg: #faf5ff; --rte-accent: #7c3aed; }

   Dark mode: the defaults below are light; the `.dark` overrides further down apply
   when a `.dark` class is present on an ancestor (Tailwind's `class` dark-mode
   strategy, which the demo uses). Consumers who prefer `prefers-color-scheme` can
   re-declare the same dark tokens inside a media query — see the README. */
.rich-text-editor {
    /* Surfaces & borders */
    --rte-toolbar-bg: #f9fafb;      /* toolbar background        (gray-50)  */
    --rte-toolbar-fg: #374151;      /* toolbar icon/text colour  (gray-700) */
    --rte-editor-bg: #ffffff;       /* editor content background            */
    --rte-editor-fg: #111827;       /* editor content text       (gray-900) */
    --rte-border: #e5e7eb;          /* frame / toolbar divider   (gray-200) */
    --rte-divider: #d1d5db;         /* in-toolbar separators     (gray-300) */
    --rte-btn-hover-bg: #e5e7eb;    /* toolbar button hover      (gray-200) */
    --rte-radius: 0.5rem;           /* frame corner radius       (rounded-lg) */

    /* Placeholder text (empty text box / empty editor hints) */
    --rte-placeholder: #9ca3af;     /* gray-400 */

    /* Floating text box ("pull quote") surface */
    --rte-textbox-bg: #ffffff;
    --rte-textbox-border: #d1d5db;  /* gray-300 */
    --rte-textbox-radius: 6px;

    /* Chrome (image + text-box control buttons / handles / overlays) */
    --rte-chrome-bg: #1e293b;       /* control button background (slate-800) */
    --rte-chrome-fg: #ffffff;       /* control button icon/text             */
    --rte-accent: #3b82f6;          /* resize handles / overlay border (blue-500) */
    --rte-danger: #dc2626;          /* delete button background  (red-600) */
    --rte-chrome-radius: 4px;

    /* Tables */
    --rte-table-border: #d1d5db;    /* cell/table border         (gray-300) */
    --rte-table-header-bg: #f3f4f6; /* (reserved) header cell bg (gray-100) */

    /* Editor focus cue — a subtle 1px inset ring shown while the content region has
       keyboard focus (#874). Kept low-contrast (accent @ ~35%) so it hints "the
       editor is focused" without the old bold 2px blue box. */
    --rte-focus-ring: rgba(59, 130, 246, 0.35); /* blue-500 @ 35% */
}

/* Dark mode via Tailwind's `class` strategy: a `.dark` class on any ancestor
   (the demo toggles it) flips the tokens to a legible dark palette. */
.dark .rich-text-editor {
    --rte-toolbar-bg: #1f2937;      /* gray-800 */
    --rte-toolbar-fg: #e5e7eb;      /* gray-200 */
    --rte-editor-bg: #111827;       /* gray-900 */
    --rte-editor-fg: #f9fafb;       /* gray-50  */
    --rte-border: #374151;          /* gray-700 */
    --rte-divider: #4b5563;         /* gray-600 */
    --rte-btn-hover-bg: #374151;    /* gray-700 */

    --rte-placeholder: #6b7280;     /* gray-500 */

    --rte-textbox-bg: #1f2937;      /* gray-800 */
    --rte-textbox-border: #4b5563;  /* gray-600 */

    --rte-table-border: #4b5563;    /* gray-600 */
    --rte-table-header-bg: #374151; /* gray-700 */

    --rte-focus-ring: rgba(96, 165, 250, 0.40); /* blue-400 @ 40% — subtle on dark */
}

/* ---------------------------------------------------------------------------
   Toolbar & editor frame — token-driven so the tokens above actually re-theme.
   Layout (flex/gap/padding/rounding of buttons) stays in Tailwind utility classes.
   --------------------------------------------------------------------------- */
.rte-frame {
    border: 1px solid var(--rte-border);
    border-radius: var(--rte-radius);
}

.rte-toolbar {
    background: var(--rte-toolbar-bg);
    color: var(--rte-toolbar-fg);
    border-bottom: 1px solid var(--rte-border);
}

.rte-toolbar-btn:hover {
    background: var(--rte-btn-hover-bg);
}

.rte-content,
[contenteditable].rte-content {
    background: var(--rte-editor-bg);
    color: var(--rte-editor-fg);
    /* Render whitespace as typed (#1047): without this the editable region inherits
       `white-space: normal` (reinforced by the demo's Tailwind `prose`), so runs of
       consecutive spaces collapse to one and a leading space is trimmed — the caret
       advances and the model/serializer already store the literal spaces, but they do
       not RENDER, so the user perceives "the spacebar does nothing". `pre-wrap` (NOT
       `pre`) preserves leading/consecutive/trailing spaces while text still WRAPS
       normally, so there is no horizontal overflow. It is an inherited property, so the
       block children (<p>, <li>, headings, table cells) pick it up. The more-specific
       `[contenteditable].rte-content` selector mirrors the existing list rules below to
       out-specify Tailwind `prose` in the cascade. */
    white-space: pre-wrap;
}

/* Softened keyboard-focus cue (#874): a subtle 1px inset ring, not the old bold 2px
   blue box. `:focus-visible` keeps it keyboard-only (a plain mouse click to place the
   caret does not trigger it), so accessibility is preserved without the distraction. */
.rte-content:focus-visible {
    outline: none;
    box-shadow: inset 0 0 0 1px var(--rte-focus-ring);
}

/* ---------------------------------------------------------------------------
   Placeholder empty-state. Rendered as a non-interactive overlay above the
   contenteditable region and shown only while the document is empty (the host
   toggles it), so it appears in an empty editor and disappears on first input.
   It shares the content region's padding so the hint sits where typing begins.
   --------------------------------------------------------------------------- */
.rte-content-wrap {
    position: relative;
}

.rte-placeholder-overlay {
    position: absolute;
    top: 0;
    left: 0;
    padding: 0.75rem; /* matches the editor's p-3 content padding */
    color: var(--rte-placeholder);
    pointer-events: none;
    user-select: none;
    white-space: pre-wrap;
}

/* Restore browser list rendering stripped by CSS resets */
.rte-content ul,
[contenteditable].rte-content ul {
    list-style-type: disc;
    padding-left: 1.625rem;
}

.rte-content ol,
[contenteditable].rte-content ol {
    list-style-type: decimal;
    padding-left: 1.625rem;
}

.rte-content ul li,
.rte-content ol li {
    padding-left: 0.25rem;
    margin-top: 0.25rem;
    margin-bottom: 0.25rem;
}

.rte-content ul li::marker,
.rte-content ol li::marker {
    font-size: 1em;
    color: currentColor;
}

/* ---------------------------------------------------------------------------
   Floating text box ("pull quote") — a bordered box you type into that floats and
   wraps like an image. The box + its text persist; the move/resize/align chrome
   (.rte-tb-controls / .rte-tb-resize) is added at runtime and stripped on save.
   The box surface appearance lives here (token-driven); only its dynamic layout
   (float / width / margin) is written inline by the JS module.
   --------------------------------------------------------------------------- */
.rte-textbox {
    box-sizing: border-box;
    border: 1px solid var(--rte-textbox-border);
    background: var(--rte-textbox-bg);
    padding: 10px 14px;
    border-radius: var(--rte-textbox-radius);
}

.rte-tb-text {
    outline: none;
    min-height: 1.4em;
    overflow: hidden;
}

[contenteditable] .rte-tb-text:empty::before,
[contenteditable] .rte-tb-text:has(> br:only-child)::before {
    content: attr(data-ph);
    color: var(--rte-placeholder);
}

.rte-textbox > .rte-tb-controls,
.rte-textbox > .rte-tb-resize {
    display: none;
}

/* In a contenteditable the host keeps focus, so :focus-within never matches a nested
   box. JS toggles .rte-tb-active on the box that holds the caret instead. */
.rte-textbox.rte-tb-active > .rte-tb-controls {
    display: flex;
}

.rte-textbox.rte-tb-active > .rte-tb-resize {
    display: block;
}

/* Text-box control bar (align / move / delete) shown above an active box. */
.rte-tb-controls {
    position: absolute;
    top: -15px;
    left: 50%;
    transform: translateX(-50%);
    gap: 4px;
    z-index: 5;
}

/* A single text-box control button. */
.rte-tb-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 22px;
    border: none;
    background: var(--rte-chrome-bg);
    color: var(--rte-chrome-fg);
    border-radius: var(--rte-chrome-radius);
    cursor: pointer;
    padding: 0;
    font-size: 12px;
    line-height: 1;
}

.rte-tb-btn-danger {
    background: var(--rte-danger);
}

/* Bottom-corner resize grip; its left/right/cursor are set inline by JS to follow
   the box's float direction. */
.rte-tb-resize {
    position: absolute;
    bottom: -6px;
    width: 12px;
    height: 12px;
    background: var(--rte-accent);
    border: 2px solid #fff;
    border-radius: 2px;
    z-index: 5;
}

/* ---------------------------------------------------------------------------
   Image selection overlay (resize handles / move grip / edit + delete buttons).
   Entirely transient chrome created by the JS module; its geometry (top/left/
   width/height and per-handle positions/cursors) is written inline, its appearance
   comes from these token-driven classes.
   --------------------------------------------------------------------------- */
.rte-img-overlay {
    position: absolute;
    pointer-events: none;
    box-sizing: border-box;
    border: 2px solid var(--rte-accent);
    z-index: 1000;
}

.rte-img-btnbar {
    position: absolute;
    bottom: 6px;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    gap: 6px;
    pointer-events: all;
    z-index: 1001;
}

.rte-img-btn {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    background: var(--rte-chrome-bg);
    color: var(--rte-chrome-fg);
    border: none;
    border-radius: var(--rte-chrome-radius);
    padding: 3px 10px;
    font-size: 12px;
    cursor: pointer;
    line-height: 1.4;
    white-space: nowrap;
}

.rte-img-btn-danger {
    background: var(--rte-danger);
}

.rte-img-move {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 26px;
    height: 26px;
    background: var(--rte-chrome-bg);
    border-radius: var(--rte-chrome-radius);
    display: flex;
    align-items: center;
    justify-content: center;
    pointer-events: all;
    cursor: move;
    z-index: 1001;
}

.rte-img-handle {
    position: absolute;
    width: 8px;
    height: 8px;
    background: var(--rte-accent);
    border: 1px solid #fff;
    border-radius: 1px;
    pointer-events: all;
}

/* Caret drop indicator shown while dragging an image to a new position; its
   left/top/display are set inline by JS. */
.rte-img-drop-indicator {
    position: absolute;
    width: 2px;
    height: 1.2em;
    background: var(--rte-accent);
    pointer-events: none;
    z-index: 9998;
    display: none;
    border-radius: 1px;
}

/* ---------------------------------------------------------------------------
   Tables (V2 engine)
   ---------------------------------------------------------------------------
   Content styling so saved tables render with visible borders, plus the table
   node view's floating row/column controls (chrome outside the model) and the
   toolbar insert-table grid picker. Cell MERGE is out of scope. */
.rte-content table.rte-table,
.rte-content .rte-nv-table table.rte-table {
    border-collapse: collapse;
    width: auto;
    margin: 0.5rem 0;
    table-layout: fixed;
}

.rte-content table.rte-table td,
.rte-content table.rte-table th {
    /* Author-styleable grid border (#863, per-side #872): the --rte-* custom
       properties are set on the <table> (by the Border popover live, and the
       serializer on save) and inherit down to every cell, so the whole grid restyles
       uniformly. Each edge reads its own --rte-b{t,r,b,l}{w,s,c} first, then the
       uniform --rte-b{w,s,c} ("All"), then the default 1px solid grid. */
    border-top: var(--rte-btw, var(--rte-bw, 1px)) var(--rte-bts, var(--rte-bs, solid)) var(--rte-btc, var(--rte-bc, var(--rte-table-border)));
    border-right: var(--rte-brw, var(--rte-bw, 1px)) var(--rte-brs, var(--rte-bs, solid)) var(--rte-brc, var(--rte-bc, var(--rte-table-border)));
    border-bottom: var(--rte-bbw, var(--rte-bw, 1px)) var(--rte-bbs, var(--rte-bs, solid)) var(--rte-bbc, var(--rte-bc, var(--rte-table-border)));
    border-left: var(--rte-blw, var(--rte-bw, 1px)) var(--rte-bls, var(--rte-bs, solid)) var(--rte-blc, var(--rte-bc, var(--rte-table-border)));
    padding: 0.25rem 0.5rem;
    vertical-align: top;
    min-width: 3rem;
}

.rte-content table.rte-table td > p:first-child,
.rte-content table.rte-table th > p:first-child {
    margin-top: 0;
    /* Keep an empty cell (a single empty <p>) from collapsing to ~0px so there is
       always a full clickable line and room for the caret — mirrors the text-box
       fix and satisfies the #827 "minimum clickable height even when empty" AC. */
    min-height: 1.2em;
}

.rte-content table.rte-table td > p:last-child,
.rte-content table.rte-table th > p:last-child {
    margin-bottom: 0;
}

/* Cell selection highlight (#918): a translucent accent tint stamped on the cells
   covered by a rectangular / whole-row / whole-column cell selection. The marker is
   a VIEW decoration (data-selected on the rendered <td>), never serialized — the
   selection is model-side state, not a document edit. */
.rte-content table.rte-table td[data-selected="true"],
.rte-content table.rte-table th[data-selected="true"] {
    background-color: color-mix(in srgb, var(--rte-accent, #3b82f6) 18%, transparent);
    /* Fallback for engines without color-mix. */
    background-color: rgba(59, 130, 246, 0.18);
}

/* Row / column select handles (#918): thin gutter affordances the table node view
   lays out ABOVE each column and to the LEFT of each row (outside the cell grid, so
   they never cover a cell centre — #827). The overlay itself is click-through; only
   the handles are interactive. */
.rte-table-handles {
    position: absolute;
    inset: 0;
    pointer-events: none;
    z-index: 1001;
}

.rte-table-handle {
    pointer-events: auto;
    background-color: var(--rte-accent, #3b82f6);
    opacity: 0.25;
    border-radius: 2px;
    cursor: pointer;
    transition: opacity 0.1s ease-in-out;
}

.rte-table-handle:hover {
    opacity: 0.6;
}

/* Border popover (#861): the small panel toggled by the Border button in the text
   box / table node-view chrome, holding the style / width / color controls. It is
   view-only chrome (contenteditable="false") and never serialized. Positioned below
   its trigger; the trigger's host span is the positioning context. */
.rte-nv-border-control {
    position: relative;
    display: inline-flex;
}

.rte-nv-border-popover {
    /* Position (fixed) + top/left are set inline by the JS so the popover follows the
       trigger button and clamps to the viewport. */
    z-index: 1000;
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 8px;
    min-width: 150px;
    /* Tall panels stay reachable: cap the height and scroll internally rather than
       running off-screen. */
    max-height: min(80vh, 460px);
    overflow-y: auto;
    /* Opaque fallbacks so the panel is never see-through even if the theme tokens are
       missing (e.g. portaled outside `.rich-text-editor`). */
    background: var(--rte-toolbar-bg, #ffffff);
    color: var(--rte-toolbar-fg, #374151);
    border: 1px solid var(--rte-border, #e5e7eb);
    border-radius: var(--rte-radius, 6px);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18);
    cursor: default;
}

.rte-nv-border-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    font-size: 12px;
    line-height: 1.2;
}

.rte-nv-border-row-label {
    white-space: nowrap;
}

.rte-nv-border-popover select,
.rte-nv-border-popover input[type="number"] {
    font-size: 12px;
    padding: 2px 4px;
    border: 1px solid var(--rte-border);
    border-radius: 4px;
    background: var(--rte-textbox-bg);
    color: var(--rte-toolbar-fg);
}

.rte-nv-border-popover input[type="number"] {
    width: 56px;
}

.rte-nv-border-popover input[type="color"] {
    width: 40px;
    height: 24px;
    padding: 0;
    border: 1px solid var(--rte-border);
    border-radius: 4px;
    background: none;
    cursor: pointer;
}

/* Per-corner radius (#873): a 2×2 grid of small inputs mirroring the physical
   corner positions (top-left / top-right on the first row, bottom-left /
   bottom-right on the second). */
.rte-nv-radius-grid {
    display: grid;
    grid-template-columns: repeat(2, auto);
    gap: 4px;
}

.rte-nv-border-popover input.rte-nv-radius-input {
    width: 48px;
}

/* Visual side picker (#913): a small box whose Top/Right/Bottom/Left edges and All
   center are clickable buttons, replacing the easy-to-miss Side dropdown. Laid out on
   a 3×3 grid so each button sits at its physical edge. */
.rte-nv-side-picker {
    display: grid;
    grid-template-columns: 13px 1fr 13px;
    grid-template-rows: 11px 1fr 11px;
    gap: 3px;
    width: 84px;
    height: 56px;
}

.rte-nv-side-btn {
    padding: 0;
    margin: 0;
    border: 1px solid var(--rte-border);
    background: var(--rte-textbox-bg);
    border-radius: 2px;
    cursor: pointer;
    line-height: 1;
}

.rte-nv-side-btn:hover {
    background: var(--rte-btn-hover-bg);
}

.rte-nv-side-top { grid-column: 2; grid-row: 1; }
.rte-nv-side-left { grid-column: 1; grid-row: 2; }
.rte-nv-side-all { grid-column: 2; grid-row: 2; font-size: 9px; color: var(--rte-toolbar-fg); }
.rte-nv-side-right { grid-column: 3; grid-row: 2; }
.rte-nv-side-bottom { grid-column: 2; grid-row: 3; }

/* The currently-edited edge. */
.rte-nv-side-btn.rte-nv-side-active,
.rte-nv-side-btn.rte-nv-side-active:hover {
    background: var(--rte-accent);
    border-color: var(--rte-accent);
    color: #fff;
}

/* An edge that already carries an override (but isn't the active one): a subtle
   accent ring so the current per-side state reads at a glance. */
.rte-nv-side-btn.rte-nv-side-set:not(.rte-nv-side-active) {
    box-shadow: inset 0 0 0 1px var(--rte-accent);
}

/* Table node view: the wrapper is relative so the control bar floats over it. The
   bar is hidden until the wrapper is hovered/focused within, so it never obscures
   editing. Everything here is view-only chrome and is never serialized. */
.rte-nv-table {
    position: relative;
    display: inline-block;
    max-width: 100%;
    /* Reserve a strip ABOVE the table for the floating chrome so it never has to
       overlap the top row of cells (#827). The chrome is pulled up into this strip. */
    margin-top: 1.75rem;
}

.rte-nv-table-chrome {
    position: absolute;
    /* Anchor the chrome fully above the table's top edge (translateY(-100%) lifts it
       by its own height) so elementFromPoint at every cell centre resolves to the
       td/th, never a chrome button (#827). */
    top: 0;
    right: 0;
    transform: translateY(-100%);
    display: none;
    gap: 2px;
    padding: 2px;
    background: var(--rte-chrome-bg);
    border-radius: var(--rte-chrome-radius);
    z-index: 1002;
}

.rte-nv-table:hover .rte-nv-table-chrome,
.rte-nv-table:focus-within .rte-nv-table-chrome {
    display: flex;
}

/* Shared node-view control button (also used by the image/text-box views). */
.rte-nv-btn {
    min-width: 20px;
    height: 20px;
    padding: 0 4px;
    font-size: 12px;
    line-height: 1;
    color: var(--rte-chrome-fg);
    background: transparent;
    border: none;
    border-radius: 3px;
    cursor: pointer;
}

.rte-nv-btn:hover {
    background: rgba(255, 255, 255, 0.18);
}

.rte-nv-btn-danger:hover {
    background: var(--rte-danger);
}

/* Insert-table grid picker (toolbar popover). */
.rte-grid-cell {
    width: 16px;
    height: 16px;
    padding: 0;
    border: 1px solid var(--rte-divider);
    background: var(--rte-editor-bg);
    cursor: pointer;
    border-radius: 2px;
}

.rte-grid-cell-on {
    background: var(--rte-accent);
    border-color: var(--rte-accent);
}

/* Image node view — live wrap layout (#813). The image node view mirrors the model
   attrs onto the wrapper as data-align / data-wrap (never serialized chrome); these
   rules translate a wrapped image into an actual float in the LIVE editor, so clicking
   Wrap makes body text flow around the image immediately, matching the float the
   serializer emits (previously the wrapper only carried a data-wrap attribute with no
   CSS float, so Wrap was a visible no-op). Wrap always implies a left/right float — the
   node view snaps a non-floatable (center/default) alignment to left when Wrap is turned
   on — so Wrap is never a silent no-op. Toggling Wrap off drops data-wrap and the image
   returns to its default block/inline layout. */
.rte-content .rte-nv-image[data-wrap="true"] {
    float: left;
    max-width: 45%;
    margin: 0 1em 0.5em 0;
}

.rte-content .rte-nv-image[data-wrap="true"][data-align="right"] {
    float: right;
    margin: 0 0 0.5em 1em;
}

/* ---------------------------------------------------------------------------
   Image node view selection chrome (#819) — Word / Google-Docs style
   ---------------------------------------------------------------------------
   The image node view (nodeviews.js) always renders its chrome DOM — the action bar
   (move / edit / delete), the align+wrap bar and the 8 resize handles — inside the
   position:relative wrapper. NONE of it may take layout space or show unless the image
   is selected, so every piece is position:absolute and the whole overlay is hidden
   until the view stamps data-selected="true" on the wrapper (view.js setSelected seam).
   This mirrors Word/Google Docs: a selection outline plus a floating overlay that never
   pushes surrounding text. */

/* Selection outline (an outline, not a border, so it adds ZERO layout size and never
   shifts surrounding text). */
.rte-content .rte-nv-image[data-selected="true"] {
    outline: 2px solid var(--rte-accent);
    outline-offset: 2px;
}

/* Suppress the browser's native blue node-selection fill over the selected image, so
   the selection reads as an outline (like Word/Google Docs), not a colored highlight.
   Scoped to the selected image only, so normal text selection elsewhere is untouched. */
.rte-content .rte-nv-image[data-selected="true"] img::selection {
    background: transparent;
}

.rte-content .rte-nv-image[data-selected="true"] img::-moz-selection {
    background: transparent;
}

/* The overlay container fills the wrapper but is inert and hidden by default; only a
   selected image reveals it. pointer-events pass through the empty areas to the image;
   the interactive pieces re-enable pointer-events individually below. */
.rte-nv-image-chrome {
    position: absolute;
    inset: 0;
    display: none;
    pointer-events: none;
    z-index: 1002;
}

.rte-content .rte-nv-image[data-selected="true"] .rte-nv-image-chrome {
    display: block;
}

/* Action bar (move / edit / delete) floats just above the image; the align + wrap bar
   floats just below it. Both are absolutely positioned pills, so the move handle no
   longer sits on its own line in normal flow (bug #2). */
.rte-nv-image-bar,
.rte-nv-image-align {
    position: absolute;
    left: 0;
    display: flex;
    gap: 2px;
    padding: 2px;
    background: var(--rte-chrome-bg);
    border-radius: var(--rte-chrome-radius);
    pointer-events: auto;
    white-space: nowrap;
}

.rte-nv-image-bar {
    bottom: calc(100% + 6px);
}

.rte-nv-image-align {
    top: calc(100% + 6px);
}

/* The in-flow move glyph becomes a draggable affordance in the floating action bar. */
.rte-nv-image-bar .rte-nv-move {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 20px;
    height: 20px;
    color: var(--rte-chrome-fg);
    cursor: move;
    border-radius: 3px;
    line-height: 1;
    user-select: none;
}

.rte-nv-image-bar .rte-nv-move:hover {
    background: rgba(255, 255, 255, 0.18);
}

/* Resize-handle layer fills the wrapper; the individual handles are the only clickable
   parts. */
.rte-nv-image-handles {
    position: absolute;
    inset: 0;
    pointer-events: none;
}

.rte-nv-handle {
    position: absolute;
    width: 10px;
    height: 10px;
    background: var(--rte-editor-bg);
    border: 1px solid var(--rte-accent);
    border-radius: 2px;
    box-sizing: border-box;
    pointer-events: auto;
}

.rte-nv-handle-nw { top: -5px; left: -5px; cursor: nwse-resize; }
.rte-nv-handle-n  { top: -5px; left: 50%; margin-left: -5px; cursor: ns-resize; }
.rte-nv-handle-ne { top: -5px; right: -5px; cursor: nesw-resize; }
.rte-nv-handle-e  { top: 50%; right: -5px; margin-top: -5px; cursor: ew-resize; }
.rte-nv-handle-se { bottom: -5px; right: -5px; cursor: nwse-resize; }
.rte-nv-handle-s  { bottom: -5px; left: 50%; margin-left: -5px; cursor: ns-resize; }
.rte-nv-handle-sw { bottom: -5px; left: -5px; cursor: nesw-resize; }
.rte-nv-handle-w  { top: 50%; left: -5px; margin-top: -5px; cursor: ew-resize; }

/* The node-view icon buttons carry an inline SVG (currentColor); size it so the glyph
   is visible in both light and dark mode (#819 AC 7). */
.rte-nv-btn svg {
    display: block;
    width: 12px;
    height: 12px;
}

/* ---------------------------------------------------------------------------
   Text box node view (V2 engine) — #826
   ---------------------------------------------------------------------------
   The text-box node view (nodeviews.js) renders a bordered box whose editable text
   lives in .rte-nv-textbox-content and whose move / align / delete chrome
   (.rte-nv-textbox-chrome) plus resize grip (.rte-nv-textbox-resize) sit OUTSIDE that
   content, are never part of the model, and are hidden until the box is hovered or
   holds the caret. These rules were missing entirely, so the V2 box opened collapsed
   with no border, no min content height, and an always-present chrome that covered the
   content area (its centre swallowed clicks, so you could not type into the box). The
   surface (border / background / padding) is token-driven; only the box's dynamic
   width / align is written inline / via [data-align] by the node view. */
.rte-content .rte-nv-textbox {
    box-sizing: border-box;
    /* Border is author-styleable (#862, per-side #872): each edge reads its own
       --rte-b{t,r,b,l}{w,s,c} custom property, falling back to the uniform
       --rte-b{w,s,c} ("All"), then to the default 1px solid frame. The properties are
       written by the Border popover (live) and the serializer (saved). */
    border-top: var(--rte-btw, var(--rte-bw, 1px)) var(--rte-bts, var(--rte-bs, solid)) var(--rte-btc, var(--rte-bc, var(--rte-textbox-border)));
    border-right: var(--rte-brw, var(--rte-bw, 1px)) var(--rte-brs, var(--rte-bs, solid)) var(--rte-brc, var(--rte-bc, var(--rte-textbox-border)));
    border-bottom: var(--rte-bbw, var(--rte-bw, 1px)) var(--rte-bbs, var(--rte-bs, solid)) var(--rte-bbc, var(--rte-bc, var(--rte-textbox-border)));
    border-left: var(--rte-blw, var(--rte-bw, 1px)) var(--rte-bls, var(--rte-bs, solid)) var(--rte-blc, var(--rte-bc, var(--rte-textbox-border)));
    background: var(--rte-textbox-bg);
    padding: 10px 14px;
    border-radius: var(--rte-textbox-radius);
    margin: 0.5rem 0;
}

/* Saved text box (serialized `.rte-text-box` div) renders with the same
   author-styled border as the live node view. */
.rte-content .rte-text-box {
    box-sizing: border-box;
    border-top: var(--rte-btw, var(--rte-bw, 1px)) var(--rte-bts, var(--rte-bs, solid)) var(--rte-btc, var(--rte-bc, var(--rte-textbox-border)));
    border-right: var(--rte-brw, var(--rte-bw, 1px)) var(--rte-brs, var(--rte-bs, solid)) var(--rte-brc, var(--rte-bc, var(--rte-textbox-border)));
    border-bottom: var(--rte-bbw, var(--rte-bw, 1px)) var(--rte-bbs, var(--rte-bs, solid)) var(--rte-bbc, var(--rte-bc, var(--rte-textbox-border)));
    border-left: var(--rte-blw, var(--rte-bw, 1px)) var(--rte-bls, var(--rte-bs, solid)) var(--rte-blc, var(--rte-bc, var(--rte-textbox-border)));
    border-radius: var(--rte-textbox-radius);
    padding: 10px 14px;
    margin: 0.5rem 0;
}

/* Saved-box alignment (from the serialized `data-align` attribute), mirroring the live
   `.rte-nv-textbox[data-align]` rules so a saved box keeps the float/centering chosen in
   the editor: a left/right box floats and body text wraps around it; a centered box is
   centered in flow via auto side margins. */
.rte-content .rte-text-box[data-align="left"] {
    float: left;
    margin-right: 1em;
}

.rte-content .rte-text-box[data-align="right"] {
    float: right;
    margin-left: 1em;
}

.rte-content .rte-text-box[data-align="center"] {
    margin-left: auto;
    margin-right: auto;
}

/* The editable content region. A min-height keeps an empty box (a single empty <p>)
   from collapsing to 0px, so there is always a visible line to click into and room for
   the caret — the core #826 regression. position:relative anchors the placeholder
   overlay so it can sit out of flow (see below). */
.rte-content .rte-nv-textbox-content {
    outline: none;
    min-height: 1.5em;
    position: relative;
}

/* An empty box holds a single empty <p>. Give that paragraph the full clickable
   min-height so it fills the content region: a click anywhere in the box then lands
   the caret INSIDE the <p> (not in the wrapper DIV), so the box is immediately
   typeable — the remaining #826 regression where the 0-height <p> was unreachable. */
.rte-content .rte-nv-textbox-content[data-empty="true"] > p:first-child {
    min-height: 1.5em;
}

.rte-content .rte-nv-textbox-content > p:first-child {
    margin-top: 0;
}

.rte-content .rte-nv-textbox-content > p:last-child {
    margin-bottom: 0;
}

/* Empty-box placeholder (data-empty toggled by the node view). Rendered as an
   ABSOLUTELY positioned overlay (out of flow) so it no longer pushes the empty <p> to
   the bottom of the box: the paragraph stays at the top, filling the clickable area, so
   a click lands the caret in the <p> and typing works (the reopened #826 root cause was
   this placeholder rendering INLINE before the <p>). Non-interactive so it never
   intercepts the click that places the caret. */
.rte-content .rte-nv-textbox-content[data-empty="true"]::before {
    content: attr(data-placeholder);
    color: var(--rte-placeholder);
    pointer-events: none;
    position: absolute;
    top: 0;
    left: 0;
}

/* Alignment mirrored from the model attr onto the box as data-align: a left/right box
   floats and text wraps around it (like an image); a centered box is centered in flow. */
.rte-content .rte-nv-textbox[data-align="left"] {
    float: left;
    margin-right: 1em;
}

.rte-content .rte-nv-textbox[data-align="right"] {
    float: right;
    margin-left: 1em;
}

.rte-content .rte-nv-textbox[data-align="center"] {
    margin-left: auto;
    margin-right: auto;
}

/* Chrome (move / align / delete) floats above the box and is hidden until the box is
   hovered / focused, so it NEVER covers the content or intercepts clicks meant for the
   text (the #826 root cause was an always-on chrome overlapping the collapsed content). */
.rte-nv-textbox-chrome {
    position: absolute;
    top: -0.5rem;
    right: -0.5rem;
    display: none;
    gap: 2px;
    padding: 2px;
    background: var(--rte-chrome-bg);
    border-radius: var(--rte-chrome-radius);
    z-index: 1002;
}

.rte-nv-textbox:hover .rte-nv-textbox-chrome,
.rte-nv-textbox:focus-within .rte-nv-textbox-chrome {
    display: flex;
}

/* Bottom-right resize grip, revealed with the chrome. */
.rte-nv-textbox-resize {
    position: absolute;
    right: -5px;
    bottom: -5px;
    width: 12px;
    height: 12px;
    display: none;
    background: var(--rte-accent);
    border: 2px solid #fff;
    border-radius: 2px;
    cursor: nwse-resize;
    z-index: 1002;
}

.rte-nv-textbox:hover .rte-nv-textbox-resize,
.rte-nv-textbox:focus-within .rte-nv-textbox-resize {
    display: block;
}
