/* =============================================================================
   FORM DESIGNER — three-panel shell (toolbox / canvas / properties)
   ==========================================================================
   Additive on top of tasks.css: every rule here is nested under
   .task-theme too, so it inherits that file's tokens (--task-*) and
   components (.task-btn, .task-card, .task-tabs, .task-modal, etc.)
   directly rather than redefining them — see architecture.md §14 for why
   the Designer reuses the Tasks module's look instead of getting its own.
   This file only adds what tasks.css doesn't already have: the 3-column
   grid itself, toolbox tiles, the compact canvas field row, the
   properties panel, and a small overflow-menu popover (tasks.css has no
   equivalent of that last one).
   ========================================================================== */

/* ---------- Slightly darker backgrounds, scoped to the Designer only ---
   The Designer reuses tasks.css's tokens directly (see header comment
   above), but darkening the shared --task-bg/--task-surface tokens
   globally would also darken every other .task-theme screen (My Work,
   Task Detail, Manage Tasks, Kanban) that has nothing to do with the
   Form Designer. Redefining them here, scoped to .task-theme.designer-page
   specifically (the class every Designer-owned root element carries —
   see designer.js's renderFormsList/renderDesignerShell/loading/error
   states and its overflow-menu popover), overrides the variables just
   for elements inside this subtree; a plain .task-theme selector
   elsewhere in the app never sees this rule at all, since specificity
   only helps where the extra class is actually present. */
.task-theme.designer-page {
  --task-bg: #eceef4;             /* was #f4f6fb */
  --task-surface: #f7f8fc;        /* was #ffffff */
  --task-surface-raised: #f7f8fc; /* was #ffffff — kept equal to --task-surface, same relationship as before */
}

/* ---------- Landing page ------------------------------------------------ */
.task-theme .designer-form-icon {
  width: 38px; height: 38px; border-radius: 10px; flex: none;
  background: var(--task-accent-dim); color: var(--task-accent);
  display: flex; align-items: center; justify-content: center;
}
.task-theme .designer-form-icon svg { width: 20px; height: 20px; }
.task-theme .designer-card-row { display: flex; align-items: center; gap: 12px; }
.task-theme .designer-card-row .task-card-title { margin-bottom: 0; }
.task-theme .designer-status-row { display: flex; align-items: center; gap: 8px; margin-top: 6px; }

/* ---------- Overflow menu (⋮) — used on form cards and the designer header */
.task-theme .designer-menu-anchor { position: relative; flex: none; }
.task-theme .designer-menu-trigger {
  width: 30px; height: 30px; border-radius: 999px; border: none; background: transparent;
  color: var(--task-text-dim); cursor: pointer; display: flex; align-items: center; justify-content: center;
  font-size: 18px; line-height: 1;
}
.task-theme .designer-menu-trigger:hover { background: var(--task-gray-bg); }
.task-theme .designer-menu-popover {
  position: absolute; top: 34px; right: 0; z-index: 30; min-width: 180px;
  background: var(--task-surface); border: 1px solid var(--task-border); border-radius: var(--task-radius-sm);
  box-shadow: var(--task-shadow); padding: 6px; display: flex; flex-direction: column;
}
.task-theme .designer-menu-item {
  display: flex; align-items: center; gap: 8px; padding: 9px 10px; border-radius: 7px;
  border: none; background: none; text-align: left; font-size: 13px; font-weight: 600;
  color: var(--task-text); cursor: pointer; width: 100%; font-family: var(--font-body);
}
.task-theme .designer-menu-item:hover { background: var(--task-gray-bg); }
.task-theme .designer-menu-item.danger { color: var(--task-red); }
.task-theme .designer-menu-item:disabled { opacity: 0.45; cursor: default; }
.task-theme .designer-menu-item:disabled:hover { background: none; }

/* ---------- Designer header ---------------------------------------------- */
.task-theme .designer-header {
  display: flex; align-items: center; gap: 10px; margin-bottom: 10px; flex-wrap: wrap;
}
.task-theme .designer-title-input {
  font-size: 17px; font-weight: 700; border: 1px solid transparent; background: transparent;
  padding: 6px 8px; border-radius: var(--task-radius-sm); min-height: auto; width: auto; flex: 1 1 220px; min-width: 120px;
}
.task-theme .designer-title-input:hover, .task-theme .designer-title-input:focus { border-color: var(--task-border); background: var(--task-surface); }
.task-theme .designer-autosave-status { font-size: 12px; font-weight: 600; color: var(--task-text-faint); flex: none; white-space: nowrap; }
.task-theme .designer-autosave-status.saving { color: var(--task-text-dim); }
.task-theme .designer-autosave-status.saved { color: var(--task-green); }
.task-theme .designer-autosave-status.failed { color: var(--task-red); }
.task-theme .designer-header-actions { display: flex; align-items: center; gap: 4px; flex: none; margin-left: auto; }
.task-theme .designer-icon-btn {
  width: 34px; height: 34px; border-radius: var(--task-radius-sm); border: 1px solid var(--task-border);
  background: var(--task-surface); color: var(--task-text); cursor: pointer;
  display: flex; align-items: center; justify-content: center; flex: none;
}
.task-theme .designer-icon-btn:disabled { opacity: 0.4; cursor: default; }
.task-theme .designer-icon-btn .ds-glyph { font-size: 15px; line-height: 1; }

/* ---------- Top tabs (Design / Preview / Settings — see designer.js) --- */
/* Reuses .task-tabs/.task-tab as-is (tasks.css) — no new rules needed. */

/* ---------- Shell: sidebar + content ------------------------------------ */
.task-theme .designer-shell { display: flex; gap: 14px; align-items: flex-start; }
.task-theme .designer-sidebar {
  flex: none; width: 168px; display: flex; flex-direction: column; gap: 2px;
  background: var(--task-surface); border: 1px solid var(--task-border); border-radius: var(--task-radius);
  padding: 8px; box-shadow: var(--task-shadow);
}
.task-theme .designer-sidebar-item {
  display: flex; align-items: center; gap: 9px; padding: 10px 10px; border-radius: 8px;
  border: none; background: none; text-align: left; font-size: 13px; font-weight: 600;
  color: var(--task-text-dim); cursor: pointer; font-family: var(--font-body);
}
.task-theme .designer-sidebar-item svg { width: 17px; height: 17px; flex: none; }
.task-theme .designer-sidebar-item .ds-nav-icon { font-size: 15px; width: 17px; text-align: center; flex: none; }
.task-theme .designer-sidebar-item:hover { background: var(--task-gray-bg); }
.task-theme .designer-sidebar-item.active { background: var(--task-accent-dim); color: var(--task-navy); font-weight: 700; }
.task-theme .designer-sidebar-item.disabled { opacity: 0.5; cursor: default; }
.task-theme .designer-sidebar-item.disabled:hover { background: none; }
.task-theme .designer-sidebar-soon { font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; background: var(--task-gray-bg); color: var(--task-text-faint); border-radius: 999px; padding: 2px 6px; margin-left: auto; }
.task-theme .designer-content { flex: 1; min-width: 0; }

/* ---------- Fields view: toolbox / canvas / properties ------------------ */
/* Toolbox/properties columns narrowed from 200px/320px (reclaiming 70px
   for the canvas column, which is what actually needs the room — see
   .rb-canvas-row's own comment on why up to 6 side-by-side blocks need
   real width to avoid wrapping) — modest enough that Form Designer's own
   toolbox tiles and property controls (this grid is shared between it
   and Report Builder) still fit comfortably at either width. */
.task-theme .designer-fields-grid { display: grid; grid-template-columns: 170px 1fr 280px; gap: 12px; align-items: start; }

/* "+ Add Block" / "+ Add Section" row (Report Builder's Design tab only)
   — side by side per direct request, and shown above .designer-fields-
   grid rather than inside it (Add Section previously lived buried
   inside the canvas's own now-removed .designer-page-toolbar) so both
   buttons share one row regardless of viewport width. "+ Add Block"
   itself is desktop-hidden by default here (it toggles the mobile
   toolbox DRAWER specifically — desktop's toolbox is already a normal,
   always-visible column, nothing to toggle open) and restored at
   \u2264900px by the existing .designer-toolbox-mobile-toggle mobile rule
   below, which already has the !important needed to win over this. */
.task-theme .rb-canvas-add-row { display: flex; gap: 8px; margin-bottom: 10px; }
/* Shadow per direct request — .task-btn has none by default (tasks.css's
   base rule is flat/bordered, no box-shadow), so these two need their
   own rule rather than inheriting one. Scoped to this row specifically
   (not .task-btn generally) so every OTHER plain button across the app
   stays exactly as flat as it's always been. */
.task-theme .rb-canvas-add-row .task-btn {
  box-shadow: 0 1px 3px rgba(20, 30, 60, 0.1), 0 3px 8px rgba(20, 30, 60, 0.08);
}
.task-theme .designer-toolbox-mobile-toggle { display: none; }

.task-theme .designer-toolbox {
  background: var(--task-surface); border: 1px solid var(--task-border); border-radius: var(--task-radius);
  padding: 10px; box-shadow: var(--task-shadow); max-height: calc(100vh - 220px); overflow-y: auto;
}
.task-theme .designer-toolbox-title { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; color: var(--task-text-faint); margin: 10px 4px 6px; }
.task-theme .designer-toolbox-title:first-child { margin-top: 2px; }
.task-theme .designer-toolbox-tile {
  display: flex; align-items: center; gap: 10px; padding: 10px 12px; border-radius: 9px;
  border: 1px solid var(--task-border); background: var(--task-surface); text-align: left; font-size: 13px; font-weight: 600;
  color: var(--task-text); cursor: grab; width: 100%; font-family: var(--font-body); margin-bottom: 8px;
  /* Same reasoning as .designer-row-drag-handle's own comment (search
     "turning blue mid-drag" in this file) — these tiles are now
     draggable (see attachToolboxDragHandlers()/the Form Designer
     toolbox's own equivalent), so they need the same long-press/
     text-selection guards a drag source needs on touch, not just a
     drop target. UNLIKE that handle though, a tile has no separate
     grip — the whole tile is the drag source AND, on mobile, nearly
     the whole scrollable surface of the "Add Block" drawer (there's
     no other open area to swipe from once several groups of tiles
     stack up). touch-action:none there killed the browser's native
     scroll gesture on virtually the entire drawer, which is what made
     it feel unscrollable rather than just "hard to scroll between
     tiles" — pan-y keeps vertical swipes native (so the drawer
     scrolls) while still blocking the browser's own horizontal
     pan/zoom handling, and attachToolboxDragHandlers()'s own
     long-press timer (which already cancels itself the moment a touch
     moves >10px, i.e. exactly what a scroll swipe does) is what tells
     a held tap from a swipe apart — pan-y doesn't fight that, it just
     stops eating swipes that were never going to become a drag. */
  touch-action: pan-y;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}
.task-theme .designer-toolbox-tile:active { cursor: grabbing; }
.task-theme .designer-toolbox-tile:hover { background: var(--task-gray-bg); }
.task-theme .designer-toolbox-tile svg { width: 16px; height: 16px; flex: none; color: var(--task-text-dim); }
.task-theme .tt-mono {
  width: 22px; height: 22px; border-radius: 0; background: none; color: var(--task-text-dim);
  display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 700; flex: none; letter-spacing: -0.02em;
}
.task-theme .designer-toolbox-tile.disabled { cursor: default; opacity: 0.5; }
.task-theme .designer-toolbox-tile.disabled .tt-mono { background: none; color: var(--task-text-faint); }
.task-theme .designer-toolbox-tile.disabled:hover { background: var(--task-surface); }
.task-theme .designer-toolbox-tile.disabled svg { color: var(--task-text-faint); }
.task-theme .designer-toolbox-soon { margin-left: auto; font-size: 9px; font-weight: 700; text-transform: uppercase; background: var(--task-gray-bg); color: var(--task-text-faint); border-radius: 999px; padding: 2px 6px; flex: none; }

/* Toolbox drag-to-add — the floating tile clone that follows the pointer
   (see attachToolboxDragHandlers() in reportBuilder.js and its Form
   Designer counterpart). Same "lifted card" treatment .dragging already
   gives an existing row being reordered, applied to the toolbox tile's
   own clone instead since there's no row yet to lift. */
.task-theme .rb-toolbox-drag-ghost,
.task-theme .designer-toolbox-drag-ghost {
  box-shadow: 0 8px 24px rgba(20,30,60,0.28); opacity: 0.95; cursor: grabbing; margin-bottom: 0;
}

/* Canvas gets the same independent-scroll treatment as the toolbox/
   properties columns beside it (see those rules' own max-height/
   overflow-y) — previously unbounded, so a long report/form just grew
   the whole page instead of scrolling in place, which also meant the
   sticky properties panel and the canvas fought over the same page
   scroll position. Capped and self-scrolling here at desktop widths;
   the @media(max-width:900px) block below removes this cap again,
   since on mobile the canvas is the only in-flow column left (toolbox/
   properties both become fixed-position overlays there) and should
   just flow with the page like any normal mobile screen. */
.task-theme .designer-canvas { min-height: 200px; min-width: 0; max-height: calc(100vh - 220px); overflow-y: auto; overflow-x: hidden; }
.task-theme .designer-page-toolbar { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
.task-theme .designer-page-tabs { display: flex; gap: 4px; overflow-x: auto; -webkit-overflow-scrolling: touch; flex: 1; }
.task-theme .designer-page-tab {
  flex: none; padding: 7px 14px; border-radius: 999px; border: 1px solid var(--task-border);
  background: var(--task-surface); color: var(--task-text-dim); font-size: 12px; font-weight: 700;
  cursor: pointer; white-space: nowrap; font-family: var(--font-body);
}
.task-theme .designer-page-tab.active { background: var(--task-accent); color: #fff; border-color: var(--task-accent); }
.task-theme .designer-page-tab-add {
  flex: none; width: 30px; height: 30px; border-radius: 999px; border: 1px dashed var(--task-border);
  background: none; color: var(--task-text-dim); cursor: pointer; font-size: 15px; font-weight: 700;
}
.task-theme .designer-page-actions { display: flex; gap: 4px; flex: none; }
.task-theme .designer-drop-slot { height: 8px; border-radius: 4px; margin: 2px 0; }
.task-theme .designer-drop-slot.drag-over { background: var(--task-accent-dim); outline: 2px dashed var(--task-accent); outline-offset: -1px; height: 34px; }

.task-theme .designer-section-group { border: 1px solid var(--task-accent-yellow); border-radius: var(--task-radius); padding: 8px; margin-bottom: 10px; }
.task-theme .designer-section-group.collapsed .designer-section-body { display: none; }
.task-theme .designer-section-body { min-height: 44px; }

/* .designer-row/.designer-section-group text (labels, badges) turning
   blue mid-drag, on both desktop (click-drag text selection) and mobile
   (iOS's long-press-to-select callout), was a native browser selection
   gesture, not anything this app's own drag code was doing — Report
   Builder's rows never had the user-select/touch-action guards Form
   Designer's rows get via app.css's .drag-handle class (that class was
   only ever applied to Form Designer's handle, not Report Builder's
   .designer-row-drag-handle). Setting it here, on the row/section
   containers themselves rather than only the handle, is deliberate:
   attachBlockDragHandlers()/attachSectionDragHandlers() move the
   dragged row via position:fixed over its *siblings* as the pointer
   moves, so it's the siblings' selectability that has to be killed too
   — a selection can start anywhere the pointer passes over mid-drag,
   not just at the handle where the gesture began. These are short
   structural labels/badges, not content anyone needs to select, so
   disabling it unconditionally (not just while .dragging is active)
   is a safe trade. */
.task-theme .designer-row,
.task-theme .designer-section-group {
  -webkit-user-select: none;
  user-select: none;
}
.task-theme .designer-row {
  display: flex; align-items: center; gap: 8px; background: var(--task-surface); border: 1px solid var(--task-border);
  border-radius: var(--task-radius-sm); padding: 9px 10px; cursor: pointer;
}
.task-theme .designer-row:hover { border-color: var(--task-text-faint); }
.task-theme .designer-row.selected { border-color: var(--task-accent); background: var(--task-accent-dim); }
.task-theme .designer-row.drag-ghost { opacity: 0.4; }
@keyframes designer-row-flash {
  0%, 100% { box-shadow: none; border-color: var(--task-border); }
  20%, 60% { box-shadow: 0 0 0 3px var(--task-accent-yellow); border-color: var(--task-accent-yellow); }
  40%, 80% { box-shadow: none; border-color: var(--task-border); }
}
.task-theme .designer-row.flash { animation: designer-row-flash 1.1s ease-in-out; }
/* touch-action:none stops iOS from starting its own scroll gesture before
   the long-press timer in attachBlockDragHandlers()/attachDragHandlers()
   gets a chance to run; -webkit-touch-callout:none stops iOS's long-press
   text-selection magnifier/"Copy/Look Up" menu from competing with the
   same long-press gesture. Same properties app.css's .drag-handle already
   has for Form Designer's own handle — restated here directly so Report
   Builder's handle (which never carried that class) gets them too. */
.task-theme .designer-row-drag-handle {
  color: var(--task-text-faint); cursor: grab; flex: none; display: flex;
  touch-action: none;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}
.task-theme .designer-row-drag-handle:active { cursor: grabbing; }
/* Block card handle icon = its TOOLBOX_GLYPH (§ direct request, replacing
   the previous fixed six-dot grip for every type) — glyph length varies
   by type ("H"/"#" vs "{ }"/three-character chart glyph), so a fixed
   min-width keeps every card's handle column the same width regardless
   of which type is showing, and a slightly smaller font than the row's
   own text keeps the longer multi-character glyphs from crowding the
   label next to them. Still the actual drag handle (same class,
   same attachBlockDragHandlers() wiring) — this only changes what's
   drawn inside it. */
.task-theme .rb-type-icon-handle {
  min-width: 20px;
  justify-content: center;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: -0.02em;
}
/* Drag-to-reorder (Report Builder, reportBuilder.js's attachBlockDragHandlers)
   — same visual language as Form Designer's .designer-field.dragging /
   .field-drag-placeholder, just scoped to .designer-row/.rb-drag-placeholder
   since Report Builder's rows aren't .designer-field cards. */
.task-theme .designer-row.dragging,
.task-theme .designer-section-group.dragging {
  cursor: grabbing;
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.25);
  opacity: 0.95;
}
.task-theme .rb-drag-placeholder {
  border: 2px dashed var(--task-accent);
  border-radius: var(--task-radius);
  margin-bottom: 6px;
  background: rgba(47, 111, 237, 0.08);
}
/* Join-mode variant (§19 canvas rework) — a thin vertical bar shown
   inside a standalone card's own slot, on whichever side the pointer is
   hovering, instead of the usual horizontal gap-between-rows bar —
   signals "drop here to sit beside this block" rather than "drop here
   to insert a new row above/below". align-self:stretch fills the
   slot's actual height regardless of the inline height startDrag() set
   for the horizontal variant (sized to the DRAGGED card's own height,
   which is irrelevant here); !important only overrides that one inline
   style, not anything else about the placeholder. */
.task-theme .rb-drag-placeholder.rb-drag-placeholder-vertical {
  width: 6px;
  height: auto !important;
  align-self: stretch;
  margin-bottom: 0;
  border: none;
  border-radius: 3px;
  background: var(--task-accent);
}
/* Form Designer's own drop-target placeholder (attachToolboxDragHandlers()
   and attachDragHandlers() in designer.js both create elements with this
   class) — was never actually styled here, only mentioned in the
   .designer-row/.designer-section-group comment above. Its cloned-tile
   drag GHOST (.designer-toolbox-drag-ghost, styled just below) was
   already visible while dragging, but with no visual for the actual
   DROP TARGET, dragging a new field from the toolbox looked like nothing
   was happening \u2014 there was no dashed line showing where it would land,
   unlike Report Builder's own .rb-drag-placeholder just above, which
   this mirrors. */
.task-theme .field-drag-placeholder {
  border: 2px dashed var(--task-accent);
  border-radius: var(--task-radius);
  margin-bottom: 6px;
  background: rgba(47, 111, 237, 0.08);
}
/* Canvas layout (§19 canvas rework) — a block is no longer always a
   full-width stacked row. A standalone block sits in a `.rb-canvas-slot`
   (full-width flex container; `justify-content`, set inline per the
   block's own `align`, is what actually moves the compact card left/
   center/right — see renderBlockRows()'s own comment for why this is
   what makes Alignment do something visible on THIS canvas, not only in
   the separate Preview tab), sized to its own content rather than
   stretched, so there's room for it to visibly sit anywhere in the row.
   Two or more blocks sharing a rowGroup render inside a `.rb-canvas-row`
   flex row instead, each in its own `.rb-canvas-row-cell` sized by its
   widthPct, where the card fills its cell completely (there's no spare
   space inside a cell to align within). Row/slot spacing (margin-bottom
   on both) — 6px originally, bumped to 14px per an earlier request for
   "a bit more padding between rows", now set to 7px per a follow-up
   direct request for a specific value — applied identically to both so
   the vertical rhythm stays consistent whether a given row is a
   standalone block or a joined multi-block row. */
.task-theme .rb-canvas-slot { display: flex; width: 100%; margin-bottom: 7px; }
.task-theme .rb-canvas-slot .designer-row { max-width: 100%; }
/* Spacer card is always full-row-width (§ direct request) regardless of
   its slot's justify-content — every other block type sizes to its own
   content and gets positioned left/center/right within the slot by that
   justify-content (see renderBlockRows()'s own comment on why), but a
   Spacer has no Alignment property any more (removed in the same
   request — see renderLayoutControls()'s own comment) precisely because
   there's nothing for it to align: it always fills the row. `width`, not
   just the shared rule's `max-width` above, is what actually forces
   that rather than leaving it sized to its own compact icon+label
   content like every other card. */
.task-theme .rb-canvas-slot[data-block-type="spacer"] .designer-row { width: 100%; }
/* No wrapping — up to MAX_ROW_MEMBERS=6 blocks (see
   findBlockDropTarget()'s own comment in reportBuilder.js) always stay
   on one visible line now, per direct request ("I need to be able to
   visually see up to 6 objects on a single row"). The previous
   flex-wrap:wrap fallback let a too-narrow canvas silently reflow a row
   into two visual lines — which, worse than just looking different,
   meant a row's own geometry (which cells sit on which line) could
   shift as membership changed mid-drag, making the join zones
   underneath genuinely harder to hit reliably, not just harder to
   read. overflow-x:auto is the fallback now instead: if six cells at
   their min-width genuinely can't fit the canvas's current width, the
   row scrolls horizontally within itself rather than wrapping — a
   single stable line is always what you're aiming at, regardless of
   viewport size (the designer-fields-grid's own toolbox/properties
   columns were also narrowed a little — see that rule's own comment —
   specifically to make six columns fit without needing to scroll at
   all on most normal window widths). */
.task-theme .rb-canvas-row {
  display: flex; flex-wrap: nowrap; gap: 8px; width: 100%; margin-bottom: 7px; align-items: stretch;
  overflow-x: auto; padding-bottom: 4px; /* clearance so a visible scrollbar doesn't sit flush against the cards */
}
/* min-width keeps a cell's card from getting so narrow that even the
   trimmed-down grouped-card content below stops fitting at all — below
   this, a card becomes unreadable rather than just compact. Cells
   themselves also get flex-shrink:0 so they hold their computed
   widthPct share (or this floor, whichever is larger) rather than the
   browser silently shrinking them to cram everyone into view — that's
   exactly the wrap-avoiding overflow-x:auto fallback's job above, not
   flex-shrink's. */
.task-theme .rb-canvas-row-cell { min-width: 64px; flex-shrink: 0; position: relative; display: flex; }
.task-theme .rb-canvas-row-cell .designer-row { width: 100%; }
/* A grouped card trims down significantly from its standalone-row
   footprint (grip + badge + full label + 4 action buttons all always
   visible) specifically because up to 6 of these now have to fit
   side-by-side on one line: the type badge is dropped (redundant once
   selected — the Properties panel already says exactly what a block
   is) to give the label what little width is left, and the action
   buttons are hidden until the card is hovered or selected, matching a
   common compact-grid-card convention rather than spending space on 4
   rarely-needed-mid-arrangement buttons per cell at all times. Nothing
   is actually lost — hover or tap-to-select still reveals every action
   exactly as before; existing ellipsis truncation on the label
   (unchanged) handles whatever text still doesn't fit. */
.task-theme .rb-canvas-row-cell .designer-row { padding: 7px 8px; gap: 5px; }
.task-theme .rb-canvas-row-cell .designer-row-type-badge { display: none; }
.task-theme .rb-canvas-row-cell .designer-row-actions { display: none; }
.task-theme .rb-canvas-row-cell .designer-row:hover .designer-row-actions,
.task-theme .rb-canvas-row-cell .designer-row.selected .designer-row-actions {
  display: flex;
}
.task-theme .rb-canvas-row-cell .designer-row-label { font-size: 12px; }

/* Card shadows, Report Builder canvas only — scoped via #rb-canvas
   (Report Builder's own canvas id; Form Designer's is #designer-canvas)
   rather than adding to the bare .designer-row/.designer-section-group
   classes directly, since those classes are shared with Form Designer
   and this request was specifically for Report Builder's cards, not a
   global Designer restyle. Sections get the same treatment as blocks
   so a section-of-blocks reads as one consistent "stack of cards" look
   rather than blocks-with-shadows sitting inside a flat section box.
   Deliberately its own literal value here, not a strengthened
   --task-shadow — that token is shared across the whole Tasks/Designer
   system (My Work, Kanban, Form Designer's own cards, etc.), so bumping
   it globally would restyle every one of those screens along with it;
   this request was specifically for Report Builder's own cards.
   Dialed back per direct follow-up ("lessen the shadows") from the
   previous, more pronounced two-layer value — still a bit heavier than
   the base --task-shadow token so cards read as raised, just not as
   dramatically as before. */
#rb-canvas .designer-row,
#rb-canvas .designer-section-group {
  box-shadow: 0 1px 3px rgba(20, 30, 60, 0.08), 0 3px 10px rgba(20, 30, 60, 0.06);
}
/* Section card made visibly distinct from the block cards nested inside
   it, per direct request — previously the section wrapper had no
   background of its own (transparent, so it just showed the canvas's
   own --task-bg through), which sits close enough in shade to the block
   cards' --task-surface that the 1px yellow border was the only real
   tell apart. A tinted background (derived from the same
   --task-accent-yellow already used for the border, at low opacity so
   it reads as "a section" rather than "a warning") plus a slightly
   heavier border now makes the container itself obviously a distinct
   layer, independent of whatever Section Style colors get set via
   Properties (section.style, see renderSectionMetaProperties()) — that
   background paints on top of this one when present.
   Superseded per direct request: background is now literal white (not
   the yellow-tinted wash above, and not --task-surface either — that
   token is redefined to an off-white #f7f8fc for the whole Designer
   scope, see .task-theme.designer-page's own comment at the top of this
   file, so it wouldn't read as true white here). Border/divider color
   now matches the app's own bottom tabbar/topbar background (--chrome-bg,
   the dark navy #01162e defined in app.css's :root — a genuinely
   different token than --task-surface, and the one actually named "the
   bottom panel bar" in the request) rather than the previous
   --task-surface guess. */
#rb-canvas .designer-section-group {
  border-color: var(--chrome-bg);
  background: #ffffff;
  border-width: 1.5px;
  /* 10px \u2192 7px, matching the row/slot spacing above exactly \u2014 "the
     Section card and the card below it" gets the same rhythm as every
     other row-to-row gap on this canvas, not a separately-tuned value.
     Scoped to #rb-canvas rather than editing the shared base rule
     (10px, just above) so Form Designer's own section spacing is
     untouched. */
  margin-bottom: 7px;
}
/* The section's own title bar — a DIRECT child .designer-row; every
   actual block's card lives several levels deeper (.designer-section-
   body > .rb-canvas-slot/.rb-canvas-row > .designer-row), so the child
   combinator here can never accidentally catch one of those instead.
   Styled as a header bar, not another floating card: no card
   background/border/shadow of its own (it already sits inside one),
   just a rule underneath separating it from the blocks below, and
   cursor:default since — unlike a block's card — clicking it selects
   the section for Properties rather than "opening" anything to drag.
   Padding trimmed (9px 10px \u2192 6px 10px, from the shared .designer-row
   base rule) specifically for this title bar, per direct request to
   make the Section object shorter — every actual block card keeps the
   original padding untouched since this only targets the direct-child
   title row, not .designer-row generally. */
#rb-canvas .designer-section-group > .designer-row {
  background: transparent;
  border: none;
  /* Divider matches the same --chrome-bg dark-navy tabbar color as the
     section's own outline (see that rule's own comment just above). */
  border-bottom: 1px solid var(--chrome-bg);
  border-radius: 0;
  box-shadow: none;
  cursor: default;
  padding: 6px 10px;
  /* 7px gap between the divider and the blocks below it, per direct
     request — the divider is this row's own border-bottom, so the gap
     has to live on this side (margin-bottom) rather than as padding on
     .designer-section-body, which would otherwise add space consistently
     on every side of the body rather than just under the divider. */
  margin-bottom: 7px;
}
#rb-canvas .designer-section-group > .designer-row:hover {
  border-color: var(--chrome-bg);
}
/* Section header height "fit content" (§ direct request) — the title
   <input> is a plain input[type="text"], which app.css's global rule
   for that selector gives min-height:var(--tap-min) (48px, sized for a
   real touch target elsewhere in the app). Since this row is
   align-items:center, that 48px floor on the input was forcing the
   WHOLE header row to be at least that tall regardless of how much
   shorter its own padding+text actually needed — overriding it here
   (scoped to this specific input, not input[type="text"] generally,
   since every other text input in the app still wants the 48px tap
   target) lets the row's own 6px/10px padding be what actually
   determines its height again. line-height:normal resets the same
   global rule's implicit line-box sizing so the text itself doesn't
   carry a leftover tall line-height either. */
.task-theme .rb-section-title-input {
  min-height: 0;
  line-height: normal;
  padding: 0;
}
#rb-canvas .designer-section-group > .designer-row.selected {
  background: rgba(242, 193, 46, 0.16);
  border-color: var(--task-accent-yellow);
}
/* Drag-to-join target highlight — intentionally removed per direct
   request ("don't highlight anything" while dragging, both builders).
   .rb-join-target is still toggled in reportBuilder.js's
   positionDropPlaceholder()/clearJoinHighlight() exactly as before (the
   vertical placeholder bar it goes with is still the actual drop-
   position indicator and stays), but with no rule here to match it, the
   class itself is now visually inert — simplest way to drop the
   highlight without touching the class-toggling logic that still needs
   to run for the vertical-vs-horizontal placeholder decision. */
/* Divider row (§19 canvas rework follow-up) — a real horizontal rule
   spanning the canvas instead of a compact icon+label+buttons card, per
   the request. Overrides .rb-canvas-slot's default justify-content-only
   layout (meant for positioning a much narrower card) with its own
   flex row: the line itself grows to fill all available width via
   flex:1, the delete button sits at a fixed size beside it. The line is
   both the click-to-select target and the drag handle (see
   buildDividerRow()'s own comment on why one element does both here,
   unlike every other block's separate grip icon) — cursor:grab signals
   that, :hover/.selected both darken it a little so there's some
   feedback despite there being no card border to highlight. */
.task-theme .rb-canvas-divider-wrap {
  display: flex; align-items: center; gap: 8px; justify-content: flex-start !important;
}
.task-theme .rb-canvas-divider-line {
  flex: 1; cursor: grab; min-height: 20px;
  display: flex; align-items: center;
}
/* The visible rule is a thin pseudo-line vertically centered inside a
   taller (min-height:20px) hit target — a bare 2px-tall element would
   be a frustratingly thin thing to have to click or grab precisely.
   Color lives only on the pseudo-element, not the hit-target div itself
   (a background on the div would fill its whole 20px height as a solid
   bar rather than a thin centered rule — caught by an actual screenshot,
   not just reasoning about the box model). */
.task-theme .rb-canvas-divider-line::before { content: ''; display: block; width: 100%; height: var(--rb-divider-thickness, 2px); border-radius: 1px; background: var(--rb-divider-color, var(--task-border)); }
.task-theme .rb-canvas-divider-line:hover::before,
.task-theme .rb-canvas-divider-wrap.selected .rb-canvas-divider-line::before {
  background: var(--task-accent);
}
.task-theme .rb-canvas-divider-delete {
  flex: none; width: 22px; height: 22px; border: none; background: none; color: var(--task-text-faint);
  cursor: pointer; display: flex; align-items: center; justify-content: center; border-radius: 6px; font-size: 12px;
}
.task-theme .rb-canvas-divider-delete:hover { background: var(--task-gray-bg); color: var(--task-red); }
.task-theme .designer-row-label { flex: 1; min-width: 0; font-size: 13px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.task-theme .designer-row-label.placeholder { color: var(--task-text-faint); font-weight: 500; font-style: italic; }
.task-theme .designer-row-required { color: var(--task-red); flex: none; font-weight: 700; }
.task-theme .designer-row-type-badge { flex: none; font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.02em; background: var(--task-gray-bg); color: var(--task-text-dim); border-radius: 999px; padding: 3px 8px; white-space: nowrap; }
.task-theme .designer-row-actions { display: flex; align-items: center; gap: 2px; flex: none; }
.task-theme .designer-row-icon-btn { width: 26px; height: 26px; border: none; background: none; color: var(--task-text-faint); cursor: pointer; display: flex; align-items: center; justify-content: center; border-radius: 6px; }
.task-theme .designer-row-icon-btn:hover { background: var(--task-gray-bg); color: var(--task-text); }
.task-theme .designer-row-icon-btn.danger:hover { color: var(--task-red); }
.task-theme .designer-row-icon-btn svg { width: 15px; height: 15px; }
.task-theme .designer-section-collapse-btn { flex: none; color: var(--task-text-dim); background: none; border: none; cursor: pointer; display: flex; padding: 2px; }
.task-theme .designer-section-caption { font-size: 11px; color: var(--task-text-faint); font-style: italic; margin: 0 0 6px 4px; }

.task-theme .designer-properties {
  background: var(--task-surface); border: 1px solid var(--task-border); border-radius: var(--task-radius);
  padding: 14px; box-shadow: var(--task-shadow); position: sticky; top: 10px; max-height: calc(100vh - 220px); overflow-y: auto;
}
.task-theme .designer-properties-empty { text-align: center; padding: 30px 10px; color: var(--task-text-dim); font-size: 13px; }
.task-theme .designer-properties-title { font-size: 15px; font-weight: 700; margin-bottom: 12px; display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.task-theme .designer-properties-section { border-top: 1px solid var(--task-border); padding-top: 12px; margin-top: 12px; }
.task-theme .designer-properties-section:first-of-type { border-top: none; padding-top: 0; margin-top: 0; }
.task-theme .designer-properties-section-title { font-size: 12px; font-weight: 700; color: var(--task-text-dim); margin-bottom: 8px; }

/* ---------- Field Properties panel — per-property boxed border ----------
   Only renderFieldProperties() (not renderPageProperties()) tags its
   sections with this extra class, so Page Properties keeps the plain
   top-divider look above while each individual property in the Field
   Properties panel (Field Type, Label, Help Text, etc.) gets its own
   fully-bordered box in dark gray, per Stuart's request. Both the plain
   and :first-of-type forms are restated here (rather than just adding
   `border`) because the pre-existing :first-of-type rule above zeroes
   out border-top/margin-top/padding-top at equal specificity — without
   an explicit override the first box in the panel would end up missing
   its top edge and touching the panel title. */
.task-theme .designer-properties-section.designer-field-prop-box,
.task-theme .designer-properties-section.designer-field-prop-box:first-of-type {
  border: 1.5px solid var(--designer-field-prop-border, #4b5563);
  border-radius: var(--task-radius-sm);
  padding: 10px 12px;
  margin: 0 0 10px;
}
.task-theme .designer-properties-section.designer-field-prop-box:last-child { margin-bottom: 0; }
.task-theme .designer-field-key-display { font-size: 12px; font-family: var(--font-mono, monospace); color: var(--task-text-faint); background: var(--task-gray-bg); border-radius: 6px; padding: 6px 8px; }

.task-theme .designer-cond-row { display: flex; gap: 6px; align-items: center; margin-bottom: 6px; flex-wrap: wrap; }
.task-theme .designer-cond-row select, .task-theme .designer-cond-row input { min-height: 36px; padding: 6px 8px; font-size: 13px; width: auto; flex: 1 1 90px; min-width: 0; }
.task-theme .designer-cond-remove { flex: none; background: none; border: none; color: var(--task-text-faint); cursor: pointer; font-size: 15px; padding: 0 4px; }

/* ---------- Preview view (Phase 6): device-frame widths ------------------ */
.task-theme .designer-preview-toolbar { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; flex-wrap: wrap; }
.task-theme .designer-preview-frame-wrap { display: flex; justify-content: center; padding-bottom: 20px; }
.task-theme .designer-preview-frame {
  background: var(--panel, #16213a); border-radius: 14px; overflow: hidden; box-shadow: var(--task-shadow);
  border: 8px solid #1a1f2e; display: flex; flex-direction: column; max-height: 78vh;
  width: 100%; max-width: 390px; transition: max-width 0.2s ease;
}
.task-theme .designer-preview-frame[data-device="tablet"] { max-width: 620px; }
.task-theme .designer-preview-frame[data-device="desktop"] { max-width: 100%; border-width: 2px; border-radius: 8px; }
.task-theme .designer-preview-frame .sheet-body { overflow-y: auto; flex: 1; }

/* ---------- Publish view -------------------------------------------------- */
.task-theme .designer-publish-summary { display: flex; gap: 10px; margin-bottom: 14px; }
.task-theme .designer-publish-stat { flex: 1; background: var(--task-surface); border: 1px solid var(--task-border); border-radius: var(--task-radius); padding: 14px; text-align: center; }
.task-theme .designer-publish-stat .task-metric-value { display: block; }
.task-theme .designer-publish-stat.has-errors .task-metric-value { color: var(--task-red); }

/* ---------- Responsive: toolbox drawer + properties bottom sheet -------- */
@media (max-width: 900px) {
  /* minmax(0, 1fr), not a bare 1fr — a bare 1fr's automatic minimum
     width is content-based (effectively minmax(auto, 1fr)), so any
     child with enough intrinsic width (a joined .rb-canvas-row of
     several cells, an unbroken label, etc.) could stretch this single
     mobile column past the viewport and push the whole page into
     horizontal scroll rather than staying contained — exactly the
     "objects in the Canvas are overflowing the right side of the page"
     symptom. minmax(0, 1fr) forces the track to the viewport width
     regardless of content, so overflow inside it is the *content's* own
     overflow-x:auto (already on .rb-canvas-row) to handle, not the
     page's. */
  .task-theme .designer-fields-grid { grid-template-columns: minmax(0, 1fr); }
  .task-theme .designer-toolbox-mobile-toggle { display: inline-flex !important; }
  /* Canvas loses its desktop scroll cap here — it's the only in-flow
     column left at this width (toolbox/properties both switch to
     fixed-position overlays below), so it should flow with the page
     like a normal mobile screen rather than scroll in its own box.
     min-width:0 is the flex/grid-item half of the same overflow fix as
     the grid-template-columns change above — without it this element's
     own automatic minimum size is still content-based even though its
     grid track isn't, which alone would defeat the fix above. */
  .task-theme .designer-canvas { max-height: none; overflow-y: visible; overflow-x: hidden; min-width: 0; }
  /* Up/Down move buttons specifically (not Duplicate/Delete) hidden on
     mobile when a block is sitting side-by-side in a joined row — per
     direct request. Move Up/Down only ever reorders STANDALONE rows
     (moveBlock() always pulls a block out to its own row first, see
     that function's own comment), so on a cramped mobile row-cell,
     where every pixel of the already-trimmed-down compact card matters
     more than on desktop, these two are the least useful of the four
     actions to keep visible — Duplicate/Delete stay exactly as before
     (still hidden until hover/selected, unchanged from the desktop
     rule above, just minus these two icons even once revealed). */
  .task-theme .rb-canvas-row-cell .designer-row-actions [data-move-block] { display: none; }
  .task-theme .designer-toolbox {
    position: fixed; top: 0; bottom: 0; left: 0; width: 260px; max-height: none; z-index: 45;
    border-radius: 0; transform: translateX(-100%); transition: transform 0.2s ease;
    box-shadow: 0 0 30px rgba(20,30,60,0.25);
    /* Base rule's overflow-y:auto carries over (not overridden here), but
       the fixed-height mobile drawer still needs the same iOS momentum-
       scroll + scroll-chaining fixes .designer-properties' bottom sheet
       already gets below — without -webkit-overflow-scrolling, iOS
       Safari's default (non-momentum) scroll on a fixed-position element
       can feel like it isn't scrolling at all on a light swipe. Bottom
       padding adds env(safe-area-inset-bottom) so the last tile in the
       list isn't sitting under a notched phone's home-indicator bar —
       same pattern app.css's own bottom-fixed panels use (search
       "safe-area" there). */
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
    padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px));
  }
  .task-theme .designer-toolbox.open { transform: translateX(0); }
  .task-theme .designer-toolbox-backdrop { display: none; position: fixed; inset: 0; background: rgba(15,23,42,0.4); z-index: 44; transition: background 0.15s ease; }
  .task-theme .designer-toolbox-backdrop.open { display: block; }
  /* While actively dragging a toolbox tile toward the canvas (see
     attachToolboxDragHandlers()'s startDrag()/cleanup() in designer.js,
     and reportBuilder.js's own copy), fade the backdrop way down —
     the canvas underneath it, including the drag placeholder tracking
     the drop position, needs to actually be visible to drop onto
     accurately, not just dimly visible through a 40%-opacity overlay
     meant for "drawer is open, canvas is inert" rather than "drawer is
     open AND you're actively interacting with the canvas through it". */
  body.toolbox-drag-active .designer-toolbox-backdrop.open { background: rgba(15,23,42,0.08); }
  /* Real bug, not just a visibility nicety: the 260px-wide drawer itself
     (not only its backdrop) still physically covers the left ~260px of
     the screen for the ENTIRE drag, even with the backdrop faded above
     — on a typical ~375-430px-wide phone that leaves only a narrow
     strip of canvas actually reachable to drop onto, which is exactly
     what "unable to drag new objects onto the Canvas" on mobile looks
     like from underneath: not literally impossible, but the real drop
     target is a sliver at the screen's right edge instead of the whole
     canvas. The dragged tile's own visual feedback is a separate fixed-
     position ghost clone appended straight to document.body (see
     startDrag() in both builders) that already follows the pointer
     independently of the drawer, so the drawer itself has nothing left
     to usefully show once a drag is actually underway — sliding it
     fully off-screen the moment dragging starts (same transform the
     closed state already uses) hands the whole screen to the canvas as
     a drop target, exactly like the ghost already implies should be
     possible. Reverted automatically the instant toolbox-drag-active
     comes off (drag ends), independent of and prior to the normal
     close-the-drawer animation. */
  body.toolbox-drag-active .task-theme .designer-toolbox.open { transform: translateX(-100%); }
  .task-theme .designer-shell { flex-direction: column; }
  /* Sticky as the page scrolls, per direct request — this bar (Design/
     Data/Parameters/Pages/Theme/Schedules/Preview) is the only way to
     switch tabs on mobile, and without this it scrolls away with
     everything else the moment you scroll down into a tab's own
     content, same as any tab strip that's worth keeping reachable.
     z-index only needs to clear ordinary in-flow canvas content (no
     z-index of its own) and .rb-canvas-add-row right below it (19) —
     nowhere near the toolbox/properties drawers' own 44-47 range, so
     there's no risk of this ending up on top of an open drawer/sheet. */
  /* border-radius:0 overrides the desktop base rule's rounded corners
     (.designer-sidebar above, var(--task-radius)) — without this, the
     bar's own opaque background doesn't quite reach its rectangular
     bounding box at the (still-rounded) corners, so canvas content
     scrolling underneath (including a Section card's own border) peeks
     through at those two corner notches as it passes behind the sticky
     bar. Reported as "I can see the Section outline on both sides of
     the top panel" while scrolling \u2014 the two "sides" being the left
     and right rounded corners. */
  .task-theme .designer-sidebar { width: 100%; flex-direction: row; overflow-x: auto; -webkit-overflow-scrolling: touch; position: sticky; top: 0; z-index: 20; border-radius: 0; }
  .task-theme .designer-sidebar-item { flex: none; }
  .task-theme .designer-sidebar-soon { display: none; }
  /* § direct request: "remove the tab at the bottom of the Report
     Designer on mobile" — the app's own global bottom navigation
     (#tabbar, "Map / Tasks / Admin" etc. — see app.css's "Bottom tab
     nav" section) has no per-view visibility toggle anywhere in this
     app (router.js's renderChrome() only ever hides it for logged-out/
     needs-password-change), so on every OTHER screen it's just always
     there, unremarkable. Report Builder's mobile layout is different
     enough from an ordinary list/detail screen — a real editing tool
     with its own sticky Design/Data tab bar immediately above (right
     here) plus fixed toolbox/properties drawers — that the app's own
     nav tabs underneath it are redundant chrome competing for the same
     cramped vertical space, not a useful way to leave mid-edit (Back,
     top-left of the header, already covers that). `:has()` is what
     reaches #tabbar at all from here — it's a sibling of #view-root
     under #app (see frontend/index.html), not a descendant of anything
     Report Builder itself renders, so there's no ordinary descendant
     selector that could ever target it; this app already relies on
     `:has()` elsewhere (see app.css's own .popup-designer-radio-pill
     rule). Scoped to this same ≤900px block (not a narrower phone-only
     width) — the redundant-chrome problem this fixes is exactly the
     same one this whole block already exists to solve for the
     toolbox/properties/sidebar just above, at exactly this breakpoint. */
  #app:has(#view-root[data-view="reportBuilder"]) #tabbar { display: none; }
  /* Sticks directly under the sidebar bar above (top: the sidebar's own
     JS-measured height — see renderSidebar()'s own comment on
     --rb-sidebar-h — not a guessed pixel value that could silently
     drift out of sync with it). Report Builder's Design tab only; opaque
     background for the same reason the sidebar needs one \u2014 scrolled
     canvas content passing underneath both bars shouldn't show through
     either of them. */
  .task-theme .rb-canvas-add-row {
    position: sticky; top: var(--rb-sidebar-h, 44px); z-index: 19;
    background: var(--task-bg); padding: 8px 0;
  }
  /* Add Field + Page tabs row (Form Designer's Fields view only) — same
     sticky-under-the-sidebar treatment as Report Builder's own
     .rb-canvas-add-row just above, using this page's own one-time-
     measured --designer-sidebar-h instead of --rb-sidebar-h (see that
     custom property's own comment in designer.js for why this one
     doesn't need re-measuring per render). Opaque background for the
     same reason the sidebar bar above it needs one \u2014 scrolled canvas
     content passing underneath both bars shouldn't show through either
     of them. margin-bottom (not the base rule's own) keeps the same
     10px gap to the fields-grid below now that this row is sticky and
     padded, rather than doubling up on both. */
  .task-theme .designer-page-toolbar {
    position: sticky; top: var(--designer-sidebar-h, 44px); z-index: 19;
    background: var(--task-bg); padding: 8px 0; margin-bottom: 2px;
  }

  /* Properties panel becomes a real bottom sheet, not a stacked section —
     fixed position, slides up over the canvas, its own backdrop and drag
     handle, matching taskCreateModal.js's own modal conventions (though
     built directly rather than routed through openModal() — see
     renderProperties()'s own comment for why: a separate modal render
     target would split where field-property mutations need to re-render
     into, depending on viewport width). */
  .task-theme .designer-properties {
    position: fixed; left: 0; right: 0; bottom: 0;
    /* top:auto is NOT redundant with bottom:0 here — without it, this
       inherits `top: 10px` from the desktop `position:sticky; top:10px`
       rule above (nothing in this mobile block had ever reset it), and
       a fixed element with BOTH top and bottom pinned gets its height
       forced to fill the gap between them rather than sized by content/
       max-height. Confirmed by actually rendering this at 390x844 and
       measuring: the sheet's real box came out as top:10/bottom:727 —
       727px short of the true viewport bottom (844) — which is where
       every one of the three reported bugs traced back to: (1) that
       727-844 dead zone showing the backdrop's dark tint over the page
       background is the "gray gap at the bottom of the properties
       screen"; (2) a box that starts at y:10 visibly overlaps/sits
       under the topbar's own 0-35px region, which is the "top of the
       properties window is hidden by the top panel bar"; (3) the CLOSED
       state's `translateY(100%)` is 100% of the box's OWN height, so a
       box that's already 117px short of the viewport only gets pushed
       down by that same shortfall — it never fully clears the screen,
       leaving that 117px visible as the "white bar at the bottom" even
       with nothing selected. top:auto lets the box size purely from
       content up to max-height, anchored from the bottom the way
       bottom:0 alone always implied it should — closing the entire
       117px shortfall at once instead of needing three separate patches
       for what was really one bug. */
    top: auto;
    z-index: 47;
    max-height: 85vh; max-height: 85svh; /* vh fallback first, then svh override for browsers that support it — same pattern as taskCreateModal.js's own iOS viewport fix */
    border-radius: 16px 16px 0 0;
    transform: translateY(100%);
    transition: transform 0.22s ease;
    box-shadow: 0 -6px 30px rgba(20, 30, 60, 0.3);
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
  }
  .task-theme .designer-properties::before {
    content: ''; display: block; width: 42px; height: 5px; border-radius: 3px;
    background: var(--task-border); margin: 10px auto 6px;
  }
  .task-theme .designer-properties.open { transform: translateY(0); }
  .task-theme .designer-properties-backdrop {
    display: none; position: fixed; inset: 0; background: rgba(15, 23, 42, 0.4); z-index: 46;
  }
  .task-theme .designer-properties-backdrop.open { display: block; }
  /* Save/Cancel footer, Report Builder's mobile properties sheet only
     (see rb-props-mobile-actions's own comment in reportBuilder.js for
     why this doesn't extend to Form Designer or to desktop). position:
     sticky, not fixed — the sheet itself (.designer-properties) is
     already the scrolling container (overflow-y:auto from the base
     rule), so a sticky child pins to ITS bottom edge while its content
     scrolls past above, without needing the panel's own display mode
     changed to flex to achieve it (that would turn every existing
     property panel's top-level elements into flex items too, a much
     bigger blast radius than this footer needs). An opaque background
     (not transparent) is what makes it actually read as "always-visible
     footer" rather than "content momentarily scrolled behind it" as the
     panel scrolls. Bottom padding includes the safe-area inset so the
     buttons aren't sitting under a notched phone's home-indicator bar —
     same pattern as .designer-toolbox's own mobile drawer padding. */
  .task-theme .rb-props-mobile-actions {
    /* bottom: -14px, not 0 — a sticky element's "stick to bottom:0"
       offset is measured against the SCROLLING CONTAINER's padding-box
       edge, i.e. .designer-properties' OWN bottom padding (14px, from
       its base rule above) still sits between a bottom:0 sticky child
       and the panel's true visible bottom edge, even though this
       footer's negative margins already cancel that same padding for
       everything else (its width, its left/right bleed). Measured
       directly with a real scrolled Playwright render: bottom:0 left a
       15px gap below the buttons — exactly what let scrolled content
       peek through underneath, the reported bug — bottom:-14px (equal
       to that padding, canceling it the same way the margins already
       do for the other three sides) closed it to a sub-2px rounding
       artifact from the vh/svh fallback, invisible in practice. */
    position: sticky; bottom: -14px; left: 0; right: 0;
    display: flex; gap: 8px; margin: 14px -14px -14px; padding: 10px 14px calc(10px + env(safe-area-inset-bottom, 0px));
    background: var(--task-surface); border-top: 1px solid var(--task-border);
  }
  .task-theme .rb-props-mobile-actions .task-btn { flex: 1; }
}
@media (min-width: 901px) {
  /* Desktop's properties panel is a persistent always-visible side
     panel with no "close" affordance (edits apply live, same as every
     other field in this app) — Save/Cancel is a mobile-sheet-specific
     concept (something to dismiss), not part of that paradigm, so this
     stays hidden here rather than changing how desktop editing works. */
  .task-theme .rb-props-mobile-actions { display: none !important; }
  .task-theme .designer-toolbox-backdrop { display: none !important; }
  .task-theme .designer-properties-backdrop { display: none !important; }
}

/* =============================================================================
   REPORT BUILDER DESKTOP — sticky horizontal tab bar
   ==========================================================================
   Direct request: on desktop, move the Design/Data/Parameters/Pages/
   Theme/Schedules/Preview tabs from the vertical sidebar (Form
   Designer's own layout — shared markup/classes) onto one horizontal
   line across the top, and keep that bar sticky together with the
   header row above it (Back button, report name, Generate, Publish)
   and the Add Section button. Scoped to .rb-page (Report Builder's own
   root class, set in reportBuilder.js's render()) rather than the
   shared .designer-shell/.designer-sidebar rules directly, so Form
   Designer's own desktop three-panel layout in the block below is
   completely untouched — same markup, same class names, deliberately
   different treatment only here.

   This reuses the exact horizontal-sidebar/sticky pattern the mobile
   block above (@media max-width:900px) already established for BOTH
   builders below that width; this just brings it to Report Builder
   specifically above it too, stacked under a now-also-sticky header
   instead of stuck to the very top of the viewport (mobile has no
   separate sticky header row to share space with — Report Builder's
   header only needs its own sticky treatment on desktop). */
@media (min-width: 901px) {
  .rb-page .designer-header {
    position: sticky; top: 0; z-index: 21;
    background: var(--task-bg); padding-top: 10px; margin-bottom: 0;
  }
  .rb-page .designer-shell { flex-direction: column; gap: 0; }
  .rb-page .designer-sidebar {
    width: 100%; flex-direction: row; align-items: center; gap: 2px;
    position: sticky; z-index: 20;
    /* --rb-header-h: reportBuilder.js's renderSidebar() measures the
       header's real height on every render rather than this guessing a
       fixed number that would silently drift the moment the header's
       own padding/font-size/wrapping changed for any reason — same
       "measured, not guessed" reasoning --rb-sidebar-h already uses
       for the mobile Add Section row below the sidebar bar. */
    top: var(--rb-header-h, 56px);
    border-radius: 0; box-shadow: none;
    border-left: none; border-right: none; border-top: none;
    overflow-x: auto; -webkit-overflow-scrolling: touch;
    margin-bottom: 14px;
  }
  .rb-page .designer-sidebar-item { flex: none; }
  .rb-page .rb-add-section-sticky { margin-left: auto; flex: none; }
  /* The Design tab's own "+ Add Section" (renderDesignTab, kept for
     mobile/narrow screens where there's no sticky tab bar for a
     duplicate button to live in) is redundant with the sticky bar's own
     copy above once there IS one — hidden here rather than removed
     from the DOM entirely, since renderDesignTab() still wires its
     click handler unconditionally regardless of viewport width. */
  .rb-page .rb-canvas-add-row #rb-add-section { display: none; }
}

/* =============================================================================
   DESKTOP LAYOUT SYSTEM — Form Designer specifics
   ==========================================================================
   The Designer is architecture.md's FluidContainer case (a genuine
   three-panel workspace screen, like the Map) — app.css's desktop
   container rules deliberately skip .task-theme.designer-page for this
   reason, so the shell below is free to use the full viewport width
   itself. What was still missing at desktop sizes: the brief's own
   spec for this screen ("LEFT TOOLBOX fixed ~260-300px, CENTER DESIGNER
   flexible, RIGHT PROPERTIES PANEL fixed ~340-400px... center column
   expands first, side panels remain fixed... do not let cards become
   extremely wide") — the shell's columns were sized for a comfortable
   laptop width (200px/320px) and never widened for a larger monitor,
   and the center canvas had no ceiling at all, so a very wide window
   stretched field rows further than is actually readable. Both fixed
   below, purely at this breakpoint — the sub-900px mobile drawer/sheet
   behavior above is untouched. */
@media (min-width: 1024px) {
  .task-theme .designer-fields-grid {
    grid-template-columns: 280px minmax(0, 1fr) 380px;
    gap: 20px;
  }
  .task-theme .designer-toolbox,
  .task-theme .designer-properties,
  .task-theme .designer-canvas {
    max-height: calc(100vh - 180px);
  }
}
/* Report Builder's sticky header + sticky tab bar above (the .rb-page
   block earlier in this file) eat real vertical space the base/1024px
   rules above don't know about — without this, the toolbox/canvas/
   properties columns' own max-height assumes only the usual top chrome
   is fixed-position, so they'd run taller than the space actually left
   under both sticky bars combined, forcing the WHOLE page to scroll
   instead of just each column internally (each already has its own
   overflow-y:auto for exactly this reason). Placed after both of the
   generic rules above so it wins the tie on source order (identical
   selector specificity either way) regardless of which one would
   otherwise apply at the current width. Subtracts both sticky bars'
   own measured heights (--rb-header-h, --rb-sidebar-h — see
   reportBuilder.js's renderSidebar()) from the same 220px baseline
   the base (sub-901px) rule already uses, rather than guessing a
   combined fixed number that could drift out of sync with either bar's
   real height. */
@media (min-width: 901px) {
  .rb-page .designer-toolbox,
  .rb-page .designer-properties,
  .rb-page .designer-canvas {
    max-height: calc(100vh - 220px - var(--rb-header-h, 56px) - var(--rb-sidebar-h, 44px));
  }
}
/* Past a genuinely wide (ultra-wide monitor) window, cap the center
   canvas's own content width rather than letting compact field rows
   stretch the full remaining span — the toolbox/properties panels stay
   at their fixed widths above; only the flexible center column gets a
   ceiling, with the freed space becoming extra margin either side of
   it instead of longer rows.
   Report Builder's own canvas (#rb-canvas) is deliberately excluded
   from this cap — per direct request, its canvas should always
   stretch to fill the full space between the toolbox and properties
   panels rather than centering with empty gutters on a wide monitor.
   Report layout blocks (multi-column rows, dividers, tables) benefit
   from the extra width in a way Form Designer's field rows don't;
   that readability concern was Form Designer-specific to begin with,
   so Report Builder was never a good fit for the same ceiling. */
@media (min-width: 1600px) {
  .task-theme .designer-canvas:not(#rb-canvas) {
    max-width: 1100px;
    margin: 0 auto;
  }
}
#rb-canvas.designer-canvas {
  max-width: none;
  margin: 0;
}


/* ---------- Layout tab (Form Layout customization) ----------------------
   See architecture.md §14's "Form Layout customization" writeup. Grouped
   into cards (Layout/Appearance/Navigation/Theme/Mobile/Media) per the
   spec's own suggested organization — flat cards, not nested <details>,
   to "avoid excessive nesting" per that same brief. */
.task-theme .designer-layout-groups { max-width: 640px; display: flex; flex-direction: column; gap: 14px; }

.task-theme .designer-choice-row { display: flex; gap: 10px; flex-wrap: wrap; }
.task-theme .designer-choice-btn {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  padding: 10px 16px; min-width: 84px;
  background: var(--task-surface); border: 1.5px solid var(--task-border); border-radius: var(--task-radius-sm);
  color: var(--task-text-dim); font-size: 12px; font-weight: 600; cursor: pointer;
}
.task-theme .designer-choice-btn.active { border-color: var(--task-accent); color: var(--task-text); background: var(--task-accent-dim); }
.task-theme .density-preview { display: flex; flex-direction: column; width: 44px; }
.task-theme .density-line { display: block; height: 4px; border-radius: 2px; background: var(--task-border); }
.task-theme .designer-choice-btn.active .density-line { background: var(--task-accent); }

.task-theme .designer-theme-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(90px, 1fr)); gap: 12px;
}
.task-theme .designer-color-field input[type="color"] {
  width: 100%; min-height: 40px; padding: 3px;
}
.task-theme .designer-contrast-warning { color: var(--task-warning, #b45309); margin-top: 4px; font-size: 11px; line-height: 1.3; }
.task-theme .designer-theme-preview {
  margin-top: 14px; padding: 14px; display: flex; align-items: center; gap: 10px;
  background: var(--task-gray-bg); border: 1px solid var(--task-border);
}
.task-theme .designer-theme-preview-btn {
  padding: 8px 18px; font-weight: 700; font-size: 13px; text-transform: uppercase; letter-spacing: 0.03em;
}
.task-theme .designer-theme-preview-swatch { width: 22px; height: 22px; border-radius: 50%; border: 1px solid rgba(0,0,0,0.15); }

/* ---------- Preview tab (device frame + progress indicator) ------------- */
.task-theme .form-progress { margin-top: 8px; }

/* Text/Field content styling controls (§19 Report Designer) — bold/
   italic/underline toggle buttons in the properties panel, styled like a
   small segmented control rather than plain checkboxes so the current
   state (which of B/I/U are on) reads at a glance. */
.task-theme .rb-style-toggle-row { display: flex; gap: 4px; margin-top: 4px; margin-bottom: 10px; }
/* Collapsed by default (§ direct request) — Label/Value/Text Style is
   the longest single block of controls on most block types' Properties
   screens (six fields' worth between the B/I/U row, Font Size, and two
   color pickers), and a Field block now shows TWO of these back to back
   (Label Style + Value Style — see renderTextStyleControls()'s own
   comment on why there are two), which made simple things like Label
   Text or Format hard to find without scrolling past a wall of styling
   controls nobody was touching that session. Plain <details>/<summary>
   rather than a hand-rolled toggle — no JS wiring needed for the
   expand/collapse itself, and it's already the same pattern this app
   uses for the Map Legend (mapIcons.js's createLegendControl()).
   summary.task-field-label (not the shared label.task-field-label rule
   above, tasks.css) restates that rule's own look for a <summary>
   specifically — a bare <summary> isn't a <label>, so it wouldn't
   otherwise match that tag-qualified selector at all and would fall
   back to the browser's own default disclosure styling instead. */
.task-theme summary.task-field-label {
  display: block; font-size: 12px; font-weight: 600; color: var(--task-text-dim);
  text-transform: uppercase; letter-spacing: 0.03em; margin-bottom: 6px;
  cursor: pointer; list-style: none;
}
.task-theme summary.task-field-label::-webkit-details-marker { display: none; }
.task-theme summary.task-field-label::before {
  content: '\25B8'; display: inline-block; margin-right: 5px; font-size: 10px;
  transition: transform 0.15s ease;
}
.task-theme details[open] > summary.task-field-label::before { transform: rotate(90deg); }
.task-theme .rb-style-toggle {
  flex: 1; min-height: 34px; border: 1px solid var(--task-border); background: var(--task-surface);
  color: var(--task-text-dim); border-radius: var(--task-radius-sm); cursor: pointer; font-size: 14px;
}
.task-theme .rb-style-toggle:hover { border-color: var(--task-text-faint); }
.task-theme .rb-style-toggle.active { border-color: var(--task-accent); background: var(--task-accent-dim); color: var(--task-accent); }
.task-theme .rb-style-prop[type="color"] { width: 44px; min-height: 34px; padding: 3px; flex: 0 0 auto; }
/* Highlight Color's swatch (see rb-color-swatch-wrap's own comment in
   reportBuilder.js for the full "turns white" story) — the WRAPPER, not
   the native color input, is the thing the user actually sees. The
   input sits on top as an invisible (opacity:0), full-cover hit target
   purely so tapping it still opens the native color picker and still
   fires 'input'/'change' with the chosen value; wireTextStyleControls()
   paints that value onto the wrapper's own background-color directly.
   "No fill" indicator for when style.backgroundColor is unset — standard
   diagonal-red-slash-over-white convention, matching how design tools
   show "no fill" rather than any particular color. */
.task-theme .rb-color-swatch-wrap {
  position: relative; display: inline-flex; width: 44px; min-height: 34px; flex: 0 0 auto;
  border: 1px solid var(--task-border); border-radius: 6px; background-color: #fff; overflow: hidden;
}
.task-theme .rb-color-swatch-wrap input[type="color"] {
  position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; border: none; padding: 0; cursor: pointer;
}
.task-theme .rb-color-swatch-wrap.rb-color-none::after {
  content: '';
  position: absolute; inset: 0;
  background-image: linear-gradient(
    to top right,
    transparent calc(50% - 1px), var(--task-red, #dc2626) calc(50% - 1px),
    var(--task-red, #dc2626) calc(50% + 1px), transparent calc(50% + 1px)
  );
  pointer-events: none; /* click-through, so the invisible input underneath still opens the native picker */
}

/* Dataset-bound Table's field list (§19 — "list all fields by default,
   Field Name in one column, Label in another, allow hide/show") —
   replaces the old one-at-a-time "+ Add Column" flow. A CSS grid rather
   than a flex row so every row's Visible/Field Name/Label/Totals cells
   line up as real columns (a flex row per field, each internally laid
   out the same way, would drift out of alignment the moment any one
   field's name or label was a different length — a grid keeps every
   row's cells sharing one fixed set of column widths instead). */
.task-theme .rb-field-list {
  border: 1px solid var(--task-border); border-radius: var(--task-radius-sm);
  overflow: hidden; margin-bottom: 4px;
}
.task-theme .rb-field-list-row {
  display: grid; grid-template-columns: 28px minmax(80px, 1fr) minmax(100px, 1.4fr) 84px;
  gap: 8px; align-items: center; padding: 6px 8px;
  border-bottom: 1px solid var(--task-border);
}
.task-theme .rb-field-list-row:last-child { border-bottom: none; }
.task-theme .rb-field-list-header {
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.02em;
  color: var(--task-text-faint); background: var(--task-gray-bg);
}
.task-theme .rb-field-list-visible { display: flex; align-items: center; justify-content: center; cursor: pointer; }
.task-theme .rb-field-list-visible input { margin: 0; }
/* Field Name is the real, immutable fieldPath this column is bound to
   (monospace so it visually reads as an identifier, matching
   .designer-field-key-display's own convention elsewhere in this
   panel) — never editable here, unlike Label; the whole point of
   auto-listing every available field is that the field itself isn't a
   free choice any more, only whether it's shown and what it's called. */
.task-theme .rb-field-list-name {
  font-family: var(--font-mono, monospace); font-size: 12px; color: var(--task-text-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.task-theme .rb-field-list-row .rb-table-col-label { min-height: 30px; padding: 4px 8px; font-size: 13px; }
.task-theme .rb-field-list-row .rb-table-col-agg { min-height: 30px; padding: 4px 6px; font-size: 12px; }
/* Hidden field — dimmed rather than removed from the list entirely, so
   its still-there Label/Totals settings stay visible (and editable) as
   a reminder they're preserved, not lost, the moment it's checked back
   on. */
.task-theme .rb-field-list-row.rb-field-hidden { opacity: 0.5; }
.task-theme .rb-field-list-row.rb-field-hidden .rb-field-list-name { text-decoration: line-through; }

@media (max-width: 480px) {
  /* Totals column dropped at the narrowest widths — Visible/Field
     Name/Label are the three things "list all fields, hide/show them"
     actually asked for; Totals is a secondary per-field option that can
     still be reached by widening the properties sheet (it's a bottom
     sheet at this breakpoint anyway, not a fixed-width side panel) —
     dropping its column here is what keeps Field Name/Label from being
     squeezed unreadably thin on a phone-width mobile bottom sheet. */
  .task-theme .rb-field-list-row { grid-template-columns: 24px minmax(60px, 1fr) minmax(80px, 1.2fr); }
  .task-theme .rb-field-list-row .rb-table-col-agg { display: none; }
}
