# Visual design language — ReaSampler + ReaSampler 9000 (Phase L) System-wide look-and-feel framing. This is the **why** and the visual spec for the whole-system look-and-feel effort, now carried as its own **Phase L** (Look-and-feel): the shared LICE drawing kit (**L1**), the extension's docked bank-panel layout redesign (**L2**), and the ReaSampler 9000 VST editor + embed-strip restyle (**L3**). It was written after Daniel DAW-tested the S1–S6 instrument: > "this shit looks like temple os. it's 2026, we need something more modern and sleek. > speed is the selling point. […] If we are going to enhance look and feel, we need to > think big and full. Scope it out for the whole system. […] the VST is dogshit. Does > Cockos have a toolkit for this type of visual design?" **Status:** framed by product-designer (2026-07-26); the DS-1/DS-2/DS-3 forks are **SETTLED (Daniel, 2026-07-26)** — recorded in §6 with the reasoning preserved. **DS-2 was revised 2026-07-26 (Daniel)** from the single electric-cyan accent to a **three-accent pastel system** (primary/secondary/tertiary = pastel lime green / pastel teal / pastel purple) — see §6. (A stylish-font upgrade was considered and **declined** by Daniel — no font bundling/redistribution; the kit keeps its current cached-font face.) The build points it fed have landed; their history is in **`docs/ARCHIVE.md`** ("Phase L — Look-and-feel", **L1** the shared LICE drawing kit, **L2** the dock-panel layout redesign, **L3** the VST editor + embed-strip restyle) and the architecture now lives in **`src/core/ui/CLAUDE.md`** / **`src/shell/panel/CLAUDE.md`** (the design-system implementation). Toolkit facts below are **verified against the vendored `vendor/WDL` tree**, not lore. > **Phase-boundary note (2026-07-26).** This look-and-feel effort was originally drafted > as Phase S points **S0-DS** (the shared kit) and **S14** (the panel refresh). It was > **lifted out of Phase S into its own Phase L** so a parallel team can take it up > without Phase S context and so Phase S feature work (S7–S13) proceeds ungated. The old > `S0-DS` content is now **L1**; the old `S14` content is now **L2** (expanded from a > light re-skin into a thorough layout redesign — see DS-3 below); the VST editor + > embed-strip restyle, which S10–S13 originally folded in, is now the explicit **L3** > point, **gated on Phase S landing on dev**. Phase S's S10–S13 build their interaction > UX with the current drawing and **adopt the L1 kit when it lands** — they are not > gated on Phase L. --- ## 0. TL;DR - **Cockos ships exactly the toolkit Daniel asked about**, and it's already vendored. Two layers: **LICE** (the immediate-mode 2D rasterizer — gradients, anti-aliased lines/circles/round-rects/beziers, alpha blending, and a real font engine with shadow/outline/glow effects) and **WDL VirtualWnd / vwnd** (a retained-mode LICE-drawn *widget* layer — buttons, sliders, knobs, listboxes, skins — the thing REAPER and SWS themselves are built on). **The current VST editor uses almost none of it** — it draws flat `LICE_FillRect` blocks and pipes text through raw GDI `DrawTextA`. *That* is the "temple os" look: no gradients, no anti-aliasing, no rounded corners, no hover, aliased system text. The ceiling is not the problem; we're drawing at the floor. - **DS-1 SETTLED (2026-07-26): LICE, and anything useful in WDL is free game.** "Don't reinvent the wheel" — if vwnd (or any WDL machinery: skin/image helpers, draw idioms) offers a usable piece, use it; otherwise build on LICE. **No external frameworks** (not JUCE, not iPlug2, not VSTGUI). LICE can deliver a 2026-modern dark synth UI — REAPER's own themed UI and SWS prove the ceiling. This is fully consistent with the settled **D-A** decision (bare SDK + LICE, no framework); the fork is recorded in §6. - **The real work is L1 — a shared drawing kit** — one small LICE-based component/palette/type layer that the VST editor, the embed strip, and the bank panel all draw through. It is the one source of drawing for the whole system. - **DS-2 SETTLED (2026-07-26), REVISED (2026-07-26): Direction B ("Neon Console") + Direction C's spectral keyboard strip** as the signature surface — now with a **three-accent pastel system** (primary = pastel lime green, secondary = pastel teal, tertiary = pastel purple) in place of the original single electric-cyan accent. (A stylish-font upgrade was considered and **declined** — no font bundling; the kit keeps its current face.) Three visual directions are in §4; B+spectral with the pastel accents is the settled target. "Speed is the selling point" is a **design constraint**, not a tagline: the UI must *feel* instant (§3.5). - **DS-3 SETTLED (2026-07-26): the dock-panel work is a real, thorough layout design, not a light reskin.** L2 lays out the *full button inventory* (including M11's additions) intuitively, uncluttered, and useful — §5.2. --- ## 1. The toolkit question, answered from the vendored source Daniel: *"Does Cockos have a toolkit for this type of visual design?"* Yes — two layers, both already in `vendor/WDL`. Verified by reading the headers, not from memory. ### 1.1 LICE — the rasterizer (this is the load-bearing answer) `vendor/WDL/WDL/lice/` is Cockos's own 2D graphics library — the same one REAPER's UI, the theme engine, and SWS draw with. The current `bank_panel` and VST editor already link it. What it actually offers, beyond the flat `LICE_FillRect` the editor currently limits itself to (all verified in `lice/lice.h` / `lice/lice_text.h`): | Capability | Function (verified) | We use it today? | |---|---|---| | **Gradient fills** | `LICE_GradRect` (per-axis R/G/B/A ramps) | **No** — this alone kills the flat look | | **Anti-aliased lines** | `LICE_Line`/`LICE_FLine`/`LICE_ThickFLine` (`aa=true` default) | No | | **Rounded rectangles** | `LICE_RoundRect` (corner radius, AA) | No | | **AA circles / knobs** | `LICE_Circle`/`LICE_FillCircle` | No | | **Bezier curves** | `LICE_DrawCBezier` (AA envelope/curve draw) | No | | **Triangles / polygons** | `LICE_FillTriangle`/`FillConvexPolygon`/`FillTrapezoid` | No | | **Alpha compositing** | every primitive takes `alpha` + blend `mode` | Barely (opaque only) | | **Real font engine** | `LICE_CachedFont` / `LICE_IFont`: `SetFromHFont`, AA glyph cache, `SetTextColor`, `DrawText` | **No — we use raw GDI `DrawTextA`** | | **Text effects** | shadow / outline / glow / blur (`LICE_FONT_FLAG_FX_SHADOW`/`_OUTLINE`/`_BLUR`) | No | **The single biggest visual lever is text.** The editor's `drawText` does `GetDC` → `SetTextColor` → `DrawTextA` — native, un-anti-aliased-in-the-bitmap GDI text that reads as "1998." Switching to a `LICE_CachedFont` (created once from a chosen `HFONT`, cached, AA glyphs, optional soft shadow) is the difference between "temple os" and "modern synth." Small, mechanical change, outsized payoff, and it belongs in L1 (§5) so every surface gets it at once. `bank_panel` already routes some text through GDI too; the kit fixes both. **Conclusion:** LICE is a genuine, sufficient 2D toolkit for a modern dark synth UI. The gap between the current editor and a sleek one is entirely in *which LICE calls we make*, not in LICE's ceiling. **No new dependency is required to get the look Daniel wants.** ### 1.2 WDL VirtualWnd (vwnd) — the retained-mode widget layer, and DS-1's "free game" rule `vendor/WDL/WDL/wingui/virtwnd*.{h,cpp}` is the layer above LICE: `WDL_VWnd` (a virtual window / control that draws itself into a shared LICE bitmap), with concrete controls — `virtwnd-iconbutton.cpp` (buttons), `virtwnd-slider.cpp` (sliders + `vwnd_slider_drawknobstack` knob rendering), `virtwnd-listbox.cpp` (scrolling lists), `virtwnd-skin.h` (image-based skins), plus `WDL_STYLE_*` hooks for background gradients and slider/knob bitmaps (`virtwnd-controls.h`). **This is the toolkit REAPER and SWS themselves use for custom UI.** **DS-1's settled rule: WDL is free game — don't reinvent the wheel.** Where a WDL piece (a skin/image helper, a draw idiom, a specific control's rendering) is genuinely usable and cheaper than re-deriving it, **use it**; otherwise build on LICE directly. The one standing caution — not a prohibition — is the **retained-mode object model**: vwnd's controls own their hit-test/drag/value internally, whereas the house discipline keeps layout/hit-test in **pure geometry modules** (`mode_switch`, `embed_strip`, `editor_geometry`, the planned `keyboard_strip`) so hit-test math stays CTest-covered outside the DAW. So the default is to **borrow vwnd's drawing craft** (read `virtwnd-slider.cpp` / `vwnd_slider_drawknobstack` as the reference for a good LICE slider/knob and reproduce the look procedurally over pure geometry we own) and to reach for a vwnd *object* only where it carries no bespoke-geometry requirement and re-deriving it is genuinely wasteful. The plain **scrolling listbox** (`virtwnd-listbox.cpp`) for a long sample list is the archetypal "just use it" case — no bespoke drawing, tedious to re-derive; adopt it if it fits at build time. The **keyboard strip and waveform** are bespoke surfaces vwnd has no widget for — custom-drawn regardless. The rule is a build-time judgment call per piece, made under one principle: **reuse what WDL gives us, keep hit-test geometry testable.** ### 1.3 The frameworks we are NOT taking (and why — DS-1 settled: no external frameworks) - **IPlug (WDL's own `IPlug/`)** — the VST2-era plugin framework in WDL. **Dated and irrelevant.** VST2 is deprecated; it predates VST3; it's not a UI toolkit anyway. Ignore. - **iPlug2** (the modern community fork — NanoVG/Skia GPU renderers, web-UI option) — a genuinely capable modern framework, but it is a **whole new plugin-framework dependency** that would **replace** the bare-SDK + LICE architecture **D-A** settled on 2026-07-26. It brings its own graphics stack (NanoVG/Skia), its own build integration, and its own editor/parameter model — none of which we need, because LICE already clears the bar. **Rejected** (DS-1 settled: no external frameworks). - **VSTGUI** (bundled with the Steinberg SDK) — the noted D-A fallback rung *if the IPlugView↔LICE bridge had proven gnarly*. On the phase-s worktree it didn't — S1 landed the bridge, the editor draws in LICE today. VSTGUI is moot; we're past the point it hedged. **Net:** the toolkit question resolves cleanly. Use **LICE** (already in hand, already the draw surface) as the toolkit, **reuse WDL/vwnd pieces where useful** (DS-1's "don't reinvent the wheel"), and **take no external framework.** Fully consistent with D-A. --- ## 2. Palette — dark, modern, punchy Daniel's standing taste: **visual punch over conservative contrast** — push vibrancy to the WCAG floor for each element class, not to the past-safe gray. The current palette (shared between `bank_panel.cpp` and the VST editor) is a muted forest-green-on-charcoal: background `RGB(28,28,30)`, waveform/accent `RGB(120,200,160)`, cell `RGB(44,44,48)`. It's *coherent* but *timid* — one desaturated accent, flat fills, hairline borders. ### 2.1 Structural roles (direction-independent) A modern dark UI is built from **elevation layers**, not borders. Define the palette by role, then let the direction set the accent. **Neutral surfaces — REAPER-theme-matching mid-grey (DS-2 revised, 2026-07-26, Daniel).** The neutral role ladder is **not near-black** — it sits in **REAPER's own mid-grey theme family** so the dock reads as *part of REAPER*, not a black slab bolted on beside it. Ground truth from Daniel's theme: REAPER's arrange/track background is a mid grey (`~#373737`), window chrome / ruler / transport a touch darker (`~#2b2b2b`), body text a light grey (`~#c8c8c8`–white). The DS-2 neutral ladder below matches that family. **The elevation-ladder discipline is unchanged:** base < panel < cell, each a few percent lighter, with the micro-gradient (§2.2) + 1px inner highlight/shadow still doing the elevation work — REAPER differentiates surfaces by subtle steps, not hard borders, which is exactly DS-2's "elevation layers, not borders" principle. Only the *values* moved up from near-black into the grey range; the *structure* is identical: | Role | Purpose | Current | Direction (DS-2 revised — REPER-grey neutrals) | |---|---|---|---| | `bg/base` | window canvas | `28,28,30` | **REAPER chrome grey ≈ `43,43,43` (`#2b2b2b`)** + a **subtle top-down `LICE_GradRect`** (base look, not flat) | | `bg/panel` | a raised region (list, waveform pane) | — (implicit) | **≈ `51,51,51` (`#333333`)** — one step lighter than base; elevation reads without a border | | `bg/cell` | a control/row surface | `44,44,48` | **≈ `58,58,58` (`#3a3a3a`)** (matches REAPER track bg `~#373737`–`#3c3c3c`), with a 1px inner top highlight (the vwnd trick) | | `line/hairline` | separators | `60,60,66` | **≈ `74,74,74` (`#4a4a4a`)** — a subtle step above cell; use sparingly, elevation should carry most separation | | `text/primary` | labels, values | `210,230,220` | **REAPER body light grey ≈ `220,220,220` (`#dcdcdc`)**, **must clear WCAG AA (4.5:1) on `bg/panel`/`bg/cell`** (clears ~8:1 — comfortable) | | `text/dim` | secondary / units | — | a dimmer light-grey step ≈ `~160,160,160` (`#a0a0a0`), **must clear AA 4.5:1 on the greyer surfaces if used for body** (see §2.1 WCAG note — this is the newly tight pair) | | `accent/primary` | live / active / selected layer | `120,200,160` | **pastel lime green** (§4) — the one that means "this is live"; where the punch lives | | `accent/secondary` | supporting categorical role A | — | **pastel teal** — a second categorical hue (e.g. secondary zones, non-primary selection state) | | `accent/tertiary` | supporting categorical role B | — | **pastel purple** — a third categorical hue (e.g. tertiary zones, a distinct affordance class) | | `accent/hot` | hover / live / drag feedback | — (absent) | a brighter tint **of the primary accent**; **today there is no hover color at all** | | `warn` | clip / destructive (prune, delete) | — | a red/amber reserved *only* for byte-deleting or clip states | **Three-accent role assignment (DS-2 revised).** The accent layer is now *three* pastel hues, not one, so the palette can carry categorical meaning without leaning on saturation Daniel finds too loud. The coherent assignment: - **`accent/primary` = pastel lime green** — the single "this is live/active/selected" signal. It does the same job the old single cyan did: it is the eye-magnet, used with discipline for the live layer only (selection, active fill, focus-adjacent, the active zone's glow). One primary keeps the "accent = live, never chrome" rule intact. - **`accent/secondary` = pastel teal** and **`accent/tertiary` = pastel purple** — the *categorical* distinctions that a single-accent palette had to fake with brightness. They mark **kinds** of thing, not intensity: e.g. non-active zone bands on the keyboard strip, a second/third action-cluster identity, or a supporting-state fill. They never out-shout primary — primary is always the "what's live now" answer; secondary/tertiary are "what category is this." The three read as a family (all pastel, adjacent-ish in the cool→warm band), so the UI stays coherent, not carnival. Primary/secondary/tertiary is a **role trio in the same constants block** as the rest — adding two accents did not change the "one file, one direction" property (§2.1 opener). **WCAG-floor discipline (the "punch" rule, re-read for pastels):** for each text-on-surface pair, pick the value that still clears its contrast floor — AA 4.5:1 for body text, 3:1 for large text and UI-state indicators (selection borders, active fills). With the old vivid cyan the rule was "push to the floor *from the vibrant side*." **Pastels are the inverse approach:** a pastel is intrinsically less saturated and (as a light-ish tint on a dark surface) usually *brighter* than a vivid accent, so it clears the *large/state* floor more easily than a vivid hue — though on the DS-2-revised **grey** surfaces the cushion is much smaller than on near-black (see the grey re-read below). The tight case flips — **text drawn on a pastel fill** (dark text on a light pastel, or the pastel as fill behind `text/primary`) is where contrast can fail. So the rule becomes: **take the most *pastel* (softest, least-saturated) value that still clears the floor, approached from the soft side** — do not over-saturate a pastel back toward neon "to be safe," and do not wash it out so far it drops a text pair below floor. The implementer runs the theme module's WCAG tests and nudges each accent *within the pastel intent* until every drawn pair clears. See §4 for concrete starting values and the pairs expected to be tight. **WCAG re-read for the greyer neutrals (DS-2 revised — the load-bearing check).** Moving the neutral surfaces *up* from near-black into REAPER's mid-grey makes two pairs *harder*, because contrast against a lighter surface is smaller. Both must be re-verified against the new grey ladder, not the old near-black: - **`text/dim` on `bg/panel`/`bg/cell` — the classic mid-grey-on-mid-grey floor failure.** A mid-grey dim text against a mid-grey surface is exactly where AA fails. At the starting values a `~#9a9a9a`-`#a0a0a0` dim on `#333333`/`#3a3a3a` lands around **~4:1–4.4:1** — under the AA 4.5:1 body floor and only just clearing the 3:1 large floor. **`text/dim` must be lifted light enough (toward `~#a8a8a8`+) to clear AA 4.5:1 on the greyest surface it draws body text on**, or be confined to large/UI-label use where the 3:1 floor applies. The implementer locks the exact value against the test; the intent is "dim, but never below the floor it is actually drawn against." - **The three pastel accents on the greyer surfaces — the cushion shrank.** Pastels have *less* contrast against mid-grey than against near-black. On near-black the three cleared the 3:1 large/indicator floor by a huge margin (~15:1); on `bg/cell` `#3a3a3a` the same pastels land around **~6:1–7:1** — still clearing 3:1, but the comfortable cushion is gone. Every pastel used as a **state indicator / active fill** (selection border, active zone, focus-adjacent) **must be re-verified to still clear the 3:1 large/indicator floor on `bg/cell`, not on the old near-black.** If any pastel would drop below floor on grey, the "punch from the soft side" rule now means **nudge that hue slightly deeper / more saturated until it clears** — the hues stay pastel lime / teal / purple; only their value/chroma may move a little. This is a real tension: the greyer background pulls the pastels *toward* the floor at the same time the "keep it soft" rule pulls them away from saturation. The implementer resolves it per-hue against the test, staying within the pastel intent. ### 2.2 Motion of color, not just color The flat look isn't only the hue — it's that every fill is a **single opaque rectangle**. A 2026 dark UI reads as dimensional because surfaces have a **micro-gradient** (a few percent lighter at the top, `LICE_GradRect`) and controls have a **1px top inner-highlight + 1px bottom inner-shadow**. This is the single cheapest "modern" upgrade after AA text, and it's exactly what `virtwnd`'s button/slider drawing does. Bake it into the kit's `fillSurface()` primitive so every button, row, and cell gets it for free. --- ## 3. Typography, spacing, interaction, motion ### 3.1 Typography within LICE's reality - **Use `LICE_CachedFont`, retire raw GDI `DrawTextA`.** Create a small set of cached fonts once (below), owned by the kit, reused across VST + embed + panel. AA glyphs in the bitmap are the "temple os → modern" switch. - **Two or three weights/sizes, no more.** A tool UI wants a **type scale**, not typographic variety: `title` (~15px semibold), `label` (~12px regular), `value`/`mono` (~12px, tabular for note numbers / ms / dB so digits don't jitter during a drag), `micro` (~10px, dim, for units/counts). Four cached fonts total. - **Font face — keep the kit's current cached-font face (DS-2: no font change).** A stylish/bundled-font upgrade was considered and **declined by Daniel** — he does not want to bundle and redistribute a font. So the kit keeps its **current** face; this doc specifies no new typeface. The win at §3.1 is entirely the *mechanism* — routing all text through a `LICE_CachedFont` (AA glyph cache, one weight/size scale, optional soft shadow on titles) instead of raw GDI `DrawTextA` — not a new type family. The `HFONT` fed to `SetFromHFont` stays the kit's existing choice; the "temple os → modern" jump is the AA cached-glyph pipeline plus the type scale above, not the face. - **Soft shadow on title/accent text only** (`LICE_FONT_FLAG_FX_SHADOW`) — subtle depth, used sparingly; body text stays crisp and flat for legibility. ### 3.2 Spacing & grid - **An 8px base unit** (4px for tight internal padding). Row heights, gaps, and pane insets are multiples of it — this is what makes a UI read as "designed" not "placed." - **Generous row height** for list/zone rows (~28–32px). In the editor, S10 deletes the seven mini-buttons per row so rows can breathe. - **Consistent 8px pane inset** everywhere (panel edges, between panes). The current editor pins controls flush to edges — a big part of the cheap feel. ### 3.3 Interaction states (the editor currently has NONE) A control looks identical whether the mouse is over it, on it, or nowhere near. **This is half of why it feels dead.** Define a state model every kit component honors: | State | Visual | Cost | |---|---|---| | **rest** | `bg/cell` + inner highlight | — | | **hover** | lighten fill toward `accent/hot`, ~8–12% | requires `WM_MOUSEMOVE` tracking (§3.5) | | **active/selected** | `accent` fill + `accent` border | already partly present | | **pressed** | darken + 1px inset (the surface "pushes in") | on `WM_LBUTTONDOWN` | | **dragging** | live element follows cursor; ghost/marker at target; rest dims slightly | S10 drag machine | | **focus (keyboard)** | 1px `text/primary` ring (distinct from selection) | already present in panel (`kColFocusBorder`) | | **disabled** | desaturate + 40% alpha | rare (stale zone, no bank) | Hover is the highest-value addition — it's what makes a UI feel *alive* and responsive, and the visible proof of "speed is the selling point" (§3.5). It requires `WM_MOUSEMOVE` (and `TrackMouseEvent` for `WM_MOUSELEAVE`). The panel's `wndProc` already runs a timer; adding hover tracking is cheap. In the editor, the S10 drag-state machine already introduces the move tracking, so hover rides in on that work at near-zero cost. ### 3.4 Component patterns (reused across VST + embed + panel) The kit defines a **small, shared vocabulary** of drawn components, each: (a) drawn by a kit function taking a `Rect` + state, (b) hit-tested by a **pure geometry** helper (the house discipline). The set: - **Button** — rounded (`LICE_RoundRect`), gradient fill, inner highlight, hover/pressed states, centered `LICE_CachedFont` label. Replaces the flat `LICE_FillRect` + `DrawRect` + GDI-text buttons in both artifacts (the whole M11 action-button inventory, panel toggles, prune button, Add Zone, etc.). - **List row** — selectable, hover, focus, optional leading thumbnail (the panel's waveform cell is a specialization). Shared by the sample list (VST + panel) and zone list. - **Slider / knob** — for ADSR (S12) and any level control; drawn in the `virtwnd-slider` idiom (track + filled portion + handle with highlight), hit-test pure. - **Marker / handle** — the draggable grip primitive: keyboard-strip zone edges (S10), waveform loop markers (S11). A consistent grip look (a bright vertical bar + a small triangular tab) so "this is draggable" reads instantly. - **Segmented switch** — the panel already has this (`mode_switch`); restyle it through the kit (gradient segments, lit-active, hover). - **Tab (named-banks strip)** — a specialization of the segmented/button idiom with the overflow/scroll affordance already specced in Phase B; drawn through the kit. - **Waveform** — peaks envelope (`peaks` module) drawn as a filled AA shape (`LICE_FillTriangle`/trapezoids or a bezier top), midline, over `bg/panel`. Shared between the panel thumbnail and the S11 editor waveform. - **Keyboard strip** — the bespoke S10 surface: white/black key lanes, zone bars as translucent colored overlays, root markers. No prior art in the kit; it *defines* new kit primitives (key-lane fill, zone overlay). **Direction C's spectral treatment (§4, DS-2 settled + revised) lives here** — the hue-mapped keyboard is the signature surface, now a **pastel** sweep anchored on the three accent hues (§4). ### 3.5 Motion policy — "speed is the selling point," read as a constraint Daniel named speed as *the* selling point. The design reading: **the UI must feel instant, and no decoration may cost that.** Concretely — - **Sub-frame response.** Hover/press/drag feedback repaints immediately on the input message (no timer latency). This is why hover tracking is a *feature*, not overhead — instant visual acknowledgment is the *perception* of speed. - **Zero-jank drawing.** Keep the existing **double-buffer** discipline (draw to a `LICE_SysBitmap`, single `BitBlt`) the editor and panel already use. Every kit component draws into the offscreen bitmap; nothing draws direct-to-DC. - **Repaint only what changed where cheap.** Full-panel repaint is fine at these sizes, but during a drag, invalidate the affected region (`InvalidateRect` with a rect, not `nullptr`). Build-time residual, flagged not mandated. - **NO decorative animation.** No easing tweens, no fades, no animated meters beyond a direct level follow. Animation that costs a frame of latency **violates** the speed promise. The one permitted case: a level/meter readout that follows the audio directly (already how the embed strip's level band works). Motion = immediate feedback, never ornament. **Direction C's glow/bloom is a drawn *state*, never a pulse** — it must stay static. - **The "fast" feeling is mostly typography + hover + no-jank**, not motion. A crisp AA label that lights on hover and commits a drag with zero flicker *reads* faster than any animation. --- ## 4. Three visual directions (DS-2 SETTLED: B + C's spectral keyboard strip; REVISED to pastel accents) Each is a complete palette + treatment, not a shade of the same thing. All three are LICE-drawable today (no new dependency); all three honor the WCAG-floor "punch" rule. Ordered safe → bold. **Daniel settled (2026-07-26) on Direction B as the target, with Direction C's spectral keyboard strip as the signature surface** — then **revised the accent layer (2026-07-26) from a single electric cyan to a three-accent pastel system** (primary pastel lime green / secondary pastel teal / tertiary pastel purple), and **revised the neutral surfaces (2026-07-26) from near-black up into REAPER's mid-grey theme family** so the dock reads as part of REAPER, not a black slab (§2.1). Only the accent layer and the neutral ladder changed; the direction (B + C's spectral strip) is unchanged. ### Direction A — "Studio Rack" (evolutionary; not selected) The current forest-green-on-charcoal, but *executed properly*: gradient surfaces, AA text, rounded controls, a hover state, inner highlights. Same hue family — accent stays green (brightened to `~140,220,170`), background gains a subtle vertical gradient, controls get the vwnd inner-highlight/shadow treatment. - **Feel:** a well-made piece of pro audio hardware. Restrained, "REAPER-native." - **Why not selected:** it's the *conservative* answer — fixes "temple os" but doesn't make anyone say "sleek." Retained as the fallback if B/C ever feel too loud for a pro-audio context, but Daniel's taste points away from it. ### Direction B — "Neon Console" (contemporary synth; SETTLED default, REVISED to pastel accents) A modern soft-synth / plugin look: **REAPER-theme mid-grey neutrals** (`bg/base` ≈ `#2b2b2b`, `bg/panel` ≈ `#333333`, `bg/cell` ≈ `#3a3a3a` — the grey ladder in §2.1, matching Daniel's REAPER theme so the dock is not a black box beside it), and the **accent layer carries all the punch** — used with intent for the *live/active/selected* layer and for a small number of categorical distinctions only. Everything else stays quiet grey. Waveforms and the keyboard strip's active zone glow in the **primary** accent; inactive/categorical elements pick up secondary/tertiary. This is the "Serum / Vital / modern Arturia" idiom — accent-on-dark — but **pastel, not neon,** and on **REAPER-grey rather than near-black**. **Accent layer (DS-2 revised — three pastel accents, replacing the single electric cyan):** Daniel's call — "it's too CYAN, I prefer pastel hues" — swaps the one vivid cyan for a **three-hue pastel family**. Concrete *starting* values (the implementer locks final hex against the WCAG tests — §2.1): | Role | Pastel | Starting RGB (hex) | Job | |---|---|---|---| | `accent/primary` | pastel lime green | `~176,224,152` (`#B0E098`) | the live/active/selected signal — the eye-magnet | | `accent/secondary` | pastel teal | `~132,214,208` (`#84D6D0`) *(superseded, see below)* | categorical role A (non-active zones, a second cluster) | | `accent/tertiary` | pastel purple | `~194,170,232` (`#C2AAE8`) | categorical role B (tertiary zones, a distinct affordance class) | | `accent/hot` | brighter primary tint | `~200,236,178` (`#C8ECB2`) | hover / live / drag feedback (a lighter pastel-lime) | **Locked values, where they diverge from the starting table (2026-07-31).** `accent/secondary` darkened to `#38A8A0` (same hue ~176°, same saturation, lightness 0.68 → 0.44). Its **binding limiter** — the pair that stops it going darker — is the velocity-curve trace on a *hover-lightened* `bg/cell` at **3.03:1** against a 3:1 floor, tighter than either the AA 4.5:1 text-on-fill pair (4.91:1 on `bg/base`) or the indicator floor on rest `bg/cell` (3.94:1). ONE new Role was added past the original three, since "exactly three accents" is no longer a constraint: `overlay/trace` `#816AA6`, a muted violet for marks drawn *over* an accent fill (see §the two-neighbour problem below). The spectral ramp's mid stop also became its own value — the original pastel teal `#84D6D0` — but as a private constant, not a `Role`. **The two-neighbour problem** (rule + numbers owned by `src/core/ui/CLAUDE.md`; the design consequence is recorded here). A mark drawn *over* an accent fill has to read against the fill *and* the surface behind it, which caps *any* single value at ≈3.07:1 against both. Two consequences shape the design rather than the palette: such a role is confined to the band it was picked for, and a **state** of that mark cannot be expressed as a hotter color — every value that clears the ceiling sits within 1.05:1 of every other, so the grabbed envelope handle signals by **size and a punched-out core** instead. That deliberately inverts the kit's "brighter = hotter" convention in this one band, because over a light fill a brighter tint is a *lower*-contrast tint (`accent/hot` measures 1.15:1 against the lime). These are light-ish, low-saturation tints. On the **REAPER-grey surfaces** (`bg/cell` ≈ `#3a3a3a`) they still clear **AA-large (3:1)** and the **state-indicator** floor — but the margin is **much smaller than it was on near-black** (~7.6:1 primary / ~5.5:1 tertiary / ~3.9:1 the darkened secondary on grey, vs. ~15:1 on `#12121x`). This is a real DS-2-revision tension: **the greyer background pulls the pastels toward the floor** at the same time the "keep it soft" rule pulls them away from saturation. Approach from the **soft side** (§2.1) — keep them as pastel as possible while still clearing the floor on **grey, not near-black** — but **if any pastel used as a state indicator / active fill drops below 3:1 on `bg/cell`, nudge that hue slightly deeper / more saturated until it clears** (hue stays lime/teal/purple; only value/chroma moves a little). Re-verify against the grey ladder, not the old near-black values. - **Feel:** a 2026 instrument, softer and calmer than a neon single-accent — the pastel family guides the eye to what's live (primary) and quietly categorizes the rest. - **Watch — the tight pairs (flag for the implementer):** - **Body text *on* a pastel fill.** When a pastel accent is a *fill* and `text/primary` (light-grey `~220,220,220` / `#dcdcdc`) or a dark label sits on top, the pair can drop below **AA 4.5:1** — pastels are mid-to-light, so light-grey-on-pastel is the risky combination. Where an accent is used as a filled surface behind body text, either (a) use a *dark* near-black label on the light pastel (which clears easily) or (b) darken that specific pastel fill until near-white clears 4.5:1. The theme test must cover every actual text-on-accent-fill pair the kit draws. - **Secondary vs. tertiary distinguishability.** Pastel teal and pastel purple are both cool-ish and desaturated; verify they read as *distinct* categories, not two greys, when adjacent (e.g. neighboring zone bands). Nudge tertiary warmer/more-violet if they muddy. - **Three pastels can drift "candy" if over-applied** — same discipline as before: accent = live + a few categorical marks, never chrome. Coherence rule: keep all three clearly in the pastel family (no one of them creeping back to full saturation). - **Reversibility:** unchanged — all three accents live in the one constants block, so a future re-pick (or a saturation nudge) is still a single-file change. ### Direction C — "Spectral" (bold; SETTLED for the keyboard strip; REVISED to a pastel sweep) Lean into depth via a **hue-mapped gradient** across the signature surface: zones and the waveform aren't a flat accent — the keyboard strip is a **spectrum you play across** (low notes cool → high notes hot), the waveform filled with a vertical accent gradient. Selection glows; the active zone has a soft outer bloom (`LICE_GradRect` halo, a *static* drawn state — §3.5). Multi-zone keymaps become genuinely *beautiful* — each zone a band of the spectrum — which is exactly the "multi-zone-in-one-instance" capability that RS5K structurally lacks (the sharpest "better than RS5K" claim, made *visual*). **How the spectral ramp relates to the three pastel accents (DS-2 revised).** The strip keeps a **full low→high sweep**, but it is now a **pastel spectral ramp anchored on the three accent hues** rather than a neon cool-blue→hot-magenta gradient — so the signature surface stays coherent with "pastel, not neon" and visibly belongs to the same palette as the rest of the UI. Concretely: the ramp runs **pastel-lime (primary, low) → pastel-teal (secondary, mid) → pastel-purple (tertiary, high)** as a three-stop gradient through the accent constants (or a slightly wider pastel arc that passes *through* those three anchor points), keeping every stop in the pastel band. This ties the spectrum to the palette: the same hues that mean "live / category A / category B" elsewhere anchor the spectrum here, so the strip reads as an extension of the accent system, not a separate neon flourish. **The mid stop is its own constant, not an alias of `accent/secondary`** (decoupled 2026-07-31): the ramp is a luminance progression while the accents are categorical roles, and darkening secondary for a categorical reason inverted lo→mid→hi. A monotonicity test now guards the ordering. The **active** zone still lifts to `accent/primary` + its bloom, so "which zone is live" stays unambiguous over the categorical spectral bands. - **Feel:** premium, almost visualizer-grade — but soft and cohesive, a pastel spectrum rather than a rave. The keyboard strip is the signature; it is unmistakably the same palette as the panel. - **Settled scope:** **C's spectral treatment lands on the keyboard strip** (the signature surface), over Direction B's Neon-Console chrome (now pastel-accented) everywhere else. This is the "modern-sleek center with one bold, identity-defining surface" Daniel chose — dial the spectrum wider (a broader pastel arc) or narrower (tight to the three anchors) after seeing it on the DAW. - **Watch:** gradients/bloom are easy to overdo into "toy"; the glow must stay a static drawn state, never a pulse (§3.5); a pastel ramp has *less* inter-note contrast than a neon one, so verify adjacent zone bands stay distinguishable (the same secondary-vs- tertiary caution as Direction B); risks reading as *flashy* over *fast* if the speed discipline slips. **Settled recommendation, now spec:** build the **kit palette abstractly** (roles, not hardcoded colors — §2.1) so the direction is *one file of constants*; **default to Direction B (Neon Console) with the three-accent pastel system** (primary lime / secondary teal / tertiary purple) and **Direction C's pastel spectral keyboard strip** as the signature surface. Because the palette is abstract, a later re-pick — or a saturation nudge within the pastel intent — is one file. --- ## 5. The build shape (landed history in `docs/ARCHIVE.md` §Phase L) Three points and their sequencing. ### 5.1 `L1` — shared LICE drawing kit (the foundation) A small shared module — the palette + type + component-draw layer that the VST editor, the embed strip, and the bank panel all draw through. Honoring the house pure/shell split: - **Pure layer (CTest-covered):** the *geometry* of components — a `theme`/palette module (role → color, direction-selectable via one constants block; pure, trivially testable that contrast pairs clear their floor with the B+spectral constants) and per-component **layout/hit-test** helpers where they don't already exist (button rect, slider track/handle geometry, list-row rect + hover hit-test). These mirror `mode_switch`/`embed_strip`/`editor_geometry` — no LICE, no host types, unit-tested. - **Shell layer (DAW-verified):** the **draw kit** — LICE functions that render each component (`drawButton`, `drawSlider`, `drawListRow`, `drawWaveform`, `fillSurface` with the micro-gradient + inner highlight, `text()` via a shared `LICE_CachedFont` set). Owns the cached-font lifecycle. **WDL/vwnd reuse is assessed here, at build time, per DS-1** — reuse a vwnd piece where it's genuinely cheaper (the scroll listbox is the archetype), else draw on LICE. This is the layer that *replaces* the flat `LICE_FillRect` + `DrawTextA` calls in every surface. - **Cross-artifact reuse (load-bearing):** the kit is the **one source** all surfaces draw through — the same instinct as the one-source-multiple-views rule. A button looks identical in the panel, the embed strip, and the VST because it's the same `drawButton`. This is what makes "scope it for the whole system" real rather than divergent restyles. - **Coordination with Phase S:** L1 is not S10's dependency — S10–S13 build their interaction UX with the *current* drawing and **adopt the kit when L1 lands** (whichever order happens). The kit is the one source of drawing; when both exist, all surfaces consume it. ### 5.2 `L2` — dock-panel layout redesign (extension-side; DS-3 SETTLED: thorough, not a re-skin) **DS-3 settled (2026-07-26): this is a real, thorough layout design, not a light reskin.** The panel already has good bones (thumbnails, selection, focus ring, a LICE grid, the multi-bank vertical split, the Design View mode switch). But **M11 (about to merge to dev)** adds a substantial button inventory — the panel must lay out the *full* inventory intuitively, uncluttered, and useful. L2 designs that layout, then applies the L1 kit to draw it. **The full button/affordance inventory L2 must place (dev, M11-aware):** *Existing (landed / specced):* - Bank **grid** — thumbnails, multi-select, keyboard nav, audition, focus ring. - Design View **segmented mode switch** (`[ Arrange | Design ]`) + per-mode membership count. - Multi-bank: **named-banks tab strip** (LICE-drawn, overflow/scroll), **active-bank indicator**, **pool/banks full-height toggles**, create/rename/delete/activate-bank affordances, per-selection **move / copy / remove** sample menu. - **Prune** button (R-E) — the byte-deleting action, `warn`-colored. *M11 adds (landed — `docs/ARCHIVE.md` Milestone 11):* - **Action trigger buttons** — clickable buttons that fire the capture + provenance action family directly (capture item / capture track scopes, re-capture from source, resample-and-mute-source, batch capture, conform-on-insert, insert-at-cursor, drag-out, null-test verify). A real cluster of buttons. - **Keybinding help labels** — each capture/provenance action surfaces its current key binding (e.g. "Capture Item → F5") or an "unbound" marker. **L2 design mandate (the thorough part):** - **Group by function, not by phase.** The user thinks in *tasks* — capture, organize, reclaim, view — not in M/B/R/D phases. Cluster the capture-action buttons together (with their keybinding labels), the bank-organization affordances together (tabs + move/copy/remove + create/rename/delete), the view/mode switch in the header, and the destructive prune apart and `warn`-marked. First-principles: what does a capture-heavy workflow reach for most, and how few clicks/pixels away is it? - **A place for every button that doesn't crowd the grid.** The grid is the centerpiece; the button inventory must not squeeze it. Consider a **compact action bar/toolbar** for the frequent capture actions (icon+label buttons drawn through the kit, keybinding as a `micro` sub-label), an **overflow/menu** for the rare ones, and reserved header space for view/mode. The keybinding labels are `micro`/`text-dim` so they inform without shouting. - **Density is a design decision, not an accident.** Use the 8px grid (§3.2), elevation layers (§2.1) to separate the action bar from the grid from the bank strip without a forest of hairlines, and the kit's `warn` role to make prune (and any destructive verb) unmistakable. Hover on every interactive element (§3.3). - **Then apply the L1 kit** to draw it all: cached-font text (retire the panel's GDI text), kit `drawButton`/segment/tab draws, palette roles, micro-gradient surfaces. - **Hit-test geometry stays pure.** The layout redesign may add new pure geometry modules (an action-bar layout module, mirror of `mode_switch`/`bank_grid`) but keeps all hit-test math in the pure, CTest-covered layer. `bank_grid`/`tab_strip`/`mode_switch` stay the source of truth for what they own; new layout gets its own pure module. **Sequence L2 AFTER M11 merges to dev** — L2 must inventory M11's *actual* landed buttons, not a forecast of them. If L2 starts before M11 lands, it designs against a moving target. ### 5.3 `L3` — VST editor + embed-strip restyle [LANDED] **L3 has landed (merged `c53683e`, 2026-07-27). Phase L is now complete (L1–L7 all landed).** The ReaSampler 9000 VST editor (`IPlugView` LICE surface, `reasampler_editor.cpp`) and the S6 embed strip (`reasampler_embed.cpp`) received the full kit treatment: kit cached-font text (§3.1 — the kit's current face, no bundled font), kit component draws, the **B palette with the three-accent pastel system** (primary lime / secondary teal / tertiary purple), the **pastel spectral keyboard strip + zone bars** (active zone lifts to `accent/primary` + a static glow) as the signature surface, hover/pressed/drag states throughout. The full editor component set was restyled: the **capture-first browser** (search box + tabs + sample thumbnails, S10), **channel mono/stereo toggles** (S7), **waveform pane with start + loop markers** (S11), **keyboard strip + zone bars** (S10/S10-Z), and **ADSR + pitch sliders + Trigger/Gate & Varispeed/Preserve mode toggles** (S12/S15/S16). **What was confirmed at build:** - **All Phase S UI draw was in exactly two shells** — `reasampler_editor.cpp` (the `IPlugView` editor) and `reasampler_embed.cpp` (the S6 embed strip). Every other `src/vst/` UI file (`editor_geometry`, `keyboard_strip`, `waveform_view`, `capture_browser`, `param_slider`, `browser_scroll`, `embed_strip`) is a **pure geometry/hit-test module with zero LICE and zero draw** — the house pure/shell discipline held. L3 was therefore a **shell-side restyle of two files**; no geometry rework required. - **Full restyle, not a born-in-kit no-op.** The Phase S surfaces arrived on dev drawing flat `LICE_FillRect` blocks + raw GDI `DrawTextA`, off a local pre-L1 forest-green palette (`kColBackground` etc.) — the coordination contract's "born in the kit" branch did not occur. L3 performed the full restyle. - **Two palettes collapsed to one.** The editor shell's local `kColBackground`/`kColCardBg`/ `kColThumb`/… forest-green-on-near-black constants block (the **pre-L1 palette** this doc's §2 set out to replace) was retired; all surfaces now draw through the L1 `theme` roles. One kit, one look. - **Beta title band — textual-only distinction (Daniel, 2026-07-27).** The S18 beta channel title band gets no distinct visual accent; L3 restyles it in the standard B pastel palette and the beta-vs-stable distinction stays purely textual (the channel-derived plugin name via `app_version`, as before). No channel-specific accent color. - **VST3 class UID unchanged.** A visual refresh is not a compat event; RT/`process` path untouched; pure geometry modules remained pure throughout. **Coordination contract (resolved).** Phase S's S10–S13 built their interaction UX with the current drawing and adopted the L1 kit via L3 (the "L3 restyles them" branch). There is now one kit and one look across both artifacts. ### 5.4 Sequencing summary ``` L1 (shared kit) ──► L2 (dock-panel layout redesign, extension-side) [LANDED] └─────────► L3 (VST editor + embed-strip restyle) [LANDED] Phase L is complete — L1, L2, L3, L4, L5, L6, and L7 have all landed. ``` L1 was the foundation both L2 and L3 consumed. L2 was extension-side and landed after M11. L3 was gated on Phase S reaching dev — Phase S merged 2026-07-27 and L3 followed immediately. **Phase L is complete.** --- ## 6. Forks — all SETTLED (Daniel, 2026-07-26) **Fork DS-1 — the toolkit. SETTLED: LICE + WDL free game, no external frameworks.** - **Settled:** draw the modern look with **LICE** directly (gradients, AA, rounded, cached fonts). **Anything useful in WDL/vwnd is free game** — "don't reinvent the wheel": reuse a vwnd piece (skin/image helper, draw idiom, a specific control like the scroll listbox) where it's genuinely usable and cheaper than re-deriving; otherwise build on LICE. The one standing caution (not a ban): keep hit-test **geometry** in pure CTest-covered modules rather than importing vwnd's retained-mode object model wholesale, so the pure/shell split and its test coverage hold. - **Rejected:** any **external framework** — iPlug2, JUCE, VSTGUI. They re-open settled D-A to solve a look problem that is *not* a toolkit-ceiling problem (REAPER's own themed UI is LICE). WDL's own VST2-era `IPlug` is dated and irrelevant. - **Reasoning preserved:** LICE is a sufficient modern-dark-UI toolkit; the current "temple os" look is drawing at LICE's floor, not hitting its ceiling. No new dependency, fully consistent with D-A. **Fork DS-2 — visual direction. SETTLED 2026-07-26: Direction B (Neon Console) + Direction C's spectral keyboard strip. REVISED 2026-07-26 (Daniel): three-accent pastel system.** - **Settled (original):** **B** (near-black + one vivid accent, modern soft-synth idiom) as the system-wide target, with **C's spectral, hue-mapped keyboard strip** as the signature surface — the multi-zone-in-one-instance advantage made visual. The single accent was **electric cyan**. - **Revised (2026-07-26, Daniel) — accent layer:** the accent layer changes from **one electric cyan** to a **three-accent pastel system** — **primary = pastel lime green, secondary = pastel teal, tertiary = pastel purple**. The spectral keyboard strip becomes a **pastel** sweep anchored on the three accents (§4 Direction C). This revision is **palette-only.** *Rationale (Daniel, verbatim intent):* "the layout is a huge improvement… the only thing I want to change is the cyan — it's too CYAN, I prefer pastel hues… a three-color accent system, primary / secondary / tertiary as pastel lime green / pastel teal / pastel purple" — too cyan; prefers pastel; wants three-accent primary/secondary/tertiary. - **Revised (2026-07-26, Daniel) — neutral surfaces:** the neutral role ladder moves **from near-black up into REAPER's mid-grey theme family**, so the dock reads as *part of REAPER, not a black slab bolted on beside it*. Concrete starting ladder (§2.1): `bg/base` ≈ `#2b2b2b` (REAPER chrome), `bg/panel` ≈ `#333333`, `bg/cell` ≈ `#3a3a3a` (REAPER track bg), `line/hairline` ≈ `#4a4a4a`, `text/primary` ≈ `#dcdcdc`, `text/dim` ≈ `~#a0a0a0`+. The **elevation-ladder discipline is unchanged** (base < panel < cell by a few %, micro- gradient + inner highlight/shadow carry elevation, not hard borders). This shifts the WCAG burden (§2.1 re-read): **`text/dim` on grey** and **the three pastels as state indicators on grey** are now the tight pairs — mid-grey-on-mid-grey for dim text, shrunken pastel cushion for the accents — the implementer re-verifies both against the grey ladder, not near-black. *Rationale (Daniel, verbatim):* "we should adjust the neutral colors to be more grey to match my REAPER theme… Text and surfaces should match" — the dock should read as part of REAPER, not a black box. - **Font upgrade — considered and DECLINED (Daniel, 2026-07-26).** A stylish/bundled-font upgrade was on the table in an earlier draft of this revision and Daniel **declined it**: he does not want to bundle and redistribute a font. The kit keeps its **current** cached-font face; no new typeface is specified (§3.1). Recorded here so the question is not re-raised — the §3.1 win is the AA cached-glyph mechanism, not the face. - **Not selected:** **A Studio Rack** (evolutionary green — retained only as a fallback if B/C read too loud for pro-audio). - **Reasoning preserved:** modern-sleek center + one bold identity surface; the revision keeps that shape but trades neon punch for pastel calm (Daniel's taste, stated on seeing the landed layout) and adds categorical meaning via the accent trio. The kit palette is abstract (roles, one constants block — now three accent roles instead of one), so this is still **reversible for ~one file** — a starting direction, not a permanent lock. The final hex is the implementer's to lock against the theme module's WCAG tests, staying within the pastel intent (the "soft-side" floor discipline, §2.1). **Fork DS-3 — panel refresh scope. SETTLED: thorough layout redesign (not a light re-skin).** - **Settled:** L2 is a **real, thorough layout design** that lays out the full button inventory (including M11's action buttons + keybinding labels) intuitively, uncluttered, and useful — then applies the L1 kit to draw it. Sequenced **after M11 merges** so it designs against the actual button set. - **Superseded:** the earlier "(a) light — apply the kit, keep the structure" lean. M11's button additions make a *layout* pass necessary, not just a skin pass — a forest of new buttons crammed into the current structure would be exactly the clutter the redesign exists to prevent. - **Reasoning preserved:** the panel's grid/vertical-split bones are sound and stay; the *layout of the button inventory around them* is the design work. Structural rework destabilizing landed Phase-B panel work is still avoided — L2 groups and places, it does not tear down the vertical-split / grid / tab structure. --- ## 7. What this does NOT change (guardrails) - **D-A stays settled** — bare Steinberg SDK + LICE, no framework. This doc *reinforces* it (LICE is sufficient), it does not re-open it. (D-A lives on the phase-s worktree; on dev it is the settled Phase S build-shape decision.) - **The pure/shell split holds** — all layout/hit-test stays in pure, CTest-covered geometry modules; the kit's *draw* half is shell, its *geometry* half is pure. No hit-test math moves into untestable code, even where a WDL piece is reused (DS-1). - **RT discipline untouched** — the kit is draw-thread only; nothing here touches `process` or the off-thread reload handoff (Phase S concern). - **Read-only-over-bank untouched** — this is look-and-feel; no data-ownership change. - **The instrument keeps the same VST3 class UID / component-state contract** — a visual refresh is not a compat event (Phase S concern; noted for when L3 runs). - **Windows-only (D5)** — the extension's GDI/HFONT path assumes Windows; no cross-platform font-fallback concern. The kit keeps its current cached-font face (no bundled font — DS-2 declined the font upgrade), so §3.1 introduces no new redistribution or cross-platform font obligation. - **Phase S is not gated on Phase L** — S7–S13 proceeded in parallel; they adopted the kit via L3 when it landed. Phase L is complete (L1–L7 all landed). --- ## 8. Antialiasing disposition — the drawn-surface audit A standing inventory of every class of drawn surface and how it answers antialiasing, so the audit is re-runnable rather than a one-off sweep. **The rule the table applies:** an axis-aligned fill or hairline has no aliasing to remove — LICE's `aa` flag is inert on a pure horizontal or vertical run — so "already clean" there is a statement about geometry, not a concession. Everything with a slope or a curve must draw through a primitive that antialiases. **Primitive gotchas this audit established (verified in `vendor/WDL/WDL/lice/`):** - `LICE_Line` takes INTEGER endpoints. `aa=true` antialiases the span, but the endpoints are still quantized; `LICE_FLine` keeps float endpoints and `LICE_ThickFLine` is *always* antialiased and adds width. - `LICE_FillTriangle` takes **no** `aa` parameter at all — its sloped edges alias, and the only fix inside the kit is to re-stroke those edges with an AA line in the same ink. - LICE has no thick-arc primitive. A wider ring is drawn as adjacent 1 px `LICE_Arc` calls at stepped radii, which keeps every ring antialiased. - A min/max waveform column plot cannot be antialiased by the column fill itself (the columns are vertical). The outline is what reads as jagged, so it is stroked separately. | Surface | Where | Disposition | |---|---|---| | Radial knob track + value arc | `editor_internal.h` `drawKnobFace` | Was AA (`LICE_Arc`, 1 px). **Widened** to a 3 px stacked-radius ring; the bigger knob is what made 1 px read thin. | | Knob needle | `drawKnobFace` | **Fixed** — was integer-endpoint `LICE_Line`; now `LICE_ThickFLine` (always AA, float endpoints, 2 px). | | Inner curve dial arc + needle | `drawInnerDial` | **Fixed** — 2 px stacked-radius arc; needle moved to `LICE_FLine` with float endpoints. | | Staged envelope segment slopes | `editor_paint_waveform.cpp` | **Fixed** — `LICE_ThickFLine` at 2 px, replacing integer-endpoint `LICE_Line`. | | Spline (drawn EG) contour | `editor_paint_waveform.cpp` `paintSplineOverlay` | **Fixed** — same treatment, one trace grammar. | | Velocity-curve popup trace | `editor_paint_curve.cpp` | **Fixed** — same treatment. | | Velocity-curve mini thumbnail | `editor_paint_curve.cpp` | Left at 1 px AA `LICE_Line` — a 2 px trace blots at thumbnail scale. | | Waveform min/max columns | `draw_kit.cpp` `drawWaveform` | **Fixed** — column fill unchanged (it cannot alias), plus an AA `LICE_FLine` stroke joining each column's extremes to its neighbour's, in the same ink. Shared with the docked bank panel and the browser cards. **Measured cost** (Release, MSVC 14.44, real LICE, 24 stereo cards × 136 columns = 6528 columns): fill alone 0.070 ms per full-grid repaint, fill+stroke 0.48 ms — the stroke is ~0.41 ms, about 2.5% of a 60 Hz frame, and the grid repaints on hover/scroll/drag, not continuously. One-off scratchpad measurement, 2026-08-01, harness not committed — not a standing regression guard; re-measure before relying on it again. | | Preview play triangle | `editor_paint_chrome.cpp` | **Fixed** — `LICE_FillTriangle` has no `aa`; its two sloped edges are re-stroked with AA `LICE_FLine`. | | Envelope/spline node handles (squares) | `editor_paint_waveform.cpp` | Already clean — axis-aligned `LICE_FillRect`. | | Envelope curve knots (circles) | `editor_paint_waveform.cpp` | Already clean — `LICE_FillCircle` with `aa=true`. | | Knob body disc | `drawKnobFace` / `drawInnerDial` | Already clean — `LICE_FillCircle` with `aa=true`. | | Buttons | `draw_kit.cpp` `drawButton` | Already clean — `LICE_RoundRect` with `aa=true`. | | Piano key faces + edges | `editor_paint_chrome.cpp` `drawKeyboard` | Already clean — axis-aligned fills and a vertical hairline. **See §8.1.** | | Loop span, crossfade region, marker bars, grab tab | `editor_paint_waveform.cpp` | Already clean — axis-aligned fills. | | Group fences, card/tab/tooltip borders, focus rings | deck, browse, panel painters | Already clean — `LICE_DrawRect`, axis-aligned. | | Surface fills + inner edge highlights | `draw_kit.cpp` `fillSurface` | Already clean — `LICE_GradRect` + axis-aligned hairlines. | | Embed strip (TCP/MCP) | `reasampler_embed.cpp` | Already clean — axis-aligned fills only. | | Docked bank panel chrome | `panel_render.cpp` | Already clean — axis-aligned fills, rects and hairlines. Its only exposure to this pass is the shared `drawWaveform`. | | Text | `draw_kit.cpp` `text` | Already clean — `LICE_CachedFont` AA glyph cache (§1.1). | ### 8.1 Was the piano-key width defect an aliasing artifact? **No.** Every piano key is an axis-aligned `LICE_FillRect` with an integer width, so there is no sloped or curved edge for aliasing to act on — the defect could not have had that cause. It was integer-division residue: `keyboard_strip` tiles same-class keys at one integer width and the indivisible remainder of the band width has to go *somewhere*. The fix put it in symmetric end margins instead of in a key, which is arithmetic, not rasterization. **Does the fix survive DPI scaling?** At the client-pixel level, yes — key widths are uniform by construction at every client width the strip's test sweep covers. Above that level it is **unverified**, and for a structural reason worth keeping visible: nothing in the instrument implements `IPlugViewContentScaleSupport`, so a host that scales the plugin window resamples the already-rasterized uniform widths at the physical-pixel level, where the guarantee no longer applies. That is a host-scaling question, not an antialiasing one, and it is recorded as a gotcha in `src/core/instrument/CLAUDE.md`.