# Phase 3: Core Components - Research

**Researched:** 2026-02-27
**Domain:** SolidJS component architecture, CSS angular geometry, custom scrollbar, accessibility
**Confidence:** HIGH

## Summary

Phase 3 builds 8 foundational UI components (button, text input, label, toggle, slider, dropdown, panel, table) and cyberpunk window chrome as interactive SolidJS components consuming Phase 2's theme token system. The existing codebase already has prototype CSS utility classes (`hh-panel`, `hh-btn`, `hh-input`, `hh-divider-glow`) in `App.css` with clip-path angular geometry, pseudo-element border techniques, and glow effects. Phase 3 promotes these into proper SolidJS components with props, state management, keyboard accessibility, and size/variant systems.

The most technically challenging aspects are: (1) the window chrome glow-line perimeter with animated gradient sweep, requiring CSS `@property` for conic-gradient angle animation (fully supported in WebView2/Chromium since Chrome 85); (2) the custom scrollbar with hexagonal crystal indicator, requiring native scrollbar hiding and a custom JS-driven scroll overlay; (3) the dropdown overlay positioning, best handled with `solid-floating-ui` + SolidJS `Portal` to escape clip-path/overflow constraints.

**Primary recommendation:** Build components from scratch using SolidJS primitives (`splitProps`, `mergeProps`, `createSignal`, `createEffect`) with manual WAI-ARIA keyboard patterns. The project's highly custom visual language (clip-path geometry, pseudo-element borders, crystal scrollbars) makes headless libraries like Kobalte more overhead than help -- their DOM structure assumptions conflict with the multi-layer pseudo-element border technique. Use `solid-floating-ui` only for dropdown positioning.

<user_constraints>
## User Constraints (from CONTEXT.md)

### Locked Decisions
- All four corners have 45-degree angled slopes, medium depth (16-24px)
- Top-right corner has a horizontal notch (deep inset) where minimize/maximize/close controls float in the gap
- Glow-line perimeter (6-8px inset) uses a gradient sweep -- a traveling hotspot that slowly moves around the border. Color follows theme accent (cyan default)
- Dot-grid overlay uses hexagonal pattern with subtle parallax that responds to mouse position within the window
- Semitransparent background behind the chrome
- No traditional scrollbar -- strong glow emits inward from right edge, hollow neon crystal shape (hexagonal, pointed top/bottom) appears on mouse proximity, fills solid on click, fades on mouse leave
- Restrained cyberpunk -- functional first, themed second
- All components have subtle-yet-visible glow at rest (always alive, never obnoxious)
- Borders: thin solid neon line (1px) + soft box-shadow glow behind it
- Tables: minimal row-based layout, no vertical column lines, rows separated by subtle dividers, header row stands out
- Corner geometry tiered: large containers (deep notch), subsection panels (notched top-right), medium components (subtle angled), small components (smaller angled)
- Dividers: thicker under header text width, 45-degree angle taper to default thickness
- Hover: glow intensification + inner background gradient grows from left (border color at 25% opacity, holds max through 20% width, fades to 0% by 65% width)
- Focus: slow subtle pulsing glow on border (CSS animation)
- Active/pressed: border color floods inward as background fill, then recedes
- Disabled: glow fades to near-zero, colors desaturate to gray/muted, component is "powered off"
- Text input focus: bottom edge glows brightly (underline-style), repeating horizontal scanline bars (3px height, 5px gaps, 4% opacity) with vignette fade
- Button variants: primary (magenta, hollow), secondary (cyan, hollow), ghost (no border, text-only with hover glow)
- Component sizes: sm, md, lg (affects padding, font size, glow intensity)
- Semantic color variants: success, error, warning, info mapped to theme colors, plus optional custom accent color prop
- Dropdown: overlay popup positioning, needs z-index management

### Claude's Discretion
- Exact glow intensity values and transition durations
- Parallax sensitivity for hex dot-grid
- Scrollbar crystal detection perimeter size
- Table row hover treatment specifics
- Slider thumb and track glow details
- Toggle switch animation specifics
- Panel border vs borderless sub-variants
- Keyboard shortcut bindings beyond Tab/Enter/Space/Escape

### Deferred Ideas (OUT OF SCOPE)
None -- discussion stayed within phase scope
</user_constraints>

<phase_requirements>
## Phase Requirements

| ID | Description | Research Support |
|----|-------------|-----------------|
| WIN-01 | Cyberpunk window chrome with angled/notched corners and 30/45-degree angles | Clip-path polygon technique already proven in Phase 2's `hh-panel`. Window chrome extends with deeper notches (16-24px), top-right horizontal notch for controls. Use multi-layer pseudo-elements: border layer + fill layer + glow perimeter layer |
| WIN-02 | Glow-line perimeter running 6-8px inside window edge | CSS `@property` with `<angle>` syntax enables conic-gradient rotation for gradient sweep. `filter: drop-shadow()` on parent wrapper provides glow that follows clip-path shape. Supported in WebView2 since Chrome 85 |
| WIN-03 | Semitransparent solid color or gradient background | Already implemented pattern: `color-mix(in srgb, var(--hh-color-surface) 25%, var(--hh-raw-void-start))` in `hh-panel::after`. Extend to window chrome background layer |
| WIN-04 | Optional dot-grid overlay on window backgrounds | SVG `<pattern>` element with hexagonal dot arrangement. Parallax via CSS `transform: translate()` driven by `mousemove` event listener, applying fractional offset based on cursor position. `will-change: transform` for GPU compositing |
| COMP-01 | Buttons with hover/active/disabled/loading states | Existing `hh-btn` class provides foundation. Promote to SolidJS component with `splitProps` for component props vs DOM passthrough. Three variants (primary/secondary/ghost), three sizes (sm/md/lg), semantic colors. WAI-ARIA: native `<button>`, no extra roles needed |
| COMP-02 | Text inputs with glow-on-focus | Existing `hh-input` class as foundation. Add scanline effect on focus per CONTEXT decisions. WAI-ARIA: native `<input>`, `aria-invalid` for error states. Variants: text, password, search |
| COMP-03 | Labels and text display | Straightforward SolidJS component. Two font variants (mono via Fira Code, display via Share Tech). Map to `<label>` with `for` attribute support. Size scale matches typography tokens |
| COMP-04 | Toggles with animated glow transition | Custom toggle built on hidden `<input type="checkbox">` with styled pseudo-element track + thumb. WAI-ARIA: `role="switch"`, `aria-checked`. Keyboard: Space to toggle |
| COMP-05 | Sliders with track glow, styled thumb, value tooltip | Custom range slider. Hidden native `<input type="range">` technique or fully custom with mouse/touch handlers. WAI-ARIA: `role="slider"`, `aria-valuemin/max/now`. Keyboard: Arrow keys for value change |
| COMP-06 | Dropdowns with animated expand | Use `solid-floating-ui` + SolidJS `Portal` for overlay positioning. WAI-ARIA: listbox pattern with `role="listbox"`, `role="option"`, `aria-expanded`. Keyboard: Arrow keys for navigation, Enter to select, Escape to close |
| COMP-07 | Panels/cards with semitransparent bg and border glow | Existing `hh-panel` as foundation. Add variant props (bordered/borderless, size). Support `children` passthrough |
| COMP-08 | Tables/data grids with sortable columns, row hover, scrolling | Custom table component using semantic `<table>` elements. Minimal row-based layout per CONTEXT. WAI-ARIA: native table semantics. Sortable via header click with `aria-sort` attribute |
</phase_requirements>

## Standard Stack

### Core
| Library | Version | Purpose | Why Standard |
|---------|---------|---------|--------------|
| solid-js | ^1.9.11 | Component framework | Already installed. Reactive primitives (createSignal, createEffect, splitProps, mergeProps) are the building blocks for all components |
| tailwindcss | ^4.2.1 | Utility CSS framework | Already installed. `@theme inline` registers semantic tokens as utility classes. Components compose Tailwind utilities with hh-* custom classes |

### Supporting
| Library | Version | Purpose | When to Use |
|---------|---------|---------|-------------|
| solid-floating-ui | ^0.3.1 | Dropdown overlay positioning | For COMP-06 dropdown. Provides `useFloating` hook with flip/shift/offset middleware. Wraps @floating-ui/dom for SolidJS reactivity |
| @floating-ui/dom | ^1.6.x | Core positioning engine | Peer dependency of solid-floating-ui. Handles popup placement math |
| clsx | ^2.x | Conditional class composition | Tiny (239B) utility for merging conditional CSS classes. Widely used with SolidJS since classList has limitations with prop spreading |

### Alternatives Considered
| Instead of | Could Use | Tradeoff |
|------------|-----------|----------|
| Hand-rolled components | @kobalte/core (headless) | Kobalte provides WAI-ARIA compliance out of the box BUT imposes DOM structure assumptions that conflict with multi-layer pseudo-element border technique (::before for border, ::after for fill). Custom clip-path geometry requires precise control over element structure. Overhead > benefit for this project's visual complexity |
| solid-floating-ui | Hand-rolled positioning | Floating UI handles edge cases (viewport overflow, flip, scroll containers) that are extremely tedious to hand-roll correctly. Worth the 8KB dependency |
| clsx | No utility (manual string concat) | clsx is 239B and eliminates error-prone template literal class building. Trivial cost, real benefit |

**Installation:**
```bash
npm install solid-floating-ui @floating-ui/dom clsx
```

## Architecture Patterns

### Recommended Project Structure
```
src/
├── components/
│   ├── ui/                    # Phase 3 component library
│   │   ├── Button.tsx         # COMP-01
│   │   ├── TextInput.tsx      # COMP-02
│   │   ├── Label.tsx          # COMP-03
│   │   ├── Toggle.tsx         # COMP-04
│   │   ├── Slider.tsx         # COMP-05
│   │   ├── Dropdown.tsx       # COMP-06
│   │   ├── Panel.tsx          # COMP-07
│   │   ├── Table.tsx          # COMP-08
│   │   └── index.ts           # Barrel export
│   ├── chrome/                # Window chrome components
│   │   ├── WindowChrome.tsx   # WIN-01/02/03/04 container
│   │   ├── GlowPerimeter.tsx  # WIN-02 animated glow border
│   │   ├── DotGrid.tsx        # WIN-04 hex dot parallax overlay
│   │   ├── CyberScrollbar.tsx # Custom scrollbar
│   │   └── index.ts           # Barrel export
│   ├── Titlebar.tsx           # Existing (refactored into chrome)
│   ├── ThemeDemo.tsx          # Existing demo
│   └── ...                    # Other existing demos
├── styles/
│   └── components.css         # Component-specific CSS (extracted from App.css)
├── theme/                     # Existing theme system (Phase 2)
│   ├── primitives.css
│   ├── effects.css
│   ├── typography.css
│   ├── semantic.css
│   └── index.css
└── App.css                    # Global styles (slimmed down)
```

### Pattern 1: Component Props with splitProps
**What:** Use `splitProps` to separate component-specific props from HTML passthrough props. Use `mergeProps` for defaults.
**When to use:** Every component that accepts both custom props and native HTML attributes.
**Example:**
```typescript
// Source: SolidJS docs - splitProps, mergeProps
import { splitProps, mergeProps, JSX, Component } from 'solid-js';
import clsx from 'clsx';

type ButtonVariant = 'primary' | 'secondary' | 'ghost';
type ButtonSize = 'sm' | 'md' | 'lg';

interface ButtonProps extends JSX.ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: ButtonVariant;
  size?: ButtonSize;
  accent?: string;  // Custom accent color override
}

export const Button: Component<ButtonProps> = (rawProps) => {
  const props = mergeProps(
    { variant: 'secondary' as ButtonVariant, size: 'md' as ButtonSize },
    rawProps
  );
  const [local, rest] = splitProps(props, ['variant', 'size', 'accent', 'class', 'children']);

  return (
    <button
      class={clsx(
        'hh-btn',
        local.variant === 'primary' && 'hh-btn--primary',
        local.variant === 'ghost' && 'hh-btn--ghost',
        `hh-btn--${local.size}`,
        local.class
      )}
      style={local.accent ? { '--hh-btn-accent': local.accent } : undefined}
      {...rest}
    >
      {local.children}
    </button>
  );
};
```

### Pattern 2: Interaction State CSS (Hover/Focus/Active/Disabled)
**What:** CSS-driven state transitions using the glow intensity hierarchy from Phase 2 tokens, with specific behaviors from CONTEXT.md.
**When to use:** Every interactive component.
**Example:**
```css
/* Hover: glow intensification + left-to-right gradient fill */
.hh-btn:hover {
  filter: drop-shadow(0 0 var(--hh-glow-blur-lg)
    color-mix(in srgb, currentColor var(--hh-glow-intensity-hover-pct), transparent));
}
.hh-btn:hover::after {
  background: linear-gradient(
    to right,
    color-mix(in srgb, currentColor 25%, var(--btn-bg)) 0%,
    color-mix(in srgb, currentColor 25%, var(--btn-bg)) 20%,
    var(--btn-bg) 65%
  );
}

/* Focus: slow pulsing glow border */
@keyframes hh-focus-pulse {
  0%, 100% { filter: drop-shadow(0 0 var(--hh-glow-blur-md)
    color-mix(in srgb, currentColor 30%, transparent)); }
  50% { filter: drop-shadow(0 0 var(--hh-glow-blur-lg)
    color-mix(in srgb, currentColor 55%, transparent)); }
}
.hh-btn:focus-visible {
  animation: hh-focus-pulse 2s ease-in-out infinite;
}

/* Active: border color floods inward, then recedes */
.hh-btn:active::after {
  background: color-mix(in srgb, currentColor 40%, var(--btn-bg));
  transition: background 80ms ease-in;
}

/* Disabled: powered off */
.hh-btn:disabled {
  filter: grayscale(0.7) brightness(0.5);
  pointer-events: none;
  opacity: 0.5;
}
```

### Pattern 3: Animated Gradient Sweep Border (Window Chrome)
**What:** CSS `@property` registers `--hh-sweep-angle` as `<angle>`, enabling smooth conic-gradient rotation for the glow-line perimeter.
**When to use:** Window chrome glow-line (WIN-02).
**Example:**
```css
/* Source: web.dev @property baseline, CSS-Tricks conic-gradient */
@property --hh-sweep-angle {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}

@keyframes hh-sweep-rotate {
  to { --hh-sweep-angle: 360deg; }
}

.hh-glow-perimeter {
  /* Positioned 6-8px inside window edge */
  position: absolute;
  inset: 7px;
  pointer-events: none;

  /* Conic gradient creates traveling hotspot */
  background: conic-gradient(
    from var(--hh-sweep-angle),
    transparent 0deg,
    transparent 340deg,
    var(--hh-color-primary) 350deg,
    color-mix(in srgb, var(--hh-color-primary) 80%, white) 355deg,
    var(--hh-color-primary) 360deg
  );

  /* Clip to match window chrome shape */
  clip-path: polygon(/* window chrome polygon */);

  /* Mask to show only the border ring (not filled center) */
  -webkit-mask: linear-gradient(#fff 0 0) content-box,
               linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  padding: 2px; /* border thickness */

  animation: hh-sweep-rotate 8s linear infinite;
  filter: blur(1px); /* Soft glow */
}
```

### Pattern 4: Custom Scrollbar with Crystal Indicator
**What:** Hide native scrollbar, render custom glow + hexagonal crystal via absolutely-positioned overlay.
**When to use:** CyberScrollbar component wrapping scrollable content.
**Example:**
```typescript
// Conceptual structure
export const CyberScrollbar: Component<{ children: JSX.Element }> = (props) => {
  const [scrollInfo, setScrollInfo] = createSignal({ top: 0, height: 0, visible: false });
  const [crystalActive, setCrystalActive] = createSignal(false);
  let contentRef: HTMLDivElement;

  // Track scroll position
  const onScroll = () => {
    const el = contentRef;
    const ratio = el.scrollTop / (el.scrollHeight - el.clientHeight);
    const thumbH = (el.clientHeight / el.scrollHeight) * el.clientHeight;
    setScrollInfo({ top: ratio * (el.clientHeight - thumbH), height: thumbH, visible: true });
  };

  return (
    <div class="hh-scroll-container">
      <div ref={contentRef!} class="hh-scroll-content" onScroll={onScroll}
           style={{ overflow: 'auto', 'scrollbar-width': 'none' }}>
        {props.children}
      </div>
      {/* Glow edge + crystal overlay */}
      <div class="hh-scroll-track">
        <div class="hh-scroll-crystal"
             classList={{ 'hh-scroll-crystal--active': crystalActive() }}
             style={{ top: `${scrollInfo().top}px`, height: `${scrollInfo().height}px` }} />
      </div>
    </div>
  );
};
```

### Pattern 5: Dropdown with Portal + Floating UI
**What:** Render dropdown options via SolidJS `Portal` to escape parent clip-path/overflow. Position with `solid-floating-ui`.
**When to use:** COMP-06 Dropdown.
**Example:**
```typescript
import { Portal } from 'solid-js/web';
import { useFloating, offset, flip, shift } from 'solid-floating-ui';

// Inside Dropdown component:
const position = useFloating(referenceEl, floatingEl, {
  placement: 'bottom-start',
  middleware: [offset(4), flip(), shift({ padding: 8 })],
});

// Render:
<button ref={setReferenceEl} role="combobox" aria-expanded={open()} ...>
  {selectedLabel()}
</button>
<Show when={open()}>
  <Portal>
    <div ref={setFloatingEl} role="listbox"
         style={{ position: position.strategy, top: `${position.y ?? 0}px`, left: `${position.x ?? 0}px` }}>
      <For each={options()}>
        {(opt) => <div role="option" aria-selected={opt.value === value()} ...>{opt.label}</div>}
      </For>
    </div>
  </Portal>
</Show>
```

### Anti-Patterns to Avoid
- **Destructuring props:** Never destructure SolidJS props directly (`const { variant, ...rest } = props`). This breaks reactivity. Always use `splitProps()`.
- **Mixing class and classList:** Using both `class` and `classList` on the same element can cause classList-set classes to be wiped when `class` re-evaluates. Use `clsx` with `class` prop instead.
- **createEffect for event listeners:** Do not attach DOM event listeners inside `createEffect`. Listeners accumulate on each re-run. Use `onMount` or inline event handlers (`on:click`).
- **clip-path + box-shadow:** box-shadow is clipped by clip-path. Always use `filter: drop-shadow()` on a PARENT wrapper element for glow effects on clipped shapes.
- **Inline SVG for every component border:** SVG borders are more flexible but significantly heavier than clip-path + pseudo-elements. Reserve SVG for the window chrome glow perimeter only if the mask-composite approach proves insufficient.

## Don't Hand-Roll

| Problem | Don't Build | Use Instead | Why |
|---------|-------------|-------------|-----|
| Dropdown overlay positioning | Custom viewport collision detection | solid-floating-ui + @floating-ui/dom | Edge cases (scroll containers, viewport edges, flip logic) are extremely difficult. Floating UI handles all of them in ~8KB |
| Conditional CSS class merging | Template literal concatenation | clsx | 239B, eliminates `undefined` and `false` class bugs, cleaner component code |
| Focus trap (future modals) | Manual focusin/focusout tracking | solid-focus-trap or corvu focus-trap | Focus trapping has edge cases with shadow DOM, iframes, dynamic content. Not needed in Phase 3 but noted for Phase 7 |

**Key insight:** The project's visual language is unique enough that standard component libraries (Kobalte, Hope UI) would require fighting against their DOM structure assumptions. The clip-path + pseudo-element border technique requires precise control of `::before`/`::after` on the component root element, which headless library compound components typically occupy. Hand-rolling is the right call here, but use focused utilities (floating-ui, clsx) for the genuinely hard sub-problems.

## Common Pitfalls

### Pitfall 1: clip-path Clips box-shadow and Glow
**What goes wrong:** Adding `box-shadow` to an element with `clip-path` -- the shadow is invisible because it falls outside the clipped region.
**Why it happens:** `clip-path` clips the entire rendering box including shadows.
**How to avoid:** Apply `filter: drop-shadow()` to a PARENT wrapper element. `drop-shadow` respects the visual shape of children, not the rectangular box. The existing `hh-panel` already uses this correctly.
**Warning signs:** Glow effects disappear when clip-path is added to an element.

### Pitfall 2: Pseudo-Element Exhaustion
**What goes wrong:** Components need `::before` for border layer, `::after` for fill layer, but also want a scanline overlay or gradient sweep. Only two pseudo-elements available per element.
**Why it happens:** CSS limits elements to `::before` and `::after`.
**How to avoid:** Use nested DOM elements for additional visual layers. Window chrome will need a dedicated `<div>` for the glow perimeter, another for the dot-grid overlay, rather than trying to fit everything into pseudo-elements. Keep `::before`/`::after` for the border+fill technique established in Phase 2.
**Warning signs:** Attempting to add a third visual layer to a component that already uses both pseudo-elements.

### Pitfall 3: SolidJS Reactivity Loss from Prop Destructuring
**What goes wrong:** `const { variant, size, ...rest } = props;` makes `variant` and `size` static values that never update.
**Why it happens:** SolidJS props are getter-based proxies. Destructuring evaluates the getter once and captures the value.
**How to avoid:** Always use `splitProps(props, ['variant', 'size'])` or access props directly as `props.variant`.
**Warning signs:** Component doesn't re-render when parent changes prop values.

### Pitfall 4: Dropdown Clipping by Parent clip-path/overflow
**What goes wrong:** Dropdown options rendered inside a panel with `clip-path` get visually clipped.
**Why it happens:** Child elements cannot escape parent `clip-path` boundaries.
**How to avoid:** Use SolidJS `<Portal>` to render dropdown options at `document.body` level. Use `solid-floating-ui` for positioning relative to the trigger element.
**Warning signs:** Dropdown options are cut off or invisible when opened inside a panel.

### Pitfall 5: CSS @property Not Working in Dev Mode
**What goes wrong:** `@property` declarations may not work if the CSS is processed/transformed in a way that strips or corrupts the at-rule.
**Why it happens:** Some CSS processing tools don't understand `@property` syntax.
**How to avoid:** Verify that Vite + Tailwind CSS v4 pass `@property` through untouched. Test the gradient sweep animation early in development. Tailwind v4 uses Lightning CSS which supports `@property`.
**Warning signs:** Conic gradient doesn't animate -- stays static with `0deg` angle.

### Pitfall 6: Scroll Position Desync in Custom Scrollbar
**What goes wrong:** Custom scrollbar thumb position drifts from actual scroll position, especially during fast scrolling or content resizing.
**Why it happens:** Using `scroll` event alone without accounting for content height changes, or using `requestAnimationFrame` with stale closure values.
**How to avoid:** Use `ResizeObserver` on the content element to recalculate scrollbar dimensions when content changes. Access `scrollTop`/`scrollHeight`/`clientHeight` directly from the DOM element in the event handler (not from a signal that may be stale).
**Warning signs:** Scrollbar thumb doesn't reach bottom when content is fully scrolled, or jumps on content resize.

### Pitfall 7: Focus Visibility vs Hover Confusion
**What goes wrong:** Focus styles trigger on mouse click, making the pulsing glow animation appear when clicking buttons (distracting).
**Why it happens:** `:focus` triggers on both keyboard and mouse focus.
**How to avoid:** Use `:focus-visible` instead of `:focus`. `:focus-visible` only activates when the browser determines focus should be visually indicated (keyboard navigation), not on mouse clicks. Fully supported in WebView2.
**Warning signs:** Button pulses after every click instead of only during keyboard navigation.

## Code Examples

Verified patterns from official sources:

### SolidJS splitProps + mergeProps (Component Foundation)
```typescript
// Source: https://docs.solidjs.com/reference/reactive-utilities/split-props
import { splitProps, mergeProps } from 'solid-js';

const MyComponent = (rawProps) => {
  // Set defaults
  const props = mergeProps({ size: 'md', variant: 'secondary' }, rawProps);
  // Split custom props from HTML passthrough
  const [local, rest] = splitProps(props, ['size', 'variant', 'class', 'children']);
  // local.size, local.variant are reactive
  // rest passes through to <div {...rest}>
};
```

### SolidJS Portal (Dropdown Escape Hatch)
```typescript
// Source: https://docs.solidjs.com/reference/components/portal
import { Portal } from 'solid-js/web';

// Renders children at document.body, escaping parent clip-path/overflow
<Portal>
  <div class="dropdown-popup" style={{ position: 'absolute', top: '100px', left: '200px' }}>
    {/* Options */}
  </div>
</Portal>
```

### WAI-ARIA Switch Pattern (Toggle)
```typescript
// Source: https://www.w3.org/WAI/ARIA/apg/patterns/switch/
<button
  role="switch"
  aria-checked={isOn()}
  onClick={() => setIsOn(!isOn())}
  onKeyDown={(e) => { if (e.key === ' ') { e.preventDefault(); setIsOn(!isOn()); } }}
>
  <span class="toggle-track">
    <span class="toggle-thumb" />
  </span>
  {label}
</button>
```

### WAI-ARIA Slider Pattern
```typescript
// Source: https://www.w3.org/WAI/ARIA/apg/patterns/slider/
<div
  role="slider"
  tabindex="0"
  aria-valuemin={min}
  aria-valuemax={max}
  aria-valuenow={value()}
  aria-label={label}
  onKeyDown={(e) => {
    switch (e.key) {
      case 'ArrowRight':
      case 'ArrowUp':
        setValue(v => Math.min(v + step, max));
        break;
      case 'ArrowLeft':
      case 'ArrowDown':
        setValue(v => Math.max(v - step, min));
        break;
      case 'Home': setValue(min); break;
      case 'End': setValue(max); break;
    }
  }}
/>
```

### WAI-ARIA Listbox Pattern (Dropdown)
```typescript
// Source: https://www.w3.org/WAI/ARIA/apg/patterns/listbox/
<button role="combobox" aria-expanded={open()} aria-haspopup="listbox" aria-controls="listbox-id">
  {selectedLabel()}
</button>
<ul id="listbox-id" role="listbox" aria-label="Options">
  <For each={options()}>
    {(opt, i) => (
      <li role="option"
          id={`option-${i()}`}
          aria-selected={opt.value === value()}
          tabindex={-1}
          onClick={() => select(opt)}
          onKeyDown={(e) => {
            if (e.key === 'ArrowDown') focusNext();
            if (e.key === 'ArrowUp') focusPrev();
            if (e.key === 'Enter' || e.key === ' ') select(opt);
            if (e.key === 'Escape') close();
          }}>
        {opt.label}
      </li>
    )}
  </For>
</ul>
```

### Hexagonal Dot Grid (SVG Pattern)
```html
<!-- Hexagonal dot pattern using SVG <pattern> -->
<svg class="hh-dotgrid-overlay" width="100%" height="100%">
  <defs>
    <pattern id="hex-dots" width="20" height="17.32" patternUnits="userSpaceOnUse">
      <!-- Row 1 -->
      <circle cx="5" cy="4.33" r="1" fill="currentColor" opacity="0.08" />
      <circle cx="15" cy="4.33" r="1" fill="currentColor" opacity="0.08" />
      <!-- Row 2 (offset) -->
      <circle cx="0" cy="12.99" r="1" fill="currentColor" opacity="0.08" />
      <circle cx="10" cy="12.99" r="1" fill="currentColor" opacity="0.08" />
      <circle cx="20" cy="12.99" r="1" fill="currentColor" opacity="0.08" />
    </pattern>
  </defs>
  <rect width="100%" height="100%" fill="url(#hex-dots)" />
</svg>
```

### Hexagonal Crystal Shape (Scrollbar Thumb)
```css
/* Hexagonal crystal: pointed top and bottom */
.hh-scroll-crystal {
  width: 12px;
  position: absolute;
  right: 2px;
  clip-path: polygon(
    50% 0%,       /* Top point */
    100% 15%,     /* Top-right */
    100% 85%,     /* Bottom-right */
    50% 100%,     /* Bottom point */
    0% 85%,       /* Bottom-left */
    0% 15%        /* Top-left */
  );
  background: transparent;
  border: 1px solid var(--hh-color-primary);
  opacity: 0;
  transition: opacity var(--hh-transition-normal);
}

.hh-scroll-crystal--visible {
  opacity: 1;
  animation: hh-crystal-flicker 0.3s ease-out;
}

.hh-scroll-crystal--active {
  background: var(--hh-color-primary);
}
```

## State of the Art

| Old Approach | Current Approach | When Changed | Impact |
|--------------|------------------|--------------|--------|
| `:focus` for keyboard styles | `:focus-visible` | Baseline 2022 | Only shows focus ring on keyboard navigation, not mouse clicks. Essential for the pulsing glow effect |
| `border-radius` for corners | `clip-path: polygon()` | N/A (project choice) | Enables arbitrary angular geometry. Project already uses this in Phase 2 |
| Vendor-specific `@property` | Standard `@property` | Baseline July 2024 | Universal browser support. Enables animatable CSS custom properties for gradient sweep |
| `overflow: hidden` + scrollbar styling | `scrollbar-width: none` + custom overlay | Baseline 2024 | Standard way to hide scrollbars while preserving scroll functionality |
| Static CSS variables | `@property` typed variables | July 2024 | Enables CSS-only animation of custom property values (angles, colors, lengths) |
| `classList` directive | `clsx` utility | Community pattern | More flexible than SolidJS `classList` which breaks with prop spreading and `<Dynamic>` |

**Deprecated/outdated:**
- `corner-shape` CSS property: Promising for angular corners but only available in Chrome M139+, not yet in WebView2 stable. Do NOT use; stick with clip-path
- `-webkit-scrollbar` as sole scrollbar customization: Limited to color/width changes. This project needs fully custom scrollbar geometry (hexagonal crystal), requiring the JavaScript overlay approach

## Open Questions

1. **Scrollbar scope: Window chrome vs Phase 7?**
   - What we know: CONTEXT.md notes "LAYO-05 (styled scroll containers) is Phase 7 -- planner should decide whether this ships as part of window chrome or defers"
   - What's unclear: Should the custom CyberScrollbar be built now as part of window chrome, or deferred entirely to Phase 7?
   - Recommendation: Build the CyberScrollbar component in Phase 3 as part of window chrome (it is deeply tied to the window chrome visual identity), but do NOT integrate it into arbitrary scroll containers yet. Phase 7 handles applying it to `<Panel>` and other containers. This way, the component exists and is tested but scope stays bounded.

2. **Gradient sweep + clip-path mask composite**
   - What we know: The `mask-composite: exclude` technique should create a ring (border only) from a filled conic-gradient. This avoids needing a third pseudo-element.
   - What's unclear: Whether `-webkit-mask-composite: xor` works reliably with `clip-path` in WebView2.
   - Recommendation: Prototype this technique early in development. Fallback: use an SVG `<rect>` with `stroke-dasharray` + `stroke-dashoffset` animation for the sweep if mask-composite fails.

3. **Performance of filter: drop-shadow on window chrome**
   - What we know: `filter: drop-shadow()` is GPU-composited but can be expensive on large elements.
   - What's unclear: Whether applying `filter: drop-shadow()` to the full-window chrome parent causes frame drops.
   - Recommendation: Benchmark early. If expensive, apply `will-change: filter` or reduce to the glow-perimeter element only (not the entire chrome wrapper).

## Sources

### Primary (HIGH confidence)
- [SolidJS Official Docs - splitProps](https://docs.solidjs.com/reference/reactive-utilities/split-props) - Props management, reactivity rules
- [SolidJS Official Docs - mergeProps](https://docs.solidjs.com/reference/reactive-utilities/merge-props) - Default props pattern
- [SolidJS Official Docs - Portal](https://docs.solidjs.com/reference/components/portal) - Escape hatch for dropdown overlay
- [SolidJS Official Docs - children](https://docs.solidjs.com/reference/component-apis/children) - Children accessor pattern
- [SolidJS Official Docs - classList](https://docs.solidjs.com/reference/jsx-attributes/classlist) - Class management limitations
- [SolidJS Official Docs - Dynamic](https://docs.solidjs.com/reference/components/dynamic) - Polymorphic component rendering
- [W3C WAI-ARIA APG Patterns](https://www.w3.org/WAI/ARIA/apg/patterns/) - Authoritative keyboard interaction patterns for all component types
- [web.dev @property baseline](https://web.dev/blog/at-property-baseline) - CSS @property universal browser support confirmed July 2024
- [MDN @property](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@property) - @property syntax and usage
- [MDN conic-gradient](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/gradient/conic-gradient) - Conic gradient reference
- [CSS-Tricks clip-path + drop-shadow](https://css-tricks.com/using-box-shadows-and-clip-path-together/) - Workaround for glow on clipped elements
- [Tauri v2 Window Customization](https://v2.tauri.app/learn/window-customization/) - Custom titlebar, decorations:false, drag region

### Secondary (MEDIUM confidence)
- [solid-floating-ui GitHub](https://github.com/lxsmnsyc/solid-floating-ui) - SolidJS bindings for Floating UI, v0.3.1
- [Floating UI Getting Started](https://floating-ui.com/docs/getting-started) - Positioning middleware (offset, flip, shift)
- [Kobalte GitHub](https://github.com/kobaltedev/kobalte) - Evaluated and rejected for this project due to DOM structure conflicts
- [codetv.dev animated gradient borders](https://codetv.dev/blog/animated-css-gradient-border) - @property + conic-gradient sweep technique
- [ibelick.com animated gradient border](https://ibelick.com/blog/create-animated-gradient-borders-with-css) - Mask-composite ring technique
- [OverlayScrollbars](https://kingsora.github.io/OverlayScrollbars/) - Reference for custom scrollbar overlay pattern (not using the lib, but the pattern)
- [clsx npm](https://www.npmjs.com/package/clsx) - 239B conditional class utility

### Tertiary (LOW confidence)
- CSS `corner-shape` property: Spotted in Chrome M139+ but NOT verified in WebView2 stable. Do not rely on this. Flagged for future phases when browser support matures.

## Metadata

**Confidence breakdown:**
- Standard stack: HIGH - SolidJS primitives and floating-ui are well-documented, existing codebase already demonstrates the patterns
- Architecture: HIGH - File structure follows SolidJS conventions, component pattern proven in Phase 2 prototypes
- Window chrome techniques: MEDIUM - @property + conic-gradient is well-documented but mask-composite + clip-path combination needs prototyping
- Custom scrollbar: MEDIUM - Pattern is established (hide native, overlay custom) but hexagonal crystal interaction specifics need iteration
- Pitfalls: HIGH - All documented from verified sources and project-specific experience
- Accessibility: HIGH - WAI-ARIA APG patterns are authoritative and well-documented

**Research date:** 2026-02-27
**Valid until:** 2026-03-27 (stable domain, 30-day validity)
