/* ===================================================================
   Merge / Insert floating window (left column)
=================================================================== */

.tool-window-title {
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--dim);
  margin-bottom: 12px;
  padding-bottom: 7px;
  border-bottom: 1px solid var(--line);
  flex-shrink: 0;
}



.tool-window .section-label {
  font-size: 10px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--dim);
  margin-bottom: 10px;
  padding-bottom: 7px;
  border-bottom: 1px solid var(--line);
}


/* Palindrome display */
#palindrome-display {
  flex-shrink: 0;
  /* Bottom padding is reduced relative to top. The center cluster
     (reverse-dot + connect button) stacks two buttons vertically and
     because #cell-view's baseline aligns with the text (not with the
     cluster's center), the cluster's extra height visually trickles
     downward. Balancing the container padding restores symmetric
     breathing room above and below the text. */
  padding: 20px 28px 10px;
  background: rgba(240, 237, 233, 0.35);
  border-bottom: 1px solid var(--line);
  min-height: 120px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.cell-empty {
  color: var(--faint);
  font-size: 13px;
  letter-spacing: 0.12em;
}

.cell-sides {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  width: 100%;
  min-width: 0;
}

.cell-side {
  overflow: visible;
  min-width: 0;
  transform: translateZ(0);
  position: relative;
  /* Search spots in the text flow (owner 2026-10-02): the side is ONE run of inline content,
     [start spot][letters][end spot], so the browser itself puts each spot right before the first letter and
     right after the last one, on whatever line that falls. (It used to be a flex row that pinned the spots to
     the cell's edges: on a wrapped cell the end spot sat at the far edge although the text ended mid-line.)
     The letters container (.cell-letters) stays the same element with the same children, so the renderer's
     content-patch path (js/03a-cell-render.js) is untouched; only its display changes, below. */
  display: block;
}
/* Same for the feed composer's halves, which were plain blocks (spots on their own lines above and below). */
:is(.cell-side, .fc-cell-left, .fc-cell-right) > .cell-letters { display: inline; }
/* The spots take NO room in the line (css/13: out of flow at their static position), so they never cause or move a
   line break: the text wraps exactly as if they were not there, and each spot simply hangs beside its letter. The
   18px side padding is where a spot hangs when its letter sits at the very edge of the line, so it never covers the
   middle buttons. */
.cell-side,
.cell-sides.cell-connected,
.fc-cell .fc-cell-left,
.fc-cell .fc-cell-right { padding-left: 18px; padding-right: 18px; }
/* .cell-sides is width:100%: count the padding inside it, or a joined segment grows 36px past its parent. */
.cell-sides.cell-connected { box-sizing: border-box; }
/* A preserved space ENDING the text hangs past a full line, and the break opportunity after it would put the end
   spot's static position on the next line (9 of 4 x 121 cases, _temp/k23/trailing_space_probe.mjs). The last real
   child and any caret spans after it get pre: same spacing, no break after them. */
:is(.cell-side, .fc-cell-left, .fc-cell-right) > .cell-letters
  > :is(.cell-word, .cell-char, .cell-spacer, .cell-placeholder, .typing-cursor, .typing-cursor-mirror):not(:has(~ :not(.typing-cursor, .typing-cursor-mirror))) {
  white-space: pre;   /* ONE compound selector: a line break before :not() would make it a descendant selector */
}
/* (text spans only) */
.cell-side.side-left .cell-letters {
  /* white-space: pre-wrap preserves spaces but wraps when content overflows */
  white-space: pre-wrap;
  overflow-wrap: break-word;
  word-break: normal;
  /* no end padding (was 4px, room beside the old edge strip): the search slot now follows the text, 3px off */
  text-align: right;
}
.side-left .cell-letters { text-align: right; }
.side-right .cell-letters { text-align: left; }
.cell-side.side-right .cell-letters {
  white-space: pre-wrap;
  overflow-wrap: break-word;
  word-break: normal;
  text-align: left;
  /* no padding (was 4px / 6px, room beside the old edge strips): the search slots now sit 3px off the text */
}
.side-left  { text-align: right; }
.side-right { text-align: left; }

.cell-sep {
  flex: 0 0 auto;
  color: rgba(130, 124, 115, 0.45);
  font-size: 24px;
  line-height: 1;
  padding: 0 10px;
  align-self: center;
  user-select: none;
  -webkit-user-select: none;
}

/* Center action cluster: three tiny buttons stacked, minimal vertical footprint */
.cell-center-cluster {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  /* M1 (Hadrian 2026-06-21): nudge the join+switch cluster vertically. position:relative below makes
     `top` a pure visual offset (no reflow). Goal: the top dot (join) sits the same distance from the
     segment's TOP edge as the bottom dot (reverse) sits from its BOTTOM edge, keeping the inter-dot gap.
     Bracketed empirically: +6px read TOO LOW, -2px read TOO HIGH -> balanced midpoint = +2px. Nudge +-1px
     if a hair off (more positive = lower). (2026-06-23) */
  top: 2px;
  /* Owner 2026-09-21: the two center buttons were too small to click. The gap is widened so each
     button's hit target can expand vertically without the two bands overlapping (dot expands 4px
     each way, connect 6px each way -> 10px < the 12px gap).
     Owner 2026-09-24: the two buttons read a bit too far apart -> pulled to 8px. The reverse-dot's
     hit target extends only 4px up into the gap and the connect button carries no vertical padding
     expansion (height 10px, dots centered), so at 8px the two clickable bands still clear each
     other (8 - 4 = 4px gap); no overlap.
     Owner 2026-09-26: pulled a bit closer again to 6px -> still 6 - 4 = 2px clearance, no overlap.
     Owner 2026-09-27: "just close enough for the animation of the lower button on hover not to overlap
     the upper button". Geometry (upper = .btn-connect 10px box, 5px dots centered; lower = 14px
     .cell-center-dot whose .rdot-l arcs to 6px above centre, i.e. its top edge reaches 8.5px above the
     dot centre = 1.5px above the dot box): visible clearance at the arc's peak = gap + 5 - 2.5 + 7 - 8.5
     = gap + 1. At gap 1px -> 2px clearance at the peak, 8px at rest. The reverse-dot's upward hit-padding
     is cut to 1px (see .cell-center-dot) so the two click bands still never overlap (1 - 1 = 0). */
  gap: 1px;
  padding: 0 4px;
  position: relative;
}
/* Owner 2026-10-02: "sometimes when my intention is to hit the inner search points, I accidentally hit one of the
   two middle buttons". The reverse dot's old hit box reached 16px sideways (negative margins) and covered the
   inner-left search handle. Now each button owns exactly one half of the middle column, top to bottom of the
   segment, and nothing reaches into the cells, where the search handles sit:
   - the column stretches over the full segment height (it no longer needs the old +2px nudge: the dots are
     placed from the column's midline below, at the very same pixels as before);
   - the join button fills the top half, the reverse dot the bottom half; the column is 30px wide.
   Dot placement (measured before the change, 2026-10-02): join dots 5px above the midline gap, reverse dot
   9px below it. */
.cell-center-cluster.cell-center-cluster {   /* doubled class: outranks the base rule above in every context (Studio, feed composer, Setup) */
  align-self: stretch;
  top: 0;
  width: 30px;
  min-height: 25px;   /* the old cluster height (10 + 1 + 14): a one-line segment keeps its 25px row */
  padding: 0;
  box-sizing: border-box;
}
.cell-center-cluster > .btn-connect,
.cell-center-cluster > .cell-center-dot {
  flex: 1 1 0;
  width: 100%;
  height: auto;
  padding: 0;
  margin: 0;
  box-sizing: border-box;
}
.cell-center-cluster > .btn-connect { min-height: 8px; }    /* dot centre 5px up + 2.5px radius */
.cell-center-cluster > .btn-connect .dot-l,
.cell-center-cluster > .btn-connect .dot-r { top: calc(100% - 5px); }
.cell-center-cluster > .cell-center-dot { min-height: 12px; }  /* dot centre 9px down + 2.5px radius; keeps a one-line (25px) segment split evenly */
.cell-center-cluster > .cell-center-dot .rdot { top: 9px; }
/* Setup fields hide the join button (css/18-profile.css): the reverse dot then owns the whole column and sits
   where it sat before (3.5px below the column's centre). */
#setup-content .su-seg-cell .cell-center-cluster > .cell-center-dot .rdot { top: calc(50% + 3.5px); }
/* Empty segment: center cluster faded and non-interactive */
.cell-center-cluster-empty { opacity: 0.3; pointer-events: none; }

/* Reverse-dot: single dot at rest; on hover the two pseudo-dots orbit 180°
   around the center  -  one arcs UP-and-over, the other DOWN-and-under.
   This communicates "the two sides swap" via circular rotation. */
.cell-center-dot {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
  flex-shrink: 0;
  font-size: 0;
  /* Owner 2026-09-21: bigger click target. Wide horizontal expansion is free (no neighbour there);
     vertical is expanded 4px each way, which stays within the widened 12px cluster gap so it never
     covers the connect button above. */
  padding: 1px 16px 4px;   /* top 1px: the cluster gap is 1px (owner 2026-09-27), keep clear of connect */
  margin: -1px -16px -4px;
  box-sizing: content-box;
  color: transparent;
  overflow: visible;
}
.rdot {
  position: absolute;
  /* Matched to the .btn-connect .dot-l/.dot-r size (5px) so the reverse-dot
     above and the connect-dots below read as uniformly-sized elements. */
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--cdot-rest);
  left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  /* No transition here: JS handles transitions on return */
}
/* At rest .rdot-r lies exactly under .rdot-l: keep it clear so the overlap does not darken the dot. */
.rdot-r { background: rgba(130, 124, 115, 0); }
/* Forward animation class  -  added by JS on mouseenter */
.rdot-l.rdot-fwd { animation: dot-reverse-l 0.75s ease-in-out forwards; }
.rdot-r.rdot-fwd { animation: dot-reverse-r 0.75s ease-in-out forwards; }
/* Dot arc: split sideways 33%, then circular arc 33-100%.
   Total 0.75s; split = 0.25s; arc = 0.50s. */
@keyframes dot-reverse-l {
  0%   { transform: translate(-50%,-50%); background: var(--cdot-rest); }
  33%  { transform: translate(calc(-50% - 6px),-50%); background: var(--blue,#3574c4); }
  51%  { transform: translate(calc(-50% - 4px),calc(-50% - 4px)); }
  67%  { transform: translate(-50%,calc(-50% - 6px)); }
  83%  { transform: translate(calc(-50% + 4px),calc(-50% - 4px)); }
  100% { transform: translate(calc(-50% + 6px),-50%); }
}
@keyframes dot-reverse-r {
  0%   { transform: translate(-50%,-50%); background: rgba(130, 124, 115, 0); }
  33%  { transform: translate(calc(-50% + 6px),-50%); background: var(--blue,#3574c4); }
  51%  { transform: translate(calc(-50% + 4px),calc(-50% + 4px)); }
  67%  { transform: translate(-50%,calc(-50% + 6px)); }
  83%  { transform: translate(calc(-50% - 4px),calc(-50% + 4px)); }
  100% { transform: translate(calc(-50% - 6px),-50%); background: var(--cdot-rest); }   /* its base is clear (rest), so name the end colour */
}
.cell-center-btn {
  background: none;
  border: 1px solid transparent;
  border-radius: 4px;
  color: rgba(130, 124, 115, 0.55);
  font-size: 10px;
  line-height: 1;
  padding: 6px 16px;   /* owner 2026-09-21: bigger click target (was 5px 10px) */
  cursor: pointer;
  white-space: nowrap;
  transition: color 0.12s, border-color 0.12s, background 0.12s;
  user-select: none;
}
.cell-center-btn:hover {
  color: var(--blue);
  border-color: rgba(53,116,196,0.35);
  background: rgba(53,116,196,0.06);
}
/* No box around the dot buttons in any state (owner 2026-09-30: "without strange 'boxes' around them that
   would visually obstruct the other dot"): the dots alone carry the state. */
.cell-center-btn.btn-connect,
.cell-center-btn.btn-connect:hover,
.cell-center-btn.btn-connect.connected { border-color: transparent; background: none; }
.cell-center-btn.btn-connect.connected { color: var(--green, #3a7050); }

/* Layout button: always present, space always reserved */
.btn-layout {
  display: inline-flex; align-items: center; justify-content: center;
  position: relative; width: 14px; height: 10px;
  padding: 0; border: none; background: none;
  opacity: 0.25; pointer-events: none; cursor: default; flex-shrink: 0;
}
.btn-layout .dot-l,
.btn-layout .dot-r {
  position: absolute; width: 4px; height: 4px; border-radius: 50%;
  background: var(--cdot-rest);
  transition: transform 0.2s ease, background 0.15s;
  transform: translate(0,0);
}
.btn-layout.active { opacity: 1; pointer-events: auto; cursor: pointer; }
.btn-layout.active .dot-l { transform: translate(-3px,-3px); background: var(--green,#3a7050); }
.btn-layout.active .dot-r { transform: translate(3px,3px);   background: var(--green,#3a7050); }
.btn-layout.active:hover .dot-l { transform: translate(0,0); background: var(--cdot-hover); }
.btn-layout.active:hover .dot-r { transform: translate(0,0); background: var(--cdot-hover); }
.btn-layout:not(.active) { pointer-events: auto; cursor: not-allowed; }
.btn-layout:not(.active):hover .dot-l { background: var(--cdot-rest); animation: dot-layout-l 0.5s ease forwards; }
.btn-layout:not(.active):hover .dot-r { background: var(--cdot-rest); animation: dot-layout-r 0.5s ease forwards; }
@keyframes dot-layout-l {
  0%   { transform: translate(0,0); }
  40%  { transform: translate(-5px,0); }
  100% { transform: translate(-3px,-4px); }
}
@keyframes dot-layout-r {
  0%   { transform: translate(0,0); }
  40%  { transform: translate(5px,0); }
  100% { transform: translate(3px,4px); }
}
.btn-layout-empty { opacity: 0.25; pointer-events: none; }

/* Connect button: two dots slide to perfectly overlap on join */
.btn-connect {
  display: inline-flex; align-items: center; justify-content: center;
  position: relative; width: 14px; height: 10px;
  padding: 0; border: none; background: none;
  cursor: pointer; user-select: none; flex-shrink: 0;
}
.btn-connect .dot-l,
.btn-connect .dot-r {
  position: absolute;
  /* Centering so dots align with the reverse dot above */
  left: 50%; top: 50%;
  display: inline-block;
  /* Both dots identical size in ALL states  -  the merged visual is just two overlapping same-size dots */
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--cdot-rest);
  /* Longer transition (0.45s) gives a readable animation and allows smooth reversal
     when the cursor leaves mid-animation  -  CSS transitions always reverse from their
     current interpolated position, so no flickering on early exit. */
  transition: transform 0.45s cubic-bezier(0.4, 0, 0.2, 1), background 0.2s;
  will-change: transform;
}
/* Rest state: dots separated ±4px from center */
.btn-connect .dot-l { transform: translate(calc(-50% - 4px), -50%); }
.btn-connect .dot-r { transform: translate(calc(-50% + 4px), -50%); }
/* Hover: both slide to exact center  -  perfectly overlap into a single merged dot */
.btn-connect:hover .dot-l { transform: translate(-50%, -50%); background: var(--blue, #3574c4); }
.btn-connect:hover .dot-r { transform: translate(-50%, -50%); background: var(--blue, #3574c4); }
/* Connected: dots merged at center, green */
.btn-connect.connected .dot-l { transform: translate(-50%, -50%); background: var(--green, #3a7050); }
.btn-connect.connected .dot-r { transform: translate(-50%, -50%); background: var(--green, #3a7050); }
/* Connected hover: separate to signal "split available" */
.btn-connect.connected:hover .dot-l { transform: translate(calc(-50% - 4px), -50%); background: var(--cdot-hover); }
.btn-connect.connected:hover .dot-r { transform: translate(calc(-50% + 4px), -50%); background: var(--cdot-hover); }

/* Post-Sop-while-unconnected nudge (owner 2026-09-27): pulse the connect dots red ONCE, gently.
   Class is added by _fccNudgeUnify() and removed on animationend so it can re-fire on the next click. */
.btn-connect.glow-red .dot-l,
.btn-connect.glow-red .dot-r { animation: connect-nudge-red 0.85s ease-in-out 1; }
@keyframes connect-nudge-red {
  0%   { background: var(--cdot-rest); box-shadow: 0 0 0 0 rgba(196,53,53,0); }
  35%  { background: var(--red, #c43535); box-shadow: 0 0 6px 2px rgba(196,53,53,0.5); }
  100% { background: var(--cdot-rest); box-shadow: 0 0 0 0 rgba(196,53,53,0); }
}

/* Connected mode: single unified text field */
.cell-sides.cell-connected {
  /* Search spots in the text flow (owner 2026-10-02): [start spot][field][end spot] as one inline run, so the end
     spot follows the last letter instead of the far edge (it was a flex row with the field stretched between). */
  display: block;
  /* #5 (owner 2026-07-18): a joined segment fades into a slightly golden tint -- once joined it is its own
     unified field (required to post), visually distinct from the still-composed split segments. */
  background: linear-gradient(180deg, rgba(214,178,74,0.16), rgba(214,178,74,0.09));
  border-radius: 8px;
  box-shadow: inset 0 0 0 1px rgba(214,178,74,0.28);
  transition: background 0.6s ease, box-shadow 0.6s ease;
}
.cell-connected-field {
  flex: 1;
  /* white-space: pre preserves spacer characters exactly like .cell-letters does in split mode */
  white-space: pre;
  font: 500 15px/1.8 var(--mono);
  line-height: 1.8;
  min-width: 0;
  overflow: visible;
  /* Use inline-block so the pre whitespace model applies to all children */
  display: inline-block;
}
.cell-half-left  { display: inline; }
.cell-half-right { display: inline; }
/* Connected-middle: the right side's first letter is the hidden duplicate of the
   shared odd-length center, so the segment reads as one continuous text. */
.cell-char.cell-center-hidden { display: none; }
.cell-connected-seam {
  display: inline;
  color: rgba(58,112,80,0.55);
  font-size: 18px;
  line-height: 1;
  padding: 0 2px;
  user-select: none;
  -webkit-user-select: none;
  flex-shrink: 0;
}
/* In connected mode the split-mode inner handles don't exist in the DOM;
   the connected btn floats inline after the outer-right handle */
.cell-sides.cell-connected .btn-connect {
  flex-shrink: 0;
}
/* Split chip: sits bottom-left on the seg-box when joined, mirrors seg-save-btn position */
.seg-split-chip {
  position: absolute;
  bottom: 3px;
  left: 24px;
  z-index: 2;
}

/* Constrain the letters block to fit-content so flex:1 side doesn't spread letters */
.cell-letters {
  display: inline-block;
  line-height: 1.3;
  white-space: pre-wrap;
  overflow-wrap: break-word;
  word-break: normal;
  min-width: 0;
}

.cell-side-label {
  font-size: 9px;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--faint);
  margin-bottom: 6px;
}

.cell-char {
  display: inline-block;
  width: auto;
}

/* Word grouping (Hadrian 2026-06-18): the cell renderer wraps each word's letters in .cell-word
   so a long segment wraps to a new line ONLY between words, never splitting a word mid-letter.
   The inline-block letters inside must not break; the wrap points are the spacers between words. */
.cell-word { white-space: nowrap; }

.cell-char {
  font-family: var(--mono);
  font-size: 15px;
  font-weight: 400;
  line-height: 1.4;
}

/* .side-left .ch color: inherited from parent via inline style */
/* .side-right .ch color: inherited from parent via inline style */

/* Spacers same color as letters  -  no reason to distinguish them visually */
.cell-spacer {
  font-family: var(--mono);
}
/* .side-left .cell-spacer color: inherited */
/* .side-right .cell-spacer color: inherited */

.cell-placeholder {
  font-family: var(--mono);
  font-size: 15px;
  color: var(--purple);
  cursor: pointer;
  padding: 0;
  border-bottom: none;
  opacity: 0.8;
  transition: opacity 0.12s;
  white-space: nowrap;
}
/* Owner 2026-10-02: a placeholder looks like the search spots' empty letter slot (css/13 .suggest-handle) instead
   of "[ _ ]"; hovering it (or lighting its pair, .ph-lit) also shows the spots' blue fade, here on both sides
   since a placeholder sits inside the text. The "_" stays in the DOM (carets and drops measure the span) but is
   not painted. */
.cell-placeholder {
  position: relative;
  display: inline-block;
  vertical-align: middle;   /* like the spots; a px offset hung it below the 21px line and grew the line */
  width: 16px;   /* same slot as the search spots, css/13 */
  height: 18px;
  margin: 0 2px;
  box-sizing: border-box;
  border: 1.5px solid currentColor;
  border-radius: 4px;
  line-height: 15px;
  overflow: visible;
  -webkit-text-fill-color: transparent;
}
.cell-placeholder::before {
  content: '';
  position: absolute;
  top: -4px; bottom: -4px; left: -28px; right: -28px;
  border-radius: 9px;
  background: linear-gradient(to right, transparent 0%, rgba(53,116,196,0.26) 38%, rgba(53,116,196,0.26) 62%, transparent 100%);
  opacity: 0;
  transition: opacity 0.12s;
  pointer-events: none;
}
.cell-placeholder:hover,
.cell-placeholder.ph-lit { opacity: 1; background: rgba(53,116,196,0.1); }
.cell-placeholder:hover::before,
.cell-placeholder.ph-lit::before { opacity: 1; }

/* Wordbank placeholder: mirror the segment's purple [ _ ] so a "_" inside an entry
   is noticeable rather than a bare underscore (Hadrian 2026-06-16). */
.wb-ph { color: var(--purple); font-weight: 700; opacity: 0.85; }
.wb-ph::before { content: '[\00a0'; opacity: 0.5; }
.wb-ph::after  { content: '\00a0]'; opacity: 0.5; }

/* Assumed seam: a single boundary letter borrowed from the active segment so a k=0
   (no-overlap) suggestion shows a visual join. Ghosted + dotted underline reads as
   "this letter is your segment's, shown for context" rather than part of the entry.
   Inherits the column color (si-left blue / si-right amber). (Hadrian 2026-06-18) */
.wb-seam {
  opacity: 0.6;
  border-bottom: 1px dotted currentColor;
  border-radius: 2px;
  padding: 0 1px;
  margin: 0 -1px;  /* visual-only highlight; do not displace text (Hadrian 2026-06-21) */
  background: rgba(127,127,127,0.16);
}
/* Wordbank suggestion seams (Hadrian 2026-06-20). Same dotted treatment, two colors:
   OVERLAP seam (blue) = letters the entry already shares with the segment, swallowed by the merge.
   BORROWED seam (violet) = existing segment letter(s) shown only when the entry has NO overlap.
   (Hadrian 2026-07-13: borrowed seam recolored green -> violet.) */
/* Colours (owner 2026-10-02): --seam-overlap / --seam-borrow, set by js/10e-utils-tabs.js from Setup > "Maestro sort-seam",
   never the user's theme colour nor each other. Dark mode lightens the ink for contrast. */
.wb-seam-overlap {
  color: var(--seam-overlap, #3574c4);
  border-bottom: 1px dotted var(--seam-overlap, #3574c4);
  background: color-mix(in srgb, var(--seam-overlap, #3574c4) 14%, transparent);
  border-radius: 2px;
  padding: 0 1px;
  /* Cancel the padding's layout width with a negative margin so the seam highlight is a pure
     visual ADDITION (background + dotted underline) and does NOT shift the text it covers or the
     text after it. The old padding-only version displaced the highlighted seam. (Hadrian 2026-06-21) */
  margin: 0 -1px;
}
.wb-seam-borrow {
  color: var(--seam-borrow, #8b5cf6);
  border-bottom: 1px dotted var(--seam-borrow, #8b5cf6);
  background: color-mix(in srgb, var(--seam-borrow, #8b5cf6) 16%, transparent);
  border-radius: 2px;
  padding: 0 1px;
  /* Cancel the padding's layout width with a negative margin so the seam highlight is a pure
     visual ADDITION (background + dotted underline) and does NOT shift the text it covers or the
     text after it. The old padding-only version displaced the highlighted seam. (Hadrian 2026-06-21) */
  margin: 0 -1px;
}


.cell-summary {
  margin-top: 10px;
  font-size: 10px;
  color: var(--faint);
  letter-spacing: 0.05em;
}

/* Typing cursor inside the cell.
   The main caret is the active-side cursor. Made deliberately taller than
   the mirror so the user can see at a glance which side they are currently
   typing on. 1px stroke  -  the height differential and full opacity already
   carry the prominence; thicker strokes read as oppressive. */
/* Caret = TRUE zero-layout-width (Hadrian 2026-07-17 fix): the old border-left(1px)+margin-right(-1px)
   "zero-width" trick still contributed BORDER-BOX width to the browser's line-wrap calculation, so the
   caret tipped its word (or a later word) over the wrap boundary  -  moving the caret with the arrow
   keys reflowed words to other lines even though no text changed. The element is now width:0 with the
   visible bar drawn by an absolutely-positioned ::before, which takes ZERO layout space, so caret
   position can never affect wrapping. */
.typing-cursor {
  display: inline-block;
  position: relative;
  width: 0;
  height: 24px;
  vertical-align: middle;
}
.typing-cursor::before {
  content: "";
  position: absolute;
  left: 0; top: 0;
  width: 1px;
  height: 100%;
  background: var(--blue);
  animation: blink 1s step-end infinite;
}
.side-right .typing-cursor::before { background: currentColor; }

/* Mirror cursor  -  solid but distinctly dimmer than the active one, so the
   user's eye is drawn to the main caret first. */
.typing-cursor-mirror {
  display: inline-block;
  position: relative;
  width: 0;
  height: 15px;
  vertical-align: middle;
}
.typing-cursor-mirror::before {
  content: "";
  position: absolute;
  left: 0; top: 0;
  width: 2px;
  height: 100%;
  background: var(--blue);
  opacity: 0.38;
  animation: blink-mirror 2s step-end infinite;
}
.side-right .typing-cursor-mirror::before { background: currentColor; }
.typing-cursor-mirror-empty::before {
  opacity: 0.55;
  background: var(--blue);
  animation: blink 1s step-end infinite; /* same rhythm as active cursor */
}
@keyframes blink-mirror { 0%,100%{opacity:0.38} 50%{opacity:0.08} }
@keyframes blink { 0%,100%{opacity:1} 50%{opacity:0} }

#cell-view {
  outline: none; cursor: text;
  font-family: var(--mono);
  font-size: 15px;
  line-height: 1.4;
  /* 500 instead of 600  -  most Google Fonts render 600 as quite heavy and
     the studio cell letters are large enough that the extra weight reads
     as oppressive. Medium gives the same legibility with less ink. */
  font-weight: 500;
  contain: layout style;
}
#cell-view:not(:focus) .typing-cursor::before { animation: none; opacity: 0.25; }
/* .side-left .cell-side-label: inherited */
.cell-active-right .side-right .cell-side-label { color: var(--seg-right-color, var(--amber)); }

/* Ctrl+A / Shift+Arrow palindrome selection highlight */
.cell-char.palin-sel,
.cell-spacer.palin-sel {
  background: rgba(53, 116, 196, 0.22);
  border-radius: 2px;
}
/* Mirror selection on the other side */
.cell-char.palin-sel-mirror,
.cell-spacer.palin-sel-mirror {
  background: rgba(53, 116, 196, 0.11);
  border-radius: 2px;
}

/* Native browser ::selection on inline-block .cell-char elements
   (Chromium does not paint ::selection background on inline-block by default;
   explicit pseudo-element forces it visible after the .ch inner span removal) */
.cell-char::selection { background: rgba(53, 116, 196, 0.30); }
.cell-spacer::selection { background: rgba(53, 116, 196, 0.30); }

/* Hover highlight on letters -- the hovered letter and its palindrome mirror */
/* Asymmetric transitions: near-instant ON (0.02s), slow gentle OFF (0.5s). */
.cell-char {
  transition: background 0.5s ease-out, color 0.5s ease-out;
  border-radius: 2px;
  will-change: background, color;
}
.cell-char.hover-lit {
  background: rgba(184, 48, 48, 0.18);
  color: #a02020;
  transition: background 0.02s ease-out, color 0.02s ease-out;
}
.cell-char.hover-mirror {
  background: rgba(154, 110, 24, 0.18);
  color: #7a5010;
  transition: background 0.02s ease-out, color 0.02s ease-out;
}

/* Composition banner hover highlight */
.comp-char {
  transition: background 0.5s ease-out, color 0.5s ease-out;
  border-radius: 2px;
}
.comp-char.comp-hover-lit {
  background: rgba(184, 48, 48, 0.18);
  color: #a02020;
  transition: background 0.02s ease-out, color 0.02s ease-out;
}
.comp-char.comp-hover-mirror {
  background: rgba(154, 110, 24, 0.18);
  color: #7a5010;
  transition: background 0.02s ease-out, color 0.02s ease-out;
}
.cell-char,
.cell-spacer {
  transition: background 0.25s ease;
}

/* Studio body (two-column) */
#studio-body {
  flex: 1;
  display: grid;
  grid-template-columns: 1fr 1fr;
  overflow: hidden;
}

.studio-section {
  padding: 18px 20px;
  border-right: 1px solid var(--line);
  overflow-y: auto;
}

.studio-section:last-child { border-right: none; }

.section-label {
  font-size: 10px;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--dim);
  margin-bottom: 14px;
}

/* Buttons */
.btn {
  background: rgba(255, 255, 255, 0.62);
  border: 1px solid rgba(180, 175, 168, 0.55);
  color: var(--txt);
  padding: 5px 12px;
  border-radius: 5px;
  font: 11px var(--mono);
  cursor: pointer;
  letter-spacing: 0.04em;
  transition: background 0.12s, color 0.12s, border-color 0.12s, transform 0.12s;
  box-shadow: 0 1px 3px rgba(0,0,0,0.06),
              inset 0 1px 0 rgba(255,255,255,0.85);
}
.btn:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.85);
  border-color: rgba(160, 155, 148, 0.70);
  box-shadow: 0 2px 8px rgba(0,0,0,0.09),
              inset 0 1px 0 rgba(255,255,255,0.90);
}
.btn:disabled { opacity: 0.35; cursor: not-allowed; }

.btn-blue   { background: rgba(53,116,196,0.88); border-color: rgba(40,95,170,0.70); color: #fff; }
.btn-blue:hover:not(:disabled)  { background: rgba(53,116,196,1); box-shadow: 0 2px 8px rgba(53,116,196,0.30); }
.btn-green  { background: rgba(46,122,80,0.85);  border-color: rgba(30,100,60,0.65); color: #fff; }
.btn-green:hover:not(:disabled) { background: rgba(46,122,80,1); }
.btn-red    { background: rgba(184,48,48,0.85);  border-color: rgba(160,32,32,0.65); color: #fff; }
.btn-red:hover:not(:disabled)   { background: rgba(184,48,48,1); }
.btn-amber  { background: rgba(154,110,24,0.85); border-color: rgba(130,88,12,0.65); color: #fff; }
.btn-amber:hover:not(:disabled) { background: rgba(154,110,24,1); }
.btn-pin    { background: rgba(138,96,8,0.82);   border-color: rgba(110,76,4,0.60);  color: #fff; }
.btn-pin:hover:not(:disabled)   { background: rgba(138,96,8,1); }
.btn-sm { padding: 3px 8px; font-size: 10px; }

/* Toolbar */
.toolbar {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin-bottom: 16px;
}

/* Form controls */
.op-block { margin-bottom: 14px; }
.op-label {
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--faint);
  margin-bottom: 5px;
  display: block;
}

.radio-row { display: flex; gap: 14px; }
.radio-row label {
  font-size: 12px;
  color: var(--dim);
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 5px;
}
.radio-row input[type=radio] { cursor: pointer; accent-color: var(--blue); }

.num-input {
  background: rgba(255,255,255,0.65);
  border: 1px solid rgba(175,170,163,0.50);
  color: var(--txt);
  padding: 5px 9px;
  border-radius: 5px;
  font: 13px var(--mono);
  width: 72px;
  box-shadow: inset 0 1px 3px rgba(0,0,0,0.05);
}

.text-input {
  background: rgba(255,255,255,0.65);
  border: 1px solid rgba(175,170,163,0.50);
  color: var(--txt);
  padding: 5px 9px;
  border-radius: 5px;
  font: 13px var(--mono);
  width: 60px;
  box-shadow: inset 0 1px 3px rgba(0,0,0,0.05);
}

.inline-row { display: flex; gap: 7px; align-items: center; }

/* Toast messages */
.toast {
  padding: 7px 11px;
  border-radius: 3px;
  font-size: 11px;
  margin-bottom: 10px;
  line-height: 1.4;
}
.toast-ok    { background: rgba(46,122,80,0.12);  border: 1px solid rgba(46,122,80,0.35);  color: var(--green); }
.toast-error { background: rgba(184,48,48,0.12); border: 1px solid rgba(184,48,48,0.35); color: var(--red); }

/* Merge panel */
#merge-panel {
  background: rgba(53,116,196,0.06);
  border: 1px solid rgba(53,116,196,0.22);
  border-radius: 6px;
  padding: 13px;
  margin-bottom: 12px;
}

#merge-panel .section-label {
  color: var(--blue);
  margin-bottom: 8px;
}

.merge-pair {
  font-size: 14px;
  margin-bottom: 12px;
  display: flex;
  align-items: center;
  gap: 8px;
}
.merge-pair .ml { color: var(--blue); font-weight: 600; }
.merge-pair .mr { color: var(--amber); font-weight: 600; }
.merge-pair .ma { color: var(--faint); font-size: 11px; }

/* Suggest results */
#suggest-results {
  margin-top: 8px;
}

.suggest-label {
  font-size: 9px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--faint);
  margin-bottom: 5px;
}
/* W4 (owner 2026-08-02): while a search runs, the label breathes so the Wordbank never reads as
   stuck. Pure CSS opacity pulse + three staggered dots - no JS, no timers, no repaint cost beyond
   the compositor's opacity channel. */
.suggest-label.searching { animation: wb-searching-pulse 1.4s ease-in-out infinite; }
.suggest-label.searching::after {
  content: '\2026';
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  animation: wb-searching-dots 1.4s steps(4, end) infinite;
  width: 0;
}
@keyframes wb-searching-pulse { 0%, 100% { opacity: 0.45; } 50% { opacity: 1; } }
@keyframes wb-searching-dots { to { width: 1.2em; } }

.suggest-empty {
  font-size: 11px;
  color: var(--faint);
  font-style: italic;
  padding: 4px 0;
}

/* Locked-suggest upsell (2026-09-22): non-subscriber teaser response. A single non-interactive-
   looking CTA, deliberately NOT a .suggest-item, so nothing in the merge/click pipeline can ever
   treat it as a mergeable row. */
/* Blurred nonsense rows shown ABOVE the CTA (owner 2026-09-26): they reuse .suggest-item structure, so
   they have the same shape as real entries, but are blurred + inert -- implying hidden, subscriber-only
   content. The gradient mask fades the list into the CTA so it reads as "more below". */
.suggest-locked-fakes {
  filter: blur(4px);
  opacity: 0.5;
  user-select: none;
  pointer-events: none;
  margin-bottom: 10px;
  -webkit-mask-image: linear-gradient(180deg, #000 40%, transparent);
  mask-image: linear-gradient(180deg, #000 40%, transparent);
}
.suggest-locked-fake { pointer-events: none; cursor: default; }
.suggest-locked-upsell {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 10px 12px;
  border: 1px dashed var(--faint);
  border-radius: 8px;
  cursor: pointer;
  background: color-mix(in srgb, var(--blue) 6%, transparent);
}
.suggest-locked-upsell:hover { background: color-mix(in srgb, var(--blue) 12%, transparent); }
.suggest-locked-upsell-msg {
  font-size: 12px;
  color: var(--dim);
  line-height: 1.4;
}
.suggest-locked-upsell-btn {
  flex-shrink: 0;
  font-size: 11px;
  font-weight: 800;
  color: #fff;
  background: var(--blue);
  border-radius: 14px;
  padding: 6px 14px;
  white-space: nowrap;
}

.suggest-item {
  display: grid;
  /* minmax(0, 1fr), NOT 1fr, on the two side tracks (owner report 2026-08-14: "some entries ...
     don't have the middle of the palindrome justified to the unified vertical line with all the
     other entries"). A bare `1fr` is `minmax(auto, 1fr)`: the AUTO minimum means the track refuses
     to shrink below its content's min-content width, so a row whose left or right half is longer
     than half the row widens its own track and shoves the centre marker off the shared line. The
     min-width:0 already set on .si-left/.si-right lets the ITEM shrink but does nothing about the
     TRACK's own auto minimum, which is exactly why the marker drifted on the long rows only.
     With an explicit 0 minimum both side tracks are always equal, so the marker sits on one fixed
     vertical line for every row in the list, whatever the text length. */
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: start;
  gap: 0 6px; /* column-gap only: breathing room so si-left/si-right text can never visually touch si-arr */
  padding: 6px 9px;
  border-radius: 5px;
  background: rgba(255,255,255,0.45);
  border: 1px solid rgba(185,180,173,0.40);
  margin-bottom: 4px;
  cursor: pointer;
  transition: border-color 0.1s, background 0.1s;
}

.suggest-item:hover {
  /* Slight accent-tinted highlight so rows read as clickable (owner 2026-09-27); dark twin in 17-dark-fixes. */
  background: color-mix(in srgb, var(--blue) 7%, rgba(255,255,255,0.8));
  border-color: color-mix(in srgb, var(--blue) 55%, transparent);
}

.suggest-item.active {
  background: rgba(53,116,196,0.10);
  border-color: var(--blue);
}

/* Bug fix 2026-08-07 (owner: "text should never overflow the div, but wrap ... to the previous line
   if it's in the left cell, or the next line if it's in the right cell"; separately reported both
   halves overlapping the middle two-way arrow). The DEFAULT/unclassified state must be wrap-safe on
   its own, not rely on _layoutTextItems always adding a .layout-* class before first paint (a
   documented prior failure mode, js/02e-suggest-render.js ~886-890). white-space:nowrap here with no
   overflow handling let an unclassified row's text burst past its 1fr grid track and paint straight
   through .si-arr: CSS grid does not clip a sibling item's overflow on its own, and the ancestor
   #suggest-results/.studio-section overflow-x:hidden (below, ~line 1270) only catches overflow of the
   whole scroll container, not bleed between two grid columns inside one row whose own border-box never
   grew (min-width:0 lets the track collapse while the unbroken text still paints past it).
   Fix: normal wrapping is now the default; .layout-single below restores nowrap once JS has actually
   confirmed the row fits on one line, so the optimized common case renders pixel-identical to before. */
.suggest-item .si-left  { color: var(--blue);  font-size: 12px; font-weight: 500; font-family: var(--mono); text-align: right; padding-right: 5px;
  white-space: normal;
  overflow-wrap: break-word;
  word-break: keep-all;
  min-width: 0;
  transition: none; }
.suggest-item .si-right { color: var(--amber); font-size: 12px; font-weight: 500; font-family: var(--mono); text-align: left;  padding-left:  5px;
  white-space: normal;
  overflow-wrap: break-word;
  word-break: keep-all;
  min-width: 0;
  transition: none; }
.suggest-item .si-arr   { color: var(--faint); font-size: 10px; text-align: center; padding-top: 2px; white-space: nowrap; }

/* Suggest-item layout  -  same 2-mode rule as entry-card. Layout chosen at
   render time by _layoutTextItems based on whether content fits. */
.suggest-item.layout-single {
  /* NO grid-template-columns here, deliberately. This rule used to re-declare "1fr auto 1fr", which
     outranks the base .suggest-item rule on specificity and applied to every row, since
     _layoutTextItems adds .layout-single to all of them before it measures. A bare 1fr is
     minmax(auto, 1fr), so with the white-space: nowrap below it the track could grow to fit its
     text, while the base rule's minmax(0, 1fr) exists precisely to keep the two side tracks equal.
     Leaving both declarations in the cascade was a straight contradiction, so the redeclaration is
     gone and the base rule is inherited.
     WHAT THIS DID NOT DO, measured rather than reasoned about, because the obvious story is wrong:
     it did NOT cause the marker drift the owner reported. Over 277 real display pairs from the
     shipped vocabulary (tests/browser/test_marker_alignment_single.mjs) the two configurations are
     indistinguishable -- same 58 rows in the fitting tier, same 219 in the flow tier, marker on one
     x either way. A half wide enough to grow its track also overflows the ROW, and the classifier's
     first test is scrollWidth against clientWidth on the row itself (js/02e-suggest-render.js:149),
     which fires and moves it to the flow tier before the wider track can paint. The override was
     inert. Do not "restore" it on the theory that removing it fixed something; nothing observable
     changed, and the drift he is seeing is in the flow tier, where the marker sits at the line
     break. */
  grid-template-rows: auto;
}
.suggest-item.layout-single .si-left,
.suggest-item.layout-single .si-right {
  white-space: nowrap; /* JS already confirmed this row fits on one line; keep the tight optimized layout */
}
/* Long rows: ONE continuous text flow across the FULL row width (owner ruling 2026-08-12).
   His words: "the left cell's right edge and right cell's left edge should always be in one line,
   just like they are in entries that do fit in one line, and the lines that wrap should wrap in
   such a way as for the left cell's text wrap up (spanning the entire div, occupying the WHOLE
   line, not just half like the parts immediately adjacent to the middle point), while the right
   cell's text should wrap underneath it also through the entire line, starting basically on the
   left side of the div."

   Retires BOTH earlier non-fitting tiers, each of which wasted a large part of the row:
   - layout-two-line put the left half in grid column 1 and the right half in column 3 of a
     `1fr auto 1fr` grid, so each half used about a third of the width and the rest of both rows
     sat empty. That is the dead space in his 2026-08-12 screenshot.
   - layout-wrap stacked the two halves as separate full-width blocks, left block right-aligned
     above and right block left-aligned below. Full width, but the two halves could never share a
     line, so the seam never appeared where he wants it.

   The shape he described is just ordinary inline text flow, so that is what this is: the row
   becomes a block box and its three children become inline, letting the browser break lines. The
   left half fills whole lines and its tail lands on the line that also carries the arrow and the
   right half's head (that IS the shared seam line, produced by line-breaking rather than by
   measuring and splitting), and the right half's remainder fills whole lines below.

   NOT the 2026-07-28 "one flowing block" the owner rejected as squished and hard to comprehend.
   That one was flex-wrap on two flex items, which drops the ENTIRE right half onto a fresh line
   and left-packs it: no shared line ever existed, and the arrow wrapped along with it, so nothing
   marked where the palindrome turned. Here the arrow is inline AT the turn, with the blue/amber
   color change landing on the same spot.

   Also not the 2026-08-11 layout-seam tier (reverted 2026-08-12, see js/02e-suggest-render.js):
   that one aimed at this same shape but built it by Canvas-measuring a split point and physically
   restructuring the row's DOM with the Range API, which broke because the new nodes it created
   inherited neither the row's font size nor its family. Nothing is measured or moved here. */
/* BROWSER-OWNED VIRTUALIZATION (2026-08-12). Every row of a 10k-60k row seam is in the DOM; these
   two declarations are what keep that cheap. content-visibility skips style, layout and paint for
   rows outside the viewport, which was the actual typing-lag cost the old chunked renderer paid.
   contain-intrinsic-size: auto tells the browser to remember each row's last rendered height, so
   the scrollbar is honest for everything the user has seen and uses the 34px placeholder only for
   rows never yet reached. This replaced the hand-rolled windowing in js/02h-virtual-list.js, whose
   spacer-and-scrollTop compensation fought the user's wheel (measured: 47 reversions and 217
   backwards jumps over 60 wheel steps, versus 0 and 0 here). See _plnRenderAll in
   js/02e-suggest-render.js. Rows are classified lazily as they approach the viewport, because an
   off-screen row under content-visibility has no layout to measure. */
#entry-list .suggest-item,
#wb2-entry-list .suggest-item,
#suggest-results .suggest-item {
  content-visibility: auto;
  contain-intrinsic-size: auto 34px;
}
/* The scrollbar must never change the content width: row height depends on wrapping, wrapping
   depends on width, and a width that flips with the scrollbar's appearance is a feedback loop. */
#entry-list, #wb2-entry-list, #suggest-results { scrollbar-gutter: stable; }

/* WORDBANK B TYPOGRAPHY RESTORED (owner, 2026-08-12: "I never asked Claude to change that font").
   Until 52f15d4 this panel rendered its own .entry-card template, which css/13-ctx-gami.css styled
   at 14px / weight 600. That commit routed every WB2 branch through the shared .suggest-item
   renderer to end a real font-size split, and in doing so silently moved these rows to 12px /
   weight 500 -- smaller, and genuinely lighter rather than just nominally: Nunito is loaded at
   400/600/700 (index.html), so weight 600 draws the real Semibold face while weight 500 falls back
   to Regular. Measured in a browser against a 2026-08-05 checkout, this is the ONLY typography
   difference anywhere in the app: the main Wordbank (#entry-list) and the Studio cell are identical
   in both builds. The shared renderer stays; only this panel's type scale comes back. */
#wb2-entry-list .suggest-item .si-left,
#wb2-entry-list .suggest-item .si-right {
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.01em;
}

.suggest-item.layout-flow {
  /* OWNER RULING 2026-09-20 (supersedes the 2026-08-13 and 2026-08-15 rulings): a long entry must
     NOT move the middle marker off the shared vertical line the fitting rows keep; each half wraps
     at spaces only (never mid-word), the left half growing upward and the right half downward, and
     the marker row still holds the left tail + marker + right head. A three-column grid identical to
     the fitting rows (minmax(0,1fr) auto minmax(0,1fr), inherited from base .suggest-item) with the
     halves as blocks and align-self end/start satisfies all three rulings at once, and the browser's
     own space-wrapping decides the line breaks (no measurement, which failed twice before). */
  display: grid;
}
.suggest-item.layout-flow .si-left,
.suggest-item.layout-flow .si-arr,
.suggest-item.layout-flow .si-right { display: block; }
.suggest-item.layout-flow .si-left,
.suggest-item.layout-flow .si-right {
  white-space: normal;
  word-break: keep-all;      /* never split a word mid-letter */
  overflow-wrap: break-word; /* except a single word wider than the column, which has nowhere else to go */
  hyphens: none;
  min-width: 0;
}
/* OWNER RULING 2026-10-05: when the halves wrap to different heights, the left half hugs the TOP of
   the row and the right half hugs the BOTTOM (the old end/start pair left a short left half sunk to
   the bottom under a taller right half). */
.suggest-item.layout-flow .si-left  { text-align: right; align-self: start; }
.suggest-item.layout-flow .si-right { text-align: left;  align-self: end; }
.suggest-item.layout-flow .si-arr   { align-self: center; padding: 0 4px; opacity: 0.75; }
.suggest-item .si-pin   { display: none; }
.suggest-item .si-note  { display: none; }

/* Wordbank display unification (owner ruling, 2026-07-25): the DB-browse list now renders through
   THIS SAME .suggest-item template (js/02e-suggest-render.js _renderSuggestItems, browseMode
   option), in a no-seam mode -- see that function's header comment. .wb-entry marks a browse row
   and opts the hidden .si-pin slot above back in, plus carries the browse-only pinned/selected/
   editing states the old standalone .entry-card (css/04-vocab-panel.css) used to own. Placed
   BEFORE .entry-cat-match below so a pinned/selected/favored row still reads clearly (mirrors the
   "applies after .pinned/.selected" ordering note on .entry-card.entry-cat-match). */
.suggest-item.wb-entry { position: relative; }
.suggest-item.wb-entry .si-pin {
  display: block;
  position: absolute;
  top: 7px;
  right: 9px;
  font-size: 11px;
  color: var(--pin);
  opacity: 0.8;
  line-height: 1;
}
.suggest-item.wb-entry.pinned {
  border-color: rgba(154,110,24,0.40);
  background: rgba(255,248,235,0.55);
}
.suggest-item.wb-entry.pinned:hover { border-color: rgba(154,110,24,0.65); }
.suggest-item.wb-entry.selected { border-color: var(--blue); background: rgba(53,116,196,0.10); }
.suggest-item.wb-entry.pinned.selected { border-color: var(--pin); background: rgba(255,248,220,0.72); }
/* Inline edit form (js/03e-vocab-crud.js _entryEditFormHtml): same toggle-on-.editing pattern as
   the old .entry-card.editing (css/07-banks.css), scoped to the new row class. */
.suggest-item.wb-entry.editing .entry-edit-form { display: block; }
.suggest-item.wb-entry.editing { cursor: default; }

.suggest-overlap {
  grid-column: 3;
  justify-self: end;
  font-size: 9px;
  color: var(--green);
  letter-spacing: 0.05em;
  border: 1px solid rgba(46,122,80,0.35);
  border-radius: 2px;
  padding: 1px 4px;
  flex-shrink: 0;
  pointer-events: none;
}

/* Wordbank rows carry NO coloured left-edge strip (owner 2026-09-28: "delete the strange left-edge
   highlight"). Overlap, pair, echo and category rows are told apart by their text/wash only. */
/* Overlap alignment preview: highlighted letters = shared zone that will merge with existing cell content */
.si-dim {
  background: rgba(53, 116, 196, 0.13);
  border-radius: 2px;
  color: var(--blue);
  font-weight: 700;
  outline: 1px solid rgba(53, 116, 196, 0.30);
  outline-offset: -1px;
}
/* Cross-dedup: entries already shown in the other wordbank */
.suggest-item.si-cross { opacity: 0.5; border-style: dashed; }
.suggest-item.si-cross:hover { opacity: 0.85; }

/* Echo entries: word-doubling merge (e.g. "so so" result from "so s" segment).
   Purple tint  -  these rise to the top because doubled words are often elegant in palindromes. */
.suggest-item.si-echo {
  background: rgba(120, 60, 200, 0.07);
}
.suggest-item.si-echo:hover {
  background: rgba(120, 60, 200, 0.14);
}
.suggest-item.si-echo .si-left,
.suggest-item.si-echo .si-right {
  color: rgba(130, 70, 200, 0.9);
}
.suggest-item.si-echo:hover .si-left,
.suggest-item.si-echo:hover .si-right { color: var(--txt); }

/* Category favor match (owner Wordbank-unify ruling, 2026-07-25): companion rule to
   .entry-card.entry-cat-match in css/04-vocab-panel.css -- same pink border+wash, applied to
   suggestion-list rows whose entry matches a chosen (include/favor) category. Never a drop,
   see js/03d-cat-filters.js _applyFavorSortAndMark. */
.suggest-item.entry-cat-match {
  background: rgba(214, 51, 132, 0.07);
}
.suggest-item.entry-cat-match:hover {
  background: rgba(214, 51, 132, 0.13);
}
body.dark-mode .suggest-item.entry-cat-match {
  background: rgba(235, 110, 175, 0.10);
}
body.dark-mode .suggest-item.entry-cat-match:hover { background: rgba(235, 110, 175, 0.17); }
/* ── Must Use shelf ─────────────────────────────────────── */
#mu-shelf-body { overflow-y: auto; scrollbar-width: thin; }
#mu-shelf-panel.open #mu-shelf-body { max-height: 200px; padding: 4px 6px; }
/* Must Use rows ARE Wordbank rows (.suggest-item, js/07e-must-use.js, owner 2026-09-29): all their look comes
   from the shared Wordbank rules. Only the shelf's own "already used" state is styled here. */
.mu-entry.mu-used { opacity: 0.3; text-decoration: line-through; }
.mu-count { font-size: 9px; color: var(--faint); margin-left: auto; font-family: var(--sans); }

/* Slot suggest */
.slot-pair-hint {
  padding: 10px 12px;
  display: flex;
  flex-direction: column;
  gap: 7px;
  font-size: 11px;
  color: var(--dim, #999);
  border-top: 1px solid rgba(255,255,255,0.07);
  margin-top: 4px;
}
.slot-pair-hint span { line-height: 1.4; }
.btn-pair-search {
  align-self: flex-start;
  font-size: 11px;
  padding: 3px 9px;
  background: rgba(100,160,255,0.13);
  color: #7ab4f5;
  border: 1px solid rgba(100,160,255,0.28);
  border-radius: 4px;
  cursor: pointer;
}
.btn-pair-search:hover { background: rgba(100,160,255,0.24); }
.suggest-item .si-dot { opacity: 0.35; margin: 0 1px; font-weight: bold; }

/* ── Social surface single-segment cell (Graffiti / Hydepark) ─────────── */
/* A single unified card that reads as ONE editable segment. The field
   itself IS both the display and the input: user clicks it to focus,
   types to extend at the outer edges, backspace to unwind. The seed
   content in the middle of the palindrome is fixed; only the user's
   own additions can be added or removed. One "Commit" button saves. */
.seg-cell-card {
  margin: 16px;
  padding: 12px 16px 12px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--bg2);
  box-shadow: 0 1px 4px rgba(0,0,0,0.04);
}
.seg-cell-header {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--faint);
  text-transform: uppercase;
  margin-bottom: 10px;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}
.seg-cell-meta {
  font-size: 10px;
  font-weight: 400;
  color: var(--dim);
  text-transform: none;
  letter-spacing: 0;
}
/* The unified editable field. A plain div with tabindex=0, NOT
   contenteditable  -  keystrokes are intercepted by OuterGrow JS. Cursor
   indication is a focus outline; no text caret is shown because the
   edit model is "type to extend outer", not "edit anywhere". */
.seg-cell-field {
  font-family: var(--mono);
  font-size: 15px;
  font-weight: 500;
  color: var(--txt);
  word-break: break-word;
  line-height: 1.7;
  padding: 10px 16px;
  margin-bottom: 8px;
  background: var(--bg);
  border: 1.5px solid var(--line);
  border-radius: 10px;
  min-height: 1.6em;
  text-align: center;
  cursor: text;
  outline: none;
  transition: border-color 0.12s, box-shadow 0.12s;
}
.seg-cell-field:hover { border-color: rgba(120,145,195,0.45); }
.seg-cell-field:focus {
  border-color: var(--blue);
  box-shadow: 0 0 0 3px rgba(53,116,196,0.12);
}
/* Owner 2026-09-20: animated "Dao-load…" only while empty AND not yet ready; once the field is
   marked ready (js/14b-palinput.js redraw) an empty state shows a hint, not "loading" forever. */
.seg-cell-field:empty:not([data-ready])::before {
  content: 'Dao-load';
  color: var(--faint);
  font-style: italic;
  font-weight: 400;
}
.seg-cell-field:empty:not([data-ready])::after {
  content: '.';
  color: var(--faint);
  font-style: italic;
  font-weight: 400;
  animation: dao-dots 1.2s steps(3, end) infinite;
}
/* Owner 2026-09-25 (R0): the Graffiti/Hydepark contribution field IS a real studio segment - PalSegment
   adds `fc-cell pal-seg` and renderFeedCell paints the same 3-pane (blue left | dot | amber right) grid
   into it. But the div also carries `.seg-cell-field`, whose rounded input-box border + centered fill made
   it read as a plain text box, "NOTHING like the studio segment". Shed that chrome when both classes are
   present so the space cell is VISUALLY IDENTICAL to a studio cell. */
.seg-cell-field.fc-cell {
  background: transparent;
  border: none;
  border-bottom: 1px solid rgba(175,170,163,0.18);
  border-radius: 0;
  text-align: left;
  margin-bottom: 0;
  line-height: normal;
}
.seg-cell-field.fc-cell:hover  { border-color: rgba(175,170,163,0.18); }
.seg-cell-field.fc-cell:focus  { border-color: rgba(175,170,163,0.18); box-shadow: none; background: rgba(255,255,255,0.55); }
body.dark-mode .seg-cell-field.fc-cell:focus { background: color-mix(in srgb, var(--bg2) 55%, transparent); }
.seg-cell-actions {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 4px 2px 2px;
}
/* Owner 2026-09-27: the Post Sop (was "Commit") button now matches the app's standard pill button
   (.tb-post-sop-style) instead of a lone solid-blue block. The reset (↺) keeps its own outline style below. */
.seg-cell-actions button {
  padding: 9px 28px;
  border: 1px solid var(--glass-border);
  border-radius: 22px;
  background: var(--glass-bg);
  color: var(--blue);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.05em;
  cursor: pointer;
  white-space: nowrap;
  box-shadow: 0 3px 12px rgba(0,0,0,0.10);
  transition: transform 0.15s;
}
.seg-cell-actions button:hover { transform: scale(1.06); }
.seg-cell-actions button.seg-cell-reset {
  background: transparent;
  color: var(--faint);
  border: 1px solid var(--line);
  padding: 8px 12px;
  font-weight: 400;
  font-size: 13px;
}
.seg-cell-actions button.seg-cell-reset:hover {
  color: var(--txt);
  border-color: var(--dim);
  filter: none;
}
/* Graffiti F-far-G and Krapedy Hydepark are ONE shared, always-connected palindrome: users must not be
   able to reverse the sides or dis/connect the cell, so hide the middle cluster (Unify + switch-side dots)
   for those two space cells only. The feed composer keeps its middle controls. (owner 2026-09-27) */
#feed-rc-cell-graffiti .cell-center-cluster,
#feed-rc-cell-hydepark .cell-center-cluster { display: none; }
/* Graffiti F-far-G participant avatar row (owner 2026-09-25): the faces of who is on the wall right now,
   in wrap-rows above the segment; hover an avatar to reveal its @handle. Krapedy Hydepark stays anonymous
   (count only, no avatars), so this styling is Graffiti-only by virtue of _renderSpacePresence. */
.seg-cell-presence .space-av-row {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px 7px;
}
.space-av-lbl { font-size: 10px; color: var(--faint); margin-right: 3px; letter-spacing: 0.02em; }
.space-av { position: relative; cursor: pointer; }
.space-av .space-avatar {
  width: 30px; height: 30px; border-radius: 9px;
  display: flex; align-items: center; justify-content: center;
  color: #fff; font-family: var(--mono); font-weight: 800; font-size: 11px;
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.25), 0 1px 3px rgba(0,0,0,0.14);
}
.space-av .space-av-ov {
  position: absolute; bottom: calc(100% + 5px); left: 50%; transform: translateX(-50%);
  background: var(--bg); color: var(--txt); border: 1px solid var(--line);
  border-radius: 7px; padding: 2px 7px; font-size: 10px; font-family: var(--sans);
  white-space: nowrap; pointer-events: none; opacity: 0; transition: opacity 0.1s;
  box-shadow: 0 3px 12px rgba(0,0,0,0.14); z-index: 5;
}
.space-av:hover .space-av-ov { opacity: 1; }
.space-av-more {
  width: 30px; height: 30px; border-radius: 9px;
  display: flex; align-items: center; justify-content: center;
  font-size: 10px; font-weight: 700; color: var(--dim); border: 1px dashed var(--line);
}
.seg-cell-compose-locked {
  padding: 10px 4px;
  font-size: 11px;
  color: var(--faint);
  text-align: center;
}
.seg-cell-compose-locked span {
  color: var(--blue);
  cursor: pointer;
}
.seg-cell-footnote {
  margin-top: 10px;
  font-size: 10px;
  color: var(--faint);
  text-align: center;
  line-height: 1.5;
}

/* ── Word definition hover tooltip (studio cell 3s dwell) ─────────── */
/* Positioned absolutely over the page via inline style. Appears when the
   user has hovered a single word in the studio cell for 3+ seconds and
   that word matches a known vocab entry. Shows the entry's commentary so
   palindromists can check word meanings mid-composition without leaving
   the flow. */
#word-def-tooltip {
  display: none;
  position: fixed;
  z-index: 10000;
  max-width: 340px;
  min-width: 180px;
  padding: 10px 12px 11px;
  background: rgb(252, 250, 246);   /* W5 (owner 2026-08-02): fully opaque, must be well-visible */
  border: 1px solid rgba(185,180,173,0.70);
  border-radius: 10px;
  box-shadow: 0 4px 18px rgba(0,0,0,0.14);
  font-family: var(--body-font, system-ui, sans-serif);
  font-size: 12px;
  line-height: 1.5;
  color: var(--txt);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  pointer-events: none;
  animation: wdt-fade 0.18s ease-out;
}
@keyframes wdt-fade {
  from { opacity: 0; transform: translateY(3px); }
  to   { opacity: 1; transform: translateY(0); }
}
#word-def-tooltip .wdt-head {
  font-family: var(--mono);
  font-weight: 800;
  font-size: 15px;
  letter-spacing: 0.01em;
  color: var(--blue);
  margin-bottom: 7px;
  padding-bottom: 6px;
  border-bottom: 1px dashed rgba(120,114,108,0.30);
}
/* Each definition: optional number badge + body (definition prominent, source pair muted below). */
#word-def-tooltip .wdt-entry {
  display: flex;
  gap: 7px;
  margin-top: 9px;
}
#word-def-tooltip .wdt-entry:first-of-type { margin-top: 0; }
#word-def-tooltip .wdt-num {
  flex: 0 0 auto;
  min-width: 16px; height: 16px;
  margin-top: 1px;
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--mono); font-size: 10px; font-weight: 700;
  color: var(--blue);
  background: rgba(53,116,196,0.12);
  border-radius: 50%;
}
#word-def-tooltip .wdt-body { flex: 1 1 auto; min-width: 0; }
#word-def-tooltip .wdt-note {
  color: var(--txt);
  font-size: 12.5px;
  line-height: 1.5;
}
#word-def-tooltip .wdt-pair {
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--dim);
  margin-top: 3px;
  opacity: 0.8;
}

/* ── Definition boxes flanking the + Segment button (WS19 s3) ───────────
   Studio-cell word definitions render here instead of a floating tooltip.
   Right box = first sense, left box = second sense (shown only when an entry
   has two distinct senses). visibility (not display) keeps the grid columns
   stable so the button never shifts. No fixed height: text wraps and the box
   grows, so the whole definition fits (Sol review). */
.word-def-box {
  max-width: 320px;
  min-width: 0;
  padding: 8px 11px 9px;
  background: rgb(252, 250, 246);   /* W5 (owner 2026-08-02): fully opaque, must be well-visible */
  border: 1px solid rgba(185,180,173,0.70);
  border-radius: 10px;
  box-shadow: 0 3px 14px rgba(0,0,0,0.12);
  font-family: var(--body-font, system-ui, sans-serif);
  font-size: 12px;
  line-height: 1.5;
  color: var(--txt);
  overflow-wrap: anywhere;
  white-space: normal;
  visibility: hidden;
  pointer-events: none;
  animation: wdt-fade 0.18s ease-out;
}
.word-def-box[aria-hidden="false"] { visibility: visible; }
.word-def-box-left  { justify-self: end;   text-align: right; }
.word-def-box-right { justify-self: start; text-align: left; }
.word-def-box .wdt-head {
  font-family: var(--mono);
  font-weight: 800;
  font-size: 14px;
  letter-spacing: 0.01em;
  color: var(--blue);
  margin-bottom: 6px;
  padding-bottom: 5px;
  border-bottom: 1px dashed rgba(120,114,108,0.30);
}
.word-def-box .wdt-note { color: var(--txt); font-size: 12.5px; line-height: 1.5; }
.word-def-box .wdt-pair {
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--dim);
  margin-top: 4px;
  opacity: 0.8;
}

/* ABSURDBANK_CSS_START -- delete this block when removing Absurdbank */
.absurd-item {
  display: grid;
  grid-template-columns: auto 1fr auto 1fr;
  align-items: center;
  padding: 5px 8px;
  cursor: pointer;
  border-radius: 4px;
  border-left: 2px solid rgba(200,55,45,0.35);
  background: rgba(200,55,45,0.06);
  margin: 1px 0;
  transition: background 0.12s, border-left-color 0.12s;
}
.absurd-item:hover { background: rgba(200,55,45,0.14); border-left-color: rgba(200,55,45,0.8); }
.absurd-item .si-left  { color:rgba(190,55,45,0.9); font-size:11px; font-weight:600; font-family:var(--mono); text-align:right; padding-right:4px; white-space:normal; overflow-wrap:break-word; word-break:keep-all; }
.absurd-item .si-right { color:rgba(160,70,65,0.85); font-size:11px; font-weight:600; font-family:var(--mono); text-align:left; padding-left:4px; white-space:normal; overflow-wrap:break-word; word-break:keep-all; }
.absurd-item .si-arr   { color:rgba(200,55,45,0.35); font-size:10px; text-align:center; }
.absurd-item .absurd-k { font-size:8px; color:rgba(200,55,45,0.6); margin-right:3px; font-family:var(--mono); }
/* Gate on .open so the panel header can actually COLLAPSE it (owner 2026-09-27): the old unconditional
   max-height:44vh !important kept Absurdbank permanently expanded, so lcwToggle removed .open but the body
   stayed open -- the reason Astrabank/Absurdbank could not be wrapped up like Must Use. Now it respects
   .open like every other left-col panel. (_absRender still refreshes regardless of .open, so no staleness.) */
#astrabank-panel.open #absurdbank-body { max-height:44vh !important; overflow-y:auto; scrollbar-width:thin; padding:4px 6px !important; }
#astrabank-panel:not(.open) #absurdbank-body { max-height:0 !important; padding:0 !important; overflow:hidden; }
#absurdbank-panel .lcw-header { border-bottom:1px solid rgba(200,55,45,0.2); }
#absurdbank-panel.open .lcw-header { border-bottom-color:rgba(200,55,45,0.35); }

/* Owner ruling 2026-07-28: "I want there to never be any horizontal scrollbar in the Wordbank."
   It was FLICKERING, which localises the cause: _layoutSuggestItem measures a row, the nowrap
   .si-left/.si-right briefly exceed their grid cell, the scrolling ancestor paints a scrollbar, then the
   layout mode switches to two-line/wrap and it vanishes. So the bar appeared during measurement, once per
   render pass, which is exactly the flicker he saw.
   Fixing it where it can never come back: the scroll container refuses the axis outright. .studio-section is
   the element that actually scrolls (overflow-y: auto, which promotes overflow-x from visible to auto and is
   what let the bar appear at all); #suggest-results and #entry-list are named too so a future restructure
   that moves the scroll onto them stays covered.
   Tradeoff, stated rather than hidden: a row wider than the panel is now CLIPPED instead of scrollable. In
   practice _layoutSuggestItem already switches long rows to two-line or wrap before that happens, so the
   clip should not be reachable; if a long suggestion ever does get cut off, the fix belongs in the layout
   chooser, not in restoring the scrollbar.
   minimal: overflow-x hidden on the container | upgrade-when: a real row is observed clipped */
.studio-section,
#suggest-results,
#entry-list { overflow-x: hidden; }

/* Rows-on-demand sentinel (js/02e-suggest-render.js _plnRenderAll): a 1px marker the next chunk loads behind. */
.pln-more { height: 1px; }

/* Dot tones. Half-transparent grey, so a dot takes on the surface it sits on (the joined segment is golden, the
   active one tinted). Owner 2026-10-01: the 09-30 opaque version, pre-mixed with the page background, read too
   bright on those tinted surfaces. The two reverse dots (.rdot) sit on top of each other at rest, so only
   .rdot-l is drawn then (see .rdot-r below); that keeps both middle buttons the same tone without the overlap
   doubling the darkness. */
:root {
  --cdot-rest:  rgba(130, 124, 115, 0.5);
  --cdot-hover: rgba(130, 124, 115, 0.7);
}
