@pieai/swimmer-ui-kit 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,112 @@
3
3
  All notable changes to `@pieai/swimmer-ui-kit`.
4
4
  Format: [Keep a Changelog](https://keepachangelog.com); versioning: semver.
5
5
 
6
+ ## 2.2.0 — 2026-09-11
7
+
8
+ Minor, not major: nothing published is removed or renamed, and no `--game-ui-*`
9
+ name changes. Two token **values** change, and every button in every product
10
+ gets a visible lip — deliberately, and described under Changed.
11
+
12
+ ### Added
13
+
14
+ - **Named liquid forms.** `LIQUID_FORMS` gives the gooey engine six looks with
15
+ names instead of a page of physical knobs: `press`, `settle`, `merge`,
16
+ `follow`, `fill`, `drain`. Each is a tested bundle of blur, contrast, outline
17
+ and spring, resolved through `liquidFormGroup` / `liquidFormItem`, which
18
+ merge overrides into a form rather than replacing it.
19
+
20
+ This layer was missing, and its absence had a measurable cost: the only
21
+ production consumer had to hand-tune 166 lines around a normal button to get
22
+ one liquid control, and the corrections it discovered never came back here.
23
+
24
+ - **`LiquidSurface`.** The packaging for the single-body forms — a
25
+ non-interactive silhouette behind, real DOM in front and never transformed,
26
+ so a pressed control keeps its hit target, its focus ring and its crisp text.
27
+ `merge` and `follow` describe a relationship between siblings and stay with
28
+ `LiquidGroup`, where they belong.
29
+
30
+ - **`GameButton` gains `surface="flat" | "liquid"`.** Deliberately a second
31
+ axis rather than a new `variant`: `variant` is a tone, and folding 'liquid'
32
+ into it would have made the brand's signature surface mutually exclusive with
33
+ saying "this action is destructive". The default path renders byte-identical
34
+ markup — asserted in a test — so no existing call site moves.
35
+
36
+ - **`blob`: an organic silhouette drawn as geometry.** `LiquidGroup.Item`
37
+ takes a `blob` shape, and the single-body forms use it. The outline swells
38
+ outward from the control's own box along a closed Catmull-Rom spline, so it
39
+ is exact at every device ratio and zoom, and it is **outward-only** by
40
+ construction — no amplitude can push the surface inside a label's padding or
41
+ across a hit target.
42
+
43
+ This replaces `waviness` as the way a form gets an irregular edge, and the
44
+ reason is a measurement rather than a preference. Chrome resamples
45
+ `feDisplacementMap` with nearest-neighbour, so a displaced contour can only
46
+ land on whole pixels. Captured at device ratio 1 with alpha recovered from
47
+ two backgrounds and sampled along a straight edge, the settings shipped in
48
+ the 2.2.0 development line moved the outline by a constant 1.5px and varied
49
+ it by 0.01px across the whole side: there was no wave at all, which is
50
+ exactly how it looked. Raising the frequency does not fix it — it trades one
51
+ whole-side step for per-pixel jitter, which is the frayed edge the effect was
52
+ blamed for in the first place.
53
+
54
+ `waviness` and `wavinessFreq` remain exported, documented and unchanged for
55
+ callers who want the filter-side texture on large merging bodies, which is
56
+ what the donor uses it for.
57
+
58
+ - **`gloss`.** Interior volume for a liquid body, from `feSpecularLighting` at
59
+ a grazing 22° elevation. An irregular outline around a flat fill still reads
60
+ as a sticker; a surface reads as a material through how it catches light. The
61
+ light is distant rather than positional so it does not have to be recomputed
62
+ from a measured box on every resize.
63
+
64
+ - **`LIQUID_GOOEY_MIN_EDGE_RAMP` and `liquidGooeyEdgeContrast`.** The floor on
65
+ how narrow the goo threshold may leave an edge.
66
+
67
+ ### Changed
68
+
69
+ - **Buttons have a lip.** `--game-ui-button-lip-ink` and
70
+ `--game-ui-button-lip-depth` add a solid, unblurred band under
71
+ `.game-ui-button`, and a press travels its height instead of only scaling in
72
+ place. This is what separates a game control from a card: a key has a height
73
+ you can see, and pressing it has somewhere to go.
74
+
75
+ **Blast radius: every button in every product using the kit.** Only
76
+ `.game-ui-button` is affected — tabs, toggles and segmented options live
77
+ inside other surfaces and keep their flat treatment — and `ghost` and
78
+ `disabled` opt out. A product that wants the old look sets
79
+ `--game-ui-button-lip-depth: 0px`.
80
+
81
+ - **The goo threshold has a minimum edge width.** `contrast` is not a look: the
82
+ alpha crossing sits at a fixed 5/12 of the ramp, so contrast decides only how
83
+ many pixels wide the edge is. Blur 4 with contrast 24 — the pairing the one
84
+ production consumer had converged on — works out at 0.43px, an edge thinner
85
+ than the pixel drawing it, which is the definition of an aliased one.
86
+ `liquidGooeyEdgeContrast` now lowers contrast until the ramp is at least
87
+ 1.3px, the width at which measured contour roughness bottoms out at the score
88
+ of a shape with no displacement at all.
89
+
90
+ This cannot move a silhouette, only soften how it is drawn, and it only
91
+ engages where an edge was too thin. `merge` and the other soft forms are
92
+ untouched.
93
+
94
+ - **Resting `waviness` is 0 by default.** `--game-ui-liquid-gooey-waviness` was
95
+ 6 and is now 0; the SSR fallback matches. 6 came from the donor and meant the
96
+ out-of-the-box look was a permanently undulating outline, which reads as a
97
+ rendering defect on a static rounded rectangle — University cancelled it with
98
+ `waviness={0}` on all eighteen surfaces that use liquid, and those overrides
99
+ are now unnecessary.
100
+
101
+ **Migration:** none required. A surface that genuinely wants the old molten
102
+ edge passes `waviness={6}`.
103
+
104
+ - **Removed before release: `LIQUID_REST_EDGE_SLOPE_MAX` and
105
+ `liquidRestEdgeSlope`.** Added earlier in this same unreleased line, on the
106
+ theory that a resting edge tears when amplitude times frequency goes too
107
+ high. The measurement above shows the cap was not protecting the edge — it
108
+ was selecting frequencies low enough for the displacement to stop varying,
109
+ and scoring well because the feature had disappeared. Nothing shipped with
110
+ them, so nothing has to migrate.
111
+
6
112
  ## 2.1.0 — 2026-09-02
7
113
 
8
114
  ### Added
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { ButtonHTMLAttributes } from 'react';
2
+ import { CSSProperties } from 'react';
2
3
  import { ForwardRefExoticComponent } from 'react';
3
4
  import { HTMLAttributes } from 'react';
4
5
  import { InputHTMLAttributes } from 'react';
@@ -16,6 +17,17 @@ export declare interface BendTuning {
16
17
  horizontal?: number;
17
18
  }
18
19
 
20
+ declare interface BlobShape {
21
+ /** Outward bulge, in px. 0 gives the plain rounded rectangle. */
22
+ readonly amplitude: number;
23
+ /** Which shape. Same seed, same silhouette, every render. */
24
+ readonly seed?: number;
25
+ /** How many swells go round the outline. 2 is lazy, 5 is busy. */
26
+ readonly lobes?: number;
27
+ /** Rotates the swells around the outline; the knob a press animates. */
28
+ readonly phase?: number;
29
+ }
30
+
19
31
  export declare const CLAY_ASSET_BASE_PATH: "/assets/game/ui/clay/phase03-clay-kit";
20
32
 
21
33
  export declare const CLAY_ASSET_SIZE_TOKENS: {
@@ -856,16 +868,6 @@ export declare interface DissolveOptions {
856
868
 
857
869
  export declare type DissolveValue = boolean | number | DissolveOptions;
858
870
 
859
- /**
860
- * Morph shape physics adapted from `liquid-gooey` by Jakub Antalik.
861
- *
862
- * Source: https://github.com/Jakubantalik/Libraries/tree/main/packages/liquid-gooey
863
- * Pinned commit: 3862ffa345217443b63696a8c331a0664eea4b04
864
- * Copyright (c) 2026 Jakub Antalik. Licensed under the MIT License.
865
- * See NOTICE for the attribution and license text. This local module keeps
866
- * the donor's centre -> size -> corner timeline and content cross-blur, but
867
- * routes every tunable value through SwimmerUIKit's token layer.
868
- */
869
871
  declare interface EvolveOptions {
870
872
  /** Spring driving the liquid mass's centre. */
871
873
  massStiffness?: number;
@@ -1374,16 +1376,24 @@ export declare interface GameBuildLibraryProps {
1374
1376
  'data-testid'?: string | undefined;
1375
1377
  }
1376
1378
 
1377
- export declare function GameButton({ children, className, onClick, sound, static: isStatic, type, variant, ...props }: GameButtonProps): ReactNode;
1379
+ export declare function GameButton({ children, className, onClick, sound, static: isStatic, surface, type, variant, ...props }: GameButtonProps): ReactNode;
1378
1380
 
1379
1381
  export declare interface GameButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
1380
1382
  children: ReactNode;
1381
1383
  sound?: GameInteractionSoundOptions | false;
1382
1384
  /** Disable the scale-on-press feedback where the motion would distract. */
1383
1385
  static?: boolean;
1386
+ /**
1387
+ * Draw the button on a liquid body. The button itself is unchanged — same
1388
+ * element, same classes, same hit target; a silhouette behind it does the
1389
+ * deforming, so text stays crisp and the tap target never shrinks.
1390
+ */
1391
+ surface?: GameButtonSurface;
1384
1392
  variant?: GameButtonVariant;
1385
1393
  }
1386
1394
 
1395
+ export declare type GameButtonSurface = 'flat' | 'liquid';
1396
+
1387
1397
  export declare type GameButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger' | 'success';
1388
1398
 
1389
1399
  /**
@@ -2330,6 +2340,11 @@ export declare interface ImageMeltOptions {
2330
2340
  waviness?: number;
2331
2341
  }
2332
2342
 
2343
+ /** Every form name, for shelves, docs and exhaustiveness checks. */
2344
+ export declare const LIQUID_FORM_NAMES: readonly LiquidForm[];
2345
+
2346
+ export declare const LIQUID_FORMS: Readonly<Record<LiquidForm, LiquidFormSpec>>;
2347
+
2333
2348
  /**
2334
2349
  * The default fraction is intentionally derived from the rendered surface,
2335
2350
  * not from the requested waviness token. A fixed pixel displacement is safe
@@ -2337,6 +2352,63 @@ export declare interface ImageMeltOptions {
2337
2352
  */
2338
2353
  export declare const LIQUID_GOOEY_WAVINESS_MAX_FRACTION = 0.3;
2339
2354
 
2355
+ /**
2356
+ * The named looks, ordered by how much cohesion the liquid is losing:
2357
+ * `press` barely deforms, `drain` stops being a shape at all.
2358
+ */
2359
+ export declare type LiquidForm = 'press' | 'settle' | 'merge' | 'follow' | 'fill' | 'drain';
2360
+
2361
+ /** Filter-level knobs a form fixes on the group. */
2362
+ export declare interface LiquidFormGroup {
2363
+ readonly blur: number;
2364
+ readonly contrast: number;
2365
+ /**
2366
+ * How far the outline swells outward from the control's box, in px. The
2367
+ * bulge is outward-only, so this never eats into a label's padding, and it
2368
+ * is clamped to 18% of the shorter side so one number suits a 44px button
2369
+ * and a 14px meter.
2370
+ */
2371
+ readonly blob: number;
2372
+ /** How many swells go round the outline. 2 is lazy, 5 is busy. */
2373
+ readonly lobes: number;
2374
+ /**
2375
+ * Volume. The edge says 「liquid」 only as an outline; this is what makes the
2376
+ * inside of the body look like a material instead of a flat sticker.
2377
+ */
2378
+ readonly gloss: number;
2379
+ readonly filterPadding: number;
2380
+ }
2381
+
2382
+ /**
2383
+ * Resolve a form into group props, letting a caller override single knobs.
2384
+ *
2385
+ * Overrides are merged rather than replacing the bundle, so a surface that
2386
+ * needs one different value does not have to restate the other three and
2387
+ * silently drift from the form it claims to be using.
2388
+ */
2389
+ export declare function liquidFormGroup(form: LiquidForm, overrides?: Partial<LiquidFormGroup>): LiquidFormGroup;
2390
+
2391
+ /** Item-level behaviour a form fixes on each participating item. */
2392
+ export declare interface LiquidFormItem {
2393
+ readonly effect?: 'morph' | 'melt' | 'bend';
2394
+ readonly morph?: MorphTuning;
2395
+ readonly transition?: Transition;
2396
+ readonly dissolve?: boolean;
2397
+ }
2398
+
2399
+ /** Resolve a form into item props, with the same merge-don't-replace rule. */
2400
+ export declare function liquidFormItem(form: LiquidForm, overrides?: LiquidFormItem): LiquidFormItem;
2401
+
2402
+ export declare interface LiquidFormSpec {
2403
+ /** One sentence on what a viewer sees, for docs and for picking. */
2404
+ readonly summary: string;
2405
+ readonly group: LiquidFormGroup;
2406
+ readonly item: LiquidFormItem;
2407
+ }
2408
+
2409
+ /** The one-line description of each form, for pickers and documentation. */
2410
+ export declare function liquidFormSummary(form: LiquidForm): string;
2411
+
2340
2412
  declare interface LiquidGooeyBudgetOptions {
2341
2413
  maxAnimatedGroups?: number;
2342
2414
  maxFilterArea?: number;
@@ -2388,6 +2460,11 @@ export declare interface LiquidGroupProps extends Omit<HTMLAttributes<HTMLDivEle
2388
2460
  blur?: number;
2389
2461
  /** Alpha-contrast slope. Larger values make the liquid edge harder. */
2390
2462
  contrast?: number;
2463
+ /**
2464
+ * Volume. 0 leaves the body a flat colour; higher values light it as a
2465
+ * curved surface so it reads as a material rather than as a silhouette.
2466
+ */
2467
+ gloss?: number;
2391
2468
  /** Surface fill. Defaults to the kit's theme surface token. */
2392
2469
  fill?: string;
2393
2470
  /** Extra filter-region slack in px for the silhouette's painted edges. */
@@ -2432,6 +2509,12 @@ export declare interface LiquidItemProps extends Omit<HTMLAttributes<HTMLDivElem
2432
2509
  delay?: number;
2433
2510
  /** Override the measured content border radius for the silhouette. */
2434
2511
  radius?: number | CornerRadii;
2512
+ /**
2513
+ * Pour the outline outward into an organic body instead of leaving it a
2514
+ * rounded rectangle. The bulge is outward-only, so the silhouette always
2515
+ * contains the content's own box however bold the amplitude gets.
2516
+ */
2517
+ blob?: BlobShape;
2435
2518
  /**
2436
2519
  * Select the adopted item surface behavior. Bend follows child geometry.
2437
2520
  * Move is a group gesture (`motion="follow"`), not an item effect.
@@ -2468,6 +2551,27 @@ export declare interface LiquidMetalButtonProps extends ButtonHTMLAttributes<HTM
2468
2551
 
2469
2552
  export declare type LiquidMetalRendererMode = 'auto' | 'css' | 'webgl';
2470
2553
 
2554
+ export declare function LiquidSurface({ children, form, active, fill, stroke, shadow, radius, className, style, }: LiquidSurfaceProps): ReactNode;
2555
+
2556
+ export declare interface LiquidSurfaceProps {
2557
+ children: ReactNode;
2558
+ /** Which named look. Defaults to the press form, the one a control wants. */
2559
+ form?: LiquidForm;
2560
+ /**
2561
+ * Whether the form is engaged: pressed for `press`, landed for `settle`,
2562
+ * leaving for `drain`. Ignored by forms with no engaged state.
2563
+ */
2564
+ active?: boolean;
2565
+ /** Silhouette paint. Defaults to the kit's raised surface token. */
2566
+ fill?: string;
2567
+ stroke?: string;
2568
+ shadow?: string;
2569
+ /** Corner radius of the body, in px. 999 gives a pill. */
2570
+ radius?: number;
2571
+ className?: string;
2572
+ style?: CSSProperties;
2573
+ }
2574
+
2471
2575
  export declare interface MorphTuning {
2472
2576
  /** Whether shape-change physics is enabled. The kit default is token-on. */
2473
2577
  shape?: boolean;