@pieai/swimmer-ui-kit 2.1.0 → 2.3.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,157 @@
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.3.0 — 2026-09-11
7
+
8
+ Minor: additive, plus one token value change on the default liquid fill.
9
+
10
+ ### Added
11
+
12
+ - **`scaleY` on `LiquidGroup.Item`.** The two scale axes can now disagree, which
13
+ is what squash and stretch needs. A body that spreads sideways as it is pushed
14
+ down is made of something; one that scales uniformly is a thing getting
15
+ smaller.
16
+
17
+ - **`wobbly` transition preset.** Damping ratio 0.24 against `bouncy`'s 0.48, so
18
+ a pressed body crosses its rest shape three or four times instead of once —
19
+ the difference between 「it bounced」 and 「it is made of something」. `press`
20
+ and `settle` use it.
21
+
22
+ - **A Liquid surface section in `GameUiPreview`.** The showcase had a section
23
+ for the WebGL metal CTA, which no product uses, and none for the surface that
24
+ actually ships — so the only way to see a liquid button was to know which
25
+ screen of which product happened to render one.
26
+
27
+ ### Changed
28
+
29
+ - **The default liquid fill is `--game-ui-accent-pale`, not
30
+ `--game-ui-surface-raised`.** `surface-raised` is the right fill for a flat
31
+ secondary button and the wrong one for a liquid body: the whole point of this
32
+ surface is a shape you can see, and a near-white shape on a cream panel has no
33
+ shape at all. It was invisible next to its own flat twin while every coloured
34
+ tone read clearly. A product that wants the old fill sets
35
+ `--game-ui-liquid-surface-fill` back.
36
+
37
+ - **The button lip is shallower at compact density.** Height reads as height
38
+ because there is room around it; four solid bands stacked close together in a
39
+ 34px row read as ruled lines instead of as four keys.
40
+
41
+ ### Not shipped
42
+
43
+ A jelly material — four lighting terms, hue-preserving sheen, luminance-scaled
44
+ headroom — was built, reviewed against the 2.2.0 look, and rejected: it read as
45
+ too abrupt on a coloured body, and the existing single specular pass was better.
46
+ The motion from that work is what shipped. The material is in the history at
47
+ `ab3d706` if a surface ever wants it, and the two measured dead ends behind it
48
+ are worth keeping: multiplying the fill by a diffuse term darkens a brand colour
49
+ to ceramic, and adding white light at gel strength desaturates it to cream.
50
+
51
+ ## 2.2.0 — 2026-09-11
52
+
53
+ Minor, not major: nothing published is removed or renamed, and no `--game-ui-*`
54
+ name changes. Two token **values** change, and every button in every product
55
+ gets a visible lip — deliberately, and described under Changed.
56
+
57
+ ### Added
58
+
59
+ - **Named liquid forms.** `LIQUID_FORMS` gives the gooey engine six looks with
60
+ names instead of a page of physical knobs: `press`, `settle`, `merge`,
61
+ `follow`, `fill`, `drain`. Each is a tested bundle of blur, contrast, outline
62
+ and spring, resolved through `liquidFormGroup` / `liquidFormItem`, which
63
+ merge overrides into a form rather than replacing it.
64
+
65
+ This layer was missing, and its absence had a measurable cost: the only
66
+ production consumer had to hand-tune 166 lines around a normal button to get
67
+ one liquid control, and the corrections it discovered never came back here.
68
+
69
+ - **`LiquidSurface`.** The packaging for the single-body forms — a
70
+ non-interactive silhouette behind, real DOM in front and never transformed,
71
+ so a pressed control keeps its hit target, its focus ring and its crisp text.
72
+ `merge` and `follow` describe a relationship between siblings and stay with
73
+ `LiquidGroup`, where they belong.
74
+
75
+ - **`GameButton` gains `surface="flat" | "liquid"`.** Deliberately a second
76
+ axis rather than a new `variant`: `variant` is a tone, and folding 'liquid'
77
+ into it would have made the brand's signature surface mutually exclusive with
78
+ saying "this action is destructive". The default path renders byte-identical
79
+ markup — asserted in a test — so no existing call site moves.
80
+
81
+ - **`blob`: an organic silhouette drawn as geometry.** `LiquidGroup.Item`
82
+ takes a `blob` shape, and the single-body forms use it. The outline swells
83
+ outward from the control's own box along a closed Catmull-Rom spline, so it
84
+ is exact at every device ratio and zoom, and it is **outward-only** by
85
+ construction — no amplitude can push the surface inside a label's padding or
86
+ across a hit target.
87
+
88
+ This replaces `waviness` as the way a form gets an irregular edge, and the
89
+ reason is a measurement rather than a preference. Chrome resamples
90
+ `feDisplacementMap` with nearest-neighbour, so a displaced contour can only
91
+ land on whole pixels. Captured at device ratio 1 with alpha recovered from
92
+ two backgrounds and sampled along a straight edge, the settings shipped in
93
+ the 2.2.0 development line moved the outline by a constant 1.5px and varied
94
+ it by 0.01px across the whole side: there was no wave at all, which is
95
+ exactly how it looked. Raising the frequency does not fix it — it trades one
96
+ whole-side step for per-pixel jitter, which is the frayed edge the effect was
97
+ blamed for in the first place.
98
+
99
+ `waviness` and `wavinessFreq` remain exported, documented and unchanged for
100
+ callers who want the filter-side texture on large merging bodies, which is
101
+ what the donor uses it for.
102
+
103
+ - **`gloss`.** Interior volume for a liquid body, from `feSpecularLighting` at
104
+ a grazing 22° elevation. An irregular outline around a flat fill still reads
105
+ as a sticker; a surface reads as a material through how it catches light. The
106
+ light is distant rather than positional so it does not have to be recomputed
107
+ from a measured box on every resize.
108
+
109
+ - **`LIQUID_GOOEY_MIN_EDGE_RAMP` and `liquidGooeyEdgeContrast`.** The floor on
110
+ how narrow the goo threshold may leave an edge.
111
+
112
+ ### Changed
113
+
114
+ - **Buttons have a lip.** `--game-ui-button-lip-ink` and
115
+ `--game-ui-button-lip-depth` add a solid, unblurred band under
116
+ `.game-ui-button`, and a press travels its height instead of only scaling in
117
+ place. This is what separates a game control from a card: a key has a height
118
+ you can see, and pressing it has somewhere to go.
119
+
120
+ **Blast radius: every button in every product using the kit.** Only
121
+ `.game-ui-button` is affected — tabs, toggles and segmented options live
122
+ inside other surfaces and keep their flat treatment — and `ghost` and
123
+ `disabled` opt out. A product that wants the old look sets
124
+ `--game-ui-button-lip-depth: 0px`.
125
+
126
+ - **The goo threshold has a minimum edge width.** `contrast` is not a look: the
127
+ alpha crossing sits at a fixed 5/12 of the ramp, so contrast decides only how
128
+ many pixels wide the edge is. Blur 4 with contrast 24 — the pairing the one
129
+ production consumer had converged on — works out at 0.43px, an edge thinner
130
+ than the pixel drawing it, which is the definition of an aliased one.
131
+ `liquidGooeyEdgeContrast` now lowers contrast until the ramp is at least
132
+ 1.3px, the width at which measured contour roughness bottoms out at the score
133
+ of a shape with no displacement at all.
134
+
135
+ This cannot move a silhouette, only soften how it is drawn, and it only
136
+ engages where an edge was too thin. `merge` and the other soft forms are
137
+ untouched.
138
+
139
+ - **Resting `waviness` is 0 by default.** `--game-ui-liquid-gooey-waviness` was
140
+ 6 and is now 0; the SSR fallback matches. 6 came from the donor and meant the
141
+ out-of-the-box look was a permanently undulating outline, which reads as a
142
+ rendering defect on a static rounded rectangle — University cancelled it with
143
+ `waviness={0}` on all eighteen surfaces that use liquid, and those overrides
144
+ are now unnecessary.
145
+
146
+ **Migration:** none required. A surface that genuinely wants the old molten
147
+ edge passes `waviness={6}`.
148
+
149
+ - **Removed before release: `LIQUID_REST_EDGE_SLOPE_MAX` and
150
+ `liquidRestEdgeSlope`.** Added earlier in this same unreleased line, on the
151
+ theory that a resting edge tears when amplitude times frequency goes too
152
+ high. The measurement above shows the cap was not protecting the edge — it
153
+ was selecting frequencies low enough for the displacement to stop varying,
154
+ and scoring well because the feature had disappeared. Nothing shipped with
155
+ them, so nothing has to migrate.
156
+
6
157
  ## 2.1.0 — 2026-09-02
7
158
 
8
159
  ### 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. */
@@ -2426,12 +2503,24 @@ export declare interface LiquidItemProps extends Omit<HTMLAttributes<HTMLDivElem
2426
2503
  x?: number;
2427
2504
  y?: number;
2428
2505
  scale?: number;
2506
+ /**
2507
+ * Vertical scale, when it should differ from `scale`. A body that squashes
2508
+ * wider as it gets shorter reads as jelly; one that scales uniformly reads
2509
+ * as a thing getting smaller.
2510
+ */
2511
+ scaleY?: number;
2429
2512
  /** Spring preset/config or an explicit duration/easing pair. */
2430
2513
  transition?: Transition;
2431
2514
  /** Delay before this item starts its group-clock transition, in ms. */
2432
2515
  delay?: number;
2433
2516
  /** Override the measured content border radius for the silhouette. */
2434
2517
  radius?: number | CornerRadii;
2518
+ /**
2519
+ * Pour the outline outward into an organic body instead of leaving it a
2520
+ * rounded rectangle. The bulge is outward-only, so the silhouette always
2521
+ * contains the content's own box however bold the amplitude gets.
2522
+ */
2523
+ blob?: BlobShape;
2435
2524
  /**
2436
2525
  * Select the adopted item surface behavior. Bend follows child geometry.
2437
2526
  * Move is a group gesture (`motion="follow"`), not an item effect.
@@ -2468,6 +2557,27 @@ export declare interface LiquidMetalButtonProps extends ButtonHTMLAttributes<HTM
2468
2557
 
2469
2558
  export declare type LiquidMetalRendererMode = 'auto' | 'css' | 'webgl';
2470
2559
 
2560
+ export declare function LiquidSurface({ children, form, active, fill, stroke, shadow, radius, className, style, }: LiquidSurfaceProps): ReactNode;
2561
+
2562
+ export declare interface LiquidSurfaceProps {
2563
+ children: ReactNode;
2564
+ /** Which named look. Defaults to the press form, the one a control wants. */
2565
+ form?: LiquidForm;
2566
+ /**
2567
+ * Whether the form is engaged: pressed for `press`, landed for `settle`,
2568
+ * leaving for `drain`. Ignored by forms with no engaged state.
2569
+ */
2570
+ active?: boolean;
2571
+ /** Silhouette paint. Defaults to the kit's raised surface token. */
2572
+ fill?: string;
2573
+ stroke?: string;
2574
+ shadow?: string;
2575
+ /** Corner radius of the body, in px. 999 gives a pill. */
2576
+ radius?: number;
2577
+ className?: string;
2578
+ style?: CSSProperties;
2579
+ }
2580
+
2471
2581
  export declare interface MorphTuning {
2472
2582
  /** Whether shape-change physics is enabled. The kit default is token-on. */
2473
2583
  shape?: boolean;
@@ -2545,6 +2655,6 @@ declare type Transition = TransitionPreset | SpringConfig | {
2545
2655
  ease?: string;
2546
2656
  };
2547
2657
 
2548
- declare type TransitionPreset = 'snappy' | 'smooth' | 'bouncy';
2658
+ declare type TransitionPreset = 'snappy' | 'smooth' | 'bouncy' | 'wobbly';
2549
2659
 
2550
2660
  export { }