/* ═══════════════════════════════════════════════════════════════════════════
   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;
       margin-bottom}` and `#tpl-modes-block{margin-top}`. KEPT. These are
       CLASS/ID-scoped, so unlike the switcher's they still match after relocation;
       neutralising them at runtime moved zero pixels, but "overridden" is not
       "unreachable" and the toggle renders outside the rail before the deferred
       script runs.
     • 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 belong to SLIDE VIEW's deck navigator, 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);
}

.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.  */
.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: calc(var(--tpl-content-max) + var(--tpl-rail-left) + var(--tpl-rail-right) + 2 * var(--tpl-gap));
    margin-inline: auto;
    /* The INTER-COLUMN gaps. The literal 26px that stood here twice is now
       --tpl-gap, shared with the outer gutter on .tpl-start.setup-sidebar and with the
       card-to-card gap down the middle column. */
    padding-left: calc(var(--tpl-rail-left) + var(--tpl-gap));
    padding-right: calc(var(--tpl-rail-right) + var(--tpl-gap));
    /* (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 ───────────────────────────────
   `top: 0` put both rails level with the form's top edge, which is the Session
   Overview card — the identity block, not the content. The rails are settings
   FOR the content, so they begin where the content does: the top of
   #tpl-structural-region, the wrapper that holds the first content card on every
   shape (Options To Compare on the ranking templates, Survey Questions And Items
   on survey and quiz, the first section card on multi-section). That wrapper is
   why no per-template selector is needed and none should be added — it is
   already the left rail's own jump target for Customise Weighting.

   [HARD] THE OFFSET IS FED BY JS AND CANNOT BE PURE CSS. It is the height of
   whatever sits above the region — an Overview card whose fields come and go
   with Individual/Group — and CSS cannot measure a sibling. setup-sidebar.js
   writes --tpl-rail-top on the form (syncRailTop) and keeps it current with a
   ResizeObserver. The 0px fallback is the old behaviour, so a page where that
   script never runs still renders both rails at the form top rather than
   nowhere. Below 768px the media query makes the rails static and `top` stops
   applying, so the fallback is not load-bearing there either. */
.tpl-start.setup-sidebar #tpl-setup-sidebar,
.tpl-start.setup-sidebar #tpl-setup-meta {
    position: absolute;
    bottom: 0;
}
/* THE TWO RAILS NO LONGER SHARE ONE ANCHOR, because they no longer align to the same
   thing. The LEFT rail is still one card of settings for the content, so it still
   begins where the content does. The RIGHT rail is two cards now, and the first of
   them aligns to the HEADING card — so its shell has to begin there instead, at
   --tpl-meta-top, or the upper card could not reach a row of the page its shell does
   not cover. The lower card gets back to --tpl-rail-top from the inside (see its
   margin-top), so the content anchor is still honoured; it is applied one level down
   rather than by the shell. */
.tpl-start.setup-sidebar #tpl-setup-sidebar { left: 0;  width: var(--tpl-rail-left);  top: var(--tpl-rail-top, 0px); }
.tpl-start.setup-sidebar #tpl-setup-meta    { right: 0; width: var(--tpl-rail-right); top: var(--tpl-meta-top, 0px); }
/* Sticky from that point to the bottom of the form: the rail's absolutely
   positioned shell runs top:--tpl-rail-top → bottom:0, and a sticky child cannot
   travel past its own container, so the card stops at the form's last edge on
   its own. Nothing has to know where the footer or Set Up Session are.

   max-height + overflow-y: A RAIL TALLER THAN THE VIEWPORT MUST STILL BE
   REACHABLE. Sticky pins the top, so without this the foot of a long rail — the
   weighting panel with many criteria, and Advanced Settings under it — sits below
   the fold and no amount of page scrolling brings it up. calc(100vh - 48px)
   leaves the 24px sticky offset at the top and the same again at the bottom. The
   overflow is on the sticky element ITSELF, which is safe; it is an overflow on an
   ANCESTOR that would take the stickiness away. */
.tpl-start.setup-sidebar .tpl-sidebar-inner {
    position: sticky;
    top: 24px;
    max-height: calc(100vh - 48px);
    overflow-y: auto;
    background: var(--card-bg);
    border-radius: 4px;
    padding: var(--tpl-sidebar-inset);
}
/* ── THE RIGHT RAIL'S TWO CARDS TRAVEL AS ONE STICKY OBJECT ──────────────────
   .tpl-sidebar-stack is the sticky element for this rail; the two cards inside it
   are STATIC. That is the whole design decision, and it was forced by measurement
   rather than chosen: two independently-sticky cards align correctly through the
   body of the scroll, but at the END of it the lower card 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 that ends
   at the content region fixes the overlap and leaves it 18px of travel, which is not
   sticky in any useful sense. Cards that never move relative to each other cannot
   overlap, at rest or pinned, so they move together.

   THE MAX-HEIGHT AND THE OVERFLOW MOVE HERE WITH THE STICKINESS. They are one
   mechanism — "a rail taller than the viewport must still be reachable" — and the
   element they belong on is whichever one is pinned. Leaving them on the cards
   would scroll each card inside a group that itself could not scroll.

   THE GRID IS WHAT DERIVES THE LOWER CARD'S TOP, and it is not decoration. Row one
   is `--tpl-rail-top - --tpl-meta-top`: the distance from the heading card's top
   (where this rail's shell begins) to the content region's top. Row two therefore
   STARTS on the content region, by construction, whatever the upper card does
   inside row one. The obvious alternative — margin-top on the lower card, keyed on
   the heading card's HEIGHT — ties the lower card's position to the upper card's height
   and makes the (a)/(b) choice below impossible to express. align-items:start keeps the
   upper card at the TOP of its row rather than stretched across it: the row is the
   heading card PLUS the gap beneath it, so stretching would overshoot by that gap. */
.tpl-start.setup-sidebar #tpl-setup-meta > .tpl-sidebar-stack {
    position: sticky;
    top: 24px;
    max-height: calc(100vh - 48px);
    overflow-y: auto;
    display: grid;
    /* [HARD] minmax(), NOT A FIXED ROW. Row one is the distance from the heading
       card's top to the content region's top, so row two starts ON the content
       region whatever the upper card does inside row one — that is the derivation
       the note above describes and it is unchanged as the MINIMUM. What changed is
       that the upper card can now GROW: Closing Date and Response Notifications
       live in it, and their radio lists expand in flow. A fixed row cannot contain
       that, so the card overflowed its row and painted straight through the lower
       one — measured at 1280 with the reminder list open: upper 113->618 while
       lower sat at 255->453. minmax keeps the alignment in every state that fits
       and pushes the lower card down only when it genuinely cannot. */
    grid-template-rows: minmax(calc(var(--tpl-rail-top, 0px) - var(--tpl-meta-top, 0px)), auto) auto;
    align-items: start;
    /* [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:sticky` establishes a STACKING CONTEXT unconditionally, z-index or not.
       This element is sticky because the two rail cards must travel as one object, so
       the context cannot be removed the way the section type pill's was (see THE TYPE
       PILL IS A NORMAL FLEX ITEM in start.blade.php, which retired the same bug class by
       deleting a transform). Everything inside the rail therefore 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 won.

       Measured on the workshop page with the panel open, by sampling elementFromPoint
       across the panel's own rect: the panel is position:fixed at z-index:100 and STILL
       returned .tpl-setup-fields-well (18 sample points), #tpl-session-setup-card,
       #tpl-group-name-wrap and their inputs. Its z-index was never in play — it competes
       only INSIDE #tpl-switcher, which is itself inside this context. No value on the
       panel could have fixed it; raising it would have been the workaround.

       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 the switcher
       hint popout (1000), the responses modals and cookie banner (9999) and the results
       fullscreen overlay (10000, the highest in the codebase). The rail and the centre
       column do not overlap geometrically, so lifting the whole rail changes nothing
       visible except the one thing that was wrong.

       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 exactly as before. Add any
       of those three properties here and both panels clip to the rail instantly. */
    z-index: 100;
}
/* The cards give up the stickiness and the group max-height to the wrapper. They KEEP
   overflow-y:auto from the base rule, and that is not redundant with the wrapper's:
   the wrapper's answers "the rail is taller than the viewport", the card's answers
   "this card's content is taller than the height it has been given". Under (b) the
   upper card has no given height, so the second case cannot arise for it today — it is
   kept because it is the base rule's behaviour for every .tpl-sidebar-inner and because
   it is exactly what variant (a) would need back. */
.tpl-start.setup-sidebar #tpl-setup-meta .tpl-sidebar-inner {
    position: static;
    max-height: none;
}
/* ── (b) THE UPPER CARD IS TOP-ALIGNED TO THE HEADING CARD AND SIZED TO ITSELF ──
   It takes no height at all: it starts on the heading card's top edge because the
   wrapper does, and it ends where its own content ends. The rule below is what makes
   that work and it is the reason there is no declaration here — the card is a grid
   item in row one, and align-items:start keeps it at the top of that row instead of
   stretching across it.

   VARIANT (a) — height pinned to the heading card — WAS built and shipped first, and
   the measurements are why it is not here. At 1280 the upper card's own content is
   151px signed in, and only 104px signed out (Save To Project renders nothing for a
   guest), against a heading card of 122px in Individual, 202px in Group and 310px in
   Group signed-out. So (a) is 29px SHORT in Individual — the one mode where it is not
   empty, it is cramped and scrolls internally — and leaves 51px of empty card in Group
   and 206px signed out. There is no mode in which pinning looks right.

   [TOGGLE] TO RESTORE (a), TWO LINES IN TWO FILES, NOT ONE. Uncomment the declaration
   below AND restore the `--tpl-meta-upper-h` setProperty in setup-sidebar.js's
   syncRailTop(), which was removed with this switch because that declaration was its
   only reader. Uncommenting alone is WORSE than doing nothing: the var would be unset,
   `var(--tpl-meta-upper-h, auto)` would fall back to `auto`, and the page would render
   exactly (b) while the CSS said (a). Nothing else changes either way — the lower card's
   top comes from the grid row above, never from this height, which is the whole reason
   the wrapper is a grid rather than a margin. */
.tpl-start.setup-sidebar #tpl-setup-meta > .tpl-sidebar-stack > .tpl-sidebar-inner--meta-upper {
    /* (a): height: var(--tpl-meta-upper-h, auto);  — off; see the [TOGGLE] note above */
}

/* ═══════════════════════════════════════════════════════════════════════════
   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.
   ═══════════════════════════════════════════════════════════════════════════ */
.tpl-start.setup-sidebar .tpl-sidebar-block.is-empty { display: none; }
/* 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). */
.tpl-start.setup-sidebar .tpl-sidebar .tpl-sidebar-row.is-empty { display: none; }
/* ── 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 .tpl-sidebar-stack > .tpl-sidebar-inner:not(:has(.tpl-sidebar-block:not(.is-empty))),
.tpl-start.setup-sidebar #tpl-setup-meta:not(:has(.tpl-sidebar-block:not(.is-empty))) .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-sidebar:not(:has(.tpl-sidebar-row:not(.tpl-group-only):not(.is-empty))) .tpl-sidebar-block {
    display: none;
}

.tpl-start.setup-sidebar .tpl-sidebar-block:not(.is-empty) + .tpl-sidebar-block:not(.is-empty) {
    border-top: 1px solid rgba(var(--ink-rgb), 0.09);
}

/* ── 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. */
.tpl-start.setup-sidebar .tpl-sidebar .tpl-switcher { text-align: left; }
.tpl-start.setup-sidebar .tpl-sidebar .tpl-switcher-trigger {
    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;
}
.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. */
/* [HARD] THE SECOND SELECTOR IS NOT BELT-AND-BRACES. Flat's toggle carries
   `#tpl-rank-type-toggle.tpl-rank-type-toggle-incard{width:fit-content;
   margin-inline:auto}` — an ID rule, so four classes do not reach it. Measured
   with only the class selector: multi-criteria's track filled the rail at 208px
   with 101px seats, and flat's sat centred at 131px with 63px seats in the same
   rail, under the same label. Two shapes, one rail, two different controls — and
   the class-only rule looked like it was working, because on the shape it was
   checked against it was. The in-card modifier is what those page rules key on,
   so it is what has to be named to release them; `margin: 0` clears the centring
   and the 12px the page put under it, both of which are the rail row's job now. */
.tpl-start.setup-sidebar .tpl-sidebar .tpl-stype-toggle,
.tpl-start.setup-sidebar .tpl-sidebar #tpl-rank-type-toggle.tpl-rank-type-toggle-incard {
    display: flex;
    width: 100%;
    margin: 0;
}
.tpl-start.setup-sidebar .tpl-sidebar .tpl-stype-toggle .tpl-flat-mode-btn {
    flex: 1 1 0;
    min-width: 0;
}

/* ── 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. */
.tpl-start.setup-sidebar .tpl-sidebar .tpl-mode-pair,
.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: 10px;
    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
   ═══════════════════════════════════════════════════════════════════════════ */

.tpl-start.setup-sidebar .tpl-sidebar .tpl-sidebar-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 8px 0;
}
.tpl-start.setup-sidebar .tpl-sidebar .tpl-sidebar-row + .tpl-sidebar-row {
    border-top: 1px solid rgba(var(--ink-rgb), 0.09);
}
/* 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(). */
.tpl-start.setup-sidebar .tpl-sidebar .tpl-sidebar-row:not(.is-empty) + .tpl-sidebar-row + #tpl-sidebar-weight-panel + .tpl-sidebar-row {
    border-top: 1px solid rgba(var(--ink-rgb), 0.09);
}
/* 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. */
.tpl-start.setup-sidebar .tpl-sidebar #tpl-sidebar-segment-panel + .tpl-sidebar-row {
    border-top: 1px solid rgba(var(--ink-rgb), 0.09);
}
.tpl-start.setup-sidebar .tpl-sidebar #tpl-sidebar-slot-project.is-empty,
/* …and an empty ROW inside it, for the same reason one level down: the slot now
   holds three, and a row that renders nothing must not take the padding and the
   hairline the other two share. See the sweep in setup-sidebar.js. */
.tpl-start.setup-sidebar .tpl-sidebar #tpl-sidebar-slot-project > .is-empty { display: none; }

/* ── 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. */
.tpl-start.setup-sidebar #tpl-setup-meta .tpl-sidebar-block { padding: 8px 0; }
.tpl-start.setup-sidebar #tpl-setup-meta #tpl-sidebar-slot-modes,
.tpl-start.setup-sidebar #tpl-setup-meta #tpl-sidebar-slot-project { padding: 0; }
/* 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. */
.tpl-start.setup-sidebar #tpl-setup-meta #tpl-sidebar-slot-modes > *,
.tpl-start.setup-sidebar #tpl-setup-meta #tpl-sidebar-slot-project > * { padding: 8px 0; 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) ~ *,
.tpl-start.setup-sidebar #tpl-setup-meta #tpl-sidebar-slot-project > :not(.is-empty) ~ * {
    border-top: 1px solid rgba(var(--ink-rgb), 0.09);
}
/* 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. */
.tpl-start.setup-sidebar #tpl-setup-meta #tpl-sidebar-slot-project .tpl-adv-row {
    border-top: 0;
}

/* ── 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-sidebar .tpl-sidebar-row-label {
    color: var(--ink);
    text-decoration: none;
    line-height: 1.4;
    pointer-events: none;
}
.tpl-start.setup-sidebar .tpl-sidebar .tpl-sidebar-row:has(input:checked) .tpl-sidebar-row-label {
    pointer-events: auto;
}

/* ── ENABLE TIES SAYS WHAT TO DO WHILE IT CANNOT BE USED ────────────────────
   The row is greyed until Enable Skip Comparison is on; the label now swaps on
   that SAME state rather than carrying a parenthetical that repeated it — and
   wrapped to four lines in a 208px rail doing so.

   [HARD] THE PREDICATE IS .tpl-adv-row-locked, WHICH ALREADY EXISTS. It is what
   applyTiesSkipsLock() puts on this row to grey it, so the label and the disabled
   state are two readings of one class and cannot drift. Adding a second condition
   — a data attribute, a class of its own, anything the JS also had to set — would
   be a parallel copy of a boolean this row already carries. Unlocked is the
   default so the row reads correctly on first paint, before any handler runs. */
.tpl-start.setup-sidebar #tpl-allow-ties-row .tpl-sidebar-row-label > [data-ties-locked] { display: none; }
.tpl-start.setup-sidebar #tpl-allow-ties-row.tpl-adv-row-locked .tpl-sidebar-row-label > [data-ties-locked] { display: inline; }
.tpl-start.setup-sidebar #tpl-allow-ties-row.tpl-adv-row-locked .tpl-sidebar-row-label > [data-ties-unlocked] { display: none; }
.tpl-start.setup-sidebar .tpl-sidebar .tpl-sidebar-row-ctrl { flex: 0 0 auto; }

/* ── 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-start.setup-sidebar #tpl-start-form > #tpl-session-setup-card      { order: 1; }
.tpl-start.setup-sidebar #tpl-start-form > #tpl-welcome-page-card       { order: 2; }

.tpl-start.setup-sidebar #tpl-start-form > #tpl-structural-region       { order: 4; }
.tpl-start.setup-sidebar #tpl-start-form > .tpl-manage                  { order: 5; }
.tpl-start.setup-sidebar #tpl-start-form > #responses-modal             { order: 5; }
.tpl-start.setup-sidebar #tpl-start-form > #tpl-completion-message-card { order: 6; }
.tpl-start.setup-sidebar #tpl-start-form > #tpl-error                   { order: 8; }
.tpl-start.setup-sidebar #tpl-start-form > .tpl-actions                 { order: 9; }
/* The three 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-setup-mode-block joined them — it used to sit
   at order 3, i.e. in the content column between the Overview card and the first
   section, which is what "floating, belonging to neither" was.) */
.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; }
.tpl-start.setup-sidebar #tpl-start-form > #tpl-setup-mode-block        { order: 0; }

/* ═══════════════════════════════════════════════════════════════════════════
   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.
   ═══════════════════════════════════════════════════════════════════════════ */

.tpl-start.setup-sidebar #tpl-welcome-intro-body .tpl-instructions-title,
.tpl-start.setup-sidebar #tpl-completion-message-body .tpl-instructions-title,
.tpl-start.setup-sidebar #tpl-welcome-intro-body .tpl-instructions-textarea,
.tpl-start.setup-sidebar #tpl-completion-message-body .tpl-instructions-textarea {
    background-color: transparent;
    border: 0;
    border-bottom: 1px solid rgba(var(--ink-rgb), 0.16);
    border-radius: 0;
    padding: 7px 0;
}
/* (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. */
/* The body keeps a usable typing area; only its chrome changed. */
.tpl-start.setup-sidebar #tpl-welcome-intro-body .tpl-instructions-textarea,
.tpl-start.setup-sidebar #tpl-completion-message-body .tpl-instructions-textarea {
    min-height: 76px;
}

/* ═══════════════════════════════════════════════════════════════════════════
   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. */
.tpl-start.setup-sidebar #tpl-add-criterion-btn,
.tpl-start.setup-sidebar #tpl-add-section-btn {
    border: 0;
    border-radius: 0;
    background: var(--ink);
    color: var(--color-white);
    padding: 5px 20px;
    letter-spacing: 0.06em;
    cursor: pointer;
}
.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. */
.tpl-start.setup-sidebar #tpl-start-form > #tpl-welcome-page-card,
.tpl-start.setup-sidebar #tpl-start-form > #tpl-completion-message-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 { 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; }
.tpl-start.setup-sidebar #tpl-start-form:has(#tpl-weighting-toggle:checked) #tpl-sidebar-weight-panel {
    display: block;
    margin-top: 10px;
}
/* [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. */
.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: 6px 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. */
.tpl-start.setup-sidebar #tpl-sidebar-weight-foot .tpl-weight-strip-card {
    margin: 10px 0 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. */
.tpl-start.setup-sidebar .tpl-weight-strip-label,
.tpl-start.setup-sidebar .tpl-weight-strip-status,
.tpl-start.setup-sidebar .tpl-segment-tag,
.tpl-start.setup-sidebar .tpl-segment-custom-btn,
.tpl-start.setup-sidebar .tpl-sidebar .tpl-adv-label {
    font-size: var(--tpl-rail-label);
}

.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.) */
.tpl-start.setup-sidebar .tpl-sidebar .tpl-adv-row {
    padding: 8px 0;
    border-top: 1px solid rgba(var(--ink-rgb), 0.09);
}

/* 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. */
/* Both panels hold .tpl-adv-sub sub-panels — the advanced rows' own, and the
   segment picker, which kept the class when it moved so its existing
   [data-toggle]/[data-sub] reveal came with it. One override, both consumers. */
/* :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"]),
.tpl-start.setup-sidebar #tpl-sidebar-segment-panel .tpl-adv-sub {
    padding: 0 0 8px;
}
/* (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] 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.
   Two hosts carry it — the Overview card's head and #mgmt-respond-header — and its
   1fr|auto|1fr grid is what centres their 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.

   --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.

   `.tpl-setup-identity` (the rust kicker + the h1 that mirrored the title) STAYS
   hidden below. 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. */
.tpl-start.setup-sidebar .tpl-setup-identity { display: none; }

/* ── STACKED ─────────────────────────────────────────────────────────────────
   Below 900px the pair cannot hold its measure, so it becomes one column with the
   sidebar on top. Sticky is dropped deliberately: a sticky element in a
   single-column stack pins settings 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.setup-sidebar #tpl-start-form {
        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. Sticky is dropped deliberately — in a single-column stack
       it would pin the settings over the field being edited. */
    .tpl-start.setup-sidebar #tpl-setup-sidebar,
    .tpl-start.setup-sidebar #tpl-setup-meta {
        position: static;
        width: auto;
        margin-bottom: 18px;
    }
    .tpl-start.setup-sidebar .tpl-sidebar-inner {
        position: static;
    }
    /* The right rail's two cards stop being two ALIGNED cards here — there is nothing
       to their left to align to in a single column — so everything that does the
       aligning comes off. The wrapper stops being a grid (its first row is a distance
       measured against a heading card that is now a block ABOVE it, which in this
       layout is just a hole) and becomes an ordinary block; the upper card sizes to its
       content again, because pinning it to a height it no longer sits beside would clip
       the picker; and the gap between the two becomes the same 18px the stacked rails
       already use between themselves two rules above. */
    .tpl-start.setup-sidebar #tpl-setup-meta > .tpl-sidebar-stack {
        display: block;
        max-height: calc(100vh - 48px);
    }
    .tpl-start.setup-sidebar #tpl-setup-meta > .tpl-sidebar-stack > .tpl-sidebar-inner--meta-upper {
        height: auto;
    }
    .tpl-start.setup-sidebar #tpl-setup-meta > .tpl-sidebar-stack > .tpl-sidebar-inner--meta-lower {
        margin-top: 18px;
    }
}
