/* ═══════════════════════════════════════════════════════════════════════════
   SETTINGS RAILS — the setup page's three-column layout.
   (Design history: docs/build-brief-setup-rail.md, which is the brief for the
   EXPERIMENT this began as and still reads as one. The experiment shipped; where
   that brief and this file disagree, this file is what runs.)

   THIS IS THE SETUP PAGE'S LAYOUT. It was `?layout=sidebar`, an experiment; the
   parameter is gone (2026-08-18) and these rules now render every setup page —
   flat, multi-criteria, multi-section, survey, quiz — on create and on manage.

   THIS FILE IS LOADED ON EVERY SETUP PAGE, INCLUDING THE SLIM AVAILABILITY POLL.
   It used to say the opposite, at [HARD], and the reason it gave was that slim's
   slot-picker card "is deliberately wider than every other setup card — measured
   1004px against a rail content column of ~630px". The measurement was right and
   the conclusion was wrong: 1004px was not the calendar's requirement, it was a
   COUPLING. The month grid's columns were `repeat(7,1fr)`, i.e. minmax(auto,1fr),
   so one slot chip forced its own min-content onto its column and the grid grew to
   the widest chip. Decoupled, the calendar renders inside the standard column and
   slim takes the same rail as every other shape. The conditional load is gone with
   it — nothing is excluded now.

   THE PRE-RAIL BODY-POSITIONING CSS: PARTLY SWEPT, THE REST DELIBERATELY KEPT.
   This note used to end "If you are here to sweep the old layout: the old layout is
   in use." That is no longer true, and the audit that replaced it found the old
   claim's MECHANISM wrong as well:

     • Template picker top-right — `.tpl-start > .tpl-switcher`, four rules.
       DELETED (start.blade.php). Not because slim stopped using them: because
       setup-sidebar.js relocates #tpl-switcher into the right rail on EVERY page,
       so a direct-child selector stops matching the moment it moves. They had been
       dead on every shape since the rail shipped.
     • Mode toggles above the card — `.tpl-modes-block{text-align:center}` KEPT and
       now LOAD-BEARING; its `margin-bottom`, `#tpl-modes-block{margin-top}` and
       `.tpl-modes-row{margin-top}` are DELETED (2026-08-31). The reasoning above was
       right for as long as the toggle was relocated at every width — "overridden is not
       unreachable" — and it stopped being the whole story the moment the phone stopped
       relocating it (MOVES' third column). The three margins then rendered ON TOP of
       #tpl-start-form's row-gap, which is the container-gap-plus-margin defect this
       page has now hit four times; text-align:center is what centres SESSION TYPE in
       its new home and is the reason that position needs no rule of its own.
     • Save-to-project against the fields well — `#tpl-save-project-block{position:
       relative;z-index:20}`. KEPT, and load-bearing: it is the positioning anchor
       the project panel drops out of, and that panel is `display:none` for a guest,
       so the pages the audit could reach never exercised it. Absence of movement
       there is absence of evidence.

   The deletion is measured, not argued: a census of every selector in this file,
   templates.css and start.blade.php's inline <style>, run against all six shapes on
   create AND manage, found the switcher's four rules matching NOTHING on all eleven
   pages, and every card on every shape rendered pixel-identical before and after.

   EVERY RULE IS SCOPED TO .setup-sidebar, and with the flag gone that scope is
   now the primary defence rather than belt-and-braces: the conditional load keeps
   the file off slim, and the scope keeps its rules off anything that is not the
   rail even if the file is ever loaded more widely.

   NAMING. This is the SIDEBAR. `.tpl-rail*`, public/js/setup-rail.js and
   window._setupRailSync belonged to SLIDE VIEW's deck navigator (deleted
   2026-08-25) — a different thing that was also called a rail, which is a
   different thing on the same page. Do not reuse those names here, and do not
   assume a `rail` symbol elsewhere in the codebase has anything to do with this.
   ═══════════════════════════════════════════════════════════════════════════ */

:root {
    /* ── [HARD] WHY THIS IS 310px AND NOT THE 248px THE BRIEF SPECIFIES ──────
       248px was chosen from the mockup, whose rail draws its own fluid 10px
       segmented buttons. The real controls are not those. #tpl-presenter-mode is
       a FIXED 328px pill slider, and it is 328px for a stated reason
       (templates.css): its seat words run to 21 characters — "WORKSHOP /
       CONFERENCE" — at 9px DM Mono with 0.08em tracking, which measures ~129px
       of text per seat.

       248px of sidebar is 198px of content box. The control needs ~270px. There
       is no arrangement of padding that closes an 72px gap, and the three ways
       to force it are all worse than widening:
         • shrink the seat type below 9px — it is already the smallest on the
           platform, and 21 characters in 99px needs ~7px;
         • shorten the seat words — that is new copy, and the strings are CSS
           `content` shared by every consumer of the control;
         • let it clip — ships a half-legible control to a review.
       So the sidebar is the smallest width that renders the widest relocated
       control at its existing copy and its existing type size: 270px of content
       box plus 2 x 20px padding.

       ONE NUMBER, ONE PLACE. Change this and the grid, the stacking breakpoint
       and every control inside follow. If the respondent-mode copy is ever
       shortened, this can come back down — measure the seat text first. */
    /* ── TWO RAILS, BOTH 248px. The right rail could not be 248 while the
       respondent-mode seat read "WORKSHOP / CONFERENCE" (21 chars, ~129px of text
       against a 100px seat). That copy is now "WORKSHOP", the widest word on
       either slider is "ASYNCHRONOUS" at 73px, and both rails fit with 27px to
       spare — measured, not estimated. */
    --tpl-rail-left: 248px;
    --tpl-rail-right: 248px;
    --tpl-sidebar-inset: 20px;

    /* ── THE PAGE GOES NEAR-FULL-WIDTH ──────────────────────────────────────
       Sidebar pinned left behind one page margin, content taking the rest up to
       a cap, both sharing the same margin each side.

       THE CAP IS A DESIGN CONSTRAINT, NOT A LEFTOVER. A criterion description
       spanning 1100px reads worse than the current 840, so the column stops at
       1000px: meaningfully wider than 840, comfortably short of the width where
       a single-line field becomes hard to track. At 1440 the arithmetic lands
       just under it — 1440 - 2x56 margin - 310 sidebar - 26 gutter = 992 — so
       1440 fills the available width and anything wider caps and lets the
       surplus fall outside the pair.

       The margin scales with the viewport and bottoms out at the page's existing
       24px padding, so nothing changes at narrow widths. */
    --tpl-content-max: 1000px;

    /* ── ONE GAP, FOUR PLACES ────────────────────────────────────────────────
       The three-column layout is [gap][rail 248][gap][content][gap][rail 248][gap]
       and every one of those gaps is THIS token. It used to be two different
       numbers: the inter-column gap was a literal `26px` written twice into the
       form's padding calc, and the outer gutter was --tpl-page-margin, a
       clamp that resolved to 51.19px at 1280 and 56px at 1440 — measured, in a
       browser, before this changed. So the page held its rails ~2x further from
       the viewport edge than from the content they sit beside, and the
       difference was invisible in the source because the two values were
       written in different units in different rules.

       WHY THE GAP WINS AND NOT THE MARGIN. Equalising upward — pushing the
       inter-column gap out to 51/56 — takes the difference out of the CONTENT
       column, which is the one thing on this page that is short of room. So
       the outer gutter comes IN to meet the gap, and the recovered 2 x 25.19px
       (1280) / 2 x 30px (1440) goes to the content column and nowhere else.
       The rails do not move relative to the content; they only stop being
       marooned against the viewport edge.

       NOT A RESIDUAL — nothing is derived from it. It is a chosen distance.

       IT IS DECLARED IN templates.css AS --tpl-gap, not here, because the same
       distance is now the vertical gap between two middle-column cards and slim
       does not load this file. One declaration, both axes — see ONE GAP, ONE
       DECLARATION, BOTH AXES beside --tpl-gap. */

    /* ── THE STACKED LAYOUT'S PAGE MARGIN, AND ONLY THAT ─────────────────────
       Below 900px the rails leave the flow and sit above the content (see
       STACKED at the foot of this file), so there is no pair to gutter and no
       inter-column gap to match: --tpl-gap's horizontal role is meaningless there
       (its vertical one, card-to-card, still applies — that stack is the page). This
       clamp is what the page falls back to, and it still bottoms out at the
       page's existing 24px padding so narrow widths are untouched by the
       equalisation above. It has exactly one consumer — the padding override
       in that media block. */
    --tpl-page-margin: clamp(24px, 4vw, 56px);
}
/* ── THE PHONE ARM NARROWS THIS, AND ONLY THIS (2026-08-31) ─────────────────────
   The clamp above floors at 24px, which was "the page's existing padding" from before
   the rail work and was never re-judged for a phone. --tpl-page-margin has exactly one
   consumer (the <=900 override two rules below), so narrowing it here does not touch
   --tpl-gap or the card-to-card vertical rhythm — those are a different token, read at
   a different width tier (>=901px), and nothing here reaches them.

   RENDERED, NOT DERIVED: 10px and 18px at <=640 (8px/14px at <=480, same ratio) were
   screenshotted at 390 and 375 on blank/start. 18 read as barely different from the
   24px original; 10 is the first value that reads as a DELIBERATE phone margin rather
   than a leftover desktop number, with the card still clearly off the viewport edge at
   both widths. 8 at <=480 is the same judgement scaled down, mirroring how
   --tpl-card-pad narrows 16->10 across the same two breakpoints. */
@media(max-width:640px){:root{--tpl-page-margin:10px;}}
@media(max-width:480px){:root{--tpl-page-margin:8px;}}

.tpl-start.setup-sidebar {
    /* The page goes wide; the CONTENT COLUMN DOES NOT. --tpl-section-col-l/-r,
       the pinned --tpl-qrow-ctrl-w mirror and `php artisan setup:desc-align` all
       pin against the existing 840px measure, and widening it for a layout
       experiment would invalidate all three at once — for a change that is not
       about the content column at all. So the extra width goes to the sidebar
       and the gutter, and the leftover stays outside the pair. */
    /* Near-full-width. `none` rather than a number: the CAP now lives on the
       content track, and capping the page as well would just reintroduce a second
       place for the width to be decided. */
    max-width: none;
    /* The OUTER gutter. Same token as the inter-column gap in the grid below, and
       as the card-to-card gap down the middle column — that identity is the whole
       point, so do not re-introduce a second value here. Below 900px this is
       overridden back to --tpl-page-margin. */
    padding-left: var(--tpl-gap);
    padding-right: var(--tpl-gap);
}

/* ── THE GRID ────────────────────────────────────────────────────────────────
   The form itself is the grid, because the sidebar has to live INSIDE it: session
   type, respondent mode, save-to-project and the completion toggle are all
   posting inputs, and a sidebar rendered outside #tpl-start-form would silently
   drop four keys on submit (INV-1's failure mode, by a different route).

   `minmax(0, …)` and not a fixed track: between 900px and ~1114px the pair does not
   fit, and a fixed track would overflow the viewport rather than shrink.

   (This paragraph used to say the track was 840px and that `justify-content:center`
   centred the pair. BOTH WERE STALE — the track is --tpl-content-max and the
   alignment is `start`, for the reason given at the rule itself. The pair IS centred
   now, but by the form's own max-width and auto margins rather than by the grid's
   alignment; see THE PAIR IS CENTRED, THE SLACK FALLS OUTSIDE IT below.)
   align-content:start stops the tracks being stretched down the page.  */
/* ── [HARD] THE SWITCHER'S AUTHORED POSITION TAKES NO SPACE — THE LOAD-TIME PAGE JUMP ──
   #tpl-switcher is authored as a DIRECT CHILD of .tpl-start, above the heading, and
   setup-sidebar.js relocates it into the right rail. Until that move it sat in the flow and
   pushed the whole page down; when it left, everything above the fold jumped up. Measured
   at 1280, sampling from the first tick after navigation:

     t=96   readyState loading    switcher a direct child, top 111, 30px tall
                                  h1 top 163   first card top 253
     t=313  readyState complete   switcher in the RAIL, top 243
                                  h1 top 127   first card top 191

   The h1 moves up 36px and the first card 62px, in one frame, on every shape — flat, MS and
   the slim poll alike (slim loads this file and the script too, whatever the older note in
   start.blade.php says). NOT the font swap: fonts settle AFTER this, and the shift lands
   exactly on the relocation tick. NOT the intro block settling: its height is a constant 70
   across every sample. NOT the heading mirror: the h1 moves WITH the cards below it, and a
   text swap moves nothing but itself.

   THE SELECTOR IS THE STATE. `>` matches only while the switcher is still a direct child —
   that is, only before the move — so it stops applying the instant the node is relocated,
   with nothing to unset and no class to keep in step. The page's own history records this
   property: four rules keyed `.tpl-start > .tpl-switcher` were deleted as dead precisely
   because "the picker stops matching the moment it is relocated anywhere". Same mechanism,
   used deliberately this time.

   IT IS NOT ONE NODE. Every id below is authored in the flow and moved by that script's
   MOVES table; the switcher (30px) and #tpl-modes-block (60px) are simply the two that sit
   above the fold on a create page. Enumerated here because CSS cannot read the JS table —
   if a source is added there, add it here, or the jump comes back for that node alone.

   GATED ON THE PASS COMPLETING, NOT ON EACH NODE ARRIVING, and that is the safety
   property: `[data-rail-ready]` is set by setup-sidebar.js after BOTH move loops and on
   its no-rails early return. A source whose destination slot does not exist on a given
   shape is skipped by design — LOCATION on a slim create is exactly that — so a rule that
   revealed each node only once it reached the rail would hide that one for ever. This
   reveals everything the moment the pass is over, moved or not.

   position:absolute + visibility, NOT display:none: it takes the node out of flow without
   touching `display`, so each source keeps its own (several are flex) and the reveal is a
   plain reversal with nothing to remember.

   THE TRADE, STATED: if the script never runs at all, these stay hidden. Scoped to
   .setup-sidebar, which the server emits ONLY on pages that ship the script, so it cannot
   reach a page that was never going to relocate. On a page that does ship it, a dead script
   already strands every rail control in the flow and builds no sidebar, so this is not the
   marginal loss. Reserving the height instead would keep the void at the top of every setup
   page for ever, which is the same defect from the other side. */
.tpl-start.setup-sidebar:not([data-rail-ready]) > #tpl-switcher,
.tpl-start.setup-sidebar:not([data-rail-ready]) #tpl-modes-block,
.tpl-start.setup-sidebar:not([data-rail-ready]) #tpl-rank-type-toggle,
.tpl-start.setup-sidebar:not([data-rail-ready]) #tpl-stype-toggle,
.tpl-start.setup-sidebar:not([data-rail-ready]) #tpl-workshop-toggle-slot,
.tpl-start.setup-sidebar:not([data-rail-ready]) #tpl-save-project-block,
.tpl-start.setup-sidebar:not([data-rail-ready]) #tpl-close-date-block,
.tpl-start.setup-sidebar:not([data-rail-ready]) #tpl-notifications-block { position: absolute; visibility: hidden; }
/* (TWO ARMS DELETED 2026-08-31 — the drift this list's own paragraph warns about, found
   from the other side. #tpl-setup-mode-block went with Slide setup mode on 2026-08-25:
   the element does not exist anywhere in the tree, so the arm matched nothing. #tpl-where-row
   still exists (slim's LOCATION row, start.blade.php) but its MOVES entry was deleted the
   same day — "it is authored inside #tpl-session-setup-card and now STAYS there" — so it has
   not been a relocation source since. Holding a node that never moves out of flow until the
   pass completes does not prevent a jump, it CAUSES one: the slim page laid out without it
   and then had to make room. Both were deleted from MOVES and left here, which is precisely
   what the paragraph above predicts a one-sided edit does.) */
/* ── THE PHONE ARM OF THE HOLD LIST — THREE SOURCES THAT DO NOT MOVE AT <=640 ──────
   MOVES' third column (setup-sidebar.js) sends #tpl-switcher and both content-type
   toggles NOWHERE on a phone: they stay exactly where they are authored, because that is
   already where the phone wants them (the picker in the heading position above
   .tpl-start-intro, the toggle under the items card's heading). A node that does not move
   has nothing to be held out of flow FOR — holding it buys no jump and costs one, because
   the reveal at [data-rail-ready] then drops it into a layout that has to make room.
   So the phone releases exactly those three, and nothing else: the other five sources in
   the list above still travel to the rail at every width and still pay the hold.

   [HARD] THIS IS THE OTHER HALF OF A LIST WRITTEN TWICE, AND IT IS WRITTEN TWICE FOR THE
   SAME REASON THE LIST ABOVE IS — CSS cannot read the JS table. If a source gains or loses
   a phone destination in MOVES, its membership here changes with it. Adding one side alone
   reinstates the load-time jump for that node alone, which is exactly the failure the
   paragraph above this list describes. */
@media(max-width:640px){
    .tpl-start.setup-sidebar:not([data-rail-ready]) > #tpl-switcher,
    /* (#tpl-rank-type-toggle LEFT THIS RELEASE LIST 2026-08-31 and #tpl-stype-toggle
       LEFT IT 2026-09-02, both for the same reason and both recorded here rather than
       silently: they MOVE at <=640 now, into the OPTIONS TO COMPARE heading's row, so
       they must stay held until the rail is ready like every other moving node.
       Releasing either would render it in its authored position and then jump it onto
       the line, which is the transient this hold exists to prevent. Neither id has a
       phone arm in MOVES any more — both entries are two-column, one destination — so
       neither belongs in a list of nodes that do not move at <=640.) */
    /* #tpl-modes-block joined the stay-home set 2026-08-31 (MOVES' third column) and its
       membership here moves with it, per the paragraph above. It is the one of the four
       that is ALSO above the fold on a phone, so holding it would be the load-time jump
       that list exists to prevent, in the direction the list was built for. */
    .tpl-start.setup-sidebar:not([data-rail-ready]) #tpl-modes-block,
    .tpl-start.setup-sidebar:not([data-rail-ready]) #tpl-workshop-toggle-slot { position: static; visibility: visible; }
    /* THE THREE SETTINGS ROWS ARE THE OTHER DIRECTION (2026-08-31): they are authored in the
       rail and MOVE on a phone, so they need the hold the list above gives the sources that
       travel — without it each is visible in the rail for the load transient and then jumps
       to a card seam. They are not in the list above because at >=641 they do not move and
       must not be held there. */
    .tpl-start.setup-sidebar:not([data-rail-ready]) #tpl-settings-row-welcome,
    .tpl-start.setup-sidebar:not([data-rail-ready]) #tpl-settings-row-pdata,
    .tpl-start.setup-sidebar:not([data-rail-ready]) #tpl-settings-row-completion { position: absolute; visibility: hidden; }
}

/* ── THE PAGE BAND — ONE DECLARATION, EVERY BLOCK THAT MUST SIT ON THE CONTENT COLUMN ──
   These four lived on #tpl-start-form when the form was the only thing that had to line up
   with the middle column. The page HEADING is a second one (2026-08-26): .tpl-start-intro
   sits OUTSIDE the form, so it spanned the whole page. Measured at 1280 before this: the h1
   was 1228px wide starting at 26 while the cards were 680 starting at 300 — the two already
   shared a centre, so only a LONG title showed it, running the full viewport while every
   card under it stopped at the band.

   THE CAP IS THE BAND'S OWN EXPRESSION, NOT A COPY OF ITS RESULT. Sharing the declaration
   is what makes the heading track the column: change a rail width or --tpl-gap and both
   move together with no second number to keep in step. Writing the measured 680px here
   would be the residual-as-an-input mistake --col-line-max is on record for, and it would
   be wrong at every viewport except the one it was measured at.

   THE TWO SELECTORS ARE THE SAME KIND OF THING — page-level blocks that must occupy the
   content column. That is what a selector list claims, and it holds here.

   max-width + margin-inline: the trio is centred as a unit and the surplus falls OUTSIDE
   it, which holds the inter-column gap at --tpl-gap at every width; the long note in the
   form's rule below is why the surplus goes outside rather than into the gaps.
   The two paddings ARE the reserved rail strips. The literal 26px that stood in them twice
   is --tpl-gap, shared with the outer gutter and the card-to-card gap. */
.tpl-start.setup-sidebar #tpl-start-form,
.tpl-start.setup-sidebar .tpl-start-intro {
    max-width: calc(var(--tpl-content-max) + var(--tpl-rail-left) + var(--tpl-rail-right) + 2 * var(--tpl-gap));
    margin-inline: auto;
    padding-left: calc(var(--tpl-rail-left) + var(--tpl-gap));
    padding-right: calc(var(--tpl-rail-right) + var(--tpl-gap));
}

.tpl-start.setup-sidebar #tpl-start-form {
    /* [HARD] THE SIDEBAR IS NOT A GRID ITEM. It was, spanning `grid-row: 1 / span
       500` because CSS cannot say "span to the end of an implicit grid" — and that
       construction made "top-aligned" depend on a 500-row span, on min-height:0
       stopping a spanning item from inflating the rows it covers, and on row-gap
       being 0 so 500 empty tracks cost nothing. Three subtleties, every one of
       them a place an engine could differ, to express one idea: put it at the top
       and let it stick.

       Now the form is simply the positioned ancestor, the content is ONE column,
       and the space for the sidebar is reserved with padding. The sidebar is
       absolutely positioned into that reserved strip at top:0, which is
       unambiguous in a way a row span is not.

       padding-left, not a grid track: a track would put the sidebar back in the
       grid. justify-content:start keeps the content against the reserved strip —
       which is now the whole story, because the strip itself is centred (below)
       rather than pinned to the page's left edge. */
    position: relative;
    display: grid;
    grid-template-columns: minmax(0, var(--tpl-content-max));
    /* ── THE PAIR IS CENTRED, AND THE SLACK FALLS OUTSIDE IT ───────────────────
       Above 1600 the page has surplus width by construction: the card stops at
       --tpl-content-max and the rails are a fixed 248, so something has to absorb
       the difference. Until now `justify-content:start` absorbed all of it in ONE
       place — the gap between the card and the RIGHT rail. Measured before this:

         viewport   gutter L/R   rail->card   card->rail
           1440       26 / 26        26           26
           1820       26 / 26        26          231
           2560       26 / 26        26          971

       The card's left edge sat at 300px at every width and the page read as
       left-weighted. This is the form taking the width of the pair it actually
       contains — two rails, two gaps and the capped track — and centring that,
       so the surplus lands OUTSIDE the trio in the page gutters instead of inside
       it. Rails, gaps and card are then identical on both sides at every width.

       [HARD] IT IS A SUM OF WHAT IS ALREADY HERE, NOT A NUMBER. Every term is the
       token the layout already reads: the two reserved strips are exactly this
       rule's own padding-left/right, and the track is --tpl-content-max. Writing
       1548 would be the residual-as-an-input mistake --col-line-max is on record
       for. If any of the three moves, this follows with no second edit.

       [HARD] THE ONE-GAP INVARIANT CANNOT HOLD ABOVE 1600, AND THIS DOES NOT
       PRETEND TO RESTORE IT. gutter == gap == --tpl-gap requires
       viewport == 2*gap + 2*rail + gap*2 + card, i.e. card == viewport - 600 — an
       UNCAPPED card. The moment the card caps, the surplus exists and must go
       somewhere. The choice is only WHERE: inside the trio (the old behaviour, and
       `justify-content:center` would merely have split it between the two inner
       gaps) or outside it (here). Outside keeps the inter-column gap at --tpl-gap
       at every width, which is the relationship the gap token was introduced for;
       the outer gutter is what grows instead. */
    /* (max-width, margin-inline and the two rail paddings have MOVED to THE PAGE BAND,
       the rule directly above this one — the form is no longer their only consumer. Every
       word of reasoning for them is there, unchanged.) */
    /* (display:grid and row-gap are NOT restated here. Both live on the base
       `.tpl-start #tpl-start-form` rule in templates.css so that slim, which never
       loads this file, is the same kind of container. This rule adds only what the
       rail layout itself needs: the reserved strips and the column cap.) */
    justify-content: start;
    align-content: start;
}

/* Every form child except the sidebar goes in column 2.
   A BLANKET RULE, NOT A LIST. Slide view's ordering block has to enumerate every
   child because a flex item with no `order` silently defaults to 0 and jumps to
   the front — completeness there is the failure mode. Here the negation covers
   whatever the form contains now or later, including children added by a future
   slice, so there is no list to keep in step. Hidden inputs are display:none per
   the HTML UA stylesheet and generate no box, so they are not grid items and
   need no exclusion. */
.tpl-start.setup-sidebar #tpl-start-form > *:not(.tpl-sidebar) {
    grid-column: 1;
}

/* [HARD] WIDTH RESTORATION — the same counter-measure Slide view needed, for the
   same reason, and it is not decoration.

   Per CSS Grid, a grid item stretches on the inline axis only when its
   inline-axis margins are NOT auto; an auto margin absorbs the free space
   instead. Every top-level form child already carries
   `max-width:840px; margin-left:auto; margin-right:auto` from the shared band
   rule in start.blade.php, so the moment the form becomes a grid, `stretch`
   stops applying, each card shrink-to-fits its own content and the auto margins
   then centre the shrunken box. Nothing has to be added to trigger it.

   `width:100%` restores a definite cross size, so max-width caps it and the auto
   margins centre it — identical to block layout (box-sizing is border-box).

   #responses-modal is excluded: position:fixed with inset:0, not a grid item. */
.tpl-start.setup-sidebar #tpl-start-form > *:not(.tpl-sidebar):not(#responses-modal) {
    width: 100%;
    /* [HARD] AND max-width:none, or the widening does nothing visible. Every
       top-level child carries `max-width:840px; margin:0 auto` from the page's
       band rule, so raising the grid TRACK to 1000 just leaves each card capped at
       840 and centred inside it — the column looks unchanged and gains 80px of
       dead space on each side, which is exactly what the first attempt measured.
       Dropping the cap here leaves the TRACK as the single place the content width
       is decided; --tpl-content-max is that one number. */
    max-width: none;
}

/* ── THE SIDEBAR ─────────────────────────────────────────────────────────────
   Slice 1 renders it empty. Its controls arrive in slice 2, by relocation into
   slots — the #tpl-workshop-toggle-slot pattern, wired by id, no markup
   re-authored. */
/* Both rails: full height of the form so the sticky inner can travel the whole
   column, and neither is a grid item.

   [HARD] BOTH ARE EXCLUDED FROM THE CONTENT-COLUMN RULE BY CLASS, NOT BY ID.
   That rule read `> *:not(#tpl-setup-sidebar):not(#responses-modal){width:100%}`
   and its own comment called it "a blanket rule, not a list" — which is exactly
   what went wrong when a SECOND rail arrived: the new aside matched it at (3,2,0)
   against this rule's (1,2,0), took width:100%, and stretched 1039px across the
   page from the content edge. It did not look like a width bug, it looked like
   the switcher had stopped painting, because its 240px trigger was sitting at
   x=350 while I was cropping the right-hand strip. Excluding `.tpl-sidebar`
   covers any rail, now or later. */
/* ── THE RAILS START AT THE FIRST CONTENT CARD, AND THE OFFSET IS FED BY JS ──────────────
   Both rails begin at the top of #tpl-session-setup-card (the Overview card — Session
   Title, not the content) rather than the form's own top edge, so each rail's first row
   sits level with SESSION TITLE. That distance is the height of whatever sits above the
   card — a Session Type / Respondent Mode block whose fields come and go with Individual/
   Group — and CSS cannot measure a sibling, so it is fed by JS: setup-sidebar.js's
   syncRailTop() measures it once and keeps it current with a ResizeObserver, published as
   --tpl-rail-top (consumed below, on .tpl-sidebar-inner — see that rule's own comment for
   why this is the ONLY consumer now). The 0px fallback is the pre-measurement default, so a
   page where that script never runs still renders both rails at the viewport top rather
   than nowhere. Below 900px the media query makes the rails static and `top` stops applying,
   so the fallback is not load-bearing there either. */
/* [HARD] THE SHELL EXISTS TO STAY OUT OF FLOW, NOTHING MORE (2026-08-28). A static
   #tpl-setup-sidebar / #tpl-setup-meta would push the form's content sideways, so it stays
   position:absolute — but its own size and position are otherwise UNUSED: nothing reads its
   rect (grepped; no getBoundingClientRect on either id), every "whole rail is empty" collapse
   selector in this file tests its CONTENT (:has(...)), not its box, and every JS relocation
   target resolves by id regardless of where the id sits. #tpl-sidebar-inner, below, is
   position:fixed and gets its geometry from the viewport directly — it does not and cannot
   derive it from this shell (position:fixed skips an ancestor that is merely position:
   absolute; see the chain check on that rule). So the shell carries no left/right/width/top/
   bottom of its own. ONE PROPERTY WAS DELETED WITH THEM: --tpl-rail-top used to be published
   twice — this form-relative version for the shell's own `top`, and a viewport-relative
   version for .tpl-sidebar-inner's — and the shell's copy is what this note replaces. Only
   the viewport-relative one survives, renamed back to --tpl-rail-top since there is now only
   one meaning to the name. */
.tpl-start.setup-sidebar #tpl-setup-sidebar,
.tpl-start.setup-sidebar #tpl-setup-meta {
    position: absolute;
}
/* [HARD] THE CHAIN CHECK BELOW SERVES position:fixed, NOT position:sticky (2026-08-28) —
   REPURPOSED, NOT DELETED, because the same fact answers a different question now. It used
   to establish that stickiness would not fail silently (sticky breaks inside any ancestor
   with overflow other than visible, or a transform/filter/contain). It now establishes the
   opposite requirement: that .tpl-sidebar-inner's containing block correctly resolves all
   the way out to the VIEWPORT rather than stopping at some ancestor. A transform/filter/
   perspective/contain/will-change on ANY of these would trap it there instead — which would
   not merely misplace it, it would turn "fixed" into "absolute relative to a box that itself
   scrolls with the page", i.e. it would scroll away instead of staying visible. Measured on
   the live page, the chain outward from #tpl-setup-meta is:
       #tpl-setup-meta [absolute] -> #tpl-start-form [relative] -> .tpl-start [relative]
       -> MAIN -> BODY -> HTML
   None of them carries overflow, transform, filter, contain or will-change. The rail's own
   card has overflow-y:auto, which clips ITS children and does not affect its own
   positioning. Re-run this chain before adding any of those properties to an ancestor.

   THIS RAIL STAYS PUT FOR THE WHOLE PAGE, FOOTER INCLUDED — IT NO LONGER STOPS AT THE
   FORM'S BOTTOM EDGE THE WAY THE STICKY CARD DID. That containment came for free from being
   a normal-flow child of the (formerly sized) shell above: a sticky element cannot travel
   past its own containing block, so the card stopped at the form's last edge on its own,
   with nothing having to know where the footer or Set Up Session were. position:fixed
   escapes that box entirely, and there is no way to bring the old stop-at-the-bottom
   behaviour back without tracking scroll position — the one CSS trick that traps a fixed
   element inside an ancestor's box (a transform/filter/contain on that ancestor) requires
   that ancestor to not itself be pinned, so the "trapped" element would scroll away with the
   page instead of staying visible, which is the opposite of the point. So: pinned for the
   entire scroll, over the footer, on purpose — a smaller change than reintroducing scroll
   bookkeeping to preserve a boundary the brief never asked to keep.

   max-height + overflow-y: A RAIL TALLER THAN THE VIEWPORT MUST STILL BE REACHABLE. Fixed
   pins the top, so without this the foot of a long rail — the weighting panel with many
   criteria, and the group-only rows under it — sits below the fold and no amount of page
   scrolling brings it up. The subtraction is --tpl-rail-top, NOT a flat number: sticky's old
   `calc(100vh - 48px)` assumed a flat 24px inset at both top and bottom, which only held
   because sticky's own `top` was also a flat 24px, and that varies by shape — get the
   subtraction wrong and a rail that starts low on a tall page runs off the bottom of the
   viewport with its own overflow never triggering to bring the tail back. The overflow is on
   this element ITSELF, which is safe; it is an overflow on an ANCESTOR that would trap it
   (see the chain check above) — the two risks read alike and are not the same risk.

   WHY --tpl-rail-top IS THE VIEWPORT OFFSET, NOT THE FORM-RELATIVE ONE IT USED TO PUBLISH.
   syncRailTop() (public/js/setup-sidebar.js) measures card 1's rect once and used to publish
   it two ways: minus the form's own viewport offset (what the OLD absolutely-positioned
   shell needed — its containing block was the form) and alone (what a position:fixed
   element needs — its containing block is the viewport, established above). Publishing only
   the form-relative one here first, because the shell had always carried it, pinned the rail
   near y=0 in production (2026-08-28) instead of level with card 1: the form itself starts
   well below the viewport's own top (nav, kicker, the page heading), so "distance from the
   form's top" undershoots "distance from the viewport's top" by exactly the form's own
   offset. Caught by screenshot before commit. Only the viewport-relative reading is
   published now — see the shell's own comment, six rules up, for why the other one has no
   consumer left to serve. "Renders where it currently starts and holds that viewport
   position for the whole scroll" is the brief's own words for this change — not a fixed 24px
   inset, which was sticky's catch-up THRESHOLD, the point at which it would stop travelling
   with the page and pin; today's offset is typically well past that threshold already, so
   sticky was already pinned surface-first. */
.tpl-start.setup-sidebar .tpl-sidebar-inner {
    position: fixed;
    top: var(--tpl-rail-top, 0px);
    max-height: calc(100vh - var(--tpl-rail-top, 0px) - 24px);
    overflow-y: auto;
    background: var(--card-bg);
    border-radius: 4px;
    padding: var(--tpl-sidebar-inset);
    /* [HARD] THIS z-index IS NOT DECORATION AND NOT A WORKAROUND — IT IS THE ONLY PLACE
       THE TEMPLATE / SAVE TO PROJECT PANELS CAN BE FIXED FROM.

       `position:fixed` establishes a STACKING CONTEXT unconditionally, same as sticky did —
       so everything inside the rail paints at THIS element's level in the form — and with
       z-index:auto that level is the same one every `position:relative` card in the centre
       column occupies, where DOM ORDER decides and the rails are emitted BEFORE the cards.
       So the centre column wins.

       Measured, twice, four months apart. The panel is position:fixed at z-index:100 and
       STILL loses: sampling elementFromPoint inside the region where the open menu overlaps
       the Overview card returns #tpl-session-setup-card, not the menu. Its own z-index is
       never in play — it competes only INSIDE #tpl-switcher, which is itself inside this
       context. No value on the panel can fix it; raising it would be the workaround.

       [HARD] IT WAS ALREADY WRITTEN DOWN AND I DELETED IT. This note and its declaration
       lived on `.tpl-sidebar-stack`, the sticky wrapper the two-card split introduced. The
       rejoin (3fe28349) removed that wrapper and moved the stickiness back here — and took
       the z-index with it, because the rule was read as part of the split rather than as the
       consequence of being the sticky element. The defect did not reappear immediately:
       the rejoin also dropped the rail to the content region, where the menu no longer
       overlapped the Overview card, so it was masked until the rails were re-anchored to
       that card. Wherever this element's positioning goes, this goes with it.

       100, from the existing ladder, is the value .tpl-switcher and .tpl-menu already use.
       It clears the centre column (whose highest is #tpl-save-project-block's 20) and stays
       UNDER the site nav (nav-menu.css, 200), the auth modal and switcher hint popout
       (1000), the responses modals and cookie banner (9999) and the results fullscreen
       overlay (10000, the highest in the codebase).

       IT DOES NOT RE-CLIP ANYTHING. z-index does not make an element a containing block for
       fixed descendants — only transform / filter / will-change / contain do — so the panels
       still escape this element's own overflow-y:auto. Add any of those here and both panels
       clip to the rail instantly (and, per the chain-check note above, clip THIS element to
       its shell too). */
    z-index: 100;
}
/* ── HORIZONTAL PLACEMENT, ON THE CHILD NOW, NOT INHERITED FROM THE SHELL ───────────────
   position:sticky respected the shell's left/right/width for free — a sticky element is
   still a normal in-flow box for every axis but the one it sticks on, so it took its
   horizontal position from being an ordinary child of the (absolutely positioned, already
   correctly placed) shell above. position:fixed has NO such relationship: its containing
   block is the viewport (established by the chain check two rules up), so it needs its own
   left/right/width stated against the viewport, or it collapses to shrink-to-fit wherever
   the browser's static-position algorithm happens to put it.

   THE FORMULA MIRRORS #tpl-start-form's OWN CENTERING (its max-width rule, above in this
   cascade) rather than restating a measured number, so the two cannot drift out of step —
   change any of the four tokens and both recompute together. It also mirrors THE PAIR IS
   CENTRED, THE SLACK FALLS OUTSIDE IT's own accounting: .tpl-start.setup-sidebar's own
   --tpl-gap padding (the OUTER gutter, one gap on each side) plus the auto-margin slack that
   centres the form within what padding leaves behind. "ONE GAP, FOUR PLACES" above names
   the four: this is the same first (outer) gap plus the same auto-margin term the form's own
   rule computes for the two inner ones.

   max(0px, …) is the auto-margin floor: once the viewport is too narrow for the form to sit
   at its own max-width, auto margins collapse to 0 and the form sits flush against
   .tpl-start's own padding edge — this floor keeps the rail flush with it too, rather than
   going negative and drifting past where the form actually is.

   NOT MEASURED ON A LIVE PAGE. This is worth checking by eye particularly in the ~900–1600px
   band, and 100vw can read a handful of pixels wider than the true content viewport on a
   platform with a visible (non-overlay) scrollbar — the same category of imprecision this
   file already accepts for max-height's 100vh, above. */
.tpl-start.setup-sidebar #tpl-setup-sidebar .tpl-sidebar-inner {
    left: calc(var(--tpl-gap) + max(0px, calc((100vw - var(--tpl-content-max) - var(--tpl-rail-left) - var(--tpl-rail-right) - 4 * var(--tpl-gap)) / 2)));
    width: var(--tpl-rail-left);
}
.tpl-start.setup-sidebar #tpl-setup-meta .tpl-sidebar-inner {
    right: calc(var(--tpl-gap) + max(0px, calc((100vw - var(--tpl-content-max) - var(--tpl-rail-left) - var(--tpl-rail-right) - 4 * var(--tpl-gap)) / 2)));
    width: var(--tpl-rail-right);
}
/* ── (THE RIGHT RAIL'S TWO-CARD CONSTRUCTION IS DELETED, 2026-08-26) ─────────
   Roughly sixty lines of reasoning stood here for a mechanism that no longer exists:
   one sticky .tpl-sidebar-stack holding two STATIC cards, a two-row grid deriving the
   lower card's top from `--tpl-rail-top - --tpl-meta-top`, and an (a)/(b) toggle for the
   upper card's height. All of it was correct, and all of it was about keeping two cards
   from overlapping at the end of a scroll — a problem one card does not have.

   [HARD] WHAT IS WORTH KEEPING FROM IT, because it is the reason nobody should re-split
   this rail casually: two INDEPENDENTLY-sticky cards do not work. They align through the
   body of the scroll, but at the end the lower one hits the form's lower edge — the "must
   not escape the form" requirement doing its job — and is pushed up underneath the
   still-pinned upper card. Measured on SWOT at a 613px viewport: 26px of overlap from
   scrollY 900. Bounding the upper card with a shell ending at the content region removes
   the overlap and leaves it 18px of travel, i.e. not sticky at all. If the rail is ever
   split again, the two cards must travel as ONE sticky object; that is not a preference,
   it is the only arrangement that satisfies both requirements.

   The stickiness, the max-height and the overflow are back on .tpl-sidebar-inner above,
   which is where the left rail has carried them throughout. */
/* (The upper card's (a)/(b) height toggle is DELETED with the split — 836706c2 had already
   settled it on (b), "sized to its own content", which is what a single card does anyway.) */

/* ═══════════════════════════════════════════════════════════════════════════
   SLICE 2 — WHAT SITS IN THE SIDEBAR
   Template picker, session type, respondent mode, save-to-project, moved here by
   public/js/setup-sidebar.js. Every rule below RE-LAYS-OUT an existing control;
   none restyles one into a different kind of control.

   [HARD] THE CONTROLS STAY PILL SLIDERS. The mockup draws two-seat segmented
   buttons with aria-pressed. These are `.tpl-pill-toggle` checkboxes and they
   remain checkboxes: restyling one to look segmented would put a second control
   family on a page that already has a real one (`.tpl-stype-toggle`), and the
   two would then have to be kept in visual step by hand for ever. Only
   ALIGNMENT changes here — centred-above-a-card becomes left-in-a-column.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Slot separation. `.is-empty` is set by the JS from the slot's RENDERED HEIGHT,
   not from :empty and not from a child count. :empty is false for a slot holding
   only whitespace, and Blade's indentation puts a whitespace text node in all
   three; a child count is true for the save-to-project block on a guest page,
   where the block exists but its inner wrap is display:none. Height answers both.
   Negative flag — see the [HARD] note in setup-sidebar.js for why it cannot be
   the other way round. */
/* ═══════════════════════════════════════════════════════════════════════════
   THE DIVISION OF LABOUR — [HARD], AND DO NOT UNIFY THESE TWO
   ═══════════════════════════════════════════════════════════════════════════
   A rail row or block can be absent for two unrelated reasons, and they are
   answered by two different mechanisms ON PURPOSE:

     STRUCTURAL — "does this shape have this control at all?"  Flat has no
       criteria, so no #tpl-weighting-toggle ever arrives in the weighting row;
       a guest's save-to-project block renders nothing. This is a property of
       the SHAPE, it is settled before the first paint, and it never changes for
       the life of the page. Answered by `.is-empty`, set once by
       public/js/setup-sidebar.js from what actually rendered. Measured, because
       "did a control arrive" is not something a selector can ask.

     LIVE — "is this control applicable in the mode we are in right now?"  The
       welcome / completion / advanced rows are .tpl-group-only and exist or not
       depending on a toggle the facilitator can flip at any moment. Answered by
       CSS on `#tpl-start-form[data-mode]`, which applyModeFields keeps current
       (start.blade.php ~8929). Declarative, because it has to stay true after
       the page has loaded.

   WHY THEY MUST NOT BE MERGED, in either direction:

     • Fold LIVE into the measured sweep and it goes stale the moment the mode
       changes. That is not hypothetical — it shipped, and it was observed by
       driving the page: on blank at 1280, Individual -> Group left welcome,
       completion and advanced at renderedHeight=0 with computed display:flex
       and no inline style, because their block still carried the .is-empty it
       earned at load. Three controls unreachable.
     • Fold STRUCTURAL into CSS and you are asking a selector whether a
       relocation happened. It cannot see that. `:has()` tests presence, and an
       empty pill shell is present.

   The sweep is therefore mode-BLIND (it counts a .tpl-group-only child as
   present however tall it is right now), and CSS owns every mode question. If a
   future change makes one of these look redundant, it is because the other is
   doing its job. Read the notes in setup-sidebar.js before touching either.
   ═══════════════════════════════════════════════════════════════════════════ */
/* ── ONE RULE HIDES A STRUCTURALLY ABSENT BAND, BOTH RAILS, EVERY LEVEL ──────
   .is-empty means one thing wherever sweepBands() puts it: nothing in this band
   renders on this shape. It used to be hidden by THREE rules — a block rule here,
   a row rule 16 lines down, and a project-slot-child rule at the bottom of the
   file — and the modes slot's children and the left rail's weighting panel were in
   none of them, which is how each came to draw a hairline over 16px of nothing.

   [HARD] !important, AND IT IS THE SPECIFICITY THAT FORCES IT, NOT LAZINESS.
   The bands this now covers are addressed BY ID where their display is set —
   `#tpl-workshop-toggle-slot{display:flex}` (start.blade.php) is (1,0,0), and
   `#tpl-sidebar-slot-project > .is-empty` only ever won because it carried an id
   itself. No class-only selector can beat (1,0,0), and the alternative is to go
   back to one hide rule per band, which is the thing that left gaps.
   The blast radius is nil BY CONSTRUCTION: this block declares exactly one
   property. `.claude/rules/css-specificity.md` is about a raised block that also
   carried font-family and font-size; there is nothing here to retype. Do not add a
   second declaration to this rule — split it instead. */
.tpl-start.setup-sidebar .tpl-sidebar .is-empty { display: none !important; }
/* Row-level counterpart of the same STRUCTURAL flag: a row whose control never
   arrived on this shape. The JS sets the class and this hides it, so all hiding
   lives with the other hiding rules rather than as an inline style — and, more
   importantly, so the mode selectors below can ASK about it. An inline display
   is invisible to a selector.

   [HARD] THE `.tpl-sidebar` TERM IS LOAD-BEARING, NOT DECORATION. The row's own
   layout rule below is `.tpl-start.setup-sidebar .tpl-sidebar .tpl-sidebar-row
   {display:flex}` at (0,4,0), and it is LATER in this file. Written without
   `.tpl-sidebar` this rule is also (0,4,0), loses the tie on order, and the row
   renders at full height with the class correctly applied — which is exactly
   what happened when it was first written: measured h=37 on flat's weighting row
   in Group mode. The inline `style.display` this replaces could not lose that
   tie, so switching to a class moved the rule into a fight it had not been in
   before. Match the layout rule's own scope and it wins at (0,5,0). */
/* (The row-level hide rule that stood here is the ONE RULE above. Its (0,5,0)
   note is preserved there as the reason the consolidated rule needs !important.) */
/* ── A RAIL THAT SHOWS NOTHING RESERVES ITS STRIP AND PAINTS NOTHING (A′) ────
   Two predicates, one for each half of the division above. Neither is a
   simplification of the other; they answer different questions and a shape can
   hit either.

   (1) STRUCTURAL — the rail holds no block that survived the measured sweep.
   (2) LIVE — the rail's only surviving rows are .tpl-group-only, and we are in
       Individual mode. The test is "is there a row here that is neither
       group-only nor structurally absent": on flat there is not (the weighting
       row is .is-empty, the other three are group-only), so the rail is empty
       right now; on multi-criteria there is (the weighting row is real and
       renders in both modes), so it keeps its card. Shapes forced to Group —
       multi-section, survey, quiz — never match, because the mode arm cannot
       fire on [data-mode="group"].

   THE STRIP IS STILL RESERVED, and that is the whole point of A′ over
   collapsing: #tpl-start-form keeps its padding-left, so the content column is
   the same width on every shape and a row on flat lands on the same x as a row
   on multi-criteria and multi-section — measured at 1280, all three on 425..832.
   Collapsing the strip instead moved flat's rows to 196..768, 229px out from the
   other two. Only the card's paint goes; the geometry does not move.

   [HARD] :has() CANNOT NEST, so the live arm is written against ROWS rather than
   against blocks-containing-rows. Reaching for the block level here needs
   :has(.tpl-sidebar-block:not(.is-empty):has(...)) and that is invalid CSS — it
   parses as nothing and the rule silently never matches. */
.tpl-start.setup-sidebar #tpl-setup-sidebar:not(:has(.tpl-sidebar-block:not(.is-empty))) .tpl-sidebar-inner,
/* PER-CARD on the right rail, because it now has two and they can empty independently:
   a page with no switcher groups whose visitor is a guest (no Save To Project) leaves
   the UPPER card holding nothing while the lower one is full, and the rail-level arm
   below cannot see that — it asks whether the whole RAIL is empty. This arm is a
   single-level :has() ON the card, not a nested one, so it is valid where the
   block-level reach the note above rules out is not.
   [HARD] IT TAKES THE PAINT, NEVER THE HEIGHT. The upper card's height is what the
   lower card's margin arithmetic subtracts; collapse it and the lower card climbs
   ~122px and stops aligning to the content region. An empty upper card must keep
   standing as invisible space. */
.tpl-start.setup-sidebar #tpl-setup-meta:not(:has(.tpl-sidebar-block:not(.is-empty))) .tpl-sidebar-inner,
/* THE LIVE ARM, FOR THE RIGHT RAIL TOO (2026-08-31). Predicate (2) above was written for
   the left rail only, and the right rail needed it the moment Session Type stopped being
   relocated into it on a phone: on flat blank in Individual mode the right rail is then
   holding a modes slot whose only child is a workshop row that shape hides outright, a
   save-to-project block a guest cannot see, and Closing Date + Response Notifications,
   which are .tpl-group-only. Every one of those is either structurally absent or waiting
   for Group mode — nothing renders — and the STRUCTURAL arm above cannot say so, because
   sweepBands() deliberately counts a group-only row as PRESENT (it lifts the mode gate
   before measuring, so the row can come back when the mode does).

   WRITTEN OVER `.tpl-sidebar-block > *`, NOT `.tpl-sidebar-row`, AND THAT IS NOT A STYLE
   CHOICE. The left rail's occupants are `.tpl-sidebar-row`s; the right rail's are blocks
   inside slots (#tpl-modes-block, #tpl-workshop-toggle-slot, #tpl-save-project-block,
   #tpl-close-date-block, #tpl-notifications-block) and not one of them carries that class.
   Copying the left arm's selector verbatim would have found no row at all, made the
   :not(:has(…)) trivially true, and collapsed the right rail on EVERY shape in Individual
   mode — including multi-criteria at 1280. One level of :has(), on the children the sweep
   itself marks, which is the same reading this predicate has always been.

   #tpl-workshop-toggle-slot IS EXCLUDED BY NAME, AND THE NAMING IS EXACT RATHER THAN A
   HEURISTIC. The slot is an empty div the page IIFE fills by id; its only ever occupant is
   #tpl-workshop-toggle-row, which carries .tpl-group-only itself (start.blade.php). So a
   candidate set that excludes group-only leaves has to exclude this WRAPPER too, or the
   wrapper stands in for the row it hides — and it did: measured on multi-criteria at 390
   in Individual mode before this term, the rail rendered a 56px card containing nothing,
   which is the empty shell this whole arm exists to remove. `:has()` cannot nest, so
   "a wrapper whose only child is group-only" is not expressible; naming the one wrapper
   that exists is. IF A NON-GROUP-ONLY CONTROL EVER LANDS IN THAT SLOT, this term makes the
   rail collapse over it — that is the thing to check before putting one there.

   INERT ABOVE 640 BY MEASUREMENT, not by scope: every shape that renders this rail at
   >=641 has Session Type in it (the phone arm is the only thing that takes it out), so a
   non-empty, non-group-only child is always present there. Verified with geom-compare at
   1280 and a full per-box census at 641/700/888 on six shapes — zero differing boxes.
   Shapes forced to Group never reach either arm. */
.tpl-start.setup-sidebar #tpl-start-form:not([data-mode="group"]) #tpl-setup-meta:not(:has(.tpl-sidebar-block > *:not(.tpl-group-only):not(.is-empty):not(#tpl-workshop-toggle-slot))) .tpl-sidebar-inner,
.tpl-start.setup-sidebar #tpl-start-form:not([data-mode="group"]) #tpl-setup-sidebar:not(:has(.tpl-sidebar-row:not(.tpl-group-only):not(.is-empty))) .tpl-sidebar-inner {
    background: transparent;
    padding: 0;
}
/* The blocks inside such a rail go too — otherwise an unpainted rail still
   reserves its rows' height. Same live predicate; the structural case is already
   covered by .tpl-sidebar-block.is-empty above. */
.tpl-start.setup-sidebar #tpl-start-form:not([data-mode="group"]) #tpl-setup-meta:not(:has(.tpl-sidebar-block > *:not(.tpl-group-only):not(.is-empty):not(#tpl-workshop-toggle-slot))) .tpl-sidebar-block,
.tpl-start.setup-sidebar #tpl-start-form:not([data-mode="group"]) #tpl-setup-sidebar:not(:has(.tpl-sidebar-row:not(.tpl-group-only):not(.is-empty))) .tpl-sidebar-block {
    display: none;
}
/* ── THE TWO TOGGLE SEAMS GENERATE NO BOX, AT ANY WIDTH (2026-08-31) ────────────────
   #tpl-mobile-toggles-overview and #tpl-mobile-toggles-content are empty wrappers that
   MOVES fills at <=640 and leaves EMPTY at >=641, where the rows they receive stay in the
   settings rail. An empty grid item is still a grid item: measured before this rule was
   unconditional, the two of them shifted 82-195 boxes on every shape at 641, 700, 888 and
   1280 — two zero-height rows each charging a row-gap at each end. display:contents is the
   right answer at BOTH widths, not a mobile arm, because the wrapper never wants a box:
   when it holds a row the row is the grid item, and when it holds nothing there is nothing.
   Scoped to the two ids rather than to the media query, so the claim is about what these
   wrappers ARE and not about a width. */
.tpl-start.setup-sidebar #tpl-start-form > #tpl-mobile-toggles-overview,
.tpl-start.setup-sidebar #tpl-start-form > #tpl-mobile-toggles-content { display: contents; }

/* ── THE GROUP HEADING IS A <=640 ELEMENT, HIDDEN BY DEFAULT ────────────────────────
   ADVANCED SETTINGS describes the group the PHONE creates by taking the card-revealing
   toggles out of this rail. At >=641 the rail still holds all of them and needs no caption,
   and one was measured to shift 82-195 boxes on every shape at 641-1280. Base display:none
   with the <=640 arm as the positive statement, so the desktop rendering is the untouched
   one and the mobile arm is the thing that has to be asked for. */
.tpl-start.setup-sidebar #tpl-advanced-settings-heading { display: none; }

/* ── <=640 ONLY: A COLLAPSED RAIL STOPS BEING A ROW, NOT JUST A PAINT (2026-08-31) ──
   Above 900 the rail is absolutely positioned and the arms above take its PAINT while
   deliberately leaving the strip standing — "#tpl-start-form keeps its padding-left, so
   the content column is the same width on every shape", and collapsing it there moved
   flat's rows 229px. Below 900 there is no strip: the rail is position:static and a grid
   ITEM of the form, so a rail with nothing in it is a zero-height row that still costs a
   row-gap at each end. Measured on flat blank at 390: Session Type ended at 227 and the
   first card began at 259 — two 16px gaps around 0px of rail.

   SCOPED TO <=640, not to the <=900 the stacking itself uses: 641-888 is out of this
   session's scope and is pixel-identical by measurement (a full per-box census at 641,
   700 and 888 on six shapes, zero differing boxes). Widening this to 900 is a separate,
   measured change.

   [HARD] IT IS NOT CIRCULAR, AND THAT IS WORTH STATING BECAUSE IT LOOKS IT. The predicate
   reads .is-empty, which sweepBands() computes by MEASURING inside this rail — and a rail
   that is display:none measures 0 for everything, which would latch it hidden for ever.
   The sweep CLEARS .is-empty on every band before it measures (its own [HARD] note: that
   clear is what makes the pass idempotent and re-runnable), so at measuring time this
   predicate is false, the rail is displayed, and the reading is real. Driven, not reasoned:
   flipping Individual -> Group on flat blank at 390 brings the rail and both group-only
   rows back, and flipping back removes them again. */
@media (max-width: 640px) {
    .tpl-start.setup-sidebar #tpl-setup-meta:not(:has(.tpl-sidebar-block:not(.is-empty))),
    .tpl-start.setup-sidebar #tpl-start-form:not([data-mode="group"]) #tpl-setup-meta:not(:has(.tpl-sidebar-block > *:not(.tpl-group-only):not(.is-empty):not(#tpl-workshop-toggle-slot))) {
        display: none;
    }
}

/* ══ WHAT A BAND IS — ONE CLASS, BOTH RAILS ═══════════════════════════════════
   A band is one row on the rail's grid. It owns the space above it, the space
   below it, and the hairline that separates it from the band before it. Nothing
   else in either rail may state any of those three.

   setup-sidebar.js markBands() is the only thing that applies these classes, and
   it applies them AFTER it has decided which bands render. So a hairline without
   a band is not expressible: --ruled only ever lands on an element that also
   carries .tpl-rail-band, and neither lands on a band that is .is-empty.

   WHAT THIS REPLACED, and why none of it can come back:
     · five sibling selectors drew the hairline — `.tpl-sidebar-block + block`,
       `.tpl-sidebar-row + row`, a four-term chain hopping the weighting panel, a
       `#tpl-sidebar-segment-panel + row` patch, and `slot > :not(.is-empty) ~ *`.
       Each carried its own :not(.is-empty) guard, and none could see ACROSS the
       rail's two levels: a slot's last row and the next slot's first row are not
       siblings, so the boundary between them needed a sixth rule that never
       existed. A class computed from what renders needs no guard and no chain.
     · four rules declared `padding: 8px 0` on different subjects, plus two that
       existed only to undo one of them.
     · the row-gap. It is 0 now, on the container: the band's own 8/8 IS the gap,
       and a container gap landed entirely ABOVE each hairline, which is why every
       rule measured 24-32px above and 9px below against an intent of 8 and 8.

   BOTH RAILS, ONE CLASS, deliberately: the rails' bands ARE the same thing —
   "nine rows across two rails sit on one grid" is this file's own words, and the
   only difference was which level of the markup carried the row, which is now
   markBands()' single decision rather than three id-scoped rules. */
/* ── THE RAIL'S TWO STEPS, AND THE CONTAINERS THAT OWN THEM ──────────────────
   The band is the GROUP step: var(--space-sm) above and below, so one control
   group clears the next by 16px and the --ruled hairline sits centred in it. The
   8px was a literal; it is the scale's own value under its own name now.

   The band is ALSO the container for the LABEL step. Its children are a label and
   the control it names (or a single row, where the flex column changes nothing),
   so row-gap:var(--step-tight) states that relationship ONCE for every band
   rather than each child carrying its own margin. --step-tight is templates.css's
   "a label and the field it names. One object" — the same token the modes rows
   and the setup form's field labels already spend. */
.tpl-start.setup-sidebar .tpl-sidebar .tpl-rail-band {
    display: flex;
    flex-direction: column;
    row-gap: var(--step-tight);
    padding: var(--space-sm) 0;
}
.tpl-start.setup-sidebar .tpl-sidebar .tpl-rail-band--ruled {
    border-top: 1px solid rgba(var(--ink-rgb), 0.09);
}

/* Closing Date and Response Notifications are `.tpl-adv-row` (templates.css),
   authored outside the rail and moved INTO `#tpl-sidebar-slot-settings` only
   at phone width (setup-sidebar.js MOVES). `markBands()` marks the WRAPPER
   they travel in (`#tpl-close-date-block` / `#tpl-notifications-block`) as a
   band once it lands there, which stacks the wrapper's own children — the
   toggle row above its date/notification panel — but `.tpl-adv-row` is a
   second, nested flex row with its own `display:flex` and never inherits
   that. Its base rule (`flex-wrap:wrap`) only stacks label-over-toggle when
   the two do not fit on one line, and a short label such as "Closing Date"
   always does, at any width — so the wrap never fires and this is the one
   row left on this grid still reading "label … toggle" on one line.
   `flex-direction:column` + `align-items:flex-start` is `.tpl-rail-band`'s
   own pair, reused rather than re-derived; scoped to the same <=640 the rest
   of this relocation is written against (setup-sidebar.js `PHONE`), because
   `.tpl-adv-row` — unlike the seam rows above — also renders at every wider
   width and its row layout there is unchanged. */
@media (max-width: 640px) {
    .tpl-adv-row {
        flex-direction: column;
        align-items: flex-start;
    }
}

/* ── Template picker ─────────────────────────────────────────────────────────
   Outside the sidebar the switcher is a right-seated trigger with a 300px menu
   hanging off its right edge, sized for a wide page. In a 198px content box that
   menu would hang out over the left margin. Full-width trigger, menu anchored to
   the left edge and at least as wide as the sidebar. */
/* The template picker stacks THREE things in the rail — the trigger, the Session
   ID line and Delete Session — and they are all the same relationship: a control
   and what belongs under it. The switcher is their container, so it states the
   step once, with the SAME --step-tight the bands use. Before this, the three
   gaps came from three places and disagreed: the chip carried margin-top:8px
   (partials/session-ref), the button carried margin-top:var(--step-tight) (10px),
   and the trigger carried nothing. Both of those margins are DELETED. */
.tpl-start.setup-sidebar .tpl-sidebar .tpl-switcher {
    text-align: left;
    display: flex;
    flex-direction: column;
    row-gap: var(--step-tight);
}
/* THE RAIL'S DELETE SESSION BUTTON TAKES THE TEMPLATE TRIGGER'S WIDTH FROM THE
   TRIGGER'S OWN RULE (2026-09-02) — it is in this selector list rather than
   carrying a second width, so the two cannot drift apart later. Both are full
   width of the rail's field lane; there is one declaration that says so.
   (max-width:none below matters for the button too: templates.css's 180px cap is
   on .tpl-switcher-trigger only, but .tpl-action-btn has its own inline-block
   sizing, and `width:100%` is what makes both fill the lane rather than hug their
   label.) */
.tpl-start.setup-sidebar .tpl-sidebar .tpl-switcher-trigger,
.tpl-start.setup-sidebar .tpl-sidebar #tpl-rail-delete-session {
    display: flex;
    width: 100%;
    /* [HARD] max-width BELONGS WITH THE width ABOVE, not as a separate concern.
       templates.css caps the trigger at 180px below 768px. The page's own release
       of that cap used to be scoped `.tpl-start > .tpl-switcher` — a direct-child
       selector the picker stopped matching the moment it was relocated, which is
       why this line had to restate it. That rule is now DELETED outright (it never
       matched anywhere), so this is not a restatement of anything: it is the ONLY
       release of the 180px cap, and without it the trigger asks for 100% in a
       stacked rail, is held at 180px, and the template name truncates mid-word in
       a column with room for all of it. */
    max-width: none;
    justify-content: space-between;
}
/* (`margin-top: var(--step-tight)` is DELETED 2026-09-02, four days after it was
   added. It was right about the STEP and wrong about the OWNER: the switcher is a
   flex column with that row-gap now, so this button, the Session ID line above it
   and the trigger above that all take one gap from one place. A margin here would
   ADD to the container's gap, not replace it.)
   justify-content:center stays — the label is centred in a full-width box, where
   the trigger's space-between exists to push its chevron to the far edge. */
.tpl-start.setup-sidebar .tpl-sidebar #tpl-rail-delete-session {
    justify-content: center;
}
.tpl-start.setup-sidebar .tpl-sidebar .tpl-switcher-menu {
    right: auto;
    left: 0;
    min-width: 268px;
}
/* [HARD] IN THE RIGHT RAIL THE MENU HANGS FROM THE OTHER EDGE. The rail is
   248px wide and flush to the page's right edge, so a 268px menu opened from the
   trigger's LEFT edge runs off the viewport by its own overhang and the far side
   of every template name is unreachable. Anchoring to the right edge puts the
   overhang over the content column, which has room for it. Keyed on the rail's
   own modifier class rather than on #tpl-setup-meta so it holds if the picker is
   ever seated in a different right-hand rail. */
.tpl-start.setup-sidebar .tpl-sidebar--right .tpl-switcher-menu {
    left: auto;
    right: 0;
}

/* ── Text / Images ───────────────────────────────────────────────────────────
   The one relocated control that is NOT a `.tpl-pill-toggle`: it is the shared
   two-seat `.tpl-stype-toggle` component, and it stays one — the seats keep their
   own type, palette and active state from templates.css, and nothing here paints.

   Only the track is re-proportioned, the same adaptation the two mode sliders get
   above and for the same reason: on the page the seats sized to their words inside
   an `inline-flex` track that shrink-wrapped them, and in a column that leaves the
   control floating at its intrinsic width against a full-width label. `flex:1 1 0`
   splits the rail's content box between the two seats, which is what makes it read
   as the same kind of control as Session Type directly beneath it. */
/* (THE RAIL'S TEXT / IMAGES RULES ARE DELETED 2026-09-02, with the band they dressed.
   Three declarations stood here — `display:flex; width:100%; margin:0` on both toggle
   ids, and `flex:1 1 0; min-width:0` on their seats — and every one of them existed to
   make the control fill a 208px rail column under a COMPARISON TYPE label. The toggle
   is on the OPTIONS TO COMPARE heading row now, at every width, where it is sized by
   its own seats and placed by the row's gap; a full-width stretch there would push the
   badge and the label off their own line.
   The [HARD] note that stood here is deleted with them rather than reworded: it warned
   that the class-only selector missed flat's id rule IN THE RAIL, and there is no rail
   arrangement left for it to be true about. The lesson it carries — an id rule is not
   reached by four classes — is live at the in-row rule in start.blade.php, which names
   both ids for exactly that reason.) */

/* ── Session type + respondent mode ──────────────────────────────────────────
   Both are [label, toggle] stacks that centre themselves above the Overview
   card. In a column they read as label-over-control, left-aligned. The centring
   is undone at the block, the row and the slot, because all three carry it. */
.tpl-start.setup-sidebar .tpl-sidebar .tpl-modes-block {
    text-align: left;
    margin-bottom: 0;
}
.tpl-start.setup-sidebar .tpl-sidebar .tpl-modes-row {
    align-items: stretch;
    margin-top: 0;
}
/* The slot is `display:flex; justify-content:center` and carries a 6px/16px
   margin from a :has()-scoped rule; both are page-position rules that mean
   nothing in a column. */
.tpl-start.setup-sidebar .tpl-sidebar #tpl-workshop-toggle-slot {
    justify-content: flex-start;
    margin: 0 !important;
}
/* width:100% as well as stretch: the row is a flex ITEM of #tpl-workshop-toggle-slot
   and carries `width:auto` from a page rule, so without a definite width it
   shrink-wraps and the toggle inside it has no percentage to resolve against. */
/* [HARD] TWO IDS, TO OUT-SPECIFY TWO IDS. The page sets
   `#tpl-workshop-toggle-slot #tpl-workshop-toggle-row{width:auto}` at (2,0,0).
   This rule used to carry the rail's id and scored (2,2,0), which beat it; when
   the rail rules were rewritten from `#tpl-setup-sidebar` to `.tpl-sidebar` so
   they would reach BOTH rails, it dropped to (1,3,0) and quietly lost — three
   classes do not beat a second id. The row then shrink-to-fit at 81px, the
   toggle inside it had no width to resolve `100%` against, and the ghost seat
   landed at 40.5px instead of 104px. Naming the slot restores the id count
   without re-introducing a single-rail scope. */
.tpl-start.setup-sidebar #tpl-workshop-toggle-slot #tpl-workshop-toggle-row {
    align-items: stretch;
    width: 100%;
}

/* [HARD] THE WRAPPER CHAIN HAS TO CARRY A DEFINITE WIDTH, or `width:100%` on the
   toggle below resolves against nothing and the track collapses to zero — the
   seat words then wrap to one character per line, which is what the first
   attempt shipped. Both wrappers are `display:inline-flex`, so they shrink-wrap
   their contents, and a percentage width inside a shrink-to-fit parent is
   circular: the parent sizes to the child, the child asks for a percentage of
   the parent.
   Scoped with :has() to the two MODE toggles only. The sidebar's other pill
   toggles — welcome, completion, advanced — are small ON/OFF controls sitting
   beside a label and must keep their intrinsic width. */
/* [HARD] THE PAIR IS DECLARED SEPARATELY FROM THE PILLS (2026-08-26). It used to be the
   first selector of the rule below — a CONTAINER tied to three CONTROLS because they
   happen to want the same two declarations. The three pill arms ARE one family (one
   subject, .tpl-pill-toggle, reached three ways); .tpl-mode-pair is the flex row that
   HOLDS one of them, and a rename or a re-fit of either would have had to reason about
   the other. Same shape as the criterion-button tie, and load-bearing the same way:
   `width:100%` has no other source anywhere, and this `display:flex` is what OVERRIDES
   `.tpl-mode-pair{display:inline-flex}` in templates.css — (0,4,0) against (0,1,0), so
   this rule wins and would go on winning silently if the pills' rule were ever retuned.
   Same two declarations, same computed result: display flex, width 208px at 1280 and
   302px at 390, measured before and after.

   flex-wrap:wrap ADDED (2026-09-03) — the Session Type control ("INDIVIDUAL"
   spilling out of its own knob) turned out to be the item-1 mechanism again,
   on a different class: .tpl-mode-pair holds the pill toggle and, once an
   autosave save fires, a THIRD child — .tpl-autosave-flag, inserted as the
   pill's next sibling by the SAME flagFor() every rail toggle uses
   (start.blade.php ~12010). The pill's own width:100% (below) and the
   flag's own width:100% (start.blade.php ~5653) were both claiming the
   pair's full 208px on ONE line with nothing to wrap onto, so the flex
   algorithm shrank BOTH to fit — measured 98px each (208 minus the 12px
   gap, split in half) the instant a flag appeared, which is exactly why
   "INDIVIDUAL" no longer fit its own knob. wrap lets the flag's own
   flex-basis:100% force it onto its own line instead, leaving the
   toggle's line-1 width untouched — measured 208px in both states after. */
.tpl-start.setup-sidebar .tpl-sidebar .tpl-mode-pair{display: flex; flex-wrap: wrap; width: 100%;}
.tpl-start.setup-sidebar .tpl-sidebar .tpl-pill-toggle:has(#tpl-mode-toggle),
.tpl-start.setup-sidebar .tpl-sidebar .tpl-pill-toggle:has(#tpl-presenter-mode),
/* The respondent-mode pill needs naming through its ROW as well. Its wrapper
   chain is slot > row > label > input, one level deeper than the session-type
   pill's, and the :has() arm above was not enough on its own: measured, the
   track came out 81px instead of 208 and the ghost seat landed at 40.5px rather
   than 104px — a shrink-to-fit parent leaving `width:100%` nothing to resolve
   against, which is the same trap as the first time these toggles collapsed. */
.tpl-start.setup-sidebar .tpl-sidebar #tpl-workshop-toggle-row > .tpl-pill-toggle {
    display: flex;
    width: 100%;
}

/* Both mode sliders fill the sidebar's content box instead of their page widths
   (240px and 328px). RESIZING, NOT RESHAPING: they stay `.tpl-pill-toggle`
   checkboxes with CSS-content seat words and a sliding knob — only the track and
   the two seats become proportional so the control can sit in a column of any
   width without a second hard-coded number to keep in step.

   THE GEOMETRY IS THE SAME AS THE PAGE RULES, expressed as a proportion. The
   track is border-box with a 1px border, so the padding box is W-2, and the page
   lays it out as 2 + seat + 2 + seat + 2. Solving for a proportion:
     seat width = (W-8)/2  → calc(50% - 3px)   (50% resolves against W-2)
     right seat left = W/2 → calc(50% + 1px)
   Both reduce to the shipped pixel values at the shipped widths, which is how
   they were checked: at W=328 that is 160px seats with the right at 164px, and
   at W=240 it is 116px with the right at 120px — the exact numbers in
   templates.css.

   Only the RIGHT-seat `left` needs overriding; the left seat is `left:2px` in
   every state and is already proportional-by-accident. Both the unchecked and
   :checked rules are covered, because the two states swap which pseudo-element
   holds which word. */
.tpl-start.setup-sidebar .tpl-sidebar #tpl-mode-toggle,
.tpl-start.setup-sidebar .tpl-sidebar #tpl-presenter-mode { width: 100%; }

.tpl-start.setup-sidebar .tpl-sidebar #tpl-mode-toggle::before,
.tpl-start.setup-sidebar .tpl-sidebar #tpl-mode-toggle::after,
.tpl-start.setup-sidebar .tpl-sidebar #tpl-presenter-mode::before,
.tpl-start.setup-sidebar .tpl-sidebar #tpl-presenter-mode::after {
    width: calc(50% - 3px);
}
/* [HARD] WHICH PSEUDO-ELEMENT IS THE RIGHT SEAT SWAPS WITH THE CHECKBOX STATE, so
   every `left` override MUST carry the state in its selector. The page states this
   in four rules per toggle, one per (state x pseudo-element), and an override that
   states it in two is not a shorter way of saying the same thing — it is a
   different thing.

   What that cost: `#tpl-mode-toggle::before` (no state) matched in BOTH states and
   outranked the page's `:checked::before{left:2px}` at (2,2,0) vs (1,1,0). So with
   Individual selected the knob stayed on the right, `:checked::after` was pushed
   right too, both pseudo-elements landed on the same seat, the filled knob painted
   over the ghost — and the control read as ONE seat with GROUP vanished and
   INDIVIDUAL jumped to the right. The two toggles are mirror images (::before is
   the right seat unchecked on mode, ::after is on presenter), so the same slip
   broke respondent mode in its checked state.

   The width rule above is deliberately state-free: both seats are always the same
   width in both states, which is the page's own rule too. */
.tpl-start.setup-sidebar .tpl-sidebar #tpl-mode-toggle:not(:checked)::before,
.tpl-start.setup-sidebar .tpl-sidebar #tpl-mode-toggle:checked::after,
.tpl-start.setup-sidebar .tpl-sidebar #tpl-presenter-mode:not(:checked)::after,
.tpl-start.setup-sidebar .tpl-sidebar #tpl-presenter-mode:checked::before {
    left: calc(50% + 1px);
}

/* ── Save to project ─────────────────────────────────────────────────────────
   The block is a right-aligned column anchored to the fields well's top-right
   corner. In the sidebar it is an ordinary left-aligned row of the settings
   stack, and its z-index (which existed to lift it over the card) is irrelevant.
   The project PANEL keeps its own push-down behaviour untouched. */
.tpl-start.setup-sidebar .tpl-sidebar #tpl-save-project-block {
    position: static;
    z-index: auto;
}
/* Save To Project reads like every row beneath it: label left, toggle right.
   The partial's source order is toggle-then-heading with the panel, the
   confirmation line and the create form after it, so a flex row would put the
   toggle first and drag the panel onto the same line. A two-column grid places
   the two row-1 members explicitly and lets everything else span the full width
   underneath, without reordering the markup. */
.tpl-start.setup-sidebar .tpl-sidebar .tpl-project-toggle-wrap {
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: center;
    column-gap: var(--gap-row);
    text-align: left;
}
.tpl-start.setup-sidebar .tpl-sidebar .tpl-project-toggle-heading {
    grid-column: 1;
    grid-row: 1;
    margin: 0;
}
.tpl-start.setup-sidebar .tpl-sidebar .tpl-project-toggle-wrap > .tpl-pill-toggle {
    grid-column: 2;
    grid-row: 1;
}
.tpl-start.setup-sidebar .tpl-sidebar .tpl-project-confirm,
.tpl-start.setup-sidebar .tpl-sidebar #tpl-project-menu,
.tpl-start.setup-sidebar .tpl-sidebar .tpl-project-create-form {
    grid-column: 1 / -1;
    text-align: left;
}

/* ═══════════════════════════════════════════════════════════════════════════
   SLICE 3 — THE OPTIONAL-BLOCK ROWS, AND WHERE THEIR BLOCKS SIT
   ═══════════════════════════════════════════════════════════════════════════ */

/* THE SEAM COPIES ARE THE SAME RULE, NOT A SECOND ONE (2026-08-31). Three of these rows
   leave the rail on a phone and land between cards; this block is LAYOUT ONLY — its own
   comment says the paint and the hairline are the band's — so the layout a row needs to read
   as "label … control" is the same wherever it renders. Naming the two seams here keeps one
   declaration list; the alternative was dropping `.tpl-sidebar` from the selector, which
   lowers it from (0,4,0) and this file already records one rule losing a tie that way. */
.tpl-start.setup-sidebar .tpl-sidebar .tpl-sidebar-row,
.tpl-start.setup-sidebar #tpl-mobile-toggles-overview .tpl-sidebar-row,
.tpl-start.setup-sidebar #tpl-mobile-toggles-content .tpl-sidebar-row {
    display: flex;
    flex-wrap: wrap;
    /* flex-start, not center (third pass, 2026-09-03 — measured, not guessed:
       every row's toggle and label were re-measured in both left/right
       edges, both ON/OFF states, 1/2/3-line labels, and the disabled Ties
       row, against the right rail's own .tpl-adv-row, which was the working
       reference at the time (see docs/decision-record.md for the full
       numbers). align-items:center was re-centring the LABEL against ctrl's
       height every time the autosave flag appeared (an IN-FLOW sibling of the
       toggle that makes ctrl taller), while the toggle itself — anchored
       to ctrl's own top — barely moved, drifting the two apart by exactly
       half the flag's height. Toggle width/position never changed between
       ON and OFF (measured: 72px, same l/r, in every row) — that
       hypothesis didn't hold here; the drift was ONLY ever the
       flag-driven height change meeting align-items:center. flex-start
       anchors both label and toggle to the row's top always, so there is
       nothing left to re-centre against in either state, and the flag
       stays in normal flow — the row still grows to make room for it, so
       nothing overlaps the row below (checked: ~8px clear in every case,
       not the ~-8px overlap a position:absolute flag produced when tried
       first and reverted). Still true with the row stacked below — flex-start
       is a cross-axis value and reads as LEFT once flex-direction is column,
       which is what actually keeps label and toggle on one shared edge. */
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--gap-row);
    /* THE DIVIDER'S INSET, RESTORED 2026-08-27. This padding straddles the hairline
   that `.tpl-sidebar-row + .tpl-sidebar-row` paints, so it belongs to the painted
   BOUNDARY, not to the junction between two siblings. It was zeroed to move a
   constraint number, and the number was the wrong measurement: the step at this
   junction is box-to-box and reads 16 with or without it. A box's own interior is
   never charged to a junction outside it. The padding itself is now the band's —
   see WHAT A BAND IS above; this block keeps only its layout. */
}
/* 2026-09-18: `.tpl-adv-row` stopped being the reference these two seams were
   measured against — it is now the ONE THING ON THIS ROW'S GRID THAT DOES NOT
   STACK, not a working example. Every row that stays inside `.tpl-sidebar`
   already reads "label, then control" because `markBands()` gives it
   `.tpl-rail-band` (`flex-direction:column`, WHAT A BAND IS, above) — a row
   relocated to one of these two seams leaves `.tpl-sidebar` and is never
   scanned by `markBands()` (it only walks `#tpl-setup-sidebar`/`#tpl-setup-
   meta`), so it kept the row's un-stacked default. Reusing `.tpl-rail-band`'s
   own value here rather than re-deriving one: same property, same reason,
   the only thing a relocated row lost by leaving. */
.tpl-start.setup-sidebar #tpl-mobile-toggles-overview .tpl-sidebar-row,
.tpl-start.setup-sidebar #tpl-mobile-toggles-content .tpl-sidebar-row {
    flex-direction: column;
}
/* ── A ROW THAT LEFT THE RAIL LOST THE RAIL'S INSET (2026-08-31) ────────────────────
   In the rail these rows sit inside .tpl-sidebar-inner, whose padding is
   --tpl-sidebar-inset (20px) on all four sides — which is why the ADVANCED SETTINGS rows
   still read 20px in from their panel on both sides. A row moved to a seam is a direct
   grid item of the form with no panel around it, so it lost that inset entirely: measured
   at 390, INTRODUCTION PAGE's label started at x=8 and its ON/OFF pill ended at x=382 —
   hard against both page edges — while every card's content sits at 19 and every rail
   row's at 28.

   THE TOKEN COMES WITH THE ROW, which is why this is the fix rather than a padding
   written on the seams or on the two cards: the moved rows read the SAME
   --tpl-sidebar-inset the panel they came from reads, so the three that left and the six
   that stayed cannot end up on two different insets.

   EQUAL ON BOTH SIDES, WHICH THE CARDS ARE NOT. A card's own horizontal padding is
   deliberately asymmetric — --col-card-pad-l 10 against --col-card-pad-r 28, the
   complement that holds the line's width while the left indent shrank ([HARD] THE CARD'S
   HORIZONTAL INSET IS ASYMMETRIC, start.blade.php) — so its content sits 11 in from the
   left and 29 in from the right, on EVERY card including these two. Measured: the
   Introduction and Participant Data cards are inset identically to the Overview and
   Completion cards, to the pixel. It was never the cards.

   SEAM-SCOPED, NOT ON THE SHARED LAYOUT LIST ABOVE: a rail row is already inside the
   panel's 20px and would be inset twice.
   HORIZONTAL ONLY — the vertical inset is the panel's own and belongs to the band (see
   WHAT A BAND IS above); a row between two cards takes its vertical rhythm from the
   grid's row-gap like every other item. */
.tpl-start.setup-sidebar #tpl-mobile-toggles-overview > .tpl-sidebar-row,
.tpl-start.setup-sidebar #tpl-mobile-toggles-content > .tpl-sidebar-row {
    padding-inline: var(--tpl-sidebar-inset);
}
/* The weighting panel is a sibling BETWEEN two rows, so `row + row` skips the row
   after it — Completion Message would render with no hairline above it, panel
   open or closed. The row following the panel takes the same one.

   [HARD] THE GUARD TESTS THE ROW ABOVE THE WEIGHTING ROW, NOT THE WEIGHTING ROW.
   Criteria Weighting now sits SECOND, under Introduction Page, so the chain is
   `visible row + weighting row + panel + the row that needs the divider`. That
   extra `.tpl-sidebar-row` link is what makes it survive a shape with no
   criteria — every survey and quiz — where the weighting row is .is-empty and
   hidden: the guard still finds Introduction Page above it, so Completion
   Message keeps its divider and the rail reads as one unbroken list.

   Anchoring on the weighting row itself (what this rule did while weighting was
   the FIRST row) now fails exactly there: `:not(.is-empty)` would be false on
   every flat shape and Completion Message would lose its hairline. The original
   reason for the guard is unchanged and still live — an unconditional rule draws
   a line above the first row the rail shows when everything above it is absent
   (measured once as a line at y=133 above Introduction Page on blank_survey,
   with nothing above it). Chained adjacent combinators still say what is meant,
   and still cost no :has(). */
/* The segment panel breaks `row + row` for Completion Message in exactly the same
   way, and takes the same hairline back.

   NO :not(.is-empty) GUARD HERE, deliberately. The guard on the weighting chain
   above exists because the weighting row DISAPPEARS on a shape with no criteria,
   which can leave the row after it first in the rail. Segment Responses is plain
   markup with a real toggle in its slot on every shape — the dead-row sweep marks
   a row .is-empty only when no INPUT arrived in its control slot, and one is
   authored here rather than moved in — so it is never absent, and the row after
   its panel is never the first thing the rail shows. A guard would be a condition
   that cannot go false, which reads as a real one and is worse than none. */
/* (The project-slot hide rules that stood here are the ONE RULE near the top of
   this file. Both the slot and its rows are covered by it.) */

/* ── ONE RHYTHM FOR BOTH RAILS ───────────────────────────────────────────────
   The left rail's row is .tpl-sidebar-row. The right rail's row is the BLOCK
   itself — Template, Content Type, Save To Project — except inside
   #tpl-sidebar-slot-modes, where setup-sidebar.js parks TWO relocated controls,
   Session Type and Respondent Mode, in one block; there the row is each child.
   That is the whole of the difference between the rails, and it is structural,
   not stylistic: the padding and the hairline below are the left rail's own
   values, unchanged, so nine rows across two rails sit on one grid — 8px above
   and below each row, 16px between adjacent rows, one hairline centred in the
   gap, none above the first row or below the last.

   The project block's own bottom hairline went with this. It dated from when the
   block sat ABOVE the toggle rows in the left rail; it is now the last block of
   the right rail, so that border was a line under the final row. */
/* THE DIVIDER'S INSET. It straddles the hairline `.tpl-sidebar-block +
   .tpl-sidebar-block` paints, so it belongs to the painted BOUNDARY and not to the
   junction between two siblings. Briefly zeroed to move a constraint number; the
   number was the wrong measurement (see .tpl-sidebar-row above) and it is back. */
/* margin:0 as well as the padding — Session Type is #tpl-modes-block, and the
   page gives that id `margin-top: var(--space-md)` at (1,2,0) for its position
   above the Overview card. In the rail that margin is 16px of gap on ONE of the
   nine rows, which is exactly the inconsistency this section removes. */
/* THE PROJECT SLOT NOW HOLDS THREE ROWS, so it takes the modes slot's rhythm —
   the SAME three declarations, extended to it rather than copied into a rule of
   their own. Save To Project, Closing Date and Response Notifications are siblings
   on one grid: 8px above and below each, one hairline between adjacent ones, none
   above the first.

   `> * + *` IS WHAT MAKES AN ABSENT ROW COST NOTHING. Both new rows are
   .tpl-group-only, so in Individual mode they are display:none — an element with
   no box draws no border, and the rows that remain keep their own. Nothing has to
   know which of the three is present. */
/* Restored: same painted-boundary inset. */
/* margin ONLY. The padding is the band's.
   [CORRECTED 2026-08-31] This note said the rule "survives because #tpl-modes-block carries
   `margin-top: var(--space-md)` at (1,2,0) from the page". That declaration is DELETED — it
   was one of the per-element margins that went when the phone stopped relocating the block —
   and so are the three `margin:6px 0 16px` rules on #tpl-workshop-toggle-slot this arm also
   used to neutralise. **The stated reason for this rule no longer exists.** It is kept rather
   than deleted because the project slot's three occupants (Save To Project, Closing Date,
   Response Notifications) were not audited for margins of their own in that pass, and two of
   them now land in the SETTINGS slot at <=640 where this rule does not reach them and they
   render correctly — which is evidence, not proof. Deleting it is a >=641-reaching change and
   wants its own census; see docs/known-debt.md. */
.tpl-start.setup-sidebar #tpl-setup-meta #tpl-sidebar-slot-modes > *,
.tpl-start.setup-sidebar #tpl-setup-meta #tpl-sidebar-slot-project > * { margin: 0; }
/* [HARD] `:not(.is-empty) ~ *`, NOT `* + *`. An adjacent combinator counts a HIDDEN
   sibling, so on a guest page — where Save To Project is marked .is-empty and
   display:none — Closing Date was still "the second child" and drew a row hairline
   1px below the slot's own block hairline. Measured: slot border-top at y=203,
   close-date border-top at y=204, i.e. two lines a pixel apart under Template. The
   general sibling combinator asks the question that actually matters: is there a
   VISIBLE row above me? The first row that renders draws nothing, whichever it is. */
.tpl-start.setup-sidebar #tpl-setup-meta #tpl-sidebar-slot-modes > :not(.is-empty) ~ *,
/* The .tpl-adv-row hairline is the ADVANCED PANEL's internal rhythm — in the left
   rail it separates one advanced row from the next. In this slot the block-level
   chain above already separates the three rows, so the row's own border would be a
   second line under the same gap. One owner per hairline.

   ── AND ONE OWNER PER INSET, WHICH IS THE SAME DECISION (2026-08-28) ──────────
   The hairline got one owner here and the INSET did not. The block takes 8px above
   and below from the `> *` rule 40 lines up, straddling the hairline it really does
   paint; .tpl-adv-row then took 8px more from its own rail rule, straddling the line
   this block deliberately deletes. Measured over sixteen cells by
   `setup:rail-baseline`: 16px held at each of the three block junctions where 8 is
   the boundary's, and .tpl-adv-row is the ONLY inset in the rail that comes back
   `paintsAnywhere: false` — it straddles nothing anywhere in this slot.

   [HARD] BOTH ENDS MOVE, NEVER ONE. The row is `align-items:center` with no box of
   its own, so what the eye reads is hairline-to-text on each side. Zeroing the top
   alone leaves 8 above the text against 16 below it, which is the off-centre this
   rule exists to avoid, not a half-measure toward it.

   [HARD] THE BOTTOM INSET HAS A SECOND JOB AND THE SUB TAKES IT OVER. Each block
   here is `.tpl-adv-row` followed by a sibling `.tpl-adv-sub` panel, and while that
   panel is OPEN the row's padding-bottom is the whole row-to-sub gap — measured 8px
   with boxGap 0, so zeroing it alone sits the panel flush against the row that
   opened it. That gap is real and it moves to the sub rather than disappearing with
   the inset it was riding on. A baseline taken with every sub CLOSED cannot see this;
   this one was re-driven with `?s=close_date` to find it. */

/* ── THE JUMP LINK IS CSS, NOT STATE ─────────────────────────────────────────
   Read off the relocated checkbox with :has(), so it cannot disagree with it.
   The alternative — a class toggled by a change handler — would be a second
   representation of the same boolean, and the sidebar's whole claim is that it
   has none. It also means the link is correct on FIRST PAINT for a card the
   server rendered already-on (a manage page with a saved welcome), which a
   handler bound to `change` would not be.

   The label is inert until the row is on: pointer-events:none rather than
   hiding it, so the row still reads as an available setting.

   THE RUST ON-DOT IS DELETED, and with it the flex gutter that reserved its
   width. It was a `::before` on this label, transparent when off and --red when
   on, on all four rows. Nothing else on the platform used it — the two rules
   were scoped `.tpl-start.setup-sidebar .tpl-sidebar .tpl-sidebar-row-label`
   and this class exists on four elements, all in the left rail — and no JS ever
   touched it, so deleting the CSS is the whole removal.

   [HARD] THE LABEL IS A PLAIN BLOCK AGAIN. `display:flex; align-items:baseline`
   existed ONLY to put the text in an anonymous block past the dot so a wrapped
   second line did not hang back to the container edge. With no dot there is
   nothing to clear, and a flex label would keep a wrapped heading in a nested
   box for no reason. Every line now starts at the block's content edge, which is
   also where the Advanced Settings panel's own labels start — one left edge for
   the whole rail, with no gutter value anywhere to keep in step.

   WHAT SAYS "ON" NOW is the ON/OFF pill beside the label, which is the only
   thing that ever said it unambiguously; the label's colour is --ink in both
   states, in both rails, as before. */
.tpl-start.setup-sidebar #tpl-mobile-toggles-overview .tpl-sidebar-row-label,
.tpl-start.setup-sidebar #tpl-mobile-toggles-content .tpl-sidebar-row-label,
.tpl-start.setup-sidebar .tpl-sidebar .tpl-sidebar-row-label {
    color: var(--ink);
    text-decoration: none;
    line-height: 1.4;
    pointer-events: none;
    /* .tpl-adv-row's own fix for this exact shape (templates.css:3181,3190),
       mirrored rather than reinvented — see the .tpl-sidebar-row-ctrl rule
       below for why both sides of the row need it together. */
    flex: 1 1 0%;
    min-width: 0;
}
.tpl-start.setup-sidebar .tpl-sidebar .tpl-sidebar-row:has(input:checked) .tpl-sidebar-row-label {
    pointer-events: auto;
}

/* A LOCKED ROW GREYS ITS LABEL, AND THE ROW IS WHAT KNOWS.
   The lock class lands on the CONTROL (the .tpl-pill-toggle inside
   .tpl-sidebar-row-ctrl), and the label is that control's SIBLING — so nothing
   the label can see says "locked". :has() on the row is the fix rather than a
   workaround: it is the same shape as the :has(input:checked) rule directly
   above, which is this file's existing answer to exactly this problem for
   exactly this element, and it means no label ever carries a hand-applied class.

   IT WORKS FOR BOTH SOURCES OF A LOCK, which is the point. The server renders
   .plan-locked on the control for the setup page's own gates (the page is
   reachable signed out, where window.planFeatures is not emitted), and
   plan-locks.js TOGGLES the same class from the entitlement map elsewhere. The
   row reads whichever put it there, and because apply() toggles rather than
   adds, the label reverts the moment a row unlocks — no JS here, and no second
   state to keep in step.

   --muted is the padlock's own colour (plan-locks.css paints
   .plan-locked::after with it), so the label and the mark beside it are one
   grey by construction. Colour ONLY in this block: the base rule above mixes
   colour with layout, and .claude/rules/css-specificity.md is explicit that a
   block mixing the two should be split rather than raised.

   Three arms because the base rule has three: the two mobile hosts are addressed
   by id, so an arm without the id would sit at (0,6,0) against their (1,3,0) and
   lose on the phone while working on the desktop rail. */
.tpl-start.setup-sidebar #tpl-mobile-toggles-overview .tpl-sidebar-row:has(.plan-locked) .tpl-sidebar-row-label,
.tpl-start.setup-sidebar #tpl-mobile-toggles-content .tpl-sidebar-row:has(.plan-locked) .tpl-sidebar-row-label,
.tpl-start.setup-sidebar .tpl-sidebar .tpl-sidebar-row:has(.plan-locked) .tpl-sidebar-row-label {
    color: var(--muted, #8C8078);
}

/* WAS flex:0 0 auto — a non-shrinking box sized to its own content. flex:1 1
   0%;min-width:0;text-align:right (below) fixed the horizontal jump this
   caused when the autosave flag appeared as ctrl's own content — that part
   held up under re-measurement and is unchanged. What did NOT hold up,
   caught on the third pass by measuring both rails' actual pixel positions
   in both toggle states rather than screenshots alone:

   welcome/completion's ctrl content is the RELOCATED .tpl-block-header-toggles
   (start.blade.php's CTRL_MOVES) — a display:flex, BLOCK-level container.
   text-align only reaches INLINE-level children, so it was silently inert
   for exactly these two rows: measured pillBox flush LEFT (l=155) while the
   four bare-.tpl-pill-toggle rows (Participant Data, Comments, Skip, Ties —
   an inline-flex label with no wrapper) measured flush RIGHT (l=182), same
   ctrl track, same 72px toggle. Screenshots alone did not separate this
   from the flag-driven vertical drift fixed above, which is why a prior
   pass reported "some rows better, some worse" — two different bugs
   sharing one symptom class.
   FIX, see .tpl-block-header-toggles below for why display:inline-flex
   (tried first) was reverted rather than kept. */
.tpl-start.setup-sidebar .tpl-sidebar .tpl-sidebar-row-ctrl,
.tpl-start.setup-sidebar #tpl-mobile-toggles-overview .tpl-sidebar-row-ctrl,
.tpl-start.setup-sidebar #tpl-mobile-toggles-content .tpl-sidebar-row-ctrl { flex: 1 1 0%; min-width: 0; text-align: right; }
/* display:inline-flex was tried first: it makes the wrapper shrink-to-fit,
   which reads text-align:right correctly and is stable with ONLY the
   toggle inside it — but not once the autosave flag (width:100%, a
   PERCENTAGE child) also lands inside: measured the toggle at TWO
   DIFFERENT left edges, before vs. after a flag appeared, because a
   flex-wrap container's shrink-to-fit width with a percentage-sized child
   is genuinely ambiguous per spec, not a number to retune.
   width:100% removes the ambiguity outright: the wrapper always takes
   ctrl's own width (ctrl's is flex:1 1 0% off the ROW's stable width, so
   it never depends on the flag either), and justify-content:flex-end
   pushes the toggle — and the flag, once flex-wrap puts it on its own
   line — to ctrl's right edge directly. margin-left:auto (this wrapper's
   OWN rule, templates.css:1117) only ever worked in its ORIGINAL
   flex-item context; relocated here as a plain block child, it has
   nothing to push against, which was the second reason text-align alone
   could not simply "reach one level deeper". */
.tpl-start.setup-sidebar .tpl-sidebar .tpl-sidebar-row-ctrl > .tpl-block-header-toggles { width: 100%; justify-content: flex-end; }

/* ── WHERE THE BLOCKS SIT IN THE CONTENT COLUMN ──────────────────────────────
   Stated as the participant's order, which is type-agnostic: the session's
   identity, then the first thing a participant meets, then the content, then
   what they see at the end, then the facilitator's own settings, then the
   action. Nothing here names multi-criteria — #tpl-structural-region is the
   content region for every session type, and the three optional cards are shared
   cards with the same ids on every shape.

   Only WELCOME actually moves: it renders after the content region and belongs
   before it. The rest are already in participant order in the source.

   [HARD] COMPLETENESS, not correctness, is the failure mode — the same trap
   Slide view's ordering block documents. A grid item with no `order` defaults to
   0 and lands in FRONT of everything, including the Overview card. So every
   child that can be in column 2 is listed, including ones that cannot currently
   be seen. The sidebar is deliberately excluded: it is in column 1, where order
   is meaningless, and giving it one would imply it participates in this
   sequence.

   .tpl-manage MOVED TO FRONT (2026-08-29). Response Management and Results &
   Analysis are the two manage cards that take no badge number (see
   [data-card-badge-host] in start.blade.php) — they now sit directly under the
   OPEN/CLOSE + DELETE button pair, above every numbered content card, same as
   .tpl-structural-lock-note already does at its own default order:0 (untouched
   here — it stays adjacent to #tpl-structural-region, the content it actually
   describes, rather than following these two cards up). -1 is deliberately
   below every other value on this page, including the 0 default, so a future
   order:0 addition still cannot land in front of it by accident. Both manage
   cards move together: they share this one selector, so there is no second
   value to keep in sync. #responses-modal (the Individual Responses dialog) is
   NOT part of this move — it is a modal, not a card in the visual column, and
   was only ever coincidentally sharing .tpl-manage's old value. */
/* ── THE SCALE IS TENS, AND THAT IS THE WHOLE REASON IT CHANGED (2026-08-31) ──────
   Every value below was multiplied by ten. Nothing here reorders: `order` is compared,
   never summed, so -10/0/10/30/50/60/70/80/90/100 is the identical sequence -1/0/1/2/3/
   4/5/6/8/9 was, and the per-box census at 641/700/888/1280 is 0 differences.
   WHAT IT BUYS is the gaps. At <=640 the three toggles that reveal a card have to sit
   BETWEEN cards — Introduction Page above the Introduction card and below the Overview,
   Participant Data above its own — and consecutive integers have nowhere to put them.
   They take 20, 40 and 65 in the <=640 block below; the tens here are what those exist
   in. A future card inserted into this sequence takes a spare ten rather than forcing a
   second renumbering. */
.tpl-start.setup-sidebar #tpl-start-form > #tpl-session-setup-card      { order: 10; }
.tpl-start.setup-sidebar #tpl-start-form > #tpl-welcome-page-card       { order: 30; }
.tpl-start.setup-sidebar #tpl-start-form > #tpl-participant-data-card   { order: 50; }

.tpl-start.setup-sidebar #tpl-start-form > #tpl-structural-region       { order: 60; }
/* [HARD] -1 IS NOT MULTIPLIED, AND IT IS THE ONE VALUE THAT MUST NOT BE. It is TIED
   with `@media (max-width:900px){#tpl-setup-sidebar … #tpl-setup-meta{order:-1}}` further
   down this file, and the tie is what puts the meta rail ABOVE the two manage cards:
   equal order, document order decides, and meta is authored first. Multiplying this to
   -10 broke the tie silently — measured at 641 on the manage fixture, the meta rail fell
   from y=267 to y=1419.88, 89 boxes, with nothing in this list appearing to have moved
   relative to anything else in it. A tie across two blocks is invisible to a renumbering
   that only reads one of them. */
.tpl-start.setup-sidebar #tpl-start-form > .tpl-manage                  { order: -1; }
.tpl-start.setup-sidebar #tpl-start-form > #responses-modal             { order: 70; }
.tpl-start.setup-sidebar #tpl-start-form > #tpl-completion-message-card { order: 80; }
/* #tpl-items-save-row — the manage "Save Items & Questions" button — moved
   here (2026-09-02) from inside #tpl-structural-region (order:60), which had
   pinned it before Completion Message regardless of markup position. One of
   the tens gaps this scale exists for: after Completion Message (80), before
   #tpl-error (85), so it lands after the last thing the user edits. */
.tpl-start.setup-sidebar #tpl-start-form > #tpl-items-save-row          { order: 82; }
.tpl-start.setup-sidebar #tpl-start-form > #tpl-error                   { order: 85; }
/* [HARD] 90 IS A CEILING, NOT A GAP — #tpl-setup-sidebar TAKES 99 UNDER 900px.
   `@media (max-width:900px){#tpl-setup-sidebar{order:99}}` (this file, THE RAIL STACKS
   UNDER THE COLUMN) is the only other order on this grid, and it is NOT in this list —
   the note above says the sidebar is excluded because at >=901 it is in column 1 where
   order is meaningless. Between 641 and 900 it is not: it is a grid item with order 99,
   and it must stay LAST.
   The first cut of the tens renumbering gave .tpl-actions 100 and flipped the two: the
   rail rendered ABOVE the SET UP SESSION button at 641, 700 and 888 on every shape, 8 to
   178 boxes differing per page on the census. Nothing below 99 is available above this
   without moving that rule too, so the sequence ends here. If a card ever has to sit
   after the actions, raise 99 in the same commit. */
.tpl-start.setup-sidebar #tpl-start-form > .tpl-actions                 { order: 90; }
/* Both mode blocks are moved into the sidebar by setup-sidebar.js, so they are
   not children of the form at layout time. Listed anyway: if the script does not
   run, they stay in the form and must still land in a sensible place rather than
   in front of the Overview card. */
.tpl-start.setup-sidebar #tpl-start-form > #tpl-modes-block             { order: 0; }
.tpl-start.setup-sidebar #tpl-start-form > #tpl-workshop-toggle-slot    { order: 0; }
/* ── <=640: SESSION TYPE IS A HEADER-BLOCK MEMBER, SO IT GOES IN FRONT OF THE RAIL ──
   (2026-08-31.) #tpl-modes-block stops being relocated at this width (MOVES' third column),
   so unlike every other line above it this one is a REAL form child here and its order is
   read rather than reserved. The header block reads picker -> heading -> SESSION TYPE, and
   the rail — which still carries Respondent Mode, Closing Date and Response Notifications on
   DM / MS / survey / quiz — belongs after it, not between the heading and the control the
   heading introduces. -2 is below the rail's and .tpl-manage's -1 and, like that -1, is
   deliberately below the 0 default so a future order:0 addition cannot land in front of it.
   INERT AT >=641 BY CONSTRUCTION, not by scope alone: the node is inside the rail there and
   is not a child of the form at all, so `#tpl-start-form > #tpl-modes-block` matches nothing.
   The media query is belt to that braces — it makes the claim checkable at one width. */
@media (max-width: 640px) {
    .tpl-start.setup-sidebar #tpl-start-form > #tpl-modes-block         { order: -2; }
    /* ── RESPONDENT MODE IS A GRID ITEM WITHOUT ITS SLOT (2026-08-31) ─────────────────
       #tpl-workshop-toggle-slot stays home at this width too, and it is a WRAPPER the page
       IIFE fills — so as a grid item it is a box that renders nothing on every shape where
       its row is hidden (flat blank at any mode, and every shape in Individual mode, since
       #tpl-workshop-toggle-row is .tpl-group-only). A zero-height grid item still costs a
       row-gap at each end, which is the same phantom distance the collapsed rail was
       charging one rule up.
       display:contents removes the wrapper's BOX without touching the DOM or the row's own
       rules: when the row renders it becomes the grid item directly and takes the gap; when
       it does not, there is no item and no gap. THE ALTERNATIVE WAS TO RESTATE THE ROW'S
       OWN VISIBILITY — `[data-mode]` plus `:has(#tpl-rank-type-toggle)` — in a second place,
       which is two conditions that must then be kept in step with the two that hide the row.
       ORDER MOVES TO THE ROW WITH THE BOX. `display:contents` changes box generation, not
       the DOM, so `#tpl-start-form > #tpl-workshop-toggle-row` still matches nothing; the
       row is selected through its real parent and ordered as the grid item it now is. Both
       sit at -2 with Session Type and fall into source order behind it, which is the order
       start.blade.php already authors them in. */
    .tpl-start.setup-sidebar #tpl-start-form > #tpl-workshop-toggle-slot { display: contents; }
    .tpl-start.setup-sidebar #tpl-workshop-toggle-slot > #tpl-workshop-toggle-row { order: -2; }
    /* ── RESPONDENT MODE IS CENTRED, CONTROL AND ALL (2026-08-31, requested) ────────
       Out of the rail it rendered as a full-bleed bar while SESSION TYPE — the control
       the [HARD] shared-geometry note calls deliberately identical to it — sat centred at
       its own 240px: measured at 390 on the manage fixture, the pill ran 8..367 (359px)
       against Session Type's 67.5..307.5 (240px), one directly under the other.
       ONE PROPERTY, THE ONE SESSION TYPE USES. Its pill is centred by `align-items:center`
       on the flex parent (.tpl-mode-pair, measured `parentAlign: center`). This row is
       already a flex column, so the same property centres both its caption and its
       control, and the caption's own `text-align:center` is deleted with this edit —
       align-items shrinks the label to its content and centres it, leaving that
       declaration nothing to do.
       IT MUST RELEASE TWO RAIL DECLARATIONS TO WORK, and both are named rather than
       out-specified blindly: `#tpl-workshop-toggle-slot #tpl-workshop-toggle-row
       {align-items:stretch;width:100%}` (2,1,0) and the rail's
       `#tpl-workshop-toggle-row > .tpl-pill-toggle{width:100%}` — the second is why
       align-items alone cannot shrink the pill, since a definite width beats stretch.
       Matched at (2,2,0) and (2,3,0) inside this media block, so the >=641 rail values
       stand untouched; Session Type is not in either selector at any width.
       max-width:100% because the pill's own page width is 328px and the lane at 320 is
       304 — the cap is what keeps this from overflowing where the toggle used to stretch. */
    .tpl-start.setup-sidebar #tpl-workshop-toggle-slot #tpl-workshop-toggle-row {
        align-items: center;
    }
    .tpl-start.setup-sidebar #tpl-workshop-toggle-slot #tpl-workshop-toggle-row > .tpl-pill-toggle {
        width: auto;
        max-width: 100%;
    }
    /* ── THE TWO TOGGLE SEAMS (2026-08-31) ─────────────────────────────────────────────
       Both are empty wrappers MOVES fills, and both are empty on a shape whose rows are
       absent or mode-gated — Individual mode hides all three, and slim renders none of the
       cards they reveal. display:contents for the same reason #tpl-workshop-toggle-slot has
       it one rule up: a zero-height grid item still charges a row-gap at each end, and the
       alternative is restating each row's own visibility in a second place.
       ORDER GOES ON THE ROWS, because display:contents changes box generation and not the
       DOM — the rows are the grid items and are selected through their real parent. Equal
       order plus document order is what seats them: the overview seam's rows share order 1
       with #tpl-session-setup-card and are authored after it; the content seam's row shares
       order 4 with #tpl-structural-region and is authored after that. */
    /* ── A REVEAL TOGGLE SITS DIRECTLY ABOVE THE CARD IT REVEALS (2026-08-31) ──────
       The three rows shared one order with the card they were seated after, and fell
       into place by document order — which put both Overview toggles together and both
       their cards after the pair: Overview, INTRODUCTION PAGE, PARTICIPANT DATA,
       Introduction card, Participant Data card. A toggle then sat nowhere near its
       effect, which is the thing the move was for.
       ONE ORDER PER ROW, in the gaps the tens above opened: 20 between the Overview
       card (10) and the Introduction card (30), 40 between that and Participant Data
       (50), 65 between the content region (60) and the Completion card (80). Selected
       through the seam because display:contents changes box generation, not the DOM —
       `#tpl-start-form > #tpl-settings-row-welcome` matches nothing.
       CARD NUMBERING IS NOT TOUCHED AND DOES NOT NEED TO BE: the badge numerals are
       assigned by the page's own numbering pass over the cards in DOM order, and the
       cards' relative order is unchanged — only rows have been threaded between them. */
    .tpl-start.setup-sidebar #tpl-mobile-toggles-overview > #tpl-settings-row-welcome { order: 20; }
    .tpl-start.setup-sidebar #tpl-mobile-toggles-overview > #tpl-settings-row-pdata   { order: 40; }
    .tpl-start.setup-sidebar #tpl-mobile-toggles-content > #tpl-settings-row-completion { order: 65; }
    /* THE GROUP'S HEADING IS CENTRED — the rail's rows read label-left / control-right;
       the heading is not a row and centres over them, the same treatment SESSION TYPE's
       and RESPONDENT MODE's captions take above the cards.
       ── ITS TIER IS THE SUBMIT BUTTON'S, NOT THE ROWS' (2026-08-31, requested) ──────
       It wore .tpl-mono + .tpl-workshop-mode-label, i.e. the tier of the labels beneath
       it — measured DM Mono 12px. It reads at the SET UP SESSION button's tier now:
       --font-heading (Bebas Neue) at --tpl-cta-size, the token split out of
       `.tpl-continue`'s own literal so the two cannot drift, in --ink rather than the
       button's white. Three properties, three existing sources, nothing restated.
       .tpl-workshop-mode-label's `text-transform:uppercase` stays and is doing real work:
       Bebas is a caps-only face by design, but the class is what makes that explicit and
       what the string is authored in mixed case against.
       ── AND IT SITS CENTRED IN ITS OWN BOX, NOT LOW IN IT ──────────────────────────
       Bebas Neue's glyphs occupy the upper part of its em box, so a `line-height:normal`
       line box leaves the caps sitting low against the space below them — measured before:
       a 12px caption in a 32px box with 8px of padding either side, the text riding the
       lower half. `line-height:1` collapses the line box onto the glyphs, which is the
       same treatment .tpl-page-title already applies to Bebas for the same reason and is
       named in its note ("line-height:1"). The 8px padding is untouched: it is the band's
       and it is symmetric, so once the line box stops adding slack of its own the text is
       centred by the padding that was already there. No margin, no nudge. */
    .tpl-start.setup-sidebar #tpl-advanced-settings-heading {
        display: block;
        text-align: center;
        font-family: var(--font-heading);
        font-size: var(--tpl-cta-size);
        color: var(--ink);
        line-height:var(--type-label-line);
    }
}

/* ═══════════════════════════════════════════════════════════════════════════
   SLICE 4 — FIELD TREATMENT INSIDE THE OPTIONAL BLOCKS

   The mockup's fields are a line, not a box: transparent fill, no border except a
   hairline underneath, the label folded into the placeholder, and the rule going
   rust on focus. The page's own treatment is a white box with a 1px border.

   SCOPE. The two optional blocks only — welcome and completion. Advanced is
   already rows-of-controls with no text fields, and the criteria block is slice
   5. Deliberately NOT applied to the Overview card: its identity fields are
   guarded by `php artisan setup:badge-probe` and the completion badge is keyed to
   `:not(:placeholder-shown):not(:disabled):valid` against the boxed treatment.

   WHAT IS NOT TOUCHED. The ring + pencil cue already exists on this page — it is
   `.tpl-inline-edit`'s edit affordance, not something this slice adds — and the
   heading/body pair already exists too: instructions_title / instructions and
   completion_message_heading / completion_message_text, with the real "Insert
   Heading" / "Insert Text" placeholders from the lang file. The mockup's single
   textarea per block, and its invented heading placeholders, are both wrong; the
   lang keys win.
   ═══════════════════════════════════════════════════════════════════════════ */

/* (DELETED 2026-08-24. This block was a HALF conversion: it took the box's fill and its
   four borders away and left a `border-bottom:1px solid rgba(ink,0.16)` ON THE FIELD — a
   static hairline that was neither rust nor ink, on the only two fields whose line never
   reported whether they were finished. The line is the PART's now, from the shared
   :is() gate, so it says what every other line on the page says.

   IT WAS ALSO THE ONLY ONE OF THE TWO RULES THAT EVER APPLIED: `$sidebarLayout` is
   hard-coded true, so .setup-sidebar is on for every render and the base
   `.tpl-instructions-title{border:1px …;background:var(--color-white);padding:10px 14px}`
   in start.blade.php never reached a page. The "boxed treatment" these fields were
   described as having was THIS block, not that one.

   [HARD] ITS `padding: 7px 0` WAS ALSO CANCELLING THE COMPLETION BADGE'S LANE. FIELD STATES
   gives every field on the surface `padding-right:calc(--tpl-badge-inset + --tpl-badge-size
   + 6px)` so the value ends before its badge; a padding SHORTHAND here reset that to 0 and
   the badge was painted over the text. Measured at 1280 before this deletion: these two
   fields' padding-right was 0px against every other field's 44px, with the badge rendering.
   That is why the replacement in start.blade.php uses longhands only. */
/* (The `:focus{border-bottom-color:var(--red)}` arm for these four is DELETED. Its
   reasoning was right and its remedy was the odd one out: "the page keeps outline:none on
   these fields, so without this there would be no focus indication at all". True — and the
   answer is the surface's ONE focus treatment, the white fill, which .tpl-instructions-title
   and .tpl-instructions-textarea are named in directly (they carry their own underline
   rather than sitting in a wrapper). See THE FOCUS TREATMENT in templates.css. */
/* (`min-height:76px` DELETED 2026-08-24 with the block above. The body is
   field-sizing:content now — it is exactly as tall as its value and grows as you type,
   which is what every other multi-line field on the surface does. A min-height would hold
   an empty field open at a height nothing else on the page has.

   SLIM'S TWO BOXED TEXTAREAS ARE NOT THIS JOB, and it is worth being exact about why: the
   Overview description and participant instructions render the SAME <textarea> elements
   through a DIFFERENT class set — `.tpl-issue .tpl-section-prompt-input` — and .tpl-issue is
   a shared boxed-field class with consumers across the surface (question names, guidance,
   option rows, the availability controls). Converting it is a change to that class's whole
   consumer set, not a repeat of this commit's two-wrapper edit. Same symptom, different
   mechanism. */

/* ═══════════════════════════════════════════════════════════════════════════
   SLICE 5 — THE CRITERIA BLOCK

   [HARD] WEIGHTING IS NOT TOUCHED HERE, and that is a correctness constraint
   rather than a scoping preference. The steppers are hidden by

     .tpl-weight-row:not(:has(.tpl-weighting-enable-cb:checked)) .tpl-weight-stepper

   — a selector keyed to the presence of that checkbox INSIDE the criterion card.
   Move it, remove it, or restyle it out and the :has() stops matching, every
   stepper unhides, and the page walks straight into D-DEBT-12:
   redistributeWeights() clobbering a saved 50/30/20 to 33/33/34 on every DM page
   load, criterion_weights[i] not reindexed on reorder, and two disagreeing
   equal-split implementations. Nobody would choose that; it would simply happen.
   So there is no rule below that names .tpl-weight-row, .tpl-weight-stepper,
   .tpl-weighting-enable-cb or .tpl-weight-strip. Keep it that way.

   NO TYPE CHIP. The mockup draws a "Type" pill on the criterion header. There is
   no such control on a multi-criteria criterion — templates/_section-type-select
   is included under @if($_msHeaderBox), multi-section only — so there is nothing
   to style and nothing to add.

   The rust rule under the criterion name and the unpainted name field already
   exist on this page; they are not something this slice introduces. What changes
   is the two fields below them, which are white boxes on the page and lines in
   the mockup, and the add buttons, which are boxed controls on the page and bare
   "+" affordances in the mockup.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── THE PARTS' UNDERLINE MOVED OUT OF THIS FILE (2026-08-20, second pass) ──
   What stood here was the second of two underline treatments: 1px hairline = optional,
   1px rust = required. THAT DISTINCTION IS RETIRED AND THE RULE IS DELETED, not
   overridden — a rule is RUST WHILE ITS FIELD IS EMPTY AND INK ONCE IT IS COMPLETE,
   for every field on the surface, optional or not. Importance is the asterisk's job;
   the line reports state. --tpl-rule-hairline went with it, having no other consumer.

   Base rule and completed arm now sit together beside .tpl-rust-rule's — see A FILLED
   FIELD'S RULE GOES INK in start.blade.php. The one fact worth carrying forward went
   with them: THE LINE IS DRAWN ON THE .tpl-crit-part, NEVER ON THE <input>. A part is
   12px of --tpl-field-inset either side of its field, so a border on the input stops
   12px short at each end (measured at 1280: 438..805 against the name's 426..817), and
   a textarea matches no input selector at all, which is why the description had no line
   before the rule moved. */

/* The fields themselves are chrome-less inside the part. .tpl-crit-part inputs and
   textareas already are (templates.css) — this arm is for the WORKSHOP slide's
   comparison line, which renders the same input outside a part, in a
   .tpl-inline-edit, and still wants its own line. */
.tpl-start.setup-sidebar .tpl-section-block .tpl-inline-edit > .tpl-section-prompt-input,
.tpl-start.setup-sidebar .tpl-section-block .tpl-issue:not(.tpl-item-input) {
    background-color: transparent;
    border: 0;
    border-bottom: var(--tpl-rule-weight) solid var(--tpl-rule-color);
    border-radius: 0;
    padding-left: 0;
    padding-right: 0;
}
/* Same state, on the input itself because the line is on the input itself here — this
   is the one place the rule is not drawn on a row. Same gate as everywhere else. */
.tpl-start.setup-sidebar .tpl-section-block .tpl-inline-edit > .tpl-section-prompt-input:not(:placeholder-shown):not(:disabled):valid,
.tpl-start.setup-sidebar .tpl-section-block .tpl-issue:not(.tpl-item-input):not(:placeholder-shown):not(:disabled):valid {
    border-bottom-color: var(--tpl-rule-ink);
}
/* (Its `:focus{border-bottom-color:var(--red)}` arm is DELETED with the pair above —
   same treatment, same reason.) */

/* [HARD] THE PARTS AFTER THE NAME PAINT WHITE, and the fields sit ON that paint —
   so removing the field's own box leaves a white slab with a hairline under it.
   The page rule is
     .tpl-start:has(#tpl-add-criterion-btn) .tpl-crit-part--name ~ .tpl-crit-part
   which scores (1,3,0) because the id inside :has() counts. #tpl-start-form plus
   the layout token clears it at (1,4,0) without an !important and without naming
   a session type in the selector.

   background-COLOR, never the `background` shorthand: the ring + pencil cue on
   these fields is a background-IMAGE layer, and the shorthand silently wipes it.
   That warning is stated at the page rule this one overrides; it applies with
   exactly the same force here, which is why every declaration in this slice and
   the one above uses the longhand. */
/* (This rule is DELETED with the white paint it cancelled — see the note at
   `.tpl-crit-part--name ~ .tpl-crit-part` in start.blade.php. The two together were a
   resting colour stated twice to arrive at the default, and their combined specificity was
   what kept DM's description and comparison question from taking the focus fill.) */

/* ── THE CARD ADDERS ARE FILLED BUTTONS ─────────────────────────────────────
   "+ ADD CRITERIA" and "+ ADD SECTION". White on ink, square corners — the platform's
   filled-button treatment, taken from the two already on this page rather than invented:

     SET UP SESSION  (.tpl-continue)               --red fill, --color-white, border:0,
                                                   border-radius:0, Bebas 16px/0.08em,
                                                   padding 0 2.5rem, height 48px
     SIGN IN  (.tpl-facilitator-signin__btn)       --red fill, --on-ink, DM Mono 12px/
                                                   0.06em uppercase, padding 5px 10px

   SCALED TO ITS SIZE MEANS TAKING SIGN IN'S, NOT AVERAGING THEM. This button already
   renders at DM Mono 12px uppercase (.tpl-add-btn) — the same type as SIGN IN, at the
   same size — so SIGN IN's padding is the one that is already in proportion with it and
   there is nothing to scale. SET UP SESSION's 48px/2.5rem belong to a 16px Bebas label
   and would make an in-card adder read as a second primary action. The x-padding is
   doubled to 20px for one reason: SIGN IN is a fixed 130/150px box whose padding never
   decides its width, and this button is shrink-wrapped to its label, so 10px leaves the
   words touching the fill.

   THE FILL IS --ink, NOT --red, and the "+" turns white with the label. The rust "+"
   was legible as a bare affordance on sand; on an ink fill it is red-on-black. The
   glyph keeps its own larger size from .tpl-add-item-plus — only the colour changes.

   ADD SECTION IS IN THIS RULE NOW, and its bare-affordance rule is deleted rather than
   left unreferenced. A section is a numbered card in the same sequence a criterion is, so
   the two adders append the same kind of object at the same level, and the earlier
   decision to exclude Add Section described a page where a section was not yet a card.
   Two ids, ONE declaration — the values are not restated.

   [HARD] `border:0` AND `border-radius:0` ARE DELETED (2026-08-26), AND `display:inline-flex`
   IS DECLARED HERE NOW. Both halves follow from the same edit: these two ids used to sit in
   the bordered ROW-adder rule in templates.css and inherit its whole block, so this rule had
   to cancel that rule's border and radius, and it silently relied on that rule for its
   display. The ids are out of it — the row and card tiers are separate treatments now — so:
     • the two resets cancel nothing. The only border upstream is the base
       `.tpl-add-item{border:none}`, and NOTHING upstream sets a border-radius at all.
       Verified by measurement, not by reading: computed border 0px none and border-radius
       0px on both ids, on blank_ms and blank_dm, at 1280 and 390, unchanged from before.
     • the display is NOT dead and must not go with them. The base `.tpl-add-item` is
       `display:flex`, and a block-level flex button stretches to its container — these are
       shrink-wrapped pills. Dropping to the base turns both into full-width ink bars.
   Keep only what paints the pill, plus the one layout term nothing else supplies. */
.tpl-start.setup-sidebar #tpl-add-criterion-btn,
.tpl-start.setup-sidebar #tpl-add-section-btn {
    background-color: var(--ink);
    color: var(--color-white);
    padding: 5px 20px;
    letter-spacing:var(--type-caption-tracking);
    cursor: pointer;
    display: inline-flex;
}

.tpl-start.setup-sidebar #tpl-add-criterion-btn .tpl-add-item-plus,
.tpl-start.setup-sidebar #tpl-add-criterion-btn .tpl-add-item-label,
.tpl-start.setup-sidebar #tpl-add-section-btn .tpl-add-item-plus,
.tpl-start.setup-sidebar #tpl-add-section-btn .tpl-add-item-label {
    color: var(--color-white);
}
.tpl-start.setup-sidebar #tpl-add-criterion-btn:hover,
.tpl-start.setup-sidebar #tpl-add-section-btn:hover { opacity: .85; }

/* ═══════════════════════════════════════════════════════════════════════════
   AN OFF BLOCK DOES NOT RENDER

   Without this, an optional block that is switched OFF still occupies the content
   column as a title and a hint line with nothing under it — the page's own
   always-present card header, which made sense when the toggle lived ON that
   header and had to be reachable. It does not now: the toggle is in the sidebar,
   so the card is a label for a control that is somewhere else.

   [HARD] THIS IS THE ONE PLACE THE SIDEBAR REACHES ACROSS THE PAGE, and it works
   only because the sidebar is INSIDE the form. `:has()` needs a common ancestor
   holding both the checkbox and the card, and #tpl-start-form is it. Move the
   sidebar out of the form — which INV-1 already forbids for a different and worse
   reason — and these rules silently stop matching, leaving every block visible.

   Still CSS off the real checkbox, not a class set by a handler: correct on first
   paint for a manage page whose welcome is already on, which a `change` listener
   would not be.

   The cards keep their inline style="display:none" for Individual mode, where
   applyModeFields() writes it directly; in Group mode it clears the inline value
   and these rules take over.

   PARTICIPANT DATA joins this pair keyed to the RAIL'S OWN pill
   (data-toggle="participant_data") rather than a header toggle of its own — see
   the note on that card for why: the pill already exists, one column over, and
   means "collect fields" as well as "show this card". */
.tpl-start.setup-sidebar #tpl-start-form > #tpl-welcome-page-card,
.tpl-start.setup-sidebar #tpl-start-form > #tpl-completion-message-card,
.tpl-start.setup-sidebar #tpl-start-form > #tpl-participant-data-card { display: none; }

.tpl-start.setup-sidebar #tpl-start-form:has(#tpl-welcome-intro-toggle:checked) > #tpl-welcome-page-card,
.tpl-start.setup-sidebar #tpl-start-form:has(#tpl-completion-message-toggle:checked) > #tpl-completion-message-card,
.tpl-start.setup-sidebar #tpl-start-form:has(input[data-toggle="participant_data"]:checked) > #tpl-participant-data-card { display: block; }

/* SLIM IS THE THIRD ARM OF THAT SAME REVEAL, AND IT IS UNCONDITIONAL BECAUSE THERE
   IS NO PILL TO READ. An availability poll needs a list of people to invite, so the
   Participant Data card is ON for every slim session — it is not an option the host
   switches. Slim emits no left rail at all (ABSENT, NOT EMPTY, start.blade.php), so
   `input[data-toggle="participant_data"]` does not exist on the page and the `:has()`
   above can never match; the arm states the ON directly rather than minting a hidden
   checkbox for the selector to find. NOT A NEW TOGGLE and not a new setting: the
   card's own boolean is still participant_data_enabled, still written by that one
   rail checkbox on every shape that emits a rail. Create reads none of it — the
   confirm() arms call buildParticipantFields() unconditionally off the posted
   field_*[] arrays — so the absent pill costs the slim create path nothing.
   Extra classes beat the two-class hide above it; source order is not what decides.

   NO `>` COMBINATOR HERE, UNLIKE EVERY RULE ABOVE, AND THAT IS LOAD-BEARING. The slim
   IIFE reparents this card into #tpl-structural-region (see THE FOUR SLIM CARDS in
   start.blade.php for why `order` cannot place it), so after DOMContentLoaded it is no
   longer a direct child of #tpl-start-form and a `>` rule would stop matching — taking
   the reveal with it and leaving the card visible only because the hide above had ALSO
   stopped matching. Reveal by accident is not reveal. Written as a descendant so the
   same rule holds on both sides of the move, which also means the card is on from
   first paint rather than after the relocation.

   :not(.manage-mode) SCOPES IT TO CREATE, and that is a deliberate stop, not an
   oversight. The slim MANAGE twin is an existing poll whose fields are already
   stored, and it emits no rail either — so an unconditional ON there would surface an
   editor the host never had, on a page this change was not asked to touch. It stays
   exactly as it renders today until that is decided on its own. */
.tpl-start.setup-sidebar.slim-mode:not(.manage-mode) #tpl-participant-data-card { display: block; }

/* Advanced Settings is NOT in this pair any more. Its card is deleted and its
   panel lives in the rail, revealed by its own `.shown` class where it stands —
   so there is no content-column block left for a reveal rule to govern. */

/* The block's hint line goes. It said the same thing as the sidebar row that now
   switches the block on, and the fields underneath carry their own "Insert
   Heading" / "Insert Text" placeholders. The TITLE stays — it is what locates the
   block in the content flow once the block only appears sometimes.
   `>` matters: .field-label is also the class on the labels INSIDE the advanced
   rows, which must not disappear. */
.tpl-start.setup-sidebar #tpl-welcome-page-card > .field-label,
.tpl-start.setup-sidebar #tpl-completion-message-card > .field-label { display: none; }

/* ═══════════════════════════════════════════════════════════════════════════
   CRITERIA WEIGHTING — THE MASK, RE-KEYED

   The per-criterion "Customise Weighting" checkbox is gone from the criterion
   card; the control is the sidebar row, which holds the canonical
   #tpl-weighting-toggle (name=weighting_enabled) itself.

   [HARD] THAT CHECKBOX WAS THE KEY TO THE STEPPER MASK, and the mask is what
   keeps D-DEBT-12 out of sight. Every rule that ghosts, unlocks or hides the
   stepper is written as

     .tpl-weight-row:has(.tpl-weighting-enable-cb:checked) …

   — and one of them, the `display:none`, lives inside a max-width media query, so
   on DESKTOP the stepper is not hidden at all but ghosted with its click and
   input handlers early-returning. Take the checkbox out of the card and every one
   of those selectors stops matching at once: the steppers unghost and go live,
   and the page walks into redistributeWeights() clobbering a saved 50/30/20 to
   33/33/34 on every load, criterion_weights[i] not reindexed on reorder, and two
   disagreeing equal-split implementations. Nobody would choose that; it would
   simply happen.

   So the mask is re-keyed to what the sidebar row actually drives. One rule, not
   five: instead of reproducing the ghost palette against a new key, the whole
   weighting row is hidden while weighting is off. Off means no weighting UI in
   the card at all — which is also the honest reading of a sidebar that says the
   feature is off — and on means the row appears with its stepper live, exactly as
   ticking the old checkbox did.

   [HARD] CORRECTED: "the original rules are left alone, they still match" was
   HALF RIGHT AND SHIPPED A DEAD PALETTE. The per-criterion checkboxes are indeed
   still in the DOM and still synced, so the :has() subject resolves — but
   syncWeightPanel() MOVES the stepper out of .tpl-weight-row, and a descendant
   selector needs the styled element to still be inside the subject. It is not, so
   the four ghost/ink rules matched nothing and the stepper rendered in its OFF
   palette even while weighting was on. They are re-scoped to
   #tpl-sidebar-weight-rows and re-keyed to #tpl-weighting-toggle in
   start.blade.php; the panel's own display gate means a visible stepper is always
   a live one, so only the inked half is reachable.

   `display:none` DOES NOT STOP criterion_weights[i] POSTING — hidden inputs are
   still submitted — so the save path is untouched, which the page's own mobile
   mask already relies on.

   D-DEBT-12 IS NOT FIXED HERE and must not be. Turning weighting on exposes it
   exactly as turning the old checkbox on does today; this changes where the
   switch is, not what it exposes. */
/* The criterion card holds only content now — name, description, participant
   question, end questions. Its weighting row goes entirely: the checkbox is
   replaced by the sidebar row, and the stepper has been MOVED into the weighting
   panel, so what is left here is an empty band. */
.tpl-start.setup-sidebar .tpl-weight-row { display: none; }
/* ── AND SO THE CONTROLS WRAPPER STOPS STANDING OFF ─────────────────────────
   .tpl-crit-ctrls carries margin-top:--stack-gap so the controls group clears the
   criterion's fields, and the end-of-section row inside it carries the same token so it
   clears whatever precedes it. With the weighting row hidden by the rule above, the row
   IS the group — and the two gaps became one gap counted twice: measured 48px against
   the rhythm's 24. The row keeps it, because the row is the object; the wrapper gives it
   up, because it no longer has content of its own to stand off from. */
.tpl-start.setup-sidebar .tpl-crit-ctrls { margin-top: 0; }

/* The panel is the steppers' new home and rides the same gate they did. The Total
   Weighting strip is inside it, so the one condition covers both — it is no longer
   named separately, because it is no longer a separate consumer. */
.tpl-start.setup-sidebar #tpl-sidebar-weight-panel { display: none; }
/* margin-top was 10 and ADDED to the settings slot's row step: the junction
   rendered 26 against a --step-group of 24. The slot states the distance. */
.tpl-start.setup-sidebar #tpl-start-form:has(#tpl-weighting-toggle:checked) #tpl-sidebar-weight-panel {
    display: flex;
    flex-direction: column;
    row-gap: var(--step-row);
}
/* [HARD] THE PANEL SCROLLS, THE SIDEBAR DOES NOT GROW. Eight criteria is enough to
   push a fixed sidebar past the viewport, and once that happens the sticky element
   is taller than the screen and its lower rows become unreachable. Bounding the
   ROW LIST rather than the panel keeps the Total Weighting foot pinned below the
   scroll, where it stays readable while the list moves. */
/* ── THE RAIL RUNS THE THREE STEPS ────────────────────────────────────────────
   Stated at THIS file's specificity on purpose. The container layer in
   start.blade.php declares `.tpl-start #tpl-sidebar-weight-panel` at (1,1,0) and
   every rule in this file that could contradict it is (1,3,0), so the step was
   declared and never applied — the rail kept its old values while the tokens read
   correct. Specificity, not the token, was the thing that had to move. */
.tpl-start.setup-sidebar .tpl-sidebar .tpl-sidebar-inner{
    display:flex; flex-direction:column; row-gap:0;
}
/* ── ONE DECLARATION PUTS THE HAIRLINE BACK IN THE MIDDLE OF ITS GAP ─────────
   The rail's own rhythm, stated 500 lines up and never achieved: "8px above and
   below each row, 16px between adjacent rows, ONE HAIRLINE CENTRED IN THE GAP".
   It was not centred. Measured over both rails, all four mode/auth states:

       above every rule  24 - 32px        below every rule  9px
       card top to first ink  8px         last ink to card bottom  8px

   The rule is a border-top on the LOWER band, so the row-gap between the boxes
   lands ENTIRELY ABOVE the line while only the lower band's 8px padding lands
   below it. The bands' own 8/8 padding is the whole of the intended gap; the
   row-gap was a second, independent answer to the same question, exactly the
   shape the --tpl-gap note in templates.css warns about.

   Zeroed at BOTH nesting levels in one place, because in the rail every band is a
   peer on one grid — "nine rows across two rails sit on one grid" — and not a
   nested rhythm. Every rule then reads 8 above and 9 below (the 9 is 8 plus the
   line's own 1px), and the card's own top and bottom insets are 8 as well, so the
   whole column is on one pitch.

   [HARD] SCOPED TO THE RAIL, AND IT HAS TO BE. The --step-row half is declared in
   start.blade.php on a SHARED list that also carries .tpl-crit-pair and
   #tpl-segment-selected — centre-column subjects whose
   nested rhythm is correct and must not move. Overriding here, at (0,5,0) against
   that list's (0,3,0), changes the rail and nothing else. Do not chase this back
   into the shared rule. */
.tpl-start.setup-sidebar .tpl-sidebar .tpl-sidebar-inner,
/* The slot level too. The shared list in start.blade.php that sets --step-row here
   also carries .tpl-crit-pair and the segment lists — centre-column subjects whose
   nested rhythm is correct — so it is overridden here rather than changed there. */
.tpl-start.setup-sidebar .tpl-sidebar .tpl-sidebar-inner > [id^="tpl-sidebar-slot-"] {
    row-gap: 0;
}
/* (.tpl-segment-options is DELETED from this list — nothing emits that container since
   the option editor became chips in a wrap. .tpl-segment-option, SINGULAR, is a
   different and still-live class: the notification and reminder radio groups wear it.
   #tpl-segment-fields / #tpl-segment-selected / .tpl-segment-card are ALSO DELETED
   from this list, 2026-08-31 — the field editor moved out of the rail into
   #tpl-participant-data-card, so this rail-scoped override no longer reaches it; the
   centre-column rule that already gave it this same row-gap, in start.blade.php's own
   <style>, is unconditional and keeps applying without help from here.) */
.tpl-start.setup-sidebar .tpl-sidebar #tpl-sidebar-weight-rows{
    display:flex; flex-direction:column; row-gap:var(--step-row);
}
/* #tpl-sidebar-weight-panel is DELETED from the list above, 2026-08-28 — it is not
   this rule's target either, and restating `display:flex` here unconditionally was
   an active bug for this one subject, not a no-op. The panel's display is OWNED by
   the show/hide gate a few lines up (display:none, then :has(#tpl-weighting-toggle
   :checked){display:flex}), and this rule's job is only to fix its row-gap the same
   way it fixes the others'. But `display:flex` at this rule's (1,3,0) OUTRANKS the
   hide rule's (1,2,0), so the panel rendered open with Customise Weighting OFF on
   every load — the exact D-DEBT-12-adjacent regression the hide rule exists to
   prevent, verified live (CUSTOMISE WEIGHTING: OFF, A/B/C steppers still visible).
   The row-gap this rule wanted moved onto the reveal rule instead, which already
   carries far higher specificity and only ever matches while the panel is the one
   thing needing it: visible. */
.tpl-start.setup-sidebar .tpl-sidebar #tpl-workshop-toggle-row,
.tpl-start.setup-sidebar .tpl-sidebar .tpl-modes-row{ row-gap:var(--step-tight); }

/* THE STEP IS THE ENTIRE DISTANCE — the two panels that only render on manage.
   The weighting foot and the segment panel's children carried 9-16px of their own
   vertical padding at junctions the row step now governs, so those junctions
   rendered 25-32 against a --step-group of 24 and inverted the ladder on this card.
   Horizontal padding is untouched: it is the rail's content inset, not a step. */
/* MARGINS ONLY. A margin between two siblings is the junction and the step states
   it; a padding inside one of them is that box's own interior and is not the
   junction's to give. The padding half of this rule is deleted.
   #tpl-sidebar-segment-panel / #tpl-segment-selected > * / .tpl-segment-custom-wrap
   are DELETED from this pair, 2026-08-31, with the rail panel they governed — the
   field editor is centre-column content now (#tpl-participant-data-card), which
   does not run at the rail's own margin-zeroed rhythm. */
.tpl-start.setup-sidebar .tpl-sidebar #tpl-sidebar-weight-foot{
    margin-top:0; margin-bottom:0;
}
/* The weighting foot's own children, same rule. #tpl-sidebar-segment-panel > * and
   #tpl-segment-fields > * are DELETED from this pair for the same reason as above. */
.tpl-start.setup-sidebar .tpl-sidebar #tpl-sidebar-weight-foot > *{
    margin-top:0; margin-bottom:0;
}

.tpl-start.setup-sidebar #tpl-sidebar-weight-rows {
    max-height: 46vh;
    overflow-y: auto;
}
/* The gap steps down one on the --space-* scale: --space-sm (8px, which is what the
   literal here was) to --space-xs. It is the LETTER-to-NAME distance that wanted
   tightening; the row's single `gap` serves both pairs, so the name-to-stepper pair
   tightens with it — and nothing moves but the name. The letter is the row's first
   child and stays on the badge column, so the panel still lines up with the rows above
   and below; the stepper is flex:0 0 auto against a flex:1 1 auto name, so the name
   absorbs the 8px the two gaps gave back and the stepper stays pinned right. */
.tpl-start.setup-sidebar .tpl-wp-row {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    padding: var(--gap-tight) 0;
}
.tpl-start.setup-sidebar .tpl-wp-row + .tpl-wp-row {
    border-top: 1px solid rgba(var(--ink-rgb), 0.09);
}
.tpl-start.setup-sidebar .tpl-wp-mark {
    font-family: var(--font-heading);
    color: var(--red);
    flex: 0 0 auto;
    min-width: 14px;
}
/* The name takes the slack and truncates rather than wrapping — a long criterion
   name must not make its row two lines tall and desynchronise the list visually
   from the cards it mirrors. */
.tpl-start.setup-sidebar .tpl-wp-name {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-family: var(--font-mono);
    /* One step up the DM Mono scale, 9px -> 12px, and --tpl-field-label is the right
       12px of the three: it is the facilitator config tier whose own note names the
       WEIGHT STEPPER as a consumer, and the stepper sits in this very row at exactly
       that size. The two halves of a row now read at one size.
       NOT --tpl-rail-label, which is also 12px: that token names the CAPTION OVER a
       toggle, has exactly one consumer (.tpl-workshop-mode-label) and is documented as
       keeping both rails moving together by construction — a second consumer would take
       that property away. NOT --tpl-toggle-label (9px), which is the pill-slider knob
       geometry and is why this label was undersized in the first place. */
    font-size: var(--tpl-field-label);
    color: var(--ink);
}
.tpl-start.setup-sidebar .tpl-wp-row .tpl-weight-stepper { flex: 0 0 auto; }
/* Inside the panel the strip is the foot, not a card on the page. `border-width:1px`
   went with the slab — the wrapper draws no border at all now, and the total's own
   ink hairline is on the row inside it. */
/* margin-top was 10 and ADDED to the weighting panel's row step, so the junction
   above the Total strip rendered 32 against a --step-group of 24. The panel states
   the distance. */
.tpl-start.setup-sidebar #tpl-sidebar-weight-foot .tpl-weight-strip-card {
    margin: 0;
}

/* ── THE TOTAL READS AT THE RAIL'S OWN LABEL SIZE ───────────────────────────
   Both halves come off --tpl-input-size (16px) from the shared rule in
   start.blade.php, which is the CARD-NAME tier — right for a card heading in the
   840px column, oversized for a foot row in a 208px rail. --tpl-rail-label is the
   size every rail row label already uses (12px, via .tpl-workshop-mode-label), so
   the total now sits on the same step as the rows above it.

   SCOPED TO THE RAIL, NOT EDITED AT SOURCE, because that shared rule also styles
   .tpl-end-toggle-label — the end-of-section toggle on the setup card, which is not
   on the rail and must keep the card-name tier. Only the size changes: the family
   (Bebas), tracking, case and colour stay as declared.

   [HARD] THE min-height IS DELETED, AND THAT IS WHAT FIXES THE BADGE SITTING LOW.
   It floored the figure at --tpl-badge-size (18px) so an 18px disc could not be
   clipped by a 13.2px line box — but a floor taller than the text is exactly what
   dropped the badge: the line box sat at the TOP of an 18px box while the
   background centred over the whole of it, so the mark rendered ~2.4px below the
   text's own centre. With the disc at the component's glyph scale (12px, see
   start.blade.php) it fits the 13.2px line box with room to spare, so the box IS
   the line box again and `background-position: center` centres on the text by
   construction — the row's own align-items:center doing the work, with nothing
   nudged. Removing the override was the fix; no counter-rule was added. */
/* THE RAIL LABEL TIER — four labels wearing one size. A type tier with many wearers is a
   legitimate manifest and is NOT split further: these four are the same KIND of thing and
   the whole point of the tier is that they move together. */
/* (.tpl-segment-tag has LEFT this tier, 2026-08-30. It is not a label any more: it is a
   two-up palette button whose whole layout depends on 16px Bebas, set on its own rule in
   start.blade.php. Leaving the entry here made that rule read as a shadow of a live
   decision rather than as the decision itself.) */
.tpl-start.setup-sidebar .tpl-weight-strip-label,
.tpl-start.setup-sidebar .tpl-weight-strip-status,
.tpl-start.setup-sidebar .tpl-sidebar .tpl-adv-label {
    font-size: var(--tpl-rail-label);
}
/* (`.tpl-start.setup-sidebar .tpl-segment-custom-btn{font-size:var(--tpl-rail-label)}`
   IS DELETED, 2026-08-31. It shrunk + CUSTOM FIELD to the rail's 12px label tier because
   it sat in a 208px column; the field editor is centre-column content now
   (#tpl-participant-data-card), so the button falls back to the base rule it was always
   shadowing — start.blade.php's `.tpl-start .tpl-segment-custom-btn{font-size:
   var(--tpl-input-size,16px)}` — which is exactly the size the field chips beside it
   already read, restoring the one-source pairing the base rule's own comment describes. */

.tpl-start.setup-sidebar #tpl-start-form:not(:has(#tpl-weighting-toggle:checked)) .tpl-weight-row,
/* The Total Weighting strip rides the SAME gate, not a second one. With weighting
   off there is nothing to total: the split is equal by definition, every stepper
   is hidden, and the strip reports 100% for ever. One selector, both consumers —
   so they cannot drift into disagreeing about whether weighting is on. */
.tpl-start.setup-sidebar #tpl-start-form:not(:has(#tpl-weighting-toggle:checked)) .tpl-weight-strip-card {
    display: none;
}

/* ═══════════════════════════════════════════════════════════════════════════
   THE .tpl-adv-* CONTROLS, IN A RAIL

   (This section described the Advanced Settings PANEL, which is deleted along
   with its container and its master toggle, and the disclosure they formed. What
   the rules below still do is dress the .tpl-adv-* controls that outlived it —
   Closing Date and Response Notifications, now in the RIGHT rail — in the rail's
   own row vocabulary instead of the content column's card chrome they were
   authored with. No new class, no new colour, no new spacing value: the padding
   is .tpl-sidebar-row's, the hairline is the one every rail row draws.) */

/* Rows take the rail's row padding, and its hairline on every row including the
   first — a line there continues the rhythm of the rows above rather than
   starting a new one, and it survives rows being hidden per shape, which a
   `+ row` chain would not. (Inside the right rail's project slot this border is
   zeroed: the block-level chain there already separates the rows. One owner per
   hairline.) */
/* [HARD] `padding: 0`, NOT DELETED. An adv-row sits INSIDE a band and contributes
   no vertical space of its own — but this declaration is also the only thing
   zeroing the 22px HORIZONTAL gutter templates.css gives .tpl-adv-row, which in a
   248px rail is a quarter of the width. Deleting the rule outright indents both
   project rows by 22px and wraps their labels; that was measured, not assumed. */
.tpl-start.setup-sidebar .tpl-sidebar .tpl-adv-row { padding: 0; }

/* Sub-panels lose the card's 22px gutters — in a 248px rail they are a quarter
   of the width. The bottom space is the row padding, so a sub reads as part of
   the row that opened it. */
/* This panel holds .tpl-adv-sub sub-panels — the advanced rows' own. (The segment
   picker's `#tpl-sidebar-segment-panel .tpl-adv-sub` arm is DELETED, 2026-08-31: the
   field editor moved out of the rail into #tpl-participant-data-card and its wrapping
   `.tpl-adv-sub` div — the [data-toggle]/[data-sub] reveal it existed for — went with
   it. One override, one consumer now.) */
/* :not([role="radiogroup"]) — the value lists are the ONE sub that must keep an
   indent. This rule zeroes the panel-level subs' gutters because they are the panel's
   own content; a value list is nested one level further, under its value row, and
   takes .tpl-adv-sub's own 22px so its options sit under the row's label rather than
   at the panel edge. Excluding it here is how it keeps that, without restating 22. */
.tpl-start.setup-sidebar .tpl-sidebar .tpl-adv-sub:not([role="radiogroup"]) {
    padding: var(--inset-control) 0;
}
/* (The `padding-left:0` that stood here is DELETED. .tpl-adv-sub-row's own 22px is
   the indent that sets a sub-control under its parent toggle row, and now that these
   rows carry labels that indent is what distinguishes them from the rail rows above.
   It was zeroed when the panel arrived and the rows were unlabelled right-aligned
   controls with nothing to indent.) */

/* THE PILL TRACKS LINE UP BECAUSE SIX EMPTY <span>s WENT, NOT BECAUSE OF A RULE
   HERE. Every .tpl-pill-toggle in this panel carried a childless <span></span>
   after its input. Nothing styles it — .tpl-pill-toggle is inline-flex with
   gap:8px, so an empty span is a zero-width flex item that still buys its gap,
   which held the 72px track 8px short of the label's right edge. In the content
   column that was invisible; in the rail it put every panel toggle 8px left of
   the three rail-row toggles directly above them. The spans are deleted at
   source rather than compensated for with a negative margin here.

   (The `width:100%` that stretched the closing-date picker and the reminder dropdown
   to the rail is DELETED with the boxes it was fitting. They are values now, sized by
   their content and pushed right by the label's flex — nothing to stretch.) */

/* ── THE SUB-CONTROLS ARE VALUE ROWS ────────────────────────────────────────
   One treatment for all three: the label takes the rail's micro tier and the slack,
   the value sits right with the shared caret beside it, and nothing draws a box.
   Every declaration below either strips something or is read off a class that
   already exists — the caret is .tpl-switcher-trigger-chev untouched, the wrapper
   is .tpl-row-controls untouched, the label tier is the one the chips and TOTAL
   read (see the font-size list further down). */
.tpl-start.setup-sidebar .tpl-sidebar .tpl-adv-label {
    flex: 1 1 auto;
    min-width: 0;
}
/* De-box what is left: the date. `.tpl-adv-select` and `.tpl-switcher-trigger` are
   OUT of both rules — the native select and the popover trigger they named do not
   exist any more, and the two dropdowns are radio disclosures whose trigger draws no
   box to begin with. .tpl-switcher-trigger-label stays below: it is still the value
   text, reused for its ellipsis behaviour. */
.tpl-start.setup-sidebar .tpl-sidebar input[type="datetime-local"] {
    appearance: none;
    -webkit-appearance: none;
    background: none;
    border: 0;
    padding: 0;
    color: var(--ink);
    cursor: pointer;
}
/* The value reads at the same tier as the label it answers to. */
.tpl-start.setup-sidebar .tpl-sidebar input[type="datetime-local"],
.tpl-start.setup-sidebar .tpl-sidebar .tpl-switcher-trigger-label,
.tpl-start.setup-sidebar .tpl-sidebar .tpl-segment-option > span {
    font-family: var(--font-heading);
    font-size: var(--tpl-rail-label);
    letter-spacing: var(--tpl-cardname-tracking, 0.02em);
    text-transform: var(--tpl-cardname-case, uppercase);
}
/* [HARD] AND ON A PHONE IT SITS ON THE iOS FOCUS-ZOOM FLOOR, NOT ON THE RAIL TIER
   (RR-109, fixed 2026-09-19). The rule directly above is (0,4,1) and tokens.css's
   floor — `input:not([type=checkbox]):not([type=radio]):not([type=range])` inside
   `@media(max-width:640px)` — is (0,3,1), so the rail tier BEAT the floor and this
   field rendered 12px on a phone: focusing the closing date zoomed the whole
   viewport. `php artisan setup:field-focus` had been reporting it as three FAILs (MS,
   survey, quiz) and naming it `input.tpl-mono`, which is this element — it carries
   class="tpl-mono" and that is the first class the probe prints, which is how the
   cause came to be written down as a `.tpl-mono` problem. It is not: `.tpl-mono` is
   (0,1,0) and loses to the floor. The rule that wins is the one above.

   RAISING THAT RULE IS WHAT THE PROBE'S OWN FAILURE MESSAGE ASKS FOR — "Something
   out-specifies the floor in tokens.css. Raise THAT rule to the floor" — and it is
   done here, in the file that owns it, at the same specificity it already has, with
   the floor's own value EXPRESSION rather than a copy of its number, so a change to
   --tpl-field-min reaches both. Desktop is untouched: above 640 the rail tier still
   applies, which is the whole point of putting the arm in a media query rather than
   changing the declaration. */
@media (max-width: 640px) {
    .tpl-start.setup-sidebar .tpl-sidebar input[type="datetime-local"] {
        font-size: max(16px, var(--tpl-field-min, 0px));
    }
}

/* [HARD] THE EMPTY DATE CANNOT CARRY CHOSEN WORDING. The HTML spec has no
   placeholder for date/time inputs — this file says so twice already, at the FIELD
   STATES exclusion and at the .is-filled sync in start.blade.php. The browser draws
   its own dd/mm/yyyy --:--, so the only honest "placeholder" is to colour THAT with
   the surface's placeholder token. .is-filled is the real value test (set in JS,
   the only place that class is written), so it is what switches the colour. */
.tpl-start.setup-sidebar .tpl-sidebar input[type="datetime-local"]:not(.is-filled)::-webkit-datetime-edit {
    color: var(--tpl-ph-color);
}
/* The UA calendar button is replaced by the shared caret, so it draws nothing — but
   it stays in the box as the native activation target for anyone not clicking the
   field itself. */
.tpl-start.setup-sidebar .tpl-sidebar input[type="datetime-local"]::-webkit-calendar-picker-indicator {
    opacity: 0;
    width: 0;
    padding: 0;
    margin: 0;
}

/* ── THE OVERVIEW HEADING COMES BACK ────────────────────────────────────────
   It was hidden here because "the card holds two labelled fields; the heading names
   the card and adds nothing the fields do not already say". True while it was only
   words — and it is the first card in a numbered sequence now, so its heading row is
   where its numeral lives. A card numbered 1 with nothing beside the 1 is worse than
   the repetition this rule was avoiding. The declaration is DELETED rather than
   overridden; there is nothing left that wants the heading gone. */

/* ── EVERY NUMBERED CARD HEADING IS A BADGE AND A HEADING, LEFT ─────────────
   The page had two heading alignments — "Options To Compare" and "Criteria" centred
   over the content column, everything else hard left — and that split is what the
   badges make untenable: a numeral centred with its words floats in the middle of a
   card whose content starts at the left edge, and the eye has nothing to run down.
   So the centring goes and ONE rule places every numbered heading.

   [HARD] THIS ALSO OVERRIDES .tpl-ws-card-top, WHICH IS A GRID, NOT A FLEX ROW.
   ONE host carries it now — the Overview card's head (#tpl-session-setup-card-head) —
   and its 1fr|auto|1fr grid is what centres the heading against a right-hand control. A
   badge prepended into that grid would take column 1 and push the heading into
   column 2's centre, leaving the badge stranded at the card's far left. `display:flex`
   here collapses all three to a row; the right-hand control keeps its edge from
   .tpl-block-header-toggles' own margin-left:auto, which is how it was pinned before
   the grid existed.

   #mgmt-respond-header used to be the SECOND host (Response Management was a numbered
   card) but dropped [data-card-badge-host] on 2026-08-29 — it and Results & Analysis
   are the two trailing manage cards that take no number now. It still needs this same
   flex collapse, but pays for it with its own unconditional id-scoped rule
   (`#mgmt-respond-header{display:flex;align-items:baseline;gap:var(--col-gap)}` in
   start.blade.php, V3 2026-08-24) rather than this attribute selector, so removing the
   marker cost it nothing.

   --col-gap between badge and words: the same gap the ordinal column already keeps
   from the field beside it on every row of every card, so a card heading and a row
   below it read on one rhythm. */
.tpl-start.setup-sidebar [data-card-badge-host] {
    display: flex;
    justify-content: flex-start;
    gap: var(--col-gap);
    text-align: left;
}
/* ── [HARD] align-items IS SPLIT OFF, AND IT IS NOT A CARVE-OUT (2026-08-21) ──────
   `align-items:center` used to sit in the rule above, and it was the container the MS
   section name could not escape. `[data-card-badge-host]` means "this row names a card
   in the top-level sequence", and for every host but one that row is a HEADING LINE:
   a badge and some words, one line tall. Centring a one-line row is the same statement
   as aligning it to its text, so the property read as obviously right.

   The MS section band is the ONE host whose content is not a line but a COLUMN — a
   name row with a type row under it. Centred, the badge, the drag handle and the ×/▾
   cluster landed on the vertical middle of that column, which is the GAP between the
   two rows: a cluster floating between the name and the type chip, and a header that
   read as two lines where DM's criterion header reads as one. Measured at 1280: the
   cluster's centre at 547.89 against a name whose text sat 514.39..548.39.

   Censused, not assumed: on all five shapes (flat, DM, MS, survey, quiz) every other
   [data-card-badge-host] is a .tpl-block-header-row 27px or 0px tall, and the band is
   the only one that is ALSO a .tpl-edit-row--header. So this :not() changes exactly one
   element on the whole platform, and what it changes it to is not new — the band falls
   through to `.tpl-edit-row--header{align-items:baseline}` in templates.css, the same
   declaration DM's criterion header has always read and the reason DM's handle, letter
   and cluster sit on the criterion NAME with a field four parts tall beneath them.

   [HARD] SPLIT RATHER THAN NARROWED WHOLESALE. display / justify-content / gap /
   text-align are right for EVERY host including the band — the band needs the row and
   the --col-gap as much as a heading does — so only the one property that means
   something different for a column moves. Narrowing the whole rule would have taken the
   band's gap with it.

   If a second multi-row badge host ever appears, this predicate is the thing to widen —
   do not give it an align-items of its own. */
.tpl-start.setup-sidebar [data-card-badge-host]:not(.tpl-edit-row--header) {
    align-items: center;
}

/* ═══════════════════════════════════════════════════════════════════════════
   THE HEADER SLOT AND THE DISPLAY-SIZE TITLE ARE DELETED
   ═══════════════════════════════════════════════════════════════════════════

   What stood here: #tpl-sidebar-slot-title (order:-1, centred, 18px bottom margin),
   a 40px Bebas type block for the title input AND its drawn placeholder, a focus /
   invalid underline pair, and two rules that took the Overview card's box away —
   transparent for a signed-in owner (`:not(:has(.tpl-facilitator-cols))`) and
   display:none in Individual mode — both justified by "with the title promoted, the
   card has nothing left in it".

   ALL OF IT WENT TOGETHER, because it was one decision: the title was the page's h1.
   It is a FIELD again — the first of the Session Title / Group / Team matched pair,
   inside the Overview card, at the same tier and width as its partner — so the card
   always has content, on every shape and in both modes, and it paints the same box
   as every other card on the page. Keeping either card rule would now hide or
   un-paint a card holding the page's first required field.

   [HARD] HALF OF THIS RULE IS REVERSED, 2026-08-26 — THE HEADING IS BACK, THE KICKER
   IS NOT. What stood here hid `.tpl-setup-identity` entirely and gave two reasons:
   "it was hidden because the promoted title replaced it, and the kicker half of that
   reason still holds: the kicker rendered $switcherLabel, which is the exact string the
   template picker in the right rail already shows, so restoring it would put the same
   words on screen twice."

   THE FIRST REASON HAS NO SUBJECT LEFT. The promoted title was deleted with the
   page-header slot (see the note in start.blade.php where that slot was), so nothing has
   replaced this heading since; the block has simply rendered nothing on every non-slim
   setup page, which is why the page has had no h1 at all. Un-hidden on instruction.

   THE SECOND REASON WAS ANSWERED, NOT OUTLIVED, and this paragraph is corrected the same
   day it was written. It read: the kicker rendered $switcherLabel, which the rail's picker
   also shows, so it was emitted only where it was hidden — dead markup, deleted with this
   rule. The duplication was real, and its cause was that a template had more than one name
   (D-DEBT-4). That is fixed at the root: assessments.title is the only name, the kicker is
   back in start.blade.php reading it, and there is nothing here to hide. Full reasoning at
   the markup; this note exists so a reader arriving from the CSS side is not left holding a
   prohibition that has been lifted.
   Nothing replaces this rule. `.tpl-setup-identity` renders on every setup shape now, and
   the h1 inside it is the point of the block. */

/* ── STACKED ─────────────────────────────────────────────────────────────────
   Below 900px the pair cannot hold its measure, so it becomes one column with the
   sidebar on top. Fixed positioning is dropped deliberately (sticky, before
   2026-08-28): a pinned element in a single-column stack sits over the field the
   operator is editing. */
@media (max-width: 900px) {
    /* The pair is gone, so the gutter-equals-gap identity has nothing to say here.
       Hand the page back its scaling margin — this is what the outer padding was
       at every width before the rails were equalised, so nothing below 900px
       moves by a single pixel. */
    .tpl-start.setup-sidebar {
        padding-left: var(--tpl-page-margin);
        padding-right: var(--tpl-page-margin);
    }
    /* .tpl-start-intro is released with the form: below 900 there are no rails to
       reserve, so a block still paying for them would sit inset against a stacked page. */
    .tpl-start.setup-sidebar #tpl-start-form,
    .tpl-start.setup-sidebar .tpl-start-intro {
        grid-template-columns: minmax(0, 1fr);
        /* Give both reserved strips back — the rails rejoin the flow below. */
        padding-left: 0;
        padding-right: 0;
    }
    /* meta -> content -> options: the decisions you make before authoring, then
       the authoring, then the switches. Same reading order the page has without
       the flag. Content children carry orders 1-9. */
    .tpl-start.setup-sidebar #tpl-setup-meta    { order: -1; }
    .tpl-start.setup-sidebar #tpl-setup-sidebar { order: 99; }
    /* Out of absolute positioning and back into normal flow, ABOVE the content:
       it is the first child in source order, so it lands first with no ordering
       rule of its own. Fixed positioning is dropped deliberately — in a
       single-column stack it would pin the settings over the field being
       edited. */
    /* [HARD] `margin-bottom: 18px` IS DELETED (2026-08-27) AND MUST NOT COME BACK.
       Back in the flow these two are GRID ITEMS of #tpl-start-form, which already
       states card-to-card as row-gap: var(--tpl-gap). The margin was a second,
       independent answer to the same question, and it did not replace the gap — it
       ADDED to it. Measured at 390 on all four shapes, both modes, before this: the
       Overview card sat 44px below the right rail's card (26 + 18) while every other
       card-to-card distance on the same page was 26. Same shape of bug as the four
       the --tpl-gap note in templates.css lists: a container gap plus a per-element
       margin that knew nothing about it.
       NOTHING REPLACES IT. The row-gap is the whole distance, and it is the same one
       the centre column uses, which is the point. */
    .tpl-start.setup-sidebar #tpl-setup-sidebar,
    .tpl-start.setup-sidebar #tpl-setup-meta {
        position: static;
        width: auto;
    }
    .tpl-start.setup-sidebar .tpl-sidebar-inner {
        position: static;
        /* width:auto ALONE DOES NOT WIN HERE — see the id-scoped <=640 fix below this
           block. This bare rule is (0,3,0); the desktop width:var(--tpl-rail-left/
           right) it would need to beat is id-scoped at (1,3,0) and outranks it at
           every width <=900 regardless of source order, so `width:auto` on THIS
           selector is inert below 900px and is kept only for `position:static`,
           which nothing else contests. */
        width: auto;
    }
    /* (The three ≤900 arms for the right rail's deleted two-card split — the wrapper
       dropping its grid, the upper card going height:auto and the lower card taking an
       18px margin — are DELETED with it. One card needs none of them: it is already
       block, already its own height, and has no sibling to be spaced from.
       The paragraph that stood above this one described that arrangement and ended by
       sourcing its gap from "the same 18px the stacked rails already use between
       themselves two rules above". Both halves of that sentence are now false — the two
       cards are one, and the rails' 18px is deleted two rules above — so it is gone
       rather than corrected: it described nothing that exists.) */
}
/* ── THE RAIL CARD'S WIDTH FIX IS <=640 ONLY, NOT <=900 (2026-08-31) ────────────────
   The bare `.tpl-sidebar-inner{width:auto}` two rules up is (0,3,0); the desktop
   `#tpl-setup-sidebar .tpl-sidebar-inner` / `#tpl-setup-meta .tpl-sidebar-inner` width
   rules it needs to beat are id-scoped at (1,3,0), and a media query adds no
   specificity of its own — so it has been losing at every stacked width, 641-900
   included, for as long as both rules have coexisted: the rail's outer shell stretches
   to the card's width correctly (grid-item stretch, unaffected), but the painted
   .tpl-sidebar-inner card inside it stays a fixed 248px. Measured before this fix at
   390: shell (#tpl-setup-sidebar) l=8 w=374, .tpl-sidebar-inner l=8 w=248.

   [HARD] SCOPED TO <=640, DELIBERATELY NARROWER THAN THE STACKED LAYOUT'S OWN <=900 —
   this session's brief is mobile only; the 641-900 band is already a live, working
   (if narrower-railed) rendering of the stacked layout and is not this session's to
   move. Fixing the specificity trap inside the shared <=900 block would fix it
   there too, which is exactly the "any desktop movement is a defect" this scope
   forbids. If the 641-900 rail width is ever revisited, that is a different, separate
   change — this block's ceiling stays 640 on purpose, not because 900 wasn't tried. */
@media(max-width:640px){
    .tpl-start.setup-sidebar #tpl-setup-sidebar .tpl-sidebar-inner,
    .tpl-start.setup-sidebar #tpl-setup-meta .tpl-sidebar-inner{width:100%;}
}

/* ══ THE HELP DRAWER (2026-09-24) ════════════════════════════════════════════════
   templates/_help-drawer.blade.php + public/js/help-drawer.js, opened by the HELP button
   under the Template dropdown (_template-switcher), which wears CREATE ACCOUNT's classes
   and no rule here: #tpl-switcher is a flex column at every width, so it stretches to the
   dropdown's width. Everything is an existing token or class except the two below, which
   are the drawer's own and are declared on it so they do not inherit across the page. */
.tpl-start.setup-sidebar .tpl-help-drawer {
    --tpl-help-drawer-w: 380px;
    --tpl-help-highlight: rgba(var(--red-rgb), 0.14);
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    width: min(var(--tpl-help-drawer-w), 92vw);
    z-index: var(--z-modal);
    display: flex;
    flex-direction: column;
    background: var(--surface-raised);
    box-shadow: -8px 0 0 rgba(var(--ink-rgb), 0.08);
    transform: translateX(100%);
    visibility: hidden;
    transition: transform .25s ease, visibility 0s linear .25s;
}
.tpl-start.setup-sidebar .tpl-help-drawer.is-open {
    transform: none;
    visibility: visible;
    transition: transform .25s ease;
}
@media (prefers-reduced-motion: reduce) {
    .tpl-start.setup-sidebar .tpl-help-drawer,
    .tpl-start.setup-sidebar .tpl-help-drawer.is-open { transition: none; }
}
.tpl-start.setup-sidebar .tpl-help-body {
    overflow-y: auto;
    padding: var(--inset-panel) var(--space-lg) var(--space-xl);
    font-family: var(--font-body);
    font-size: var(--type-note);
    line-height: 1.6;
    color: var(--ink);
}
.tpl-start.setup-sidebar .tpl-help-heading { margin: var(--space-lg) 0 var(--space-xs); }
.tpl-start.setup-sidebar .tpl-help-body > .tpl-help-heading:first-child { margin-top: 0; padding-right: var(--space-xl); }
.tpl-start.setup-sidebar .tpl-help-body p { margin: 0 0 var(--space-sm); }
.tpl-start.setup-sidebar .tpl-help-list { margin: 0 0 var(--space-sm); padding-left: var(--space-lg); }
.tpl-start.setup-sidebar .tpl-help-list .tpl-help-list { margin: var(--space-xs) 0 0; }
.tpl-start.setup-sidebar .tpl-help-entry { margin-bottom: var(--space-xs); }
/* An item's details: a link under its text, then the details indented beneath it in the
   body's own p / list style (the rules above apply inside, unchanged). */
.tpl-start.setup-sidebar .tpl-help-more {
    display: block; margin: var(--space-xs) 0 0; padding: 0; border: 0; background: none;
    font: inherit; color: var(--red); text-decoration: underline; text-underline-offset: 2px; cursor: pointer; text-align: left;
}
.tpl-start.setup-sidebar .tpl-help-details { margin: var(--space-sm) 0 0; padding-left: var(--space-md); }
.tpl-start.setup-sidebar .tpl-help-details[hidden] { display: none; }
.tpl-start.setup-sidebar .tpl-help-entry.is-current {
    background: var(--tpl-help-highlight);
    margin-left: calc(-1 * var(--inset-control));
    margin-right: calc(-1 * var(--inset-control));
    padding: 2px var(--inset-control);
}
/* The close is .tpl-panel-close (absolute, top-right); the body scrolls under it. */
.tpl-start.setup-sidebar .tpl-help-drawer > .tpl-panel-close { background: var(--surface-raised); z-index: 1; }
