
/* ── the defaults, and the reason every rule below is valid ──────── */
/* WRITER #1. Without these four declarations every rule in this sheet
   references an UNDECLARED custom property, which makes the whole declaration
   invalid at computed-value time rather than merely wrong: the row loses its
   padding and its gap, both rails fall back to width:auto and size to content,
   and the nav plate collapses to a shrink-to-fit box glued to the chrome bar.
   Measured on a cold load with them missing — nav 808px wide at x=418, row at
   x=0 with no inset at all, and the grips parked at the rail's static position
   instead of on the edge they claim to resize.

   These are the geometry the app shipped with (248 / 272 / 12), so declaring
   them is a visual no-op until somebody moves something, and FULL is 6000
   because on any real display the "100% minus two gutters" term of the min()
   wins and the cap never binds. */
:root {
  --ec-gutter: 12px;
  --ec-max-w: 1600px;
}

/* ── NO SCROLLBARS INSIDE THE CHASSIS ─────────────────────────────
   One rule, stated once, for every centre container and every rail on every
   route: nothing in the row renders a scrollbar.

   WHY IT BELONGS HERE AND NOT IN EACH COMPONENT. It was in each component —
   the profileview pages, the hub rails, the nav strip all carry their own
   scrollbar-width: none, and every new scrolling surface had to remember to
   repeat it. The settings body is what that costs: it is the one scroller
   nobody remembered, so the preferences page grew a 15px gutter down its side
   that no other page in the app has. A convention every author must re-apply
   is not a convention, it is a chance to forget.

   WHAT REPLACES THE SCROLLBAR, so this is a substitution and not a removal.
   The rails carry useRailScroll, the reach that scrolls them from the
   pointer's position along the edge. Wheel, trackpad, keyboard, Page Up/Down
   and programmatic scrolling are all untouched — this hides the INDICATOR,
   not the ability. Content that overflows still scrolls by every means except
   dragging a bar that was never part of this chassis's vocabulary.

   Scoped to .ec-row deliberately: the two chrome bars, the nav and any
   future modal are outside it and keep whatever they declare for themselves. */
.ec-row,
.ec-row * {
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.ec-row::-webkit-scrollbar,
.ec-row *::-webkit-scrollbar {
  width: 0;
  height: 0;
  display: none;
}

/* ── the inset row — every route that fills the well ─────────────── */
.ec-row {
  width: min(100% - 2 * var(--ec-gutter), var(--ec-max-w));
  margin-inline: auto;
  padding-block: var(--ec-gutter);
  gap: var(--ec-gutter);
  /* The containing block for the control-mode corner brackets below. Safe to
     add unconditionally: the row has no absolutely-positioned children of its
     own, and both rails already establish their own contexts. */
  position: relative;
}

/* ── the nav plate, inset to agree with the rails ────────────────── */
.ec-nav-inset {
  width: min(100% - 2 * var(--ec-gutter), var(--ec-max-w));
  margin-inline: auto;
  margin-top: var(--ec-gutter);
}

/* ── rails: WIDTHS AT lg AND UP ONLY ─────────────────────────────── */
/* Below 1024 the rails carry no width at all, exactly as lg:w-[248px]
   carried none, so the stacked column layout is byte-identical to today.
   1024 is Tailwind v4's lg and also the BrowserWindow minWidth. */
@media (min-width: 1024px) {
  /* ONE width, both rails, and it is a constant rather than a token read from
     storage — a stored value from the old two-preference build must not be
     able to come back and make the pair lopsided again. The percentage is
     still there as the narrow-window safety net it always was. */
  .ec-rail-left,
  .ec-rail-right { width: min(252px, 26.00%); }
}

/* ── control mode: no boxes, just the handles ────────────────────── */
/* THERE IS DELIBERATELY NOTHING HERE. The mode used to outline the row and
   both rails -- first as a dashed border, then as inset corner brackets -- and
   both readings were the same mistake: the mode's job is to hand you the two
   handles, not to redraw the app around them. Marking three boxes to explain
   two grips is more chrome than the thing it is explaining, and it lands on
   every rail edge at once, which is exactly what made it read as clutter.

   If a future edit wants to signal "the mode is on", signal it ON THE HANDLES
   -- they are the only thing the mode actually changes. */
/* A live drag must not paint a text selection across everything it sweeps. */
html[data-layout-resizing="1"] { user-select: none; }

/* ── THE SPLIT, declared once, in the one place that can enforce it ─ */
/* NOTHING EASES WHILE THE POINTER IS DOWN. This is the only rule in the app
   that transitions a rail's width, and it is gated twice over:

     1. the attribute is only ever set by an action that is NOT a drag — a
        single arrow press, a jump between stops, a reset;
     2. the selector refuses outright while a drag is live, so a future edit
        that forgot (1) still cannot make the drag lag the pointer.

   DO NOT "improve" this by adding easing to the drag path. A rail edge that
   arrives even 60ms behind the pointer reads as the application being slow,
   and no amount of polish elsewhere buys that back. Everything else — the
   entrance, the release settle, a nudge, a reset, a jump — glides, and glides
   on the curve below.

   SCOPE. The ROW is what glides now, because the row is what resizes: the
   rails are a fixed width and the container is the only thing a grip moves.
   The gutter still does not glide -- it moves the row, the nav plate and both
   rails at once, and half-animating a whole-window relayout looks worse than
   changing it outright. */
html[data-layout-glide]:not([data-layout-resizing]) .ec-row {
  transition: width var(--ec-glide-t) var(--ec-glide-e);
}
html[data-layout-glide="nudge"] { --ec-glide-t: 110ms; --ec-glide-e: linear; }
html[data-layout-glide="jump"]  { --ec-glide-t: 260ms;  --ec-glide-e: cubic-bezier(0.16, 1, 0.3, 1); }

/* ── the grips ───────────────────────────────────────────────────── */
/* The 16px hit box is FIXED and does not track the gutter: at a 4px gutter a
   gutter-width target would be a 4px target. The visible marks sit on the
   gutter's centre line, so the box overhangs the rail and the centre column by
   about 2px at the default gutter — which is right, because while the mode is
   on you are resizing rather than clicking cards, and while it is off the
   element is display:none and takes no pointer events at all.
   col-resize is declared even though the custom reticle usually hides the
   native cursor: it is correct for a coarse pointer, for a user who has the
   reticle off, and for tooling.

   THREE TIMING CHANNELS, as inherited custom properties rather than literals.
   Every state rule below spends one of them, which is what lets the whole grip
   drop to instant under prefers-reduced-motion from a single declaration. The
   alternative — a reduced-motion block restating every state selector — has to
   match or beat each one's specificity to land, and the hover, drag and latch
   rules are all at different weights, so it is a list that silently stops
   covering a rule the moment somebody adds one.

     --ec-t-bite    engage: hover, focus, taking hold. Short and linear.
     --ec-t-settle  release: letting go, parking the readout. Longer, eased.
     --ec-t-latch   the detent. Ease IN, because a clamp bites rather than
                    easing out of its last two pixels. */

/* DECLARED ON BOTH ROOTS, and that is a correctness fix rather than belt and
   braces. The stop marks are a SIBLING subtree — .ec-grip-stops is out of flow
   BESIDE the grip inside the rail, not inside it — and a custom property
   inherits only down from where it is declared. Declared on .ec-grip alone,
   subtrees. Keep the two class names together if either is ever renamed. */
.ec-grip, .ec-grip-stops {
  --ec-t-bite: 110ms;
  --ec-t-settle: 240ms;
  --ec-t-latch: 130ms;
}
.ec-grip {
  position: absolute; top: 0; bottom: 0; width: 16px; z-index: 5;
  display: none; cursor: col-resize; touch-action: none;
  /* How much of the edge the handle actually DRAWS. Declared once, read by the
     mark and by the stop marks, so the graduation and the detents can never
     end up different lengths. */
  --ec-mark-len: 148px;
  /* Declared here so the gating below has one source of truth. Both ramps are
     defaulted as well as the gate: a selector that reads one of them before
     the pointer has ever moved would otherwise resolve to an invalid calc and
     drop the whole declaration. */
  --ec-arm: 0;
  --ec-glow: 0;
  --ec-hex: 0;
  /* ── WHERE THE MARK SITS ──────────────────────────────────────
     Measured UP FROM THE BOTTOM of the rail, not down from the top and not
     from the middle. The grip spans the full height of the row, so a centred
     mark drifts with the row: add three bands and the thing you reach for has
     moved. An offset from the bottom edge holds it in one place on screen
     whatever the board is doing above it.

     KEEP THIS IN STEP WITH MARK_UP in layout-controls.tsx. That file measures
     the pointer's distance to the same point, and if the two ever disagree the
     glow lights somewhere the gem is not -- which is the one failure this whole
     surface is built to avoid.

     The min() is the short-rail guard. Below a 200px row, 100px from the
     bottom would put the mark above the middle and eventually off the top of
     its own box; clamping at 50% degrades it to centred instead. The JS
     mirrors the same min(). */
  --ec-mark-up: 100px;
  --ec-mark-y: min(var(--ec-mark-up), 50%);
}
/* The delay table. Slot 0 and slot 1; the panel takes slot 2 in its own sheet. */
/* ON THE ROW'S OUTER EDGES. The grips used to live in the inner gutters
   because they moved a rail; they now move the CONTAINER, so they belong on
   the container's own edges. -8px centres the 16px box on the edge itself. */
.ec-grip[data-side="left"]  { left:  -8px; }
.ec-grip[data-side="right"] { right: -8px; }
/* ── PROXIMITY: the handle answers the hand before you reach it ──── */
/* The grip is now MOUNTED at every desktop width, not only in control mode,
   and what the mode changes is whether it is pinned on. At rest it is at zero
   ink and deaf to the pointer; as the pointer comes within reach of the
   container's edge, --ec-glow ramps 0 -> 1, the border lights and swells, and
   the box takes its own events back. The mark itself waits for --ec-hex.

   POINTER-EVENTS FOLLOW THE RAMP, and that is load-bearing rather than tidy.
   The 16px box sits on the row's outer edge and overlaps the rail behind it by
   a few pixels; a permanently live box there would quietly eat clicks along
   the whole outside edge of the rail forever. 'auto' only once the pointer is
   already near means the box is live exactly when it is wanted and inert the
   rest of the time. The document-level listener still sees every move, so
   nothing has to be hovered for the ramp to start. */
/* TWO STAGES, AND A DWELL TIMER WAS REFUSED FOR THE FIRST ONE.
   A handle you have to wait a second for reads as broken the first three
   times you reach for it: a dwell suits a tooltip, whose content is
   supplementary, and not a control, where the wait sits between the hand and
   the action -- and it charges the most confident approach the most. The real
   complaint behind it is a FALSE POSITIVE: the edge lighting up when the
   pointer only swept past on its way somewhere else. That is answered by
   intent, not by waiting -- see the velocity gate and the hysteresis in
   layout-controls.tsx.

   STAGE 1, --ec-glow: the BORDER glows, swelling where the mark is. Ambient,
   no control drawn, so a pointer passing nearby learns the edge is live
   without anything appearing to be clicked.
   STAGE 2, --ec-hex: the mark itself, and only inside 15px. By then the hand
   has arrived, so what resolves is what you are about to grab.
   --ec-arm gates both: neither exists until the approach is deliberate. */
.ec-grip {
  /* The box is always here so its glow can be; what gates is the ink. */
  opacity: 1;
  pointer-events: none;
}
@media (min-width: 1024px) {
  .ec-grip { display: block; }
}
/* ── NOTHING AT REST, AND THAT IS THE RULE NOW ──────────────────────
   There is deliberately no resting ink on the container's edges. Five
   separate attempts to put something there have now been rejected in turn --
   a 148px graduation, a dashed outline, inset corner brackets, and finally a
   1px seam -- and they failed for one reason: the grip spans the full height
   of the row, so ANY mark on it draws a line from under the top bar to under
   the footer. At that length even one device pixel at border ink reads as a
   seam in the window rather than as an affordance.

   Do not reintroduce one. If discoverability needs help, spend it on REVEAL
   in layout-controls.tsx -- the distance at which the tick appears -- because
   that puts the ink where the hand already is instead of down the whole
   edge. */

/* THE EDGE ANSWERS FIRST; THE MARK ANSWERS LAST.
   --ec-arm is the GATE (0 or 1 -- deliberate approach, or just crossing?) and
   there are now TWO ramps rather than one, because two different objects are
   being revealed at two very different ranges:

     --ec-glow  the border. Driven by a 50px-across by 200px-along box centred
                on the mark, so it answers "am I coming for this edge".
     --ec-hex   the mark itself. Driven by 15px to the mark's own box, so it
                answers "am I on it".

   ONE RAMP COULD NOT DO BOTH. The reach that makes an edge discoverable is an
   arm's length; the reach at which a 10x18 mark should resolve is a fingertip.
   Sharing a number meant the mark was already fully lit long before the hand
   arrived, which is what made it read as a thing hanging in space rather than
   as part of the edge. */
.ec-grip__thumb { opacity: calc(var(--ec-arm) * var(--ec-hex, 0)); }
.ec-grip[data-armed="1"] { pointer-events: auto; }

/* ── THE APPROACH: the BORDER lights, and it swells at the mark ────
   Two objects, both driven by --ec-glow, and neither of them is the mark:

     SEAM   a soft vertical bar ON the border, brightest level with the mark
            and fading to nothing 200px above and below it. This is the "left
            and right border glow" -- the edge itself answering the hand.
     SWELL  an elliptical bloom centred on the mark, so the seam THICKENS
            where the grabbable point is. It is the swell that pulses, which
            is what puts the motion at the mark rather than along the whole
            edge.

   THIS IS NOT THE RESTING INK THAT WAS REJECTED FIVE TIMES, and the note
   further up still stands. Those failed because a mark on a full-height grip
   is a full-height mark: a uniform line from under the top bar to under the
   footer, present at rest, reading as a seam in the window rather than as an
   affordance. Both objects here are absent at rest, both are bounded to
   +/-200px so neither can ever span the row, and both are a gradient that is
   zero at its own ends -- there is no straight edge for the eye to read as a
   division of the window.

   WHY A PULSE IS ALLOWED, given this file bans them. The rule is that a
   control pulsing INDEFINITELY is one that is not holding anything and becomes
   noise in ten seconds. This one cannot: it does not exist until a deliberate
   approach enters the box, and it is gone again outside it. Bounded by the
   hand, so it can never go ambient.

   IT STOPS ON CONTACT. Approach is the only stage that moves; hover and held
   are still and answer with geometry and a bloom. Motion means "coming",
   stillness means "here". */
.ec-grip__seam {
  position: absolute;
  bottom: var(--ec-mark-y); left: 50%;
  width: 2px;
  /* 2 * REACH_Y in layout-controls.tsx. Keep them together: the bar has to
     reach exactly as far as the box that lights it, or the gradient gets cut
     off mid-value and the fade ends on a visible step. */
  height: 400px;
  transform: translate(-50%, 50%);
  background: linear-gradient(
    to bottom,
    transparent 0%,
    color-mix(in srgb, var(--theme-accent-2) 55%, transparent) 50%,
    transparent 100%);
  filter: blur(0.5px);
  pointer-events: none;
  opacity: 0;
}
.ec-grip__swell {
  position: absolute;
  bottom: var(--ec-mark-y); left: 50%;
  width: 30px; height: 150px;
  transform: translate(-50%, 50%);
  background: radial-gradient(
    ellipse at center,
    color-mix(in srgb, var(--theme-accent-2) 42%, transparent) 0%,
    transparent 68%);
  /* The blur IS the glow. Putting it on the tick's filter instead would make
     it share that property with the hover and held blooms, and a value driven
     off the ramp changes every frame -- it would fight their transition and
     smear. */
  filter: blur(7px);
  pointer-events: none;
  opacity: 0;
}
/* NOT OVERPOWERING is a number, not an intention: the seam tops out at 0.5 of
   a 55% mix and the swell at 0.6 of a 42% mix, so even with the hand exactly
   on the mark the edge is a suggestion rather than a rule. These two
   multipliers are the volume knob -- turn them down before touching anything
   else in this block. */
.ec-grip[data-armed="1"] .ec-grip__seam { opacity: calc(var(--ec-glow, 0) * 0.5); }
.ec-grip[data-armed="1"] .ec-grip__swell {
  opacity: calc(var(--ec-glow, 0) * 0.6);
  animation: ec-grip-pulse 1800ms ease-in-out infinite;
}
/* The mark warms toward the answer colour over its own short reveal, so
   arriving on it is a step rather than a jump between the two lights. */
.ec-grip[data-armed="1"] .ec-grip__thumb {
  background: color-mix(in srgb, var(--theme-accent-2) calc(var(--ec-hex, 0) * 100%),
              color-mix(in srgb, var(--nilla-accent) 72%, transparent));
}
/* The centring translate is restated in both frames: an animation REPLACES
   transform outright, so a bare scale would drop the -50% and fling the swell
   a half-width off the edge for as long as it ran. Scale is deliberately
   uneven -- more across than along -- so the pulse reads as the edge
   THICKENING at the mark rather than as a blob growing beside it. */
@keyframes ec-grip-pulse {
  0%, 100% { transform: translate(-50%, 50%) scale(0.82, 0.94); }
  50%      { transform: translate(-50%, 50%) scale(1.24, 1.06); }
}
/* CONTACT STOPS THE MOTION and takes both objects with it -- the hover rung's
   own 6px bloom is the glow from here on, and three glows on one 12px mark is
   the "too much" this surface keeps being told about. */
.ec-grip:hover .ec-grip__seam,
.ec-grip:focus-visible .ec-grip__seam,
.ec-grip[data-keys="1"] .ec-grip__seam,
.ec-grip[data-dragging="1"] .ec-grip__seam { opacity: 0; }
.ec-grip:hover .ec-grip__swell,
.ec-grip:focus-visible .ec-grip__swell,
.ec-grip[data-keys="1"] .ec-grip__swell,
.ec-grip[data-dragging="1"] .ec-grip__swell { opacity: 0; animation: none; }
/* THE MARK NO LONGER SHIVERS. The jitter went with the model that revealed it
   from 100px out: at a 15px reveal the mark is already under the hand by the
   time it resolves, and a control that vibrates once you have reached it reads
   as instability rather than as invitation. The motion budget for the approach
   is spent on the swell instead, which is where the request put it. */
/* Pinned: the mode's whole visible effect is now these two lines. */
html[data-layout-control="1"] .ec-grip { pointer-events: auto; }
html[data-layout-control="1"] .ec-grip__thumb { opacity: 1; }
/* Never let a ramp hide a handle that is being held or driven. */
.ec-grip[data-dragging="1"],
.ec-grip[data-keys="1"],
.ec-grip:focus-visible { pointer-events: auto; }
.ec-grip[data-dragging="1"] .ec-grip__thumb,
.ec-grip[data-keys="1"] .ec-grip__thumb,
.ec-grip:focus-visible .ec-grip__thumb { opacity: 1; }


/* display:none below 1024 is the WHOLE mobile story: such an element is out of
   the tab order and out of the accessibility tree, so the stacked layout —
   which has no vertical rail edge to drag — needs no JS, no matchMedia and no
   hydration branch. */
@media (max-width: 1023px) {
  .ec-grip,
  html[data-layout-control="1"] .ec-grip { display: none; }
}

/* [data-keys="1"] — THE KEYBOARD-VISIBLE STATE, AND WHY IT IS NOT JUST
   :focus-visible. A pointerdown on the grip focuses it explicitly, so that a
   mouse user who has just dragged a rail can reach for the arrow keys and find
   the handle already under them. Focus moved programmatically during a POINTER
   gesture does not match :focus-visible, so on that exact path — the one the
   explicit focus() call exists to serve — every rule below would stay dark
   while the arrow keys moved the edge: no readout, no engaged mark, and no
   latch when you landed on a stop. The grip therefore mirrors "the keyboard is
   driving now" into an attribute on its first keydown, and every selector that
   :focus-visible appears in carries it too. Keep the two together: a rule that
   gains one and not the other is a state that half-lights. */
/* NO OUTLINE ON THE HIT BOX -- AND outline: none IS REQUIRED, not tidy.
   Deleting the rule that drew one is not enough: the element is tabbable, so
   the user agent then draws ITS default ring instead, and the cage comes
   straight back in a colour the app does not even own (measured: an 'auto 1px'
   ring in rgb(229,151,0) after the author rule was removed). It has to be
   suppressed explicitly. It is 16px wide and the full height of the row,
   so an outline on it drew a tall thin cage around the whole edge -- and
   because pointerdown focuses the grip explicitly, it appeared the instant you
   grabbed the bar rather than only for keyboard users. The keyboard state is
   already carried by the tick itself, which crosses to --theme-accent-2 and
   grows under [data-keys="1"]; that is the same channel every other engaged
   state in this file uses, and it is on the object you are actually driving.
   An outline on the thumb is not the fix either -- its clip-path eats one. */
.ec-grip:focus-visible,
.ec-grip[data-keys="1"] { outline: none; }

/* ── the rule: structure, and the full height of the edge ────────── */
/* Two lights, and this is the first of them. The rule says WHERE THE EDGE IS,
   which is a fact about the layout rather than a response to the user, so at
   rest it is --nilla-accent. It crosses to --theme-accent-2 the moment the
   mechanism is engaged, which is the second light and the only other one. */

/* ── the thumb: the part you actually grab ───────────────────────── */
/* A splitter that is only a hairline gives the hand nothing to aim at, so the
   centre of the edge carries a chamfered slug. It is the state ladder's whole
   vocabulary — width, height and ink — and it never moves off the centre line,
   because the thing that moves is the edge itself.

   The chamfer is the house's 45deg substitute for a radius, cut through
   clip-path rather than a border, which is why the held glow is a filter:
   drop-shadow and not a box-shadow: clip-path eats an outer box-shadow
   outright (eva-frame.tsx:388-391) and leaves a drop-shadow alone. */
.ec-grip__thumb {
  position: absolute;
  /* FIXED AT THE CENTRE. It used to track the pointer's y. That is gone: a
     handle that slides to meet you never has a place, so you cannot learn
     where it is and you end up watching it instead of using it. It sits in
     one spot on each rail and only its ink changes. */
  bottom: var(--ec-mark-y);
  left: 50%;
  width: 10px; height: 18px;
  transform: translate(-50%, 50%);
  background: color-mix(in srgb, var(--nilla-accent) 72%, transparent);
  /* THE HEX. 'polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%)' is
     not a shape invented here: it is the pointy-top hexagon the EVA pack uses
     892 times across its buttons, plates and locks, and it is the same cell
     the app's own hive lattice is tiled from. A chamfered oblong said
     "splitter"; this says which application it belongs to. Percentages, so the
     silhouette survives every size in the state ladder below. */
  clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
  transition:
    width var(--ec-t-settle) cubic-bezier(0.16, 1, 0.3, 1),
    height var(--ec-t-settle) cubic-bezier(0.16, 1, 0.3, 1),
    background var(--ec-t-settle) cubic-bezier(0.16, 1, 0.3, 1),
    filter var(--ec-t-settle) cubic-bezier(0.16, 1, 0.3, 1);
}


/* :hover LEADS THIS LIST, and its absence was a real hole rather than a
   nicety. The rule was written to be the HOVER step -- the comment below has
   said so since it landed -- but the selector only ever named :focus-visible
   and the keyboard flag, so a pointer arriving ON the tick changed nothing at
   all: the ladder ran approach (the opacity ramp), then nothing, then held.
   The one :hover selector that did exist is the latched rule further down, and
   data-latched is only ever written during a drag, so it could not cover this
   either. Measured against the live CSSOM before the fix: a hovered thumb
   still computed to 10x18 at --nilla-accent with filter: none.

   It costs nothing at rest. The grip is pointer-events: none until the
   proximity gate arms it, so :hover cannot match until the approach has
   already been judged deliberate -- the hover state is unreachable by a
   pointer that merely swept past. */
.ec-grip:hover .ec-grip__thumb,
.ec-grip:focus-visible .ec-grip__thumb,
.ec-grip[data-keys="1"] .ec-grip__thumb {
  width: 12px; height: 21px;
  background: var(--theme-accent-2);
  /* HOVER: a soft bloom, so the tick answers the pointer arriving on it and
     not merely being near. */
  filter: drop-shadow(0 0 6px color-mix(in srgb, var(--theme-accent-2) 45%, transparent));
  transition-duration: var(--ec-t-bite);
  transition-timing-function: linear;
}
.ec-grip[data-dragging="1"] .ec-grip__thumb {
  width: 13px; height: 23px;
  background: var(--theme-accent-2);
  /* HELD: the bloom doubles and tightens. Geometry plus light, one step up
     from hover, so press is legible without a third hue. */
  filter: drop-shadow(0 0 11px color-mix(in srgb, var(--theme-accent-2) 70%, transparent));
  transition-duration: var(--ec-t-bite);
  transition-timing-function: linear;
}

/* LATCHED — sitting on a detent. The jaw bites DOWN: full width, half height.
   Geometry and never a third hue, and never alpha either — an earlier build in
   the source pack measured a latched mark brightened by alpha at 1.63:1 and
   had to be redone by width. It is scoped to the engaged states on purpose:
   the default IS a detent, so an unscoped rule would leave a glowing clamped
   thumb sitting on every fresh window, which is the "becomes noise in ten
   seconds" failure with extra steps. */
.ec-grip[data-latched="1"]:hover .ec-grip__thumb,
.ec-grip[data-latched="1"]:focus-visible .ec-grip__thumb,
.ec-grip[data-latched="1"][data-keys="1"] .ec-grip__thumb,
.ec-grip[data-latched="1"][data-dragging="1"] .ec-grip__thumb {
  width: 20px; height: 17px;
  filter: drop-shadow(0 0 5px color-mix(in srgb, var(--theme-accent-2) 45%, transparent));
  transition-duration: var(--ec-t-latch);
  transition-timing-function: cubic-bezier(0.4, 0, 1, 1);
}

/* ── the live readout ────────────────────────────────────────────── */
/* THERE IS NO READOUT AT THE GRIP ANY MORE, and this note is here so the next
   person does not re-add one. The number moved to the status bar's readout
   plate — the same 96x18 window that names a footer button under the pointer —
   and it went for the reason the rest of this file keeps repeating: nothing
   decorative at rest, and nothing drawn over the work while the work is being
   done. A number pinned to the pointer is drawn over the board by definition,
   and it followed the hand up and down a full-height edge to stay there.

   What that deleted, in this sheet: the readout's own block, its two
   `[data-side]` offsets, its three opacity gates, its forced-colors restatement
   and the `--ec-grip-read-y` custom property the drag wrote once a frame to aim
   it. What it deleted in layout-controls.tsx: a ref, a span, a DOMRect cached at
   pointerdown and a clamp constant that existed only to keep the number off the
   ends of the rail.

   The grip still says what it is doing. `aria-valuetext` carries the number for
   anything reading the separator, the swell thickens the seam under the hand,
   and the thumb latches on a detent — none of which were ever the readout. */

/* ── the entrance ────────────────────────────────────────────────── */
/* ONE ITERATION, ALWAYS. A control that pulses indefinitely is a control that
   is not holding anything, and it becomes noise inside ten seconds; the source
   pack has four of these looping and every one of them is refused here.

   backwards and not forwards: the fill has to hold the FROM state through
   the stagger delay, but must release at the end so hover, focus and the drag
   can own filter and transform again. The 100% frame is therefore written
   to match the resting CSS exactly — if the two ever drift, the thumb will
   visibly jump the instant the entrance ends. The bloom is a blur radius
   travelling 0 -> 10 -> 0 at a fixed colour rather than a colour fading up
   from transparent, so nothing interpolates through grey. */
/* translateY(-50%) is carried in BOTH frames because the mark is centred by
   transform now. Animating a bare scaleY() would drop the centring for the
   length of the entrance and the segment would visibly slide up into place. */
@keyframes ec-grip-flash-hold { from { opacity: 1; } to { opacity: 0; } }

/* ── motion, off ─────────────────────────────────────────────────── */
/* The entrance, the settle and the bloom stop being motion and become state.
   The information survives: the grips still appear when the mode opens, the
   latched thumb is still a different shape, the reset still leaves a mark for
   half a second, and the readout still reads. Three custom properties carry
   every transition in the section, so this is four rules rather than a
   restatement of every state selector at matching specificity. */
@media (prefers-reduced-motion: reduce) {
  /* Both roots, matching the declaration above — the stop marks live in the
     sibling subtree and would otherwise keep their 130ms latch. */
  .ec-grip { --ec-t-bite: 0ms; --ec-t-settle: 0ms; --ec-t-latch: 0ms; }
  /* [data-side] and not the bare class: the two side rules that carry the
     delay also carry an attribute, so a plain .ec-grip here would lose the
     specificity tie and leave the stagger standing. It is belt and braces
     against the animation: none below, which is what actually stops the
     entrance — but a dead delay left in place is a trap for whoever adds the
     next animated child. */
  html[data-layout-glide="nudge"], html[data-layout-glide="jump"] { --ec-glide-t: 0ms; }
  /* THE APPROACH KEEPS ITS INFORMATION AND LOSES ITS MOVEMENT. Every channel
     that carries distance -- the seam's opacity, the swell's, the mark's --
     is untouched, because those are the answer to "how close am I" and
     removing them would remove the affordance rather than the motion. What
     goes is the part that is only motion: the swell stops breathing. */
  .ec-grip[data-armed="1"] .ec-grip__swell { animation: none; }
}

/* ── forced colors ───────────────────────────────────────────────── */
/* Author colour is stripped, so every mark that carries meaning has to be
   restated in a system colour or the grip vanishes entirely. The two lights
   collapse to CanvasText and Highlight, which is exactly the distinction they
   were carrying. Both glows are dropped: a drop-shadow in a system colour is
   a smear, not a state. */
@media (forced-colors: active) {
  /* 'color' as well as 'background': the graduation's ticks and both end caps
     paint with currentColor, and a forced-colors sheet that set only the
     background would leave the caps in a stripped author colour. The gradient
     itself is deliberately flattened to a solid CanvasText bar here — a
     dashed system-coloured hairline is not more legible, just noisier. */
  .ec-grip__thumb { background: CanvasText; filter: none; }
  /* Forced colours has no palette for a blurred tint -- a system-coloured blur
     reads as a smear, not a glow -- and the mark's own reveal already carries
     the approach. Both objects go: a gradient flattened to a system colour is
     a solid bar down the edge, which is precisely the seam this design exists
     to avoid drawing. */
  .ec-grip__seam, .ec-grip__swell { display: none; }
  /* ENGAGE -- size plus the second light. */
.ec-grip:hover .ec-grip__thumb,
  .ec-grip:focus-visible .ec-grip__thumb,
  .ec-grip[data-keys="1"] .ec-grip__thumb,
  .ec-grip[data-dragging="1"] .ec-grip__thumb { background: Highlight; color: Highlight; filter: none; }
  .ec-grip[data-latched="1"]:hover .ec-grip__thumb,
  .ec-grip[data-latched="1"]:focus-visible .ec-grip__thumb,
  .ec-grip[data-latched="1"][data-keys="1"] .ec-grip__thumb,
  .ec-grip[data-latched="1"][data-dragging="1"] .ec-grip__thumb { filter: none; }
}

/* ── announcements ───────────────────────────────────────────────── */
/* Visually hidden, still announced, and still out of flow — the grips' live
   region is a sibling inside a flex rail, so anything that took layout would
   add a row to it. Clipped rather than display:none, which would take it out
   of the accessibility tree along with the announcement.

   Declared HERE rather than borrowed from the panel's sheet: the panel is
   mounted whenever the grips are visible, so borrowing would work today and
   would silently take the grips' announcements down the first time somebody
   changed when the panel mounts. */
.ec-sr {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}
