@keenmate/pure-admin-core 2.9.0-rc01 → 2.9.0-rc03

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,20 +2,22 @@
2
2
 
3
3
  Lightweight, data-focused CSS/SCSS admin framework with Corporate theme as default.
4
4
 
5
- ## What's New in 2.9.0-rc01
6
-
7
- - **`color-scheme` now emitted per theme via the new `$theme-color-scheme` SCSS variable.** Themes signal their colour scheme to the browser so native UA elements (scrollbars, form controls, `<input type="date">`) and the CSS `light-dark()` function resolve correctly. Previously themes applied dark palettes via `--pa-*` / `--base-*` overrides alone, so the browser still saw the host page as `color-scheme: normal` (effectively light) embedded web components (`web-multiselect`, `web-daterangepicker`) using `light-dark()` for adaptive palettes silently picked the light value on dark themes. Themes set `$theme-color-scheme: dark` (always-dark) or add a one-line `color-scheme: dark;` to their `.pa-mode-dark` block (dual-mode). The unthemed `dist/css/main.css` now ships `color-scheme: light` at `:root`.
8
- - **Six `--base-*` legacy aliases dropped (breaking for consumers using them directly).** `--base-surface-1`, `--base-surface-2`, `--base-surface-3`, `--base-surface-inverse`, `--base-primary-bg`, `--base-primary-bg-hover` are gone they aliased to existing semantic tokens (`--base-main-bg` / `--base-page-bg` / `--base-subtle-bg` / `--base-inverse-bg`) with no behavioural difference and added taxonomy confusion. `--base-primary-bg` specifically broke `web-multiselect@1.10.0+` dark-mode hover (its smart `color-mix` fallback was short-circuited by our compile-time `#ffffff` emit). Migration: rename to the matching `--base-*-bg` semantic name; for `--base-primary-bg-hover` consumers, switch to `--base-hover-bg` or compose with `color-mix`.
9
- - **Profile panel role chip migrated to `.pa-badge` (markup-breaking).** The bespoke `.pa-profile-panel__role` class hardcoded uppercase + letter-spacing and pointed at `--pa-header-profile-name-color`, which rendered as black-on-dark in panel context. Replaced with the standard `.pa-badge` component (theme-tuned for both modes). Snippet markup updated: `<span class="pa-profile-panel__role">…</span>` `<span class="pa-badge">…</span>`. Use `.pa-badge--light` / `--primary` / `--info` / `--success` / `--warning` / `--danger` for variants.
10
- - **Coloured card variants no longer leak white pixels around the header chrome.** Two independent causes addressed together — corner-radius mismatch at the header's TOP corners (header's 8px curved more than the card's effective 7px inner radius, exposing a thin slice of card bg) and a gray hairline along the header's BOTTOM edge (border-bottom stayed `--pa-border-color` on coloured variants). The header now omits `border-top-*-radius` (card's `overflow: hidden` clips it) and each variant overrides `border-bottom-color` to match its background. Affects `.pa-card--primary` / `--success` / `--warning` / `--danger` and `.pa-card--color-1` through `--color-9`.
11
-
12
- ## What's New in 2.8.0
13
-
14
- - **CSS variable defaults now ship at `:root` in `dist/css/main.css`.** The unthemed bundle previously had no `--pa-*` / `--base-*` values — they were emitted only by theme stylesheets, so consuming `@keenmate/pure-admin-core/css` standalone (or any page before its theme link resolved) left tokens unresolved and components rendered with broken fallbacks (KPI sparklines and deltas in near-black via inherited text colour, web components reverting to hardcoded literals). `main.scss` now emits `output-base-css-variables`, `output-pa-css-variables`, and `output-pa-alert-variables-light` at `:root` for a complete neutral default. Themes are unaffected — they emit their own `:root` from their theme file and all 15 themes rebuild byte-identical. Supersedes the partial 2.7.1-era sentiment-scale fallback patch.
15
- - **KPI layout modifiers and CSS-variable knobs across four showcases.** `.pa-kpi-spark-list--no-delta` drops the Δ% column (with track widths now lifted to local SCSS variables for one source of truth); `.pa-kpi-hero-list__layout--hero-2-3` / `--hero-3-4` shift the hero/rail split off the locked 50/50; `.pa-kpi-bento__grid--hero-right` mirrors the default and `--5-tile` swaps to a five-tile composition, plus a new `--pa-kpi-bento-row-height` variable for tuning row height per instance.
16
- - **KPI grids redesigned as cell-min-driven auto-fit (Visual breaking).** `.pa-kpi-gauge-list__grid` and `.pa-kpi-edit__grid` swap fixed `repeat(N, 1fr)` + `@container` breakpoints for `repeat(auto-fit, minmax(var(--pa-kpi-X-cell-min, …), 1fr))` intrinsic responsive cascade, no `@container` queries on the grids. Five new cap-at-N modifiers (`--max-2` through `--max-6`) constrain column count without forcing it; a `--2col` modifier forces a deterministic 2×N rhythm. Migration: bare grids with fixed item counts may produce uneven rows at widths that fit 4+ columns add the appropriate `--max-N` to preserve a fixed rhythm.
17
- - **`.pa-kpi-terminal` view-mode toggle generalised to a tab strip (Visual breaking).** `__viewtoggle`/`__viewbtn` renamed to `__tabs`/`__tab`; `data-view` attribute on the root is gone in favour of `data-tab` on tabs and panes; the per-tile `__value[data-mode]` triple-value mechanism is removed. Each tab now swaps in a separate `__pane` with its own tile set and grid layout authors can put a different number of tiles (and a different grid modifier) behind each tab. JS contract: `initTerminalViewToggle` `initTerminalTabs`.
18
- - **`.pa-kpi-strip` column toggles extended to a composable 2–5 column family.** Three independently composable toggle modifiers (`--no-prev`, `--no-delta`, `--no-target`) replace the previous fixed 4/5-col shapes, yielding eight visible-column combinations. Per-column header classes (`__head--metric`/`--now`/`--prev`/`--delta`/`--target`) keep the header row in sync with the data rows. Track widths lifted to local SCSS variables so the eight precomputed templates share one source of truth.
5
+ ## What's New in 2.9.0-rc03
6
+
7
+ - **`.pa-splitter` drag model reworked to "rebalance-on-drag".** Each gutter now resizes only its primary neighbour (edge-closer side, LTR/RTL tiebreaker on ties); the matching delta is absorbed by the immediate adjacent non-rail pane (classic boundary feel), or when the adjacent pane is rail tunnels through the rail wall to the contiguous non-rail block beyond. Eliminates the "boundary locks when both sides are rail" deadlock from the prior model drag-from-rail with a railed neighbour now grows the primary by pulling room from the next section.
8
+ - **Middle panes can now minimize to rail** (previously first / last only). Any pane with `data-pa-splitter-minimize` collapses on toggle / dblclick / drag-snap regardless of position. Snap-into-rail enforces an "at least one expanded pane" invariant you can't drag the last visible pane to rail and end up with nothing to grab.
9
+ - **Restoring a minimized pane reclaims layout gaps cleanly.** When a prior minimize hit an absorber's `max` cap and left empty space in the layout, restoring the pane now reaches its remembered size by drawing from the gap (not just from other panes' shrink headroom) fixes the "restored pane stuck at min width" surprise. Conversely, restoring no longer aggressively inflates a sibling pane to fill remaining gap — empty space stays as empty space and gets consumed as more panes restore. No more "sibling pane suddenly jumps" UX.
10
+ - **Rail width in `--pa-splitter-rail-size` now correctly resolves `rem` / non-px units.** Previously `parseFloat("4rem")` returned `4` and snapped rail panes to 4px-wide strips; rebuilt via a hidden probe element so the browser does the unit resolution. The three-step fallback chain (per-instance attribute CSS var literal 40) still applies.
11
+ - **Gutter highlighting now isolates the dragged gutter only.** During a drag, the splitter `--dragging` modifier suppresses `:hover` / `:focus-visible` on sibling gutters so they don't light up as the cursor passes over them or as a stale focus ring lingers — only the active gutter carries the highlight.
12
+ - **Splitter padding subtracted from available space.** Flex children sit inside the splitter's content box but `clientWidth` includes padding — without subtracting it the last pane's `flex-basis` overflowed and got clipped by `overflow: hidden`. Visible in the N-pane demo where the rightmost rail's right edge touched the splitter border.
13
+
14
+ ## What's New in 2.9.0-rc02
15
+
16
+ - **`.pa-splitter` resizable container with optional collapse-to-rail.** Vanilla JS + SCSS, two orientations (`--horizontal` / `--vertical`), per-instance constraints (`min-start` / `max-start` accept px or %), and `localStorage` persistence via `data-pa-splitter-id`. Pointer-event drag (mouse / touch / pen), `gap`-aware constraint math, keyboard a11y on the gutter (`role="separator"`, arrow keys, `Home`/`End`, `Enter`/`Space` to toggle), and a `ResizeObserver`-driven re-clamp on container resize. Opt-in `data-pa-splitter-minimize="start" | "end"` collapses a pane to a thin sideways-rotated header rail; dragging past a snap threshold commits, dragging back outward restores.
17
+ - **`.pa-splitter` N-pane mode (3+ panes).** `init()` is now a dispatcher: the legacy 2-pane path (selected by `pane--start` + `pane--end` markup) is preserved byte-identical; everything else goes through `initNPane()`. Per-pane API: `data-pa-splitter-size="240px|30%"` for initial sizing, `-min` / `-max` for per-pane clamps, `data-pa-splitter-minimize` as presence marker (first / last pane only middle-pane rail rotation has nowhere clean to dock). Drag math is per-gutter stop-at-min (Split.js basic-mode semantics). Container-resize redistributes proportionally to unminimized panes; minimized panes stay pinned to rail. Storage shape versioned under the same `pa-splitter:<id>` key.
18
+ - **`pa-card__actions--responsive` CSS-only collapse to a split button when the header runs out of space.** Render both forms inline (`pa-card__actions-full` + `pa-card__actions-collapsed`, the latter hosting a `pa-btn-split`); a container query on `.pa-card__header:has(> .pa-card__actions--responsive)` swaps which is visible. Threshold lives in one SCSS variable (`$card-actions-collapse-at`, default `28rem`). No JS, no `ResizeObserver` but action data is duplicated in the DOM.
19
+ - **`pa-card__actions--overflow` — JS-driven progressive collapse into a "..." menu.** Complement to `--responsive`: walks buttons into an overflow menu one at a time as space shrinks, restoring them in original DOM order as it grows. `data-pa-actions-overflow-from="end" | "start"` controls drop direction; `data-pa-actions-priority="N"` per button pins primary actions. Menu reuses `pa-btn-split__menu` styling (one menu source across split-button and overflow). Positioning via Floating UI (`computePosition` + `offset` + `flip` + `shift` + `autoUpdate`), with a hand-rolled fallback when `FloatingUIDOM` isn't loaded. Auto-closes on any wrapper resize or when the trigger goes `display: none` (e.g. splitter rail mode).
20
+ - **`pa-btn--ghost` variant defined in core.** Previously referenced by demo files (`alerts`, `notifications`, `splitter`) but had no SCSS rule — every usage silently rendered as a base `.pa-btn`. Now defined in `_buttons.scss`: transparent background and border, `var(--pa-text-secondary)` text, hover snaps to `var(--pa-surface-hover)` + `--pa-text-color-1`. No new tokens introduced; themes need a rebuild to pick up the variant.
19
21
 
20
22
  ## Installation
21
23
 
package/dist/css/main.css CHANGED
@@ -5484,6 +5484,12 @@ body:not(.sidebar-hidden) .pa-layout__sidebar--icon-collapse .pa-sidebar__icon {
5484
5484
  .pa-card__header .pa-btn-group {
5485
5485
  flex-shrink: 0;
5486
5486
  }
5487
+ .pa-card__header .pa-card__actions--overflow {
5488
+ flex-shrink: 1;
5489
+ }
5490
+ .pa-card__header:has(> .pa-card__actions--overflow) > .pa-card__title {
5491
+ min-width: 6rem;
5492
+ }
5487
5493
  .pa-card__header .pa-btn {
5488
5494
  margin-top: -0.25rem;
5489
5495
  margin-bottom: -0.25rem;
@@ -5593,6 +5599,25 @@ body:not(.sidebar-hidden) .pa-layout__sidebar--icon-collapse .pa-sidebar__icon {
5593
5599
  gap: 0.8rem;
5594
5600
  align-items: center;
5595
5601
  }
5602
+ .pa-card__actions--responsive > .pa-card__actions-full {
5603
+ display: flex;
5604
+ gap: 0.8rem;
5605
+ align-items: center;
5606
+ }
5607
+ .pa-card__actions--responsive > .pa-card__actions-collapsed {
5608
+ display: none;
5609
+ }
5610
+ .pa-card__actions--overflow {
5611
+ min-width: 0;
5612
+ overflow: hidden;
5613
+ }
5614
+ .pa-card__actions--overflow > * {
5615
+ flex-shrink: 0;
5616
+ }
5617
+ .pa-card__header:has(> .pa-card__actions--responsive) {
5618
+ container-type: inline-size;
5619
+ container-name: pa-card-header;
5620
+ }
5596
5621
  .pa-card__meta {
5597
5622
  color: var(--pa-text-color-2);
5598
5623
  font-size: 1.4rem;
@@ -5922,6 +5947,48 @@ a.pa-card p {
5922
5947
  padding-bottom: 0.8rem;
5923
5948
  }
5924
5949
 
5950
+ @container pa-card-header (max-width: 28rem) {
5951
+ .pa-card__actions--responsive > .pa-card__actions-full {
5952
+ display: none;
5953
+ }
5954
+ .pa-card__actions--responsive > .pa-card__actions-collapsed {
5955
+ display: flex;
5956
+ align-items: center;
5957
+ }
5958
+ }
5959
+ .pa-splitter__pane--minimized > .pa-card {
5960
+ height: 100%;
5961
+ display: flex;
5962
+ flex-direction: column;
5963
+ margin: 0;
5964
+ }
5965
+ .pa-splitter__pane--minimized > .pa-card > .pa-card__body,
5966
+ .pa-splitter__pane--minimized > .pa-card > .pa-card__footer {
5967
+ display: none;
5968
+ }
5969
+ .pa-splitter__pane--minimized > .pa-card > .pa-card__header {
5970
+ flex: 1 1 auto;
5971
+ justify-content: flex-start;
5972
+ writing-mode: sideways-rl;
5973
+ }
5974
+ .pa-splitter__pane--minimized > .pa-card > .pa-card__header i,
5975
+ .pa-splitter__pane--minimized > .pa-card > .pa-card__header svg {
5976
+ writing-mode: initial;
5977
+ }
5978
+ .pa-splitter__pane--minimized > .pa-card > .pa-card__header .pa-card__title {
5979
+ padding-top: calc((4rem - 1.6rem) / 2 - 0.5rem);
5980
+ }
5981
+ .pa-splitter__pane--minimized > .pa-card > .pa-card__header .pa-btn,
5982
+ .pa-splitter__pane--minimized > .pa-card > .pa-card__header .pa-btn-group,
5983
+ .pa-splitter__pane--minimized > .pa-card > .pa-card__header button,
5984
+ .pa-splitter__pane--minimized > .pa-card > .pa-card__header input {
5985
+ display: none;
5986
+ }
5987
+
5988
+ .pa-splitter--minimize-mirror .pa-splitter__pane--minimized > .pa-card > .pa-card__header :is(h1, h2, h3, h4, h5, h6) {
5989
+ transform: scale(-1, -1);
5990
+ }
5991
+
5925
5992
  /* ========================================
5926
5993
  Tabs Component
5927
5994
  Standalone tabs for organizing content into separate sections
@@ -7306,6 +7373,15 @@ a.pa-card p {
7306
7373
  background-color: var(--pa-main-bg);
7307
7374
  border-color: var(--pa-accent);
7308
7375
  }
7376
+ .pa-btn--ghost {
7377
+ background-color: transparent;
7378
+ border-color: transparent;
7379
+ color: var(--pa-text-secondary);
7380
+ }
7381
+ .pa-btn--ghost:hover {
7382
+ background-color: var(--pa-surface-hover);
7383
+ color: var(--pa-text-color-1);
7384
+ }
7309
7385
  .pa-btn--xs {
7310
7386
  height: 3.1rem;
7311
7387
  padding: 0.6rem 0.8rem;
@@ -15242,6 +15318,138 @@ code {
15242
15318
  transform: rotate(180deg);
15243
15319
  }
15244
15320
 
15321
+ /* ========================================
15322
+ Splitter Component
15323
+ Resizable container with draggable gutters (2 or more panes).
15324
+ Modifiers:
15325
+ --horizontal panes side-by-side, vertical gutter (default)
15326
+ --vertical panes stacked, horizontal gutter
15327
+ --dragging applied to root while user is dragging
15328
+ --collapsed start pane has been collapsed to 0
15329
+ --minimize-mirror flip rail-title text 180° (bottom-to-top reading)
15330
+
15331
+ Rail mode is fully decoupled from any specific component. The splitter
15332
+ only rotates elements marked `[data-pa-splitter-rail-title]` inside a
15333
+ minimized pane; per-component adaptation (e.g. card body / footer
15334
+ hiding) lives in that component's SCSS, keyed off
15335
+ `.pa-splitter__pane--minimized > .pa-foo`. Card integration is in
15336
+ `_cards.scss`.
15337
+ ======================================== */
15338
+ :root {
15339
+ --pa-splitter-rail-size: 4rem;
15340
+ }
15341
+
15342
+ .pa-splitter {
15343
+ display: flex;
15344
+ width: 100%;
15345
+ height: 100%;
15346
+ min-width: 0;
15347
+ min-height: 0;
15348
+ overflow: hidden;
15349
+ }
15350
+ .pa-splitter--horizontal {
15351
+ flex-direction: row;
15352
+ }
15353
+ .pa-splitter--horizontal > .pa-splitter__gutter {
15354
+ cursor: col-resize;
15355
+ width: var(--pa-splitter-gutter-size, 0.6rem);
15356
+ height: auto;
15357
+ align-self: stretch;
15358
+ }
15359
+ .pa-splitter--horizontal > .pa-splitter__gutter::before {
15360
+ width: 2px;
15361
+ height: 2.4rem;
15362
+ }
15363
+ .pa-splitter--vertical {
15364
+ flex-direction: column;
15365
+ }
15366
+ .pa-splitter--vertical > .pa-splitter__gutter {
15367
+ cursor: row-resize;
15368
+ width: auto;
15369
+ height: var(--pa-splitter-gutter-size, 0.6rem);
15370
+ align-self: stretch;
15371
+ }
15372
+ .pa-splitter--vertical > .pa-splitter__gutter::before {
15373
+ width: 2.4rem;
15374
+ height: 2px;
15375
+ }
15376
+ .pa-splitter--dragging {
15377
+ user-select: none;
15378
+ }
15379
+ .pa-splitter--dragging > .pa-splitter__pane {
15380
+ pointer-events: none;
15381
+ }
15382
+ .pa-splitter--dragging > .pa-splitter__gutter:not(.pa-splitter__gutter--active) {
15383
+ pointer-events: none;
15384
+ }
15385
+ .pa-splitter--dragging > .pa-splitter__gutter:not(.pa-splitter__gutter--active):focus-visible {
15386
+ box-shadow: none;
15387
+ }
15388
+ .pa-splitter--dragging > .pa-splitter__gutter:not(.pa-splitter__gutter--active):focus-visible::before {
15389
+ background-color: rgba(128, 128, 128, 0.45);
15390
+ }
15391
+ .pa-splitter__gutter--active {
15392
+ background-color: rgba(0, 123, 255, 0.35);
15393
+ }
15394
+ .pa-splitter__gutter--active::before {
15395
+ background-color: #007bff;
15396
+ }
15397
+ .pa-splitter__pane {
15398
+ overflow: auto;
15399
+ min-width: 0;
15400
+ min-height: 0;
15401
+ }
15402
+ .pa-splitter__pane--start {
15403
+ flex: 0 0 auto;
15404
+ }
15405
+ .pa-splitter__pane--end {
15406
+ flex: 1 1 0;
15407
+ }
15408
+ .pa-splitter__pane--minimized {
15409
+ cursor: pointer;
15410
+ overflow: hidden;
15411
+ }
15412
+ .pa-splitter__pane--minimized [data-pa-splitter-rail-title] {
15413
+ writing-mode: sideways-rl;
15414
+ }
15415
+ .pa-splitter__pane--minimized [data-pa-splitter-rail-title] i,
15416
+ .pa-splitter__pane--minimized [data-pa-splitter-rail-title] svg {
15417
+ writing-mode: initial;
15418
+ }
15419
+ .pa-splitter--minimize-mirror .pa-splitter__pane--minimized [data-pa-splitter-rail-title] :is(h1, h2, h3, h4, h5, h6) {
15420
+ transform: scale(-1, -1);
15421
+ }
15422
+ .pa-splitter__gutter {
15423
+ position: relative;
15424
+ flex: 0 0 auto;
15425
+ background-color: rgba(128, 128, 128, 0.08);
15426
+ transition: background-color 0.15s ease;
15427
+ touch-action: none;
15428
+ outline: none;
15429
+ }
15430
+ .pa-splitter__gutter::before {
15431
+ content: "";
15432
+ position: absolute;
15433
+ top: 50%;
15434
+ left: 50%;
15435
+ transform: translate(-50%, -50%);
15436
+ background-color: rgba(128, 128, 128, 0.45);
15437
+ border-radius: 2px;
15438
+ transition: background-color 0.15s ease;
15439
+ }
15440
+ .pa-splitter__gutter:hover {
15441
+ background-color: rgba(128, 128, 128, 0.18);
15442
+ }
15443
+ .pa-splitter__gutter:hover::before {
15444
+ background-color: #007bff;
15445
+ }
15446
+ .pa-splitter__gutter:focus-visible {
15447
+ box-shadow: inset 0 0 0 2px rgba(0, 123, 255, 0.4);
15448
+ }
15449
+ .pa-splitter__gutter:focus-visible::before {
15450
+ background-color: #007bff;
15451
+ }
15452
+
15245
15453
  /* ========================================
15246
15454
  Data Display Components
15247
15455
  Read-only label-value field pairs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keenmate/pure-admin-core",
3
- "version": "2.9.0-rc01",
3
+ "version": "2.9.0-rc03",
4
4
  "description": "Lightweight, data-focused HTML/CSS admin framework built with PureCSS foundation and comprehensive component system",
5
5
  "style": "dist/css/main.css",
6
6
  "exports": {
@@ -0,0 +1,210 @@
1
+ <!-- ================================
2
+ SPLITTER
3
+ Pure Admin Visual Framework
4
+
5
+ Resizable container with two or more panes. Drag a gutter to resize,
6
+ double-click to collapse / restore, arrow keys nudge size when the
7
+ gutter is focused.
8
+
9
+ CSS + structure here; drag, keyboard, persistence behaviour is wired up
10
+ by packages/core/src/js/splitter.js. The script auto-initializes on
11
+ [data-pa-splitter] at DOMContentLoaded.
12
+
13
+ Two markup flavours:
14
+ * 2-pane shorthand — uses --start / --end modifiers and root-level
15
+ min-start / max-start / default attributes (sections 1–3 below).
16
+ * N-pane (N ≥ 2) — generic panes with per-pane size / min / max
17
+ attributes (section 4 below). Use this when you have 3+ panes
18
+ or want more than one minimizable pane.
19
+
20
+ Orientation modifiers (match flexbox direction of the panes):
21
+ --horizontal panes side-by-side, vertical gutter (default)
22
+ --vertical panes stacked, horizontal gutter
23
+
24
+ The splitter takes whatever width/height its parent gives it — make
25
+ sure the parent has a sized cross axis (height for --horizontal,
26
+ width for --vertical) or the panes will collapse.
27
+ ================================ -->
28
+
29
+
30
+ <!-- ================================
31
+ HORIZONTAL SPLIT
32
+ Side-by-side panes; vertical gutter. Start pane has a 200px floor
33
+ and 60% ceiling, defaults to 280px, persists under "demo-sidebar".
34
+ ================================ -->
35
+
36
+ <div class="pa-splitter pa-splitter--horizontal"
37
+ data-pa-splitter
38
+ data-pa-splitter-id="demo-sidebar"
39
+ data-pa-splitter-min-start="200px"
40
+ data-pa-splitter-max-start="60%"
41
+ data-pa-splitter-default="280px"
42
+ style="height: 400px;">
43
+ <div class="pa-splitter__pane pa-splitter__pane--start">
44
+ <!-- Your sidebar / list / nav content -->
45
+ </div>
46
+ <div class="pa-splitter__gutter"
47
+ role="separator"
48
+ aria-orientation="vertical"
49
+ tabindex="0"></div>
50
+ <div class="pa-splitter__pane pa-splitter__pane--end">
51
+ <!-- Your main / detail content -->
52
+ </div>
53
+ </div>
54
+
55
+
56
+ <!-- ================================
57
+ VERTICAL SPLIT
58
+ Stacked panes; horizontal gutter. Useful for editor + console / log
59
+ pane patterns.
60
+ ================================ -->
61
+
62
+ <div class="pa-splitter pa-splitter--vertical"
63
+ data-pa-splitter
64
+ data-pa-splitter-id="demo-console"
65
+ data-pa-splitter-min-start="80px"
66
+ data-pa-splitter-max-start="80%"
67
+ data-pa-splitter-default="60%"
68
+ style="height: 400px;">
69
+ <div class="pa-splitter__pane pa-splitter__pane--start">
70
+ <!-- Editor / primary content -->
71
+ </div>
72
+ <div class="pa-splitter__gutter"
73
+ role="separator"
74
+ aria-orientation="horizontal"
75
+ tabindex="0"></div>
76
+ <div class="pa-splitter__pane pa-splitter__pane--end">
77
+ <!-- Console / log / secondary -->
78
+ </div>
79
+ </div>
80
+
81
+
82
+ <!-- ================================
83
+ SPACED CARDS
84
+ Set `gap` on the splitter root to put breathing room between the
85
+ panes and the gutter — the JS subtracts it from the available
86
+ space so percent constraints still resolve correctly. Override
87
+ gutter thickness via the --pa-splitter-gutter-size custom property.
88
+ ================================ -->
89
+
90
+ <div class="pa-splitter pa-splitter--horizontal"
91
+ data-pa-splitter
92
+ style="height: 280px; gap: 1.6rem; --pa-splitter-gutter-size: 1rem;">
93
+ <div class="pa-splitter__pane pa-splitter__pane--start" style="padding: 0;">
94
+ <div class="pa-card" style="height: 100%; margin: 0;">…</div>
95
+ </div>
96
+ <div class="pa-splitter__gutter"
97
+ role="separator"
98
+ aria-orientation="vertical"
99
+ tabindex="0"></div>
100
+ <div class="pa-splitter__pane pa-splitter__pane--end" style="padding: 0;">
101
+ <div class="pa-card" style="height: 100%; margin: 0;">…</div>
102
+ </div>
103
+ </div>
104
+
105
+
106
+ <!-- ================================
107
+ N-PANE LAYOUT (3 panes — sidebar + content + inspector)
108
+ Per-pane attributes replace the root-level --start ones. Only the
109
+ first and last panes honour the minimize marker. Panes without a
110
+ data-pa-splitter-size share the leftover equally.
111
+ ================================ -->
112
+
113
+ <div class="pa-splitter pa-splitter--horizontal"
114
+ data-pa-splitter
115
+ data-pa-splitter-id="demo-three-pane"
116
+ style="height: 400px;">
117
+ <div class="pa-splitter__pane"
118
+ data-pa-splitter-size="240px"
119
+ data-pa-splitter-min="180px"
120
+ data-pa-splitter-max="360px"
121
+ data-pa-splitter-minimize>
122
+ <!-- File tree / nav (minimizable to a left rail) -->
123
+ </div>
124
+ <div class="pa-splitter__gutter"
125
+ role="separator"
126
+ aria-orientation="vertical"
127
+ tabindex="0"></div>
128
+ <div class="pa-splitter__pane"
129
+ data-pa-splitter-min="240px">
130
+ <!-- Editor / main content (fills the rest) -->
131
+ </div>
132
+ <div class="pa-splitter__gutter"
133
+ role="separator"
134
+ aria-orientation="vertical"
135
+ tabindex="0"></div>
136
+ <div class="pa-splitter__pane"
137
+ data-pa-splitter-size="280px"
138
+ data-pa-splitter-min="220px"
139
+ data-pa-splitter-max="420px"
140
+ data-pa-splitter-minimize>
141
+ <!-- Inspector / detail (minimizable to a right rail) -->
142
+ </div>
143
+ </div>
144
+
145
+
146
+ <!-- ================================
147
+ DATA ATTRIBUTES — root (both modes)
148
+ data-pa-splitter marker; required
149
+ data-pa-splitter-id enables localStorage persistence
150
+ under "pa-splitter:<id>"
151
+ data-pa-splitter-step keyboard step in px (default: 10)
152
+ data-pa-splitter-rail-size rail width in px (default: 40)
153
+ data-pa-splitter-minimize-threshold drag-to-rail snap ratio (default: 0.40)
154
+
155
+ DATA ATTRIBUTES — root (legacy 2-pane only)
156
+ data-pa-splitter-min-start "200px" or "20%" (default: 0)
157
+ data-pa-splitter-max-start "60%" or "800px" (default: available)
158
+ data-pa-splitter-default initial size when no saved state
159
+ data-pa-splitter-minimize "start" or "end" — opt into rail collapse
160
+
161
+ DATA ATTRIBUTES — per pane (N-pane only)
162
+ data-pa-splitter-size "240px" or "30%" (default: shared leftover)
163
+ data-pa-splitter-min "150px" or "10%" (default: 0)
164
+ data-pa-splitter-max "400px" or "50%" (default: available)
165
+ data-pa-splitter-minimize marker — only honoured on the first
166
+ and last panes
167
+
168
+ CHILD ATTRIBUTES (both modes)
169
+ data-pa-splitter-toggle put on any element inside the splitter
170
+ (typically a button in the card header)
171
+ to trigger collapse / restore on click.
172
+ In N-pane mode, toggles the enclosing
173
+ pane (if it's minimizable).
174
+
175
+ RAIL-TITLE HOOK (non-card content)
176
+ Any element marked with `[data-pa-splitter-rail-title]` inside a
177
+ minimized pane rotates to vertical writing (sideways-rl). Use this
178
+ when you put plain content (not a `.pa-card`) in a minimizable pane:
179
+
180
+ <div class="pa-splitter__pane" data-pa-splitter-minimize>
181
+ <div data-pa-splitter-rail-title>📁 Files</div>
182
+ <!-- The rest of your pane content here -->
183
+ </div>
184
+
185
+ Cards adapt automatically — `_cards.scss` rotates the card header
186
+ inside any `.pa-splitter__pane--minimized` without needing the
187
+ attribute. No markup change needed for the card snippets above.
188
+
189
+ CSS CUSTOMIZATION
190
+ --pa-splitter-gutter-size gutter thickness (default: 6px)
191
+ --pa-splitter-rail-size rail width when a pane is minimized
192
+ (default: 40px / $splitter-rail-size).
193
+ JS picks this up via getComputedStyle, so
194
+ setting it on `:root` or per-instance via
195
+ inline style overrides the rail width
196
+ without also setting the data-attr.
197
+ gap space between panes and gutter
198
+ (native flexbox property)
199
+
200
+ MODIFIER CLASSES
201
+ pa-splitter--minimize-mirror flip the minimized title 180°
202
+ (transform: scale(-1,-1) on the heading
203
+ inside any `[data-pa-splitter-rail-title]`
204
+ or `.pa-card__header`)
205
+
206
+ JAVASCRIPT API (window.PaSplitter)
207
+ PaSplitter.init(el) initialize a single element (idempotent)
208
+ PaSplitter.initAll(root?) initialize all uninitialized splitters
209
+ under root (default: document)
210
+ ================================ -->
@@ -111,6 +111,9 @@
111
111
  // Settings Panel
112
112
  @use 'core-components/settings-panel' as *;
113
113
 
114
+ // Splitter (resizable two-pane container)
115
+ @use 'core-components/splitter' as *;
116
+
114
117
  // Data Display (read-only fields)
115
118
  @use 'core-components/data-display' as *;
116
119
 
@@ -44,6 +44,21 @@
44
44
  }
45
45
  }
46
46
 
47
+ // Chromeless action button — no fill, no border, just the label / icon.
48
+ // Used for inline affordances inside dense surfaces (card-header actions,
49
+ // notification dismiss, toast close, splitter minimize) where a full button
50
+ // would compete with the content.
51
+ &--ghost {
52
+ background-color: transparent;
53
+ border-color: transparent;
54
+ color: var(--pa-text-secondary);
55
+
56
+ &:hover {
57
+ background-color: var(--pa-surface-hover);
58
+ color: var(--pa-text-color-1);
59
+ }
60
+ }
61
+
47
62
  &--xs {
48
63
  height: $btn-height-xs;
49
64
  padding: $btn-padding-xs-v $btn-padding-xs-h;
@@ -100,6 +100,24 @@
100
100
  flex-shrink: 0;
101
101
  }
102
102
 
103
+ // …unless it's the progressive-overflow variant, which intentionally
104
+ // shrinks below content so the JS can read `scrollWidth > clientWidth`
105
+ // as the "needs overflow" signal. Nested under `&__header` so the
106
+ // selector matches `.pa-card__header .pa-card__actions--overflow` —
107
+ // the same specificity as the rule above, but later in source order
108
+ // and explicit on the modifier.
109
+ .pa-card__actions--overflow {
110
+ flex-shrink: 1;
111
+ }
112
+
113
+ // When the actions wrapper can shrink (overflow variant), give the title
114
+ // a floor so it can't be squeezed to 0 — icon + a few chars + ellipsis
115
+ // stay visible no matter how narrow. `:has()` scopes the override so
116
+ // ordinary cards keep `min-width: 0` and their full ellipsis range.
117
+ &:has(> .pa-card__actions--overflow) > .pa-card__title {
118
+ min-width: 6rem;
119
+ }
120
+
103
121
  // Buttons in card headers - negative margin to prevent header height growth
104
122
  .pa-btn {
105
123
  margin-top: -0.25rem;
@@ -237,6 +255,61 @@
237
255
  display: flex;
238
256
  gap: $spacing-sm;
239
257
  align-items: center;
258
+
259
+ // Responsive actions — render both the spread button list AND a
260
+ // collapsed split-button form; a container query on the header swaps
261
+ // which is visible. Stays CSS-only (no ResizeObserver / layout reads)
262
+ // but the markup carries both forms.
263
+ //
264
+ // <div class="pa-card__header">
265
+ // <div class="pa-card__title">…</div>
266
+ // <div class="pa-card__actions pa-card__actions--responsive">
267
+ // <div class="pa-card__actions-full">
268
+ // …spread buttons…
269
+ // </div>
270
+ // <div class="pa-card__actions-collapsed">
271
+ // <div class="pa-btn-split">…same actions as a menu…</div>
272
+ // </div>
273
+ // </div>
274
+ // </div>
275
+ //
276
+ // Container is the header; threshold is $card-actions-collapse-at.
277
+ // Wired up via :has() (Baseline 2023) so authors don't need a second
278
+ // modifier on the header.
279
+ &--responsive {
280
+ > .pa-card__actions-full {
281
+ display: flex;
282
+ gap: $spacing-sm;
283
+ align-items: center;
284
+ }
285
+ > .pa-card__actions-collapsed {
286
+ display: none;
287
+ }
288
+ }
289
+
290
+ // Progressive overflow variant — driven by card-actions-overflow.js.
291
+ // `min-width: 0` lets flex shrink the wrapper below its content; with
292
+ // `overflow: hidden` the wrapper clips its children when narrow, which
293
+ // is what makes `scrollWidth > clientWidth` a reliable overflow signal
294
+ // for the JS. Buttons themselves get `flex-shrink: 0` so they don't
295
+ // squeeze to fit — they either fit fully or get moved to the menu.
296
+ &--overflow {
297
+ min-width: 0;
298
+ overflow: hidden;
299
+
300
+ > * {
301
+ flex-shrink: 0;
302
+ }
303
+ }
304
+ }
305
+
306
+ // Enable the container query on any header that contains a responsive
307
+ // actions wrapper. Container query rule itself is emitted at the bottom
308
+ // of this file (outside the .pa-card block) since `@container` can't
309
+ // hide inside an arbitrarily-nested selector cleanly.
310
+ &__header:has(> .pa-card__actions--responsive) {
311
+ container-type: inline-size;
312
+ container-name: pa-card-header;
240
313
  }
241
314
 
242
315
  &__meta {
@@ -499,3 +572,79 @@ a.pa-card {
499
572
  border-bottom: $border-width-medium solid var(--pa-accent);
500
573
  padding-bottom: $spacing-sm;
501
574
  }
575
+
576
+ // Responsive card actions — container query for the spread/collapsed swap.
577
+ // Container context set by `.pa-card__header:has(> .pa-card__actions--responsive)`
578
+ // above. Threshold needs explicit interpolation in `@container` (the Sass
579
+ // auto-interpolation that works in `@media` doesn't apply here).
580
+ @container pa-card-header (max-width: #{$card-actions-collapse-at}) {
581
+ .pa-card__actions--responsive {
582
+ > .pa-card__actions-full {
583
+ display: none;
584
+ }
585
+ > .pa-card__actions-collapsed {
586
+ display: flex;
587
+ align-items: center;
588
+ }
589
+ }
590
+ }
591
+
592
+ // Card adaptation inside a minimized splitter pane.
593
+ // Lives here (not in `_splitter.scss`) so the splitter stays unaware of card
594
+ // internals — the rotation hook on the splitter side is `[data-pa-splitter-rail-title]`
595
+ // and this rule applies that attribute's contract to the card header.
596
+ //
597
+ // Layout: pa-card already has `height: 100%` etc., we just hide body/footer,
598
+ // promote the header to fill the rail, and pin the title to the top.
599
+ .pa-splitter__pane--minimized > .pa-card {
600
+ height: 100%;
601
+ display: flex;
602
+ flex-direction: column;
603
+ margin: 0;
604
+
605
+ > .pa-card__body,
606
+ > .pa-card__footer {
607
+ display: none;
608
+ }
609
+
610
+ > .pa-card__header {
611
+ flex: 1 1 auto;
612
+ // Pin title to the rail's top (inline-start in sideways-rl writing mode).
613
+ justify-content: flex-start;
614
+ writing-mode: sideways-rl;
615
+
616
+ // Icons reset to natural orientation (descendant combinator covers both
617
+ // flat markup and the BEM `pa-card__title > i` nesting).
618
+ i,
619
+ svg {
620
+ writing-mode: initial;
621
+ }
622
+
623
+ // Vertically align the icon between expanded and minimized states.
624
+ // In expanded mode, `align-items: center` centers the title vertically
625
+ // (icon center at header-min-height / 2 from top). In minimized mode,
626
+ // `justify-content: flex-start` pins the title to the rail's top edge.
627
+ // Push the title down by the difference so the icon sits at the same
628
+ // visual y-position in both states — no "jump" when toggling.
629
+ .pa-card__title {
630
+ padding-top: calc((#{$card-header-min-height} - #{$font-size-base}) / 2 - #{$card-header-padding-v});
631
+ }
632
+
633
+ // Hide interactive controls inside the header — they'd be sideways and
634
+ // unreadable. The list here is intentional: cards know their own header
635
+ // contents better than the splitter ever could.
636
+ .pa-btn,
637
+ .pa-btn-group,
638
+ button,
639
+ input {
640
+ display: none;
641
+ }
642
+ }
643
+ }
644
+
645
+ // Mirror modifier — when the splitter root carries `pa-splitter--minimize-mirror`,
646
+ // the card heading flips 180° (matches the generic mirror behaviour, scoped
647
+ // to the card title heading).
648
+ .pa-splitter--minimize-mirror .pa-splitter__pane--minimized > .pa-card > .pa-card__header :is(h1, h2, h3, h4, h5, h6) {
649
+ transform: scale(-1, -1);
650
+ }
@@ -0,0 +1,206 @@
1
+ /* ========================================
2
+ Splitter Component
3
+ Resizable container with draggable gutters (2 or more panes).
4
+ Modifiers:
5
+ --horizontal panes side-by-side, vertical gutter (default)
6
+ --vertical panes stacked, horizontal gutter
7
+ --dragging applied to root while user is dragging
8
+ --collapsed start pane has been collapsed to 0
9
+ --minimize-mirror flip rail-title text 180° (bottom-to-top reading)
10
+
11
+ Rail mode is fully decoupled from any specific component. The splitter
12
+ only rotates elements marked `[data-pa-splitter-rail-title]` inside a
13
+ minimized pane; per-component adaptation (e.g. card body / footer
14
+ hiding) lives in that component's SCSS, keyed off
15
+ `.pa-splitter__pane--minimized > .pa-foo`. Card integration is in
16
+ `_cards.scss`.
17
+ ======================================== */
18
+ @use '../variables' as *;
19
+
20
+ // Default rail width — exposed as a runtime token so themes and per-instance
21
+ // inline styles can override without having to set both the SCSS variable
22
+ // (compile-time) and the JS attribute. The JS reads this same custom
23
+ // property via getComputedStyle to keep its default in sync.
24
+ :root {
25
+ --pa-splitter-rail-size: #{$splitter-rail-size};
26
+ }
27
+
28
+ .pa-splitter {
29
+ display: flex;
30
+ width: 100%;
31
+ height: 100%;
32
+ min-width: 0;
33
+ min-height: 0;
34
+ overflow: hidden;
35
+
36
+ // Horizontal split: panes laid out as a row, gutter is a vertical bar.
37
+ // Gutter thickness is overridable per-instance via --pa-splitter-gutter-size;
38
+ // breathing room around the gutter via native `gap` (the JS subtracts it
39
+ // from the available space, so percent constraints stay accurate).
40
+ &--horizontal {
41
+ flex-direction: row;
42
+
43
+ > .pa-splitter__gutter {
44
+ cursor: col-resize;
45
+ width: var(--pa-splitter-gutter-size, #{$splitter-gutter-size});
46
+ height: auto;
47
+ align-self: stretch;
48
+
49
+ &::before {
50
+ width: $splitter-gutter-grip-thickness;
51
+ height: $splitter-gutter-grip-length;
52
+ }
53
+ }
54
+ }
55
+
56
+ // Vertical split: panes stacked as a column, gutter is a horizontal bar.
57
+ &--vertical {
58
+ flex-direction: column;
59
+
60
+ > .pa-splitter__gutter {
61
+ cursor: row-resize;
62
+ width: auto;
63
+ height: var(--pa-splitter-gutter-size, #{$splitter-gutter-size});
64
+ align-self: stretch;
65
+
66
+ &::before {
67
+ width: $splitter-gutter-grip-length;
68
+ height: $splitter-gutter-grip-thickness;
69
+ }
70
+ }
71
+ }
72
+
73
+ // While dragging: kill text selection and transitions, neutralize pane content
74
+ // pointer events so dragging over an iframe/embedded widget doesn't get hijacked.
75
+ // Only the gutter that's actually being dragged gets the "active" highlight —
76
+ // siblings stay in their resting state. The JS adds `--active` on the dragged
77
+ // gutter in pointerdown and removes it in pointerup. Sibling gutters also get
78
+ // `pointer-events: none` so their `:hover` doesn't fire as the cursor passes
79
+ // over them during the drag (otherwise they'd light up via hover).
80
+ &--dragging {
81
+ user-select: none;
82
+
83
+ > .pa-splitter__pane {
84
+ pointer-events: none;
85
+ }
86
+
87
+ // Sibling gutters during drag: kill pointer events (no hover firing as
88
+ // cursor passes over them) AND override any lingering focus-visible
89
+ // styling (a previous keyboard focus on a non-active gutter would
90
+ // otherwise look identical to the active state — both colour the
91
+ // gutter blue).
92
+ > .pa-splitter__gutter:not(.pa-splitter__gutter--active) {
93
+ pointer-events: none;
94
+
95
+ &:focus-visible {
96
+ box-shadow: none;
97
+
98
+ &::before {
99
+ background-color: $splitter-gutter-grip-color;
100
+ }
101
+ }
102
+ }
103
+ }
104
+
105
+ &__gutter--active {
106
+ background-color: $splitter-gutter-active-bg;
107
+
108
+ &::before {
109
+ background-color: $splitter-gutter-grip-hover-color;
110
+ }
111
+ }
112
+
113
+ &__pane {
114
+ overflow: auto;
115
+ min-width: 0;
116
+ min-height: 0;
117
+
118
+ // Start pane carries the explicit size set inline by JS (flex-basis).
119
+ // flex-grow: 0 keeps it from absorbing free space; flex-shrink: 0 stops
120
+ // the browser from squishing it below the JS-applied size when content
121
+ // overflows.
122
+ &--start {
123
+ flex: 0 0 auto;
124
+ }
125
+
126
+ // End pane fills whatever's left over.
127
+ &--end {
128
+ flex: 1 1 0;
129
+ }
130
+
131
+ // Minimized state — pane locks to rail width via flex-basis (JS-set).
132
+ // The splitter's only job here is providing the rail-title rotation hook;
133
+ // it does NOT hide sibling content or know about specific components.
134
+ // Per-component adaptation (e.g. hiding card body / footer) lives in
135
+ // that component's SCSS, see `_cards.scss` for the card integration.
136
+ &--minimized {
137
+ cursor: pointer;
138
+ overflow: hidden;
139
+ }
140
+ }
141
+
142
+ // Generic rail-title hook. Any element marked `[data-pa-splitter-rail-title]`
143
+ // inside a minimized pane rotates to vertical writing. Consumers can put
144
+ // this attribute on a card header, a plain heading, a div with an icon —
145
+ // anything they want shown rotated when the pane is railed.
146
+ //
147
+ // sideways-rl: text reads top-to-bottom, glyphs rotated 90° clockwise
148
+ // (tilt head right). Script-agnostic — for Latin text this matches
149
+ // vertical-rl visually.
150
+ &__pane--minimized [data-pa-splitter-rail-title] {
151
+ writing-mode: sideways-rl;
152
+
153
+ // Keep icons upright — they inherit the parent writing-mode by default,
154
+ // which rotates the glyph along with the title. Resetting to initial
155
+ // (horizontal-tb) keeps them in their natural orientation.
156
+ i,
157
+ svg {
158
+ writing-mode: initial;
159
+ }
160
+ }
161
+
162
+ // Optional: mirror the minimized title 180° so the text reads bottom-to-top
163
+ // with upside-down glyphs. Applied to the heading element inside any
164
+ // rail-title element — padding, alignment, and sibling content are
165
+ // unaffected.
166
+ &--minimize-mirror .pa-splitter__pane--minimized [data-pa-splitter-rail-title] :is(h1, h2, h3, h4, h5, h6) {
167
+ transform: scale(-1, -1);
168
+ }
169
+
170
+ &__gutter {
171
+ position: relative;
172
+ flex: 0 0 auto;
173
+ background-color: $splitter-gutter-bg;
174
+ transition: $splitter-transition;
175
+ touch-action: none; // prevents browser scroll/zoom while dragging on touch
176
+ outline: none;
177
+
178
+ // Centered grip indicator. Width/height per orientation modifier above.
179
+ &::before {
180
+ content: '';
181
+ position: absolute;
182
+ top: 50%;
183
+ left: 50%;
184
+ transform: translate(-50%, -50%);
185
+ background-color: $splitter-gutter-grip-color;
186
+ border-radius: $border-radius-sm;
187
+ transition: $splitter-transition;
188
+ }
189
+
190
+ &:hover {
191
+ background-color: $splitter-gutter-hover-bg;
192
+
193
+ &::before {
194
+ background-color: $splitter-gutter-grip-hover-color;
195
+ }
196
+ }
197
+
198
+ &:focus-visible {
199
+ box-shadow: inset 0 0 0 $splitter-focus-ring-width $splitter-focus-ring-color;
200
+
201
+ &::before {
202
+ background-color: $splitter-gutter-grip-hover-color;
203
+ }
204
+ }
205
+ }
206
+ }
@@ -187,6 +187,11 @@ $card-tab-inline-padding-v: 0.3rem !default; // 3px - compact for header
187
187
  $card-tab-inline-padding-h: 0.8rem !default; // 8px
188
188
  $card-tab-hover-opacity: 0.1 !default;
189
189
 
190
+ // Responsive actions — header width below which the spread button list
191
+ // hides and the collapsed (split-button) form takes over. Default 28rem
192
+ // = 280px, which fits a 3-button action set comfortably.
193
+ $card-actions-collapse-at: 28rem !default;
194
+
190
195
  // ============================================================================
191
196
  // TABLE SYSTEM
192
197
  // ============================================================================
@@ -772,3 +777,20 @@ $bar-list-bar-bg: rgba(0, 0, 0, 0.06) !default;
772
777
  $bar-list-label-font-size: $font-size-sm !default;
773
778
  $bar-list-value-font-size: $font-size-sm !default;
774
779
  $bar-list-value-font-weight: $font-weight-semibold !default;
780
+
781
+ // ============================================================================
782
+ // SPLITTER SYSTEM
783
+ // ============================================================================
784
+ $splitter-gutter-size: 0.6rem !default; // 6px gutter thickness
785
+ $splitter-gutter-bg: rgba(128, 128, 128, 0.08) !default;
786
+ $splitter-gutter-hover-bg: rgba(128, 128, 128, 0.18) !default;
787
+ $splitter-gutter-active-bg: rgba($accent-color, 0.35) !default; // mid-drag
788
+ $splitter-gutter-grip-length: 2.4rem !default; // 24px grip indicator
789
+ $splitter-gutter-grip-thickness: 2px !default;
790
+ $splitter-gutter-grip-color: rgba(128, 128, 128, 0.45) !default;
791
+ $splitter-gutter-grip-hover-color: $accent-color !default;
792
+ $splitter-focus-ring-color: rgba($accent-color, 0.4) !default;
793
+ $splitter-focus-ring-width: 2px !default;
794
+ $splitter-transition: background-color 0.15s ease !default;
795
+ $splitter-pane-min-size: 0 !default; // absolute floor regardless of constraints
796
+ $splitter-rail-size: 4rem !default; // 40px — start pane width when minimized to rail