/* Canvas (Whiteboard + Documents) Freeform editor --pairs with js/canvasInterop.js.
   Same visual language as the WebReports designer's .rc- and .rcf- prefixed rules (app.css), but
   namespaced tt-canvas- so the two interaction engines never collide when both are loaded. */

.tt-canvas-board {
    position: relative;
    background: var(--mud-palette-surface);
    box-shadow: 0 2px 10px rgba(0,0,0,0.18);
    border: 1px solid #d0d0d0;
    touch-action: none;
    overflow: hidden;
}

.tt-canvas-board.tt-canvas-grid {
    background-image:
        linear-gradient(to right, rgba(128,128,128,0.15) 1px, transparent 1px),
        linear-gradient(to bottom, rgba(128,128,128,0.15) 1px, transparent 1px);
    background-size: var(--tt-canvas-grid, 8px) var(--tt-canvas-grid, 8px);
}

/* Hand tool (data-pan-mode) — click-through-everything cursor, same convention as CadDesigner's
   own CadTool.Pan. !important overrides per-element cursors (resize handles, text, etc.) that
   would otherwise win on hover; harmless since pointerdown itself is already fully redirected to
   panning in canvasInterop.js while this class is present. */
.tt-canvas-panmode, .tt-canvas-panmode * { cursor: grab !important; }
/* An ACTIVE pan drag (either gesture: Hand-tool left-drag or the always-on right-drag) --
   canvasInterop.js's startPan/onPointerUp toggle this regardless of tool, so right-drag panning
   over a Pointer-tool board also gets the grabbing cursor, not just Hand-tool drags. */
.tt-canvas-panning, .tt-canvas-panning * { cursor: grabbing !important; }

/* Connectors (diagram arrows) --an SVG overlay under the element divs. The container itself is
   click-through (pointer-events:none) so empty areas still reach the board's own marquee-select;
   each connector's invisible wide hit-line opts back in. */
.tt-canvas-connectors { position: absolute; inset: 0; pointer-events: none; }
.tt-canvas-connector { stroke: currentColor; fill: none; stroke-linecap: round; }
/* stroke-width AND stroke-dasharray are both set inline per-connector (CanvasSurface.razor) --
   an inline style always wins over these class rules regardless of specificity, so declaring either
   here would silently stop applying the moment a connector gets a custom width/line-style. (This
   class used to also carry a "marching ants" stroke-dasharray for the selected look -- removed once
   a REAL user-chosen dash pattern needed to always win instead.) */
.tt-canvas-connector-hit { stroke: transparent; stroke-width: 14px; fill: none; cursor: pointer; pointer-events: all; }
/* Connector label -- positioned at the path's actual visual midpoint (CanvasSurface.LabelPoint), not
   the endpoints' arithmetic mean. Halo via paint-order+stroke (not a background rect -- SVG has no
   auto-sizing background primitive, and this reads well against any line color/board background
   without needing JS text measurement). fill:currentColor picks up the connector's OWN stroke color
   via the wrapping <g style="color:...">, same idiom the arrowhead marker already uses. */
.tt-canvas-connector-label {
    fill: currentColor;
    text-anchor: middle;
    dominant-baseline: middle;
    paint-order: stroke;
    stroke: var(--mud-palette-surface);
    stroke-width: 4px;
    stroke-linejoin: round;
    pointer-events: none;
    user-select: none;
}
/* The live drag-to-connect preview --created/destroyed directly by canvasInterop.js, not Blazor
   (it exists only for the duration of the drag, well under a render's worth of state). */
.tt-canvas-connector-preview { stroke: var(--mud-palette-primary); stroke-width: 2px; stroke-dasharray: 5 4; fill: none; opacity: .85; }
/* Orthogonal connector corners are rounded with a small SVG arc fillet at render time
   (CanvasSurface.BuildOrthogonalPath / canvasInterop.js's buildOrthogonalPath) rather than via
   stroke-linejoin -- the arc needs the actual bend geometry (which segment is longer, which way
   the turn goes), not something a CSS join style alone can produce. */
/* The bend handle for a selected Orthogonal connector's free-sliding middle segment -- only
   rendered (CanvasSurface.razor) for the two same-axis anchor pairings that have one to adjust.
   The SVG overlay itself is pointer-events:none so this opts back in, same pattern as the
   connector hit-line. */
.tt-canvas-connector-bend {
    fill: var(--mud-palette-surface); stroke: var(--mud-palette-primary); stroke-width: 2px;
    cursor: move; pointer-events: all;
}
.tt-canvas-connector-bend:hover { fill: var(--mud-palette-primary); }
/* The 2 endpoint handles on a selected connector -- grab either one and drag to a different
   element/anchor to "redraw" that end (canvasInterop.js's endpointHandle branch, unchanged -- only
   the SHAPE changed here). Triangular "tip" polygons (CanvasSurface.TipPoints), not plain dots --
   bigger and easier to grab than the old 6px-radius circle (user's own complaint: "the little
   circle... is not very easy to click to, or to click and drag"), and read as an arrowhead so the
   handle itself shows direction: FROM points forward (where the line departs), TO points into the
   target (where the real arrowhead marker arrives). */
.tt-canvas-connector-endpoint {
    fill: var(--mud-palette-primary); stroke: var(--mud-palette-surface); stroke-width: 1.5px;
    cursor: crosshair; pointer-events: all;
}
.tt-canvas-connector-endpoint:hover { filter: brightness(1.15); }
/* Manual waypoints (double-click-added along a connector's path, see CanvasSurface's waypoints
   branch of ConnectorGeometry / CatmullRomPath) -- drag to move (same idiom as the bend handle),
   double-click to remove (canvasInterop.js's onDblClick). Same look as the bend handle so the
   whole "draggable route point" family reads consistently. Radius bumped up from an earlier, too-
   small 5px -- imprecise double-clicks were landing on the wider connector hit-line underneath
   instead of the dot, reading as "double-click doesn't remove the point" when really it was adding
   a near-duplicate point next to the one that was there instead. */
.tt-canvas-connector-waypoint {
    fill: var(--mud-palette-surface); stroke: var(--mud-palette-primary); stroke-width: 2px;
    cursor: move; pointer-events: all;
}
.tt-canvas-connector-waypoint:hover { fill: var(--mud-palette-primary); }
/* Click (not drag) a waypoint dot to make IT, not the whole connector, the Delete key's target --
   filled solid so it's obvious at a glance which single point Delete would remove
   (CanvasSurface.SelectedWaypoint / canvasInterop.js's OnWaypointClicked). */
.tt-canvas-connector-waypoint--selected { fill: var(--mud-palette-primary); }

.tt-canvas-el {
    position: absolute;
    cursor: move;
    box-sizing: border-box;
    user-select: none;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--mud-palette-text-primary);
    overflow: hidden;
}

.tt-canvas-el--selected { outline: 2px solid var(--mud-palette-primary); outline-offset: -2px; overflow: visible !important; }
.tt-canvas-el--dragging { opacity: 0.85; }
/* Un-clip on hover too (not just selected) so a hovered element's anchor dots --offset outside its
   own border box --can actually paint there instead of being cut off by the default overflow:hidden
   (which exists to clip oversized text/image content, not these handles). */
.tt-canvas-el:hover { overflow: visible; }
/* JS-driven equivalent of :hover for the element currently under the cursor during a connect-drag
   (canvasInterop.js's updateDropHover) -- :hover itself can't be trusted to keep updating on OTHER
   elements while the pointer is captured on the board for the drag gesture, so this is toggled
   explicitly instead, but visually does exactly what hovering would. */
.tt-canvas-el--drop-hover { overflow: visible; }
/* Bridges the GAP between the element's own box and its anchor dots (offset ~17px past the edge --
   see .tt-canvas-anchor below). Without this, moving the mouse from inside the box toward a dot
   crosses a patch of nothing in between; :hover goes false there, the dot disappears mid-transit,
   and it can never actually be reached. Generated content still counts as part of the host element
   for :hover purposes even though it paints outside the element's own layout box (negative inset),
   so this keeps hover --and the dots --alive continuously out to (and past) the dots themselves.
   It has no visible content, so it's invisible; a click landing on it resolves to the host element
   in JS (pseudo-elements aren't real DOM nodes), which onPointerDown already treats as a normal
   element click -- true and harmless for every OTHER element type (nothing interactive is ever
   nested inside a Shape/Text/Image), but this pseudo-element paints ON TOP of the element's own
   children in the normal stacking order, and without pointer-events:none it hit-tests -- so for
   Table specifically, whose cells DO have real nested interactive content (inputs, insert/delete
   buttons), this was silently swallowing every click meant for a cell before it ever reached the
   cell at all. THIS WAS THE ACTUAL ROOT CAUSE of the "can't type in a table cell" bug that survived
   4 earlier fix attempts aimed at the wrong layer (JS event handling, Blazor diffing, unrelated CSS)
   -- live-diagnosed via DevTools element-picker, which showed a ".tt-canvas-el.tt-canvas-el-table
   ::after" layer sitting on top when inspecting a cell directly. Excluded via :not() rather than
   adding pointer-events:none globally, since pointer-events:none would ALSO stop this halo from
   contributing to :hover detection at all (an element not hit-tested can't trigger :hover through
   it), which would silently reintroduce the original anchor-dot-disappears-mid-transit bug for
   EVERY element type, not just tables -- scoping the exclusion to just Table keeps that fix intact
   everywhere it still matters, accepting the anchor-dot transit flicker as a real but far smaller
   trade-off specifically on Table (which needs full click-through far more than it needs perfectly
   smooth anchor-dot hovering). */
/* .tt-canvas-el-text excluded too, for the SAME reason Table/Grid are: it now has real interactive
   content nested inside (a live QuillEditor while selected/editing — CanvasSurface.ElementInner's
   isEditing branch), which this unrestricted-pointer-events halo would otherwise silently swallow
   clicks for, exactly like it did for Table before that was found and fixed. Rect/Ellipse Shape
   excluded proactively for the identical reason once they ALSO gained an optional live-Quill label
   (Line is deliberately not excluded -- it never gets a label, so it never has any interactive
   content nested inside, and keeps the halo's normal hover-bridging behavior for its own anchors). */
.tt-canvas-el:not(.tt-canvas-el-table):not(.tt-canvas-el-grid):not(.tt-canvas-el-text):not(.tt-canvas-el-shape-rect):not(.tt-canvas-el-shape-ellipse)::after { content: ''; position: absolute; inset: -22px; }

/* z-index 25, above the floating rich-text toolbar's own 20 (.tt-canvas-rich .ql-toolbar below) --
   a selected Text element's rotate handle sits directly in the toolbar's overlap zone (both occupy
   the strip just above the element), and without this the toolbar drew on top and silently ate every
   click aimed at the handle underneath it. */
.tt-canvas-handle { position: absolute; z-index: 25; pointer-events: auto; }
.tt-canvas-handle-del {
    top: -9px; right: -9px; width: 16px; height: 16px; border-radius: 50%;
    background: var(--mud-palette-error); color: #fff; font-size: 12px; line-height: 14px;
    text-align: center; cursor: pointer; box-shadow: 0 1px 3px rgba(0,0,0,0.4); font-weight: 700;
}

/* Resize handles sit at the 4 CORNERS (a square) --kept clear of the 4 connection anchors below,
   which sit at the mid-edges, so the two handle families never overlap or fight for a click. */
.tt-canvas-rz { background: var(--mud-palette-primary); border: 1px solid #fff; box-shadow: 0 1px 3px rgba(0,0,0,0.4); border-radius: 2px; width: 10px; height: 10px; }
.tt-canvas-rz-nw { top: -12px;    left: -12px;  cursor: nwse-resize; }
.tt-canvas-rz-ne { top: -12px;    right: -12px; cursor: nesw-resize; }
.tt-canvas-rz-sw { bottom: -12px; left: -12px;  cursor: nesw-resize; }
.tt-canvas-rz-se { bottom: -12px; right: -12px; cursor: nwse-resize; }

/* Rotate handle -- a circle above the element, connected by a thin decorative stem, offset further
   out (-34px) than the N connect-anchor dot (-17px, see .tt-canvas-anchor-n below) so the two never
   collide. Drag it around in a circle to rotate about the element's own center
   (canvasInterop.js's rotateDrag); the numeric-entry field in the property panel stays available as
   a precise alternative. */
.tt-canvas-handle-rotate {
    top: -34px; left: 50%; transform: translateX(-50%);
    width: 14px; height: 14px; border-radius: 50%;
    background: var(--mud-palette-surface); border: 2px solid var(--mud-palette-primary);
    cursor: grab; box-shadow: 0 1px 3px rgba(0,0,0,0.4);
}
.tt-canvas-handle-rotate:active { cursor: grabbing; }
.tt-canvas-handle-rotate:hover { background: var(--mud-palette-primary); }
.tt-canvas-handle-rotate::after {
    content: ''; position: absolute; left: 50%; top: 100%; width: 1px; height: 20px;
    background: var(--mud-palette-primary); transform: translateX(-50%); pointer-events: none;
}

/* Connection anchors --the 4 fixed points (top/right/bottom/left center) every element exposes for
   drag-to-connect (canvasInterop.js's onPointerDown .tt-canvas-anchor branch). Circular and offset
   well past the border (not hugging the edge) so they read as "docking points" you can aim for from
   a short distance, distinct from the square resize handles. Rendered for EVERY element always
   (CanvasSurface.razor) but invisible/non-interactive by default --revealed by whichever of the 2
   rules below applies, never via a Blazor re-render. */
.tt-canvas-anchor {
    width: 14px; height: 14px; border-radius: 50%;
    background: #fff; border: 2px solid var(--mud-palette-primary);
    box-shadow: 0 1px 3px rgba(0,0,0,0.35);
    cursor: crosshair;
    opacity: 0; pointer-events: none;
    transition: opacity .12s, transform .12s;
}
/* 3 triggers: this element is the one currently selected, it's the one directly under the mouse
   right now (plain hover, e.g. when picking a drag START point), or it's the current drop target
   during an active connect-drag (--drop-hover, JS-driven -- see .tt-canvas-el--drop-hover above).
   Deliberately NOT board-wide during a connect-drag (an earlier version lit every element's dots up
   at once; user feedback was that's noisy -- only the element you're actually pointing at should
   show its dots). */
.tt-canvas-el--selected .tt-canvas-anchor,
.tt-canvas-el:hover .tt-canvas-anchor,
.tt-canvas-el--drop-hover .tt-canvas-anchor {
    opacity: 0.55; pointer-events: auto;
}
.tt-canvas-anchor:hover,
.tt-canvas-anchor--drop-target {
    opacity: 1 !important; transform: scale(1.3); background: var(--mud-palette-primary);
}
.tt-canvas-anchor-n { left: 50%; top: -17px;    transform: translateX(-50%); }
.tt-canvas-anchor-s { left: 50%; bottom: -17px; transform: translateX(-50%); }
.tt-canvas-anchor-w { top: 50%;  left: -17px;   transform: translateY(-50%); }
.tt-canvas-anchor-e { top: 50%;  right: -17px;  transform: translateY(-50%); }
.tt-canvas-anchor-n:hover, .tt-canvas-anchor-s:hover,
.tt-canvas-anchor-n.tt-canvas-anchor--drop-target, .tt-canvas-anchor-s.tt-canvas-anchor--drop-target {
    transform: translateX(-50%) scale(1.3);
}
.tt-canvas-anchor-w:hover, .tt-canvas-anchor-e:hover,
.tt-canvas-anchor-w.tt-canvas-anchor--drop-target, .tt-canvas-anchor-e.tt-canvas-anchor--drop-target {
    transform: translateY(-50%) scale(1.3);
}

.tt-canvas-marquee { position: absolute; z-index: 10; border: 1px dashed var(--mud-palette-primary); background: rgba(25,118,210,0.10); pointer-events: none; }

.tt-canvas-guide   { position: absolute; z-index: 40; pointer-events: none; background: #E5397E; display: none; }
.tt-canvas-guide-v { top: 0; bottom: 0; width: 1px; }
.tt-canvas-guide-h { left: 0; right: 0; height: 1px; }

/* Element type chrome */
/* white-space:pre-wrap deliberately absent -- content here is real Quill-produced HTML (since the
   Text-element rich-text conversion earlier this session), not plain text with literal newlines to
   preserve; pre-wrap fights HTML's own paragraph/whitespace handling (double-spacing artifacts
   between tags), the exact same issue found and fixed for Flow's own .tt-canvas-flow-text. */
.tt-canvas-el-text { padding: 6px 8px; word-break: break-word; text-align: left; align-items: flex-start; justify-content: flex-start; }
.tt-canvas-el-shape-rect    { border-radius: 2px; }
.tt-canvas-el-shape-ellipse { border-radius: 50%; }
.tt-canvas-el-shape-line    { background: none !important; border: none !important; }
.tt-canvas-el-image img { width: 100%; height: 100%; object-fit: contain; pointer-events: none; }

.tt-canvas-el-embed {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 10px;
    justify-content: flex-start;
    background: var(--mud-palette-background-grey);
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 8px;
    font-size: 0.8rem;
    font-weight: 600;
}
.tt-canvas-el-embed span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.tt-canvas-el-empty-hint {
    display: flex; align-items: center; justify-content: center; width: 100%; height: 100%;
    color: var(--mud-palette-text-secondary); opacity: .6; font-size: 11px; pointer-events: none;
}

/* A placed Symbol instance's inner content (CanvasSurface.RenderSymbolContent) -- purely decorative,
   positioned by percentage of the symbol's own def-time box (set inline per element) so it rescales
   automatically as the placed instance is resized. pointer-events:none so clicks/drags always land
   on the OUTER symbol element (one draggable/resizable unit), never an individual inner piece.
   display:flex/align-items/justify-content mirrors .tt-canvas-el's OWN flex-centering exactly --
   without it, a Shape's label (rendered inside this box the SAME way ElementInner renders it at the
   top level) had nothing centering its own block within the shape at all, only the text-align inside
   that block (already correct per the earlier alignment fix) -- direct feedback: "I have [it set to]
   center... in the component[/symbol] added is not centered." Table/Grid/Image sub-elements are
   unaffected (they already fill 100% of their own box explicitly, same as their top-level versions
   do under the identical .tt-canvas-el rule). */
.tt-canvas-symbol-inner { position: absolute; pointer-events: none; overflow: hidden; display: flex; align-items: center; justify-content: center; }

/* A Connector saved as part of a Symbol (CanvasSurface.RenderSymbolContent/SymbolConnectorPath) --
   drawn in the SAME 0-100 percentage viewBox as the inner elements' own percentage-based
   left/top/width/height, so it rescales identically as the placed instance is resized. Direct
   feedback: "when I save a group as a symbol is not storing the connection lines." Simplified v1
   compared to the top-level connector overlay: always a soft curve (no Orthogonal/waypoints/labels),
   see SymbolConnectorPath's own doc comment for why. vector-effect keeps the stroke a constant SCREEN
   width regardless of the viewBox's own scale transform -- without it the line would visibly get
   thicker/thinner as the placed instance is resized, unlike every other line-width in this file. */
.tt-canvas-symbol-connectors { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; overflow: visible; }
.tt-canvas-symbol-connector { fill: none; stroke: currentColor; stroke-width: 1.5px; stroke-linecap: round; vector-effect: non-scaling-stroke; }

/* Table (CanvasSurface.RenderTableContent) -- a real grid, not a Shape variant. .tt-canvas-el's own
   default `display:flex; align-items:center; justify-content:center` would only size the table to
   its content and center it, so the table itself gets explicit width/height:100% to fill the whole
   element box regardless. Column widths come from inline grid-template-columns (percentages,
   CanvasSurface computes them from TableColWidths) -- resizing the OUTER element rescales every
   column proportionally for free, no JS/C# recompute needed for that case, only for an actual
   column-resize drag. */
.tt-canvas-el-table { padding: 0; cursor: default; }
.tt-canvas-table {
    display: grid; width: 100%; height: 100%; position: relative;
    align-content: start; cursor: default;
}
.tt-canvas-table-cell {
    border: 1px solid var(--mud-palette-lines-default);
    background: var(--mud-palette-surface);
    padding: 2px 6px; overflow: hidden; min-width: 0;
    position: relative;
    /* display:flex (+ default align-items:stretch) makes the MudTextField's own root div stretch to
       fill the cell's FULL height -- without this, a cell that's been row-stretched taller than its
       own (single-line, empty) text field's natural height left real dead space where a click would
       land on the cell's own background/padding instead of the actual <input>, never focusing it.
       Live-diagnosed: document.activeElement after clicking a cell stayed on an unrelated toolbar
       button, proving the click wasn't reaching the input at all -- not a JS/event-handling bug. */
    display: flex;
}
/* Lifts the cell's own overflow:hidden clip while hovering ANYWHERE on the table, so the inline "+"
   insert buttons (anchored to specific cells' corners with negative offsets -- see
   RenderTableContent/.tt-canvas-table-plus below) aren't clipped away; a harmless side effect is a
   hovered cell's own overflowing text also becomes visible during that same hover. */
.tt-canvas-table:hover .tt-canvas-table-cell { overflow: visible; }
.tt-canvas-table-cell--active { outline: 2px solid var(--mud-palette-primary); outline-offset: -1px; z-index: 2; }
.tt-canvas-table-cell--active-row,
.tt-canvas-table-cell--active-col { background: color-mix(in srgb, var(--mud-palette-primary) 8%, var(--mud-palette-surface)); }
.tt-canvas-table-cell-text { font-size: 13px; word-break: break-word; white-space: pre-wrap; align-self: center; }
.tt-canvas-table-cell-input { font-size: 13px; width: 100%; }
.tt-canvas-table-cell-input .mud-input-control { height: 100%; }
.tt-canvas-table-cell-input .mud-input-control-input-container,
.tt-canvas-table-cell-input .mud-input { height: 100%; align-items: stretch; }
.tt-canvas-table-cell-input input { height: 100%; }
/* Outside of editing, a cell should just look like plain text, not a visible form field -- hides
   MudTextField's own resting + animated-focus underline (Variant.Text draws both via ::before/
   ::after on .mud-input) by making them transparent, restoring ONLY the focus line's color while
   the cell's input actually has focus. Best-effort against MudBlazor's own internal markup (not
   something this codebase controls) -- if a MudBlazor upgrade ever changes how the underline is
   implemented, this may need retuning against the ACTUAL rendered DOM at that point. */
.tt-canvas-table-cell-input .mud-input:before,
.tt-canvas-table-cell-input .mud-input:after { border-bottom-color: transparent !important; }
.tt-canvas-table-cell-input:focus-within .mud-input:after { border-bottom-color: var(--mud-palette-primary) !important; }
/* Absolutely positioned so it's taken OUT of the cells' own grid auto-placement flow entirely (a
   grid container's absolutely-positioned children don't consume a grid cell) -- otherwise these
   would be auto-placed as if they were extra table cells, breaking the layout. */
.tt-canvas-table-colresize {
    position: absolute; top: 0; bottom: 0; width: 8px; margin-left: -4px;
    cursor: col-resize; z-index: 4; pointer-events: all;
}
.tt-canvas-table-colresize:hover { background: color-mix(in srgb, var(--mud-palette-primary) 35%, transparent); }
/* Inline "+" insert affordances -- anchored to a SPECIFIC existing cell's corner (column 0's cells
   for row inserts, row 0's cells for column inserts; see RenderTableContent's own comment for why
   rows can't use the same cumulative-fraction positioning columns do) rather than a separate grid
   gutter track. Reveal is scoped to the SPECIFIC row/column under the pointer (canvasInterop.js's
   onTableHoverOver toggles --hover-owner on just the 1-2 cells that own that row's/column's buttons),
   not the whole table at once -- an earlier version revealed everything on any table hover and was
   reported as too busy/cluttered. Hidden via `display:none` (not opacity) by default -- an
   opacity:0 element still hit-tests unless pointer-events is ALSO toggled in lockstep on a SEPARATE
   property, two things that have to stay in sync everywhere this class is referenced; `display:none`
   removes the box from hit-testing and layout unconditionally, by definition, with nothing else to
   keep synchronized. This is what several of these buttons actually need: e.g. the row-above "+" is
   anchored at a cell's top edge with translateY(-50%), so its bottom half sits OVER the cell's own
   content area whenever it's shown -- while hidden it must not be clickable there at all. */
/* Subtle by default (a soft surface-tinted circle, not a loud solid primary dot), full primary/error
   color only on :hover of the SPECIFIC button -- direct feedback: "should be pretty much transparent
   color when they are not [hovered]... if you move the mouse over, then make it red or primary...
   something that doesn't interfere with the background so hard." Revealing on row/column hover
   (--hover-owner) still shows them, just quietly, so a second, deliberate hover on the button itself
   is what signals "about to click this." */
.tt-canvas-table-plus {
    position: absolute; width: 20px; height: 20px; border-radius: 50%; padding: 0;
    border: 1px solid var(--mud-palette-lines-default);
    background: color-mix(in srgb, var(--mud-palette-primary) 12%, var(--mud-palette-surface));
    color: var(--mud-palette-primary);
    font: 14px/18px sans-serif; cursor: pointer; z-index: 5; display: none;
    align-items: center; justify-content: center;
    transition: background .12s, color .12s, border-color .12s, transform .12s;
}
.tt-canvas-table-cell--hover-owner .tt-canvas-table-plus { display: flex; }
.tt-canvas-table-plus:hover { background: var(--mud-palette-primary); color: var(--mud-palette-primary-text); border-color: var(--mud-palette-primary); }
.tt-canvas-table-plus--row-above { left: -10px; top: 0; transform: translateY(-50%); }
.tt-canvas-table-plus--row-below { left: -10px; top: 100%; transform: translateY(-50%); }
.tt-canvas-table-plus--col-left { top: -10px; left: 0; transform: translateX(-50%); }
.tt-canvas-table-plus--col-right { top: -10px; left: 100%; transform: translateX(-50%); }
.tt-canvas-table-plus--row-above:hover,
.tt-canvas-table-plus--row-below:hover { transform: translateY(-50%) scale(1.2); }
.tt-canvas-table-plus--col-left:hover,
.tt-canvas-table-plus--col-right:hover { transform: translateX(-50%) scale(1.2); }
/* Delete affordances -- same per-row/column hover-owner idiom (including display:none, not opacity)
   as the "+"s above, tinted with the error palette instead of primary (subtle by default, same as
   above) so add vs. remove still read as visually distinct actions once actually hovered. Positioned
   at the MID-point of the row/column they belong to (not a boundary, unlike the "+"s) so they never
   collide with the row-above/row-below or col-left/col-right insert buttons, which only ever sit at
   top:0%/100% or left:0%/100% respectively. */
.tt-canvas-table-x {
    position: absolute; width: 18px; height: 18px; border-radius: 50%; padding: 0;
    border: 1px solid var(--mud-palette-lines-default);
    background: color-mix(in srgb, var(--mud-palette-error) 12%, var(--mud-palette-surface));
    color: var(--mud-palette-error);
    font: 13px/16px sans-serif; cursor: pointer; z-index: 5; display: none;
    align-items: center; justify-content: center;
    transition: background .12s, color .12s, border-color .12s, transform .12s;
}
.tt-canvas-table-cell--hover-owner .tt-canvas-table-x { display: flex; }
.tt-canvas-table-x:hover { background: var(--mud-palette-error); color: var(--mud-palette-error-text); border-color: var(--mud-palette-error); }
.tt-canvas-table-x--row { left: -9px; top: 50%; transform: translateY(-50%); }
.tt-canvas-table-x--col { top: -9px; left: 50%; transform: translateX(-50%); }
.tt-canvas-table-x--row:hover { transform: translateY(-50%) scale(1.2); }
.tt-canvas-table-x--col:hover { transform: translateX(-50%) scale(1.2); }

/* Grid (CanvasSurface.RenderGridContent) -- the "container grid" companion to Table: cells hold
   real embedded elements (stacked, not freely positioned), not plain text. Excluded from the
   .tt-canvas-el::after hover halo above for the SAME reason Table is -- it has real interactive
   content nested inside that an unrestricted click-catching overlay would swallow. */
.tt-canvas-el-grid { padding: 0; cursor: default; }
/* No gap/padding at the container level (unlike an earlier version) -- the col/row-resize divider
   strips below are positioned via raw cumulative-fraction percentages of the FULL container box,
   the same exact-math approach Table's own .tt-canvas-table-colresize already relies on; any
   container-level gap/padding would eat into that space and make the percentage math drift away
   from the real visual boundary. Cell borders (below) provide the visual separation instead. */
.tt-canvas-grid {
    display: grid; width: 100%; height: 100%; position: relative;
    box-sizing: border-box; cursor: default;
}
.tt-canvas-grid-cell {
    border: 1px dashed var(--mud-palette-lines-default);
    background: var(--mud-palette-background-grey);
    padding: 4px; overflow: auto; min-width: 0; min-height: 0;
    position: relative; display: flex; flex-direction: column; gap: 4px;
}
/* Marks which cell the main toolbar's Add buttons will target next -- CanvasEditorWindow's
   _gridActivePath (may match more than one cell at once, for nested grids), set by clicking
   anywhere in a cell (see GridCellClicked). */
/* outline (not background) so the active indicator stays visible even when a cell has its own
   custom background color set (GridCellColors) -- an inline style always wins over this class's own
   background, but outline is a separate box entirely, unaffected either way. */
.tt-canvas-grid-cell--active { outline: 2px solid var(--mud-palette-primary); outline-offset: -2px; }
/* Same clip Table's own cell has (see .tt-canvas-table:hover rule above) and the same fix -- a Grid
   cell's own overflow:auto (needed so oversized cell CONTENT scrolls) clips the "+"/"×" buttons below,
   which are anchored with negative offsets at the cell's OUTER corner/edge, not inside it. Missing this
   left them rendering but visually cut off by their own cell's frame. */
.tt-canvas-grid:hover .tt-canvas-grid-cell { overflow: visible; }
/* Row/column insert-"+"/delete-"×" reveal — same hover-owner idiom as Table (canvasInterop.js's
   onTableHoverOver, generalized to toggle this class on Grid cells too), reusing Table's OWN
   .tt-canvas-table-plus/-x CSS classes directly on the buttons themselves for a visually uniform
   look (only the reveal TRIGGER is Grid-specific, not the button appearance). */
.tt-canvas-grid-cell--hover-owner .tt-canvas-table-plus,
.tt-canvas-grid-cell--hover-owner .tt-canvas-table-x { display: flex; }
.tt-canvas-grid-child { position: relative; flex: 0 0 auto; }
.tt-canvas-grid-child--selected { outline: 2px solid var(--mud-palette-primary); outline-offset: 1px; }
/* Resize handle for a SELECTED grid child -- bottom-right corner only (v1 simplification vs. the 4
   corners top-level elements get), same visual language as .tt-canvas-rz. A grid child has no
   handles at all otherwise: it's rendered stacked (CanvasSurface.RenderGridContent), not through
   the top-level .tt-canvas-el loop that normally supplies them. */
.tt-canvas-grid-child-rz {
    position: absolute; bottom: -6px; right: -6px; width: 10px; height: 10px;
    background: var(--mud-palette-primary); border: 1px solid #fff; box-shadow: 0 1px 3px rgba(0,0,0,0.4);
    border-radius: 2px; cursor: nwse-resize; z-index: 6;
}
/* Column/row-resize dividers -- same absolutely-positioned/out-of-grid-flow idiom as Table's own
   .tt-canvas-table-colresize (a grid container's absolutely-positioned children don't consume a
   cell), just added for BOTH axes here since Grid (unlike Table) supports row-height dragging too. */
.tt-canvas-grid-colresize {
    position: absolute; top: 0; bottom: 0; width: 8px; margin-left: -4px;
    cursor: col-resize; z-index: 4; pointer-events: all;
}
.tt-canvas-grid-colresize:hover { background: color-mix(in srgb, var(--mud-palette-primary) 35%, transparent); }
.tt-canvas-grid-rowresize {
    position: absolute; left: 0; right: 0; height: 8px; margin-top: -4px;
    cursor: row-resize; z-index: 4; pointer-events: all;
}
.tt-canvas-grid-rowresize:hover { background: color-mix(in srgb, var(--mud-palette-primary) 35%, transparent); }

/* Drag-to-move handle chip, shared by Table and Grid (RenderTableContent/RenderGridContent) --
   once a cell's own click selects/edits instead of dragging the whole container, there's no longer
   any click-and-drag-anywhere way to move it; this dedicated, always-a-sibling-of-any-cell chip is
   the replacement (see canvasInterop.js's onPointerDown: a click here isn't inside a
   .tt-canvas-table-cell/.tt-canvas-grid-cell, so it falls through to the ordinary element-move
   logic every other element already uses -- no new JS needed for the drag itself). Sized to ~25% of
   the container's own width, soft/rounded per direct feedback ("very soft and rounded"). */
.tt-canvas-container-draghandle {
    position: absolute; top: -27px; left: 8px; width: 25%; min-width: 64px; max-width: 140px;
    height: 22px; border-radius: 11px; z-index: 6; cursor: move;
    background: color-mix(in srgb, var(--mud-palette-background-grey) 85%, black);
    border: 1px solid color-mix(in srgb, var(--mud-palette-lines-default) 60%, var(--mud-palette-primary));
    color: var(--mud-palette-text-secondary); font-size: 12px;
    display: flex; align-items: center; justify-content: center; gap: 2px;
    box-shadow: 0 1px 4px rgba(0,0,0,0.25);
}

/* Compact inline rich-text editing (Freeform Text elements, Table cells, Grid text children) --
   wraps QuillEditor (ToolbarPreset="compact") so it reads as PLAIN TEXT until focused, then reveals
   Quill's own toolbar as a FLOATING overlay below the field instead of Quill's normal always-visible
   inline block -- direct feedback: "should not appear any little line... only when they get focus."
   Below (not above) the field specifically because the strip ABOVE a selected top-level element is
   where the rotate handle/resize corners/N connect-anchor already live -- direct feedback: "normally
   at the bottom you don't have anything pending." Quill's toolbar/editor normally stack vertically
   in-flow; making .ql-toolbar position:absolute removes it from that flow entirely, so .ql-container
   collapses to just the text itself with the toolbar floating below (top:100%) only while revealed. */
/* font-size uses --tt-canvas-zoom (set on .tt-canvas-board, falls back to 1 anywhere that variable
   isn't defined -- Flow mode's own .tt-canvas-flow tree, which has no zoom concept at all) instead of
   a flat 13px -- a class-selector rule targeting the element ITSELF always wins over whatever the
   ancestor .tt-canvas-el's own inline (zoom-scaled) font-size would otherwise have contributed via
   plain inheritance, so without this override every rich-text field silently stayed a fixed 13px
   regardless of Zoom. Direct feedback: "the font size is not zooming." ALSO multiplies by
   --tt-canvas-symbol-scale (set on a placed Symbol instance's own .tt-canvas-el, BoxStyle) -- a
   SEPARATE factor for that one instance's own resize ratio, independent of the board's overall zoom;
   defaults to 1 everywhere else so this is a no-op outside an actual Symbol instance. Direct
   feedback: "the text of all the labels are not resizing uniformly with the symbol... it's the
   resizing because the symbol is a smaller one." */
.tt-canvas-rich { position: relative; font-size: calc(13px * var(--tt-canvas-zoom, 1) * var(--tt-canvas-symbol-scale, 1)); }
.tt-canvas-rich .ql-toolbar {
    position: absolute; top: 100%; left: 0; z-index: 20; margin-top: 4px;
    background: var(--mud-palette-surface); border: 1px solid var(--mud-palette-lines-default) !important;
    border-radius: 6px; box-shadow: 0 2px 8px rgba(0,0,0,0.25);
    opacity: 0; pointer-events: none; transition: opacity .12s; white-space: nowrap;
    /* An absolutely-positioned box with only `left` set (no `right`) still shrink-to-fits against
       the containing block's REMAINING width, not its own natural content width -- for a freshly
       added, narrow Text field this silently capped the toolbar at the field's own width, truncating
       whichever button group (the size picker, being last) ran out of room first. `max-content`
       forces the toolbar to its true natural width regardless of how narrow the field under it is. */
    width: max-content;
}
.tt-canvas-rich:focus-within .ql-toolbar { opacity: 1; pointer-events: auto; }
.tt-canvas-rich .ql-container { border: none !important; font-size: calc(13px * var(--tt-canvas-zoom, 1) * var(--tt-canvas-symbol-scale, 1)); }
.tt-canvas-rich .ql-editor { padding: 2px 4px; min-height: unset; }
.tt-canvas-rich:focus-within .ql-container { outline: 1px solid var(--mud-palette-primary); border-radius: 3px; }
/* The floating toolbar pops up OUTSIDE the editor's own box (bottom:100%) -- any ancestor between
   here and the element's own boundary that clips overflow (Grid/Table cells scroll their own
   content via overflow:auto/hidden) clips the toolbar right along with it, which is what made it
   render "behind the edges of the grid" instead of floating on top. Un-clip every such ancestor
   while focus is anywhere inside it -- :focus-within evaluates independently per ancestor, so this
   also covers a grid nested inside another grid's cell with no extra rules needed. */
.tt-canvas-el:focus-within { overflow: visible; }
.tt-canvas-grid-cell:focus-within,
.tt-canvas-table-cell:focus-within { overflow: visible; }
/* Read-only display of the SAME HTML (not editing right now) -- a plain markup div, not a whole
   second Quill instance, since only the ACTIVELY-selected/focused cell needs a live editor at all;
   giving every cell of a large table its own permanent Quill instance would be real, unnecessary
   overhead for content that isn't being edited at that moment. */
.tt-canvas-rich-display { font-size: calc(13px * var(--tt-canvas-zoom, 1) * var(--tt-canvas-symbol-scale, 1)); overflow-wrap: break-word; }
.tt-canvas-rich-display p { margin: 0; }
/* Quill's own formatting classes (ql-size-large/huge/small, ql-align-center/right/justify) only have
   any visual effect inside a real .ql-editor -- quill.snow.css scopes their font-size/text-align rules
   to a ".ql-editor .ql-size-large" style ancestor selector, which never matches this plain display div.
   Bold/italic/underline/color survive here fine
   (real <strong>/<em>/<u> tags + an inline color style, not Quill-specific classes), but size/align
   silently did nothing the moment a field lost focus and swapped from the live editor to this static
   view -- direct feedback: "it applies bold/underline/color but not large/small/huge or alignment."
   Values copied verbatim from quill.snow.css's own .ql-editor-scoped rules so both views agree. */
.tt-canvas-rich-display .ql-size-small  { font-size: 0.75em; }
.tt-canvas-rich-display .ql-size-large  { font-size: 1.5em; }
.tt-canvas-rich-display .ql-size-huge   { font-size: 2.5em; }
.tt-canvas-rich-display .ql-align-center  { text-align: center; }
.tt-canvas-rich-display .ql-align-right   { text-align: right; }
.tt-canvas-rich-display .ql-align-justify { text-align: justify; }
/* A label inside a Rect/Ellipse shape (CanvasSurface.ElementInner's Shape case) -- .tt-canvas-el's
   own flex centering already centers this box within the shape, so no extra positioning is needed
   here, just an inset so the text never touches (or, for Ellipse, gets clipped by) the shape's own
   edge/curve. max-width keeps long unbroken text from pushing past the curved corners of a small
   ellipse instead of wrapping within it. */
/* text-align: LEFT, not center -- matches Quill's own real default. Quill only ever adds a
   ql-align-center/-right/-justify class for a NON-default choice (quill.snow.css), so picking "left"
   in the toolbar leaves the paragraph with no align class at all. While actively editing, that's
   invisible either way: .ql-editor's own `text-align:left` rule (quill.snow.css) wins over whatever
   this wrapper says, being a more specific match on the element itself. But the STATIC display div
   (.tt-canvas-rich-display, swapped in once editing ends) has no .ql-editor class and thus no such
   rule protecting it -- it fell straight through to whatever THIS rule said, which used to be
   `center`, silently reverting a "left" choice back to centered the moment editing finished. Direct
   feedback: "when I edit the label and align it to the left... and I finish the edit, it's centered." */
.tt-canvas-shape-label,
.tt-canvas-shape-label-display { max-width: calc(100% - 16px); text-align: left; }
.tt-canvas-shape-label-display { padding: 0 8px; overflow-wrap: break-word; }

/* -- Flow/Document renderer (CanvasMode.Flow) -- pairs with CanvasFlowSurface.razor -- */

.tt-canvas-flow {
    margin: 0 auto;
    background: var(--mud-palette-surface);
    box-shadow: 0 2px 10px rgba(0,0,0,0.18);
    border: 1px solid #d0d0d0;
    padding: 32px 40px;
    min-height: 400px;
}

/* v2: NOT display:flex any more -- .tt-canvas-flow-controls used to be a flex sibling of the actual
   content, but a flex row's height is always the TALLEST child regardless of align-items, so a short
   block (a one-line Panel especially) got stretched to match the controls column's own height (a
   drag-handle icon + 2 stacked buttons, taller than a short callout), reading as a big empty gap below
   short content even though the controls themselves were invisible (opacity:0) at the time. Fixed by
   taking .tt-canvas-flow-controls OUT of normal flow entirely (position:absolute in the padding-left
   gutter reserved below) so it can never affect this element's own height. */
.tt-canvas-flow-block {
    position: relative;
    margin-bottom: 10px;
    border-radius: 6px;
    padding: 4px 4px 4px 34px;
    cursor: default;
}
.tt-canvas-flow-block:hover { background: color-mix(in srgb, var(--mud-palette-primary) 4%, transparent); }
/* The ONE selected block — a stronger, deliberate highlight (not just the passive hover tint above),
   same role CanvasSurface's own .tt-canvas-el--selected plays. Selection is what gates every editing
   affordance below (move/duplicate/delete, and each block type's own controls) — see IsSelected. */
.tt-canvas-flow-block--selected {
    background: color-mix(in srgb, var(--mud-palette-primary) 6%, transparent);
    outline: 1px solid color-mix(in srgb, var(--mud-palette-primary) 45%, transparent);
    outline-offset: 2px;
}

/* v2: ALWAYS rendered (when !ReadOnly), not conditionally — CSS-driven hover-OR-selected visibility
   instead, same idiom RenderInsertZone's own hover reveal already uses elsewhere in this file.
   Matches the plan's own "a handle to the left of a selected/hovered block" — Notion/Confluence's own
   drag-handle discoverability, not click-to-select-first. Floats in the block's own left padding
   gutter (see .tt-canvas-flow-block's own comment above for why absolute, not a flex sibling) — a
   drag handle plus a single kebab "⋮" menu (Duplicate/Delete), down from the old 4 buttons
   (Move Up/Down replaced by MudDropZone drag-reorder — see OnBlockDroppedAsync). */
.tt-canvas-flow-controls {
    position: absolute; left: 2px; top: 4px;
    display: flex; flex-direction: column; align-items: center; width: 28px;
    opacity: 0; pointer-events: none; transition: opacity .12s;
}
.tt-canvas-flow-block:hover .tt-canvas-flow-controls,
.tt-canvas-flow-block--selected .tt-canvas-flow-controls { opacity: 1; pointer-events: auto; }
.tt-canvas-flow-draghandle { cursor: grab; color: var(--mud-palette-text-secondary); }
.tt-canvas-flow-draghandle:active { cursor: grabbing; }

/* MudDropZone wrapping the top-level block list (v2, new) -- deliberately unstyled/borderless by
   default (unlike .cad-dropzone's dashed-border treatment in app.css): a document should read as a
   plain document, not a UI, when nothing is being dragged -- same "clean/printable" reasoning as
   everywhere else in this file. mud-drop-zone-drag-block/mud-drop-item-placeholder ARE styled, since
   those only appear mid-drag and need to be visible then. */
.tt-canvas-flow-dropzone { display: flex; flex-direction: column; }
.tt-canvas-flow-dropzone .mud-drop-item-placeholder {
    border: 2px dashed var(--mud-palette-primary); border-radius: 6px;
    background: color-mix(in srgb, var(--mud-palette-primary) 6%, transparent);
    min-height: 34px; margin-bottom: 10px;
}

/* Insert-between-blocks (CanvasFlowSurface.RenderInsertZone) -- a thin hover strip between every pair
   of top-level blocks (plus one before the first, one after the last). Only rendered when !ReadOnly
   at all (see the markup) -- deliberately does NOT replace .tt-canvas-flow-block's own margin-bottom
   above, which stays the SOLE source of inter-block spacing in read-only/printed output; this is
   purely ADDITIONAL space that only exists in edit mode, so a read-only document's spacing is
   completely unaffected by this feature existing. Line + "+" menu both start at opacity:0/pointer-
   events:none (same idiom as .tt-canvas-flow-table-cell-bg) and reveal together on hovering the
   WHOLE zone, not each other individually -- the zone is a small isolated strip that doesn't overlap
   either neighboring block's own box, so it can't steal clicks meant for them. Direct feedback: "show
   only when the mouse moves between the two elements... a little line like a divider... with a
   button... with a plus sign... a drop down to select what type." */
.tt-canvas-flow-insertzone { position: relative; height: 12px; }
.tt-canvas-flow-insertzone-line {
    position: absolute; left: 0; right: 0; top: 50%; height: 2px; margin-top: -1px;
    background: var(--mud-palette-primary); border-radius: 1px;
    opacity: 0; pointer-events: none; transition: opacity .12s;
}
.tt-canvas-flow-insertzone-add {
    position: absolute; left: -8px; top: 50%; transform: translateY(-50%);
    opacity: 0; pointer-events: none; transition: opacity .12s;
    background: var(--mud-palette-surface); border-radius: 50%;
}
.tt-canvas-flow-insertzone:hover .tt-canvas-flow-insertzone-line,
.tt-canvas-flow-insertzone:hover .tt-canvas-flow-insertzone-add { opacity: 1; pointer-events: auto; }

/* Panel (v2, new) -- Confluence's own colored-callout macro. Left accent bar + tinted background per
   kind, using MudBlazor's own semantic palette tokens (info/warning/success/error) for automatic
   theme/dark-mode correctness, same color-mix idiom already used elsewhere in the app (see app.css)
   -- "Note" has no direct MudBlazor semantic token, so it gets a neutral tertiary tint instead. */
.tt-canvas-flow-panel { display: flex; align-items: flex-start; gap: 10px; padding: 10px 12px; border-radius: 4px; border-left: 4px solid; }
.tt-canvas-flow-panel-icon { flex-shrink: 0; margin-top: 2px; }
.tt-canvas-flow-panel-body { flex: 1 1 auto; min-width: 0; }
.tt-canvas-flow-panel--info    { background: color-mix(in srgb, var(--mud-palette-info) 10%, var(--mud-palette-surface)); border-left-color: var(--mud-palette-info); }
.tt-canvas-flow-panel--info    .tt-canvas-flow-panel-icon { color: var(--mud-palette-info); }
.tt-canvas-flow-panel--note    { background: color-mix(in srgb, var(--mud-palette-tertiary) 10%, var(--mud-palette-surface)); border-left-color: var(--mud-palette-tertiary); }
.tt-canvas-flow-panel--note    .tt-canvas-flow-panel-icon { color: var(--mud-palette-tertiary); }
.tt-canvas-flow-panel--warning { background: color-mix(in srgb, var(--mud-palette-warning) 12%, var(--mud-palette-surface)); border-left-color: var(--mud-palette-warning); }
.tt-canvas-flow-panel--warning .tt-canvas-flow-panel-icon { color: var(--mud-palette-warning); }
.tt-canvas-flow-panel--success { background: color-mix(in srgb, var(--mud-palette-success) 10%, var(--mud-palette-surface)); border-left-color: var(--mud-palette-success); }
.tt-canvas-flow-panel--success .tt-canvas-flow-panel-icon { color: var(--mud-palette-success); }
.tt-canvas-flow-panel--error   { background: color-mix(in srgb, var(--mud-palette-error) 10%, var(--mud-palette-surface)); border-left-color: var(--mud-palette-error); }
.tt-canvas-flow-panel--error   .tt-canvas-flow-panel-icon { color: var(--mud-palette-error); }

/* Expand (v2, new) -- Confluence's own collapsible-section macro. Title bar is always the click
   target (no selection-gated chrome, unlike Grid's container-handle -- see RenderFlowExpand's own
   doc comment for why none is needed here), body gets a left rule + indent, matching Confluence's
   own visual treatment of a nested/expandable region. */
.tt-canvas-flow-expand { border: 1px solid var(--mud-palette-lines-default); border-radius: 4px; }
.tt-canvas-flow-expand-title {
    display: flex; align-items: center; gap: 4px; padding: 6px 8px; cursor: pointer;
    color: var(--mud-palette-text-primary); font-weight: 500;
}
.tt-canvas-flow-expand-title:hover { background: color-mix(in srgb, var(--mud-palette-primary) 6%, transparent); }
.tt-canvas-flow-expand-title-input { flex: 1 1 auto; min-width: 0; }
.tt-canvas-flow-expand-title-input .mud-input-slot { font-weight: 500; }
.tt-canvas-flow-expand-body {
    display: flex; flex-direction: column; gap: 6px; min-height: 36px;
    padding: 4px 12px 10px 30px; border-left: 2px solid var(--mud-palette-lines-default); margin-left: 14px;
}
/* Expand's own "+" add-content menu reuses Grid's .tt-canvas-flow-grid-add class verbatim (see
   RenderFlowExpand) -- both are conditionally RENDERED only when selected now (IsSelected(el) in the
   Razor), so no separate reveal rule is needed here the way an always-rendered/hover-toggled version
   would have needed (see .tt-canvas-flow-grid-add's own doc comment in the Grid section above for
   the two failed attempts that preceded this). Each child's own delete "x" needs no separate rule
   either: .tt-canvas-flow-grid-child:hover already covers it, since Expand's children use that same
   wrapper class regardless of container. */

.tt-canvas-flow-content { flex: 1 1 auto; min-width: 0; }

/* .tt-canvas-flow-text now pairs with .tt-canvas-rich-display (real HTML from Quill, not plain
   text) -- word-wrap/paragraph-margin is THAT class's job now, this one just keeps the padding/
   color a plain text node used to need. white-space:pre-wrap deliberately dropped: it was there to
   preserve literal newlines in the OLD plain-text model, but fights real HTML's own paragraph/
   whitespace handling (double-spacing artifacts between tags) once the content is markup. */
.tt-canvas-flow-text { padding: 6px 4px; color: var(--mud-palette-text-primary); }
/* Wraps a Text block's own background/border (FlowTextStyle) -- a separate element from
   .tt-canvas-flow-text itself so the fill/border box always matches the QuillEditor's own live-
   editing footprint too, not just the static ReadOnly display. No background/border by default
   (unlike Shape, which always has SOME fill) -- matches Freeform's own Text default exactly. */
.tt-canvas-flow-text-wrap { border-radius: 2px; }
.tt-canvas-flow-img   { max-width: 100%; height: auto; display: block; border-radius: 4px; }
/* Flow's own QuillEditor usage (top-level Text blocks AND Row column blocks) -- .tt-canvas-rich
   already gives the floating-toolbar-on-focus behavior Freeform uses; the only Flow-specific need
   is filling the row's flex width (a plain block used to get this via MudTextField's FullWidth) and
   floating the toolbar ABOVE instead of below. Freeform puts it below specifically because the
   strip above a selected element is where the rotate handle/resize corners live -- Flow blocks have
   none of that, and "below" would sit right in the ~10px gap before the NEXT block starts, likely
   overlapping it; "above" has the same free space Freeform's own connect-anchor/rotate-handle strip
   would occupy if this were a Freeform element, just genuinely unused here. */
.tt-canvas-flow-rich { flex: 1 1 auto; min-width: 0; }
.tt-canvas-flow-rich.tt-canvas-rich .ql-toolbar { top: auto; bottom: 100%; margin-top: 0; margin-bottom: 4px; }

/* display:flex + centering so a label (see RenderFlowShape) sits centered inside the shape's own
   box, the same role CanvasSurface's own .tt-canvas-el flex-centering plays for Freeform's Shape
   label — .tt-canvas-shape-label/-display (shared, canvas.css above) rely on their PARENT to do this
   centering, they don't do it themselves. No overflow:hidden here (unlike .tt-canvas-el) -- would
   clip the QuillEditor's own floating toolbar, which is deliberately positioned OUTSIDE this box
   (bottom:100%, see .tt-canvas-flow-rich above). */
.tt-canvas-flow-shape { width: 100%; height: 60px; border-radius: 2px; display: flex; align-items: center; justify-content: center; }
/* Bug fix: .tt-canvas-el-shape-ellipse (border-radius:50%, defined much earlier in this file for
   Freeform) and .tt-canvas-flow-shape (border-radius:2px, above) are both single-class selectors of
   EQUAL specificity — on an element carrying BOTH classes (an Ellipse shape in Flow), the one later
   in SOURCE ORDER wins, which was this file's own 2px, silently making every "Ellipse" render as a
   plain rounded rect indistinguishable from a Rectangle. Reported directly: "when I select add
   ellipse... it's adding a rectangle instead" (the shape kind WAS correct — RenderFlowShape/
   RenderFlowGrid's add-menu were never the bug — only this cascade collision was). Fixed with a
   combined-class selector, which is MORE specific than either single-class rule and so wins
   regardless of source order, without touching the shared .tt-canvas-el-shape-ellipse rule itself
   (still used elsewhere) or depending on fragile ordering. */
.tt-canvas-flow-shape.tt-canvas-el-shape-ellipse { border-radius: 50%; }
.tt-canvas-flow-shape-line { height: 0; background: none !important; }
.tt-canvas-flow-shape-controls { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; margin-top: 4px; }

/* Slash-command menu (v2, new) -- appears right under a text field the instant its content becomes
   exactly "/" (see OnFlowTextChanged), offering the same insert choices as the hover "+" insert zone.
   A plain flex row of buttons, not a MudMenu popover -- avoids needing a Dictionary<Guid, MudMenu> of
   @ref's for a component rendered per-block in a loop, and reads as an inline palette either way. */
.tt-canvas-flow-slashmenu {
    display: flex; flex-wrap: wrap; gap: 2px; margin-top: 4px; padding: 4px;
    border: 1px solid var(--mud-palette-lines-default); border-radius: 4px;
    background: var(--mud-palette-surface); box-shadow: var(--mud-elevation-3);
}

.tt-canvas-flow-embed { max-width: 320px; }
.tt-canvas-flow-embed-open { cursor: pointer; }
.tt-canvas-flow-embed-open:hover { border-color: var(--mud-palette-primary); }

/* Symbol in a Document (CanvasFlowSurface.RenderFlowSymbol) -- a live-linked preview, sized to the
   symbol definition's own aspect ratio rather than stretching to the full document column (most
   symbols are small reusable pieces -- a logo, an icon, a little diagram -- not full-width content).
   Reuses .tt-canvas-symbol-inner/-connectors/-connector as-is (already position:absolute + flex-
   centered / positioned) for its sub-elements and any connectors between them, which is why THIS
   wrapper needs position:relative -- the same role CanvasSurface's own .tt-canvas-el plays for those
   classes there. Width is INLINE (el.Width, default 280px) rather than a fixed CSS max-width -- see
   the resize slider below -- max-width:100% still caps it at the document column on a narrow page. */
.tt-canvas-flow-symbol {
    position: relative; max-width: 100%;
    border: 1px solid transparent; border-radius: 4px;
    background: var(--mud-palette-surface); cursor: default; overflow: hidden;
}
.tt-canvas-flow-symbol:hover,
.tt-canvas-flow-symbol--selected { border-color: var(--mud-palette-primary); }
.tt-canvas-flow-symbol-controls { display: flex; align-items: center; gap: 8px; margin-top: 4px; }

/* Row (Confluence-style layout, CanvasFlowSurface's Row block) -- each column is its own mini
   vertical stack of text blocks, laid out side by side via flex. Column COUNT is v2, new (see
   CanvasElement.RowColumnCount) -- was a fixed 2 for v1. */
.tt-canvas-flow-row { display: flex; gap: 20px; width: 100%; }
.tt-canvas-flow-row-col { position: relative; flex: 1 1 0; min-width: 0; }
.tt-canvas-flow-row-block {
    display: flex; align-items: flex-start; gap: 4px;
    border-radius: 4px; padding: 2px; margin-bottom: 4px;
}
.tt-canvas-flow-row-block:hover { background: color-mix(in srgb, var(--mud-palette-primary) 4%, transparent); }
/* Both conditionally RENDERED now (only when the parent Row is the selected block), same reasoning
   as .tt-canvas-flow-controls above — no opacity dimming needed. */
.tt-canvas-flow-row-block-del { flex-shrink: 0; }
.tt-canvas-flow-row-add { text-transform: none; }

/* Column insert/delete (v2, new) -- "how can we put also a button to add another column or to
   insert between two columns?" A thin hover strip sits before the first column, between each pair,
   and after the last (N+1 total for N columns, so every position is reachable), same "hover reveals
   a small circular +" idiom as RenderInsertZone's own divider, just a single direct action instead
   of a type-picker menu (a Row column has no "type" to choose). Deliberately narrow (flex-basis, not
   a big reserved gap) with the button itself allowed to overflow slightly via negative margin, so it
   doesn't visibly widen the space between columns while idle. The per-column delete "x" sits in that
   column's own top-right corner, hover-revealed, and is never rendered at all when it's the row's
   only remaining column (see RenderTopLevelBlock's own rowCols > 1 check) -- a Row always needs at
   least one column. */
.tt-canvas-flow-row-colinsert { flex: 0 0 10px; display: flex; align-items: flex-start; justify-content: center; }
.tt-canvas-flow-row-colinsert button {
    margin: 2px -4px 0; width: 18px; height: 18px; border-radius: 50%; padding: 0;
    border: 1px solid var(--mud-palette-lines-default);
    background: color-mix(in srgb, var(--mud-palette-primary) 12%, var(--mud-palette-surface));
    color: var(--mud-palette-primary); font: 13px/16px sans-serif; cursor: pointer;
    opacity: 0; pointer-events: none; transition: opacity .12s, background .12s, color .12s;
}
.tt-canvas-flow-row-colinsert:hover button { opacity: 1; pointer-events: auto; }
.tt-canvas-flow-row-colinsert button:hover { background: var(--mud-palette-primary); color: var(--mud-palette-primary-text); }
.tt-canvas-flow-row-col-del {
    position: absolute; top: -4px; right: -4px; z-index: 2;
    opacity: 0; pointer-events: none; transition: opacity .12s;
}
.tt-canvas-flow-row-col:hover .tt-canvas-flow-row-col-del { opacity: 1; pointer-events: auto; }

/* Table in a Document (CanvasFlowSurface.RenderFlowTable) -- a real HTML <table>, natural content-
   driven row height (unlike Freeform's fixed-height CSS-grid simulation), full column width via
   table-layout:fixed + <colgroup><col> percentages. Reuses CanvasSurface's own .tt-canvas-table-plus/
   -x buttons/CSS for a visually uniform look ("parity between the document and the whiteboard"). v2:
   insert/delete buttons are now ALWAYS RENDERED (see RenderFlowTable), with the hover/selected reveal
   rule near .tt-canvas-flow-resizable below doing the "no buttons in a clean/printable document"
   gating instead of a selection-gated Razor @if -- no more click-to-select-the-table-first step. The
   cell's own dashed border stays always-visible regardless -- that's real table structure a reader
   needs to see, not editing chrome. No overflow:hidden/auto on the cell (unlike Freeform's Table/Grid,
   which need it to scroll oversized nested content) -- a <td>'s natural overflow:visible is exactly
   what these negative-offset buttons need. */
.tt-canvas-flow-table { width: 100%; border-collapse: collapse; margin: 4px 0 10px; table-layout: fixed; }
.tt-canvas-flow-table-cell {
    position: relative; border: 1px dashed var(--mud-palette-lines-default);
    padding: 6px 10px; vertical-align: top; background: var(--mud-palette-surface);
}
/* Per-cell background swatch -- revealed via :focus-within on the whole <td> (same pure-CSS trigger,
   and same reasoning, as the QuillEditor's own floating toolbar: reacts to the QuillEditor inside
   gaining focus too, not just a click directly on the swatch itself), instead of the table needing to
   be selected at all. Pinned INSIDE the cell's own top-right corner rather than floating above it —
   an earlier pass floated it in the SAME row as the QuillEditor's own toolbar (bottom:100%, like the
   toolbar itself), which on any cell narrower than the toolbar's own natural width put the swatch
   (higher z-index) visibly on top of the toolbar's own buttons. The toolbar's rendered width isn't
   knowable from pure CSS (it varies with the compact preset's own button count), so rather than guess
   a safe horizontal offset, this sidesteps the collision entirely by not sharing that floating row at
   all. Direct feedback: "the background of the cell is a drop down inside the cell... move it to...
   the floating toolbar," then a follow-up once that overlapped: "the toolbar is now showing on top of
   other buttons... align it to the end." */
.tt-canvas-flow-table-cell-bg {
    position: absolute; top: 4px; right: 4px; z-index: 4;
    opacity: 0; pointer-events: none; transition: opacity .12s;
}
.tt-canvas-flow-table-cell:focus-within .tt-canvas-flow-table-cell-bg { opacity: 1; pointer-events: auto; }
/* Forces the swatch to stay visible/clickable for as long as ITS OWN popover is open (CadColorButton's
   IsOpenChanged, tracked in RenderFlowTable), overriding the :focus-within rule above -- CadColorButton's
   popover renders through MudPopover, which portals its actual content OUTSIDE this <td>'s own DOM
   subtree, so clicking into the picker moves focus away and the cell stops being :focus-within while
   the picker is still open. Without this the button (and its own click-outside-to-close backdrop, a
   CHILD of it) went pointer-events:none the instant the picker was clicked into, leaving it stuck open
   with nothing left to click to close it. Reported directly: "it stays open once I open the color
   picker." !important because this must win over the plain :focus-within rule regardless of which one
   happens to be more specific/later in the cascade. */
.tt-canvas-flow-table-cell-bg--open { opacity: 1 !important; pointer-events: auto !important; }

/* Grid in a Document (CanvasFlowSurface.RenderFlowGrid) -- the "container grid" companion to Table:
   cells hold real embedded elements via recursion into RenderBlock, not plain text. A CSS grid of
   divs (grid-template-columns from TableColWidths percentages), not a <table> -- cell content is
   arbitrary blocks (Shape/Text/Table/...), not just text. Row/col insert-delete buttons reuse the
   same .tt-canvas-table-plus/-x classes as Table -- v2: ALWAYS RENDERED now too (see RenderFlowGrid),
   same hover/selected reveal rule as Table's own, near .tt-canvas-flow-resizable below.
   The dashed cell border is unconditional for a genuinely EMPTY grid (no children in ANY cell) --
   otherwise there'd be nothing at all to see or click on. Once at least one cell has real content
   (.tt-canvas-flow-grid--has-content, set in RenderFlowGrid), the borders switch to hover/selected-
   only -- real content already gives the grid visible shape, so the dashed outline is only needed
   as active editing chrome from then on, same "clean/printable document" reasoning as everywhere
   else in this file. Reported directly: "if the container grid has something inside, we will not
   show the dashed lines." */
.tt-canvas-flow-grid { display: grid; gap: 0; width: 100%; margin: 4px 0 10px; }
.tt-canvas-flow-grid-cell {
    position: relative; border: 1px dashed transparent;
    padding: 8px; background: var(--mud-palette-background-grey);
    display: flex; flex-direction: column; gap: 6px; min-height: 36px;
}
.tt-canvas-flow-grid:not(.tt-canvas-flow-grid--has-content) .tt-canvas-flow-grid-cell {
    border-color: var(--mud-palette-lines-default);
}
.tt-canvas-flow-resizable:hover .tt-canvas-flow-grid--has-content .tt-canvas-flow-grid-cell,
.tt-canvas-flow-block--selected .tt-canvas-flow-grid--has-content .tt-canvas-flow-grid-cell {
    border-color: var(--mud-palette-lines-default);
}
.tt-canvas-flow-grid-child { position: relative; }
/* Each child's own delete "x" (.tt-canvas-flow-grid-child-del) is always rendered (v2), hover-
   revealed on the child itself -- position:absolute relative to the CHILD's own small box, so it
   never affects the cell's own layout height either way.
   The per-cell "+" add-content menu (.tt-canvas-flow-grid-add) went through TWO failed attempts
   before landing back here at conditionally-RENDERED-when-selected (its original, pre-v2 behavior):
   (1) always-rendered + flex-column child + opacity toggle stretched every cell to the button's own
   height even while invisible, reading as a permanent empty gap below short content; (2) always-
   rendered + position:absolute fixed that but then had zero clearance once actually revealed,
   overlapping the last child's own content, and a fixed reserved bottom-padding to compensate
   created a gap of its OWN between grid rows even while nothing was selected. Reported directly (2):
   "the button to add is overlapping between the last element of the cell" then (3): "there is a gap
   between them because of the button... this button appear only, and this space appear only when
   the row is selected." Conditionally rendering it (IsSelected(el), matching RenderFlowGrid/
   RenderFlowExpand's own Razor) sidesteps the whole class of bugs at once: not in the DOM at all
   when not selected (zero space, zero gap), a normal flex-column sibling of the real content when it
   is (natural reflow, no absolute-position overlap to reason about). */
.tt-canvas-flow-grid-child-del {
    position: absolute; top: -2px; right: -2px; z-index: 2;
    opacity: 0; pointer-events: none; transition: opacity .12s;
}
.tt-canvas-flow-grid-child:hover .tt-canvas-flow-grid-child-del { opacity: 1; pointer-events: auto; }
.tt-canvas-flow-grid-add { align-self: flex-start; opacity: 0.7; }
.tt-canvas-flow-grid-add:hover { opacity: 1; }

/* Column-resize drag (canvasFlowInterop.js) -- shared by both Table and Grid. .tt-canvas-flow-resizable
   is the plain wrapper RenderFlowTable/RenderFlowGrid put around the <table>/.tt-canvas-flow-grid so
   the resize handles (siblings of the table/grid, not children -- a <table> can't host a bare <div>
   child) have something to position themselves against. Same geometry/z-index/hover treatment as
   Freeform's own .tt-canvas-table-colresize/.tt-canvas-grid-colresize -- z-index MUST stay 4 (below
   .tt-canvas-table-plus/-x's own 5), matching Freeform exactly: this handle spans the table's FULL
   height (top:0;bottom:0), and an earlier pass used z-index:6 here, which put it ABOVE the insert-
   row/column buttons wherever they geometrically overlapped, silently blocking clicks to them.
   Reported directly: "I cannot add more rows or columns inside a table/grid container." Still
   selection-gated (see the Razor markup) -- resize wasn't part of the v2 hover-only rework, only
   add/delete row-column was (see the plan's own scoping). */
.tt-canvas-flow-resizable { position: relative; }
.tt-canvas-flow-colresize {
    position: absolute; top: 0; bottom: 0; width: 8px; margin-left: -4px;
    cursor: col-resize; z-index: 4; pointer-events: all;
}
.tt-canvas-flow-colresize:hover { background: color-mix(in srgb, var(--mud-palette-primary) 35%, transparent); }
/* v2: the old always-visible .tt-canvas-flow-container-handle (a small labeled click target that
   selected the table/grid, working around the buttons below only rendering once selected) is gone --
   the buttons themselves are hover/selected-revealed now instead, same idiom as .tt-canvas-flow-
   controls, so there's no more chicken-and-egg problem left to work around. Reveals on EITHER hovering
   anywhere in the table/grid (no more precise "hover exactly the right edge" requirement -- simpler
   and still satisfies "no click-to-select-first needed") OR the block being the selected one, for
   touch/keyboard users who can't hover. */
.tt-canvas-flow-resizable .tt-canvas-table-plus,
.tt-canvas-flow-resizable .tt-canvas-table-x { display: none; }
.tt-canvas-flow-resizable:hover .tt-canvas-table-plus,
.tt-canvas-flow-resizable:hover .tt-canvas-table-x,
.tt-canvas-flow-block--selected .tt-canvas-table-plus,
.tt-canvas-flow-block--selected .tt-canvas-table-x { display: flex; }
