@anyknown/ui 0.7.0 → 0.8.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/README.md +7 -8
- package/dist/brand.css +101 -214
- package/dist/components/button/Button.d.ts +3 -2
- package/dist/components/button/Button.js +32 -48
- package/dist/components/checkbox/Checkbox.js +24 -48
- package/dist/components/dialog/Dialog.js +1 -1
- package/dist/components/empty-state/EmptyState.js +1 -1
- package/dist/components/ghost/Ghost.d.ts +16 -0
- package/dist/components/ghost/Ghost.js +50 -0
- package/dist/components/group/Group.d.ts +66 -0
- package/dist/components/group/Group.js +166 -0
- package/dist/components/handoff-receipt/HandoffReceipt.js +12 -42
- package/dist/components/icon/Chevron.d.ts +9 -0
- package/dist/components/icon/Chevron.js +6 -0
- package/dist/components/icon/icon.d.ts +35 -0
- package/dist/components/icon/icon.js +10 -0
- package/dist/components/icon-button/IconButton.d.ts +17 -0
- package/dist/components/icon-button/IconButton.js +66 -0
- package/dist/components/page/Page.d.ts +64 -0
- package/dist/components/page/Page.js +163 -0
- package/dist/components/popover/Popover.js +1 -1
- package/dist/components/progress/Progress.js +49 -104
- package/dist/components/radio/Radio.js +17 -28
- package/dist/components/segmented/Segmented.d.ts +12 -0
- package/dist/components/segmented/Segmented.js +43 -0
- package/dist/components/settings-rows/SettingsRows.d.ts +25 -0
- package/dist/components/settings-rows/SettingsRows.js +63 -0
- package/dist/components/spin/Spin.d.ts +5 -0
- package/dist/components/spin/Spin.js +25 -0
- package/dist/components/status-chip/StatusChip.d.ts +18 -0
- package/dist/components/status-chip/StatusChip.js +68 -0
- package/dist/components/switch/Switch.js +20 -63
- package/dist/components/table/Table.d.ts +39 -0
- package/dist/components/table/Table.js +125 -0
- package/dist/components/table/TableCells.d.ts +52 -0
- package/dist/components/table/TableCells.js +103 -0
- package/dist/components/tabs/Tabs.js +16 -48
- package/dist/components/text/Text.js +2 -2
- package/dist/components/toast/Toast.js +7 -8
- package/dist/index.d.ts +11 -0
- package/dist/index.js +11 -0
- package/dist/themes.stylex.d.ts +33 -496
- package/dist/themes.stylex.js +25 -253
- package/dist/tokens.css +53 -126
- package/dist/tokens.stylex.d.ts +36 -54
- package/dist/tokens.stylex.js +62 -90
- package/package.json +1 -1
- package/dist/lib/paths.d.ts +0 -3
- package/dist/lib/paths.js +0 -11
- package/dist/lib/progress.d.ts +0 -2
- package/dist/lib/progress.js +0 -28
- package/dist/lib/silk.d.ts +0 -66
- package/dist/lib/silk.js +0 -337
- package/dist/lib/svgId.d.ts +0 -1
- package/dist/lib/svgId.js +0 -4
- package/dist/lib/weave.d.ts +0 -28
- package/dist/lib/weave.js +0 -83
package/dist/tokens.stylex.js
CHANGED
|
@@ -1,107 +1,41 @@
|
|
|
1
1
|
import * as stylex from "@stylexjs/stylex";
|
|
2
|
-
// Semantic tokens for all AnyKnown products
|
|
3
|
-
//
|
|
2
|
+
// Semantic tokens for all AnyKnown products. The default palette is neutral:
|
|
3
|
+
// white paper, ink text, ink accent. Ledger (warm paper, viridian accent) is the
|
|
4
|
+
// opt-in palette in scripts/ledger.mjs. Light is the primary mode;
|
|
4
5
|
// dark follows the OS unless a theme from themes.stylex.ts is applied.
|
|
5
6
|
const DARK = "@media (prefers-color-scheme: dark)";
|
|
6
7
|
export const color = stylex.defineVars({
|
|
7
|
-
bg: { default: "#
|
|
8
|
-
surface: { default: "#
|
|
9
|
-
surfaceRaised: { default: "#FFFFFF", [DARK]: "#
|
|
10
|
-
border: { default: "#
|
|
11
|
-
borderStrong: { default: "#
|
|
12
|
-
text: { default: "#
|
|
13
|
-
textMuted: { default: "#
|
|
14
|
-
textFaint: { default: "#
|
|
15
|
-
accent: { default: "#
|
|
16
|
-
accentText: { default: "#
|
|
17
|
-
accentSubtle: { default: "#
|
|
8
|
+
bg: { default: "#FFFFFF", [DARK]: "#000000" },
|
|
9
|
+
surface: { default: "#F5F5F7", [DARK]: "#1C1C1E" },
|
|
10
|
+
surfaceRaised: { default: "#FFFFFF", [DARK]: "#2C2C2E" },
|
|
11
|
+
border: { default: "#E5E5EA", [DARK]: "#38383A" },
|
|
12
|
+
borderStrong: { default: "#C7C7CC", [DARK]: "#48484A" },
|
|
13
|
+
text: { default: "#1D1D1F", [DARK]: "#F5F5F7" },
|
|
14
|
+
textMuted: { default: "#6E6E73", [DARK]: "#98989D" },
|
|
15
|
+
textFaint: { default: "#86868B", [DARK]: "#8E8E93" },
|
|
16
|
+
accent: { default: "#1D1D1F", [DARK]: "#F5F5F7" },
|
|
17
|
+
accentText: { default: "#FFFFFF", [DARK]: "#000000" },
|
|
18
|
+
accentSubtle: { default: "#E8E8ED", [DARK]: "#2C2C2E" },
|
|
18
19
|
danger: { default: "#B3402E", [DARK]: "#DD7059" },
|
|
19
20
|
dangerSubtle: { default: "#F7E7E3", [DARK]: "#3D231E" },
|
|
20
21
|
success: { default: "#23705A", [DARK]: "#4FA184" },
|
|
21
22
|
successSubtle: { default: "#E7F0EB", [DARK]: "#22352E" },
|
|
22
|
-
warning: { default: "#
|
|
23
|
+
warning: { default: "#B25000", [DARK]: "#FFB340" },
|
|
23
24
|
warningSubtle: { default: "#F5EBD9", [DARK]: "#3A2F1D" },
|
|
24
25
|
info: { default: "#2C5C86", [DARK]: "#6FA3CE" },
|
|
25
26
|
infoSubtle: { default: "#E4EDF5", [DARK]: "#1E2C38" },
|
|
26
|
-
focusRing: { default: "#
|
|
27
|
-
bone: { default: "#
|
|
28
|
-
sheen: { default: "#
|
|
27
|
+
focusRing: { default: "#1D1D1F", [DARK]: "#F5F5F7" },
|
|
28
|
+
bone: { default: "#E6E6EA", [DARK]: "#2C2C2E" },
|
|
29
|
+
sheen: { default: "#F2F2F5", [DARK]: "#3F3F42" },
|
|
29
30
|
successHl: { default: "#C6E0C6", [DARK]: "#2F4A2E" },
|
|
30
31
|
dangerHl: { default: "#EFCEC3", [DARK]: "#573328" },
|
|
31
32
|
// 分層底色:rail → main → 訊息 → fold → 列,越深的一階數字越大。
|
|
32
33
|
// 面與面靠深淺分,不靠邊框;hover 升一階,對應表在 lib/layers.ts。
|
|
33
|
-
layer1: { default: "#
|
|
34
|
-
layer2: { default: "#
|
|
35
|
-
layer3: { default: "#
|
|
36
|
-
layer4: { default: "#
|
|
37
|
-
layer5: { default: "#
|
|
38
|
-
});
|
|
39
|
-
// 織體紗線階(TEXTURE-GUIDE §3.4):un 底紗、sh 縫隙陰影、y0–y4 面紗(深→淺)、hi 挑面亮紗、
|
|
40
|
-
// shadow 是整塊布的落影(§3.3「實心感落地」,掛在元件的 CSS filter 上)
|
|
41
|
-
export const yarn = stylex.defineVars({
|
|
42
|
-
un: { default: "#11362C", [DARK]: "#173629" },
|
|
43
|
-
sh: { default: "#123C31", [DARK]: "#1B4437" },
|
|
44
|
-
y0: { default: "#1E6250", [DARK]: "#3F8A70" },
|
|
45
|
-
y1: { default: "#216A55", [DARK]: "#47957A" },
|
|
46
|
-
y2: { default: "#23705A", [DARK]: "#4FA184" },
|
|
47
|
-
y3: { default: "#28795F", [DARK]: "#56A98B" },
|
|
48
|
-
y4: { default: "#2E8266", [DARK]: "#5EB193" },
|
|
49
|
-
hi: { default: "#5CAA89", [DARK]: "#8FD2B4" },
|
|
50
|
-
shadow: {
|
|
51
|
-
default: "drop-shadow(0 1px 1.5px rgba(18, 60, 49, 0.3))",
|
|
52
|
-
[DARK]: "drop-shadow(0 1px 2px rgba(0, 0, 0, 0.45))",
|
|
53
|
-
},
|
|
54
|
-
});
|
|
55
|
-
// secondary(淺色布)、ghost(疏織)、danger(§3.4 原則從 danger 衍生)、
|
|
56
|
-
// subtle(accent-subtle 色系,tabs pills 取景窗用)
|
|
57
|
-
export const yarnSecondary = stylex.defineVars({
|
|
58
|
-
un: { default: "#CBC5B4", [DARK]: "#0E0C09" },
|
|
59
|
-
sh: { default: "#C9C4B4", [DARK]: "#15120E" },
|
|
60
|
-
y0: { default: "#E4E0D3", [DARK]: "#2B2620" },
|
|
61
|
-
y1: { default: "#E8E5D9", [DARK]: "#2F2A23" },
|
|
62
|
-
y2: { default: "#EDEAE0", [DARK]: "#332E26" },
|
|
63
|
-
y3: { default: "#F1EEE6", [DARK]: "#38322A" },
|
|
64
|
-
y4: { default: "#F5F3EC", [DARK]: "#3D372E" },
|
|
65
|
-
hi: { default: "#FFFFFF", [DARK]: "#5E5546" },
|
|
66
|
-
shadow: {
|
|
67
|
-
default: "drop-shadow(0 1px 1.5px rgba(80, 74, 60, 0.22))",
|
|
68
|
-
[DARK]: "drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5))",
|
|
69
|
-
},
|
|
70
|
-
});
|
|
71
|
-
export const yarnGhost = stylex.defineVars({
|
|
72
|
-
un: { default: "transparent", [DARK]: "transparent" },
|
|
73
|
-
sh: { default: "transparent", [DARK]: "transparent" },
|
|
74
|
-
y0: { default: "#DCD8CA", [DARK]: "#2A251F" },
|
|
75
|
-
y1: { default: "#DFDBCE", [DARK]: "#2F2A23" },
|
|
76
|
-
y2: { default: "#E3E0D5", [DARK]: "#35302A" },
|
|
77
|
-
y3: { default: "#E7E4D9", [DARK]: "#39342D" },
|
|
78
|
-
y4: { default: "#EBE8DE", [DARK]: "#3E3830" },
|
|
79
|
-
hi: { default: "#C8C3B4", [DARK]: "#57503F" },
|
|
80
|
-
shadow: { default: "none", [DARK]: "none" },
|
|
81
|
-
});
|
|
82
|
-
export const yarnDanger = stylex.defineVars({
|
|
83
|
-
un: { default: "#401209", [DARK]: "#38130B" },
|
|
84
|
-
sh: { default: "#4A170D", [DARK]: "#451A10" },
|
|
85
|
-
y0: { default: "#8F3122", [DARK]: "#C25842" },
|
|
86
|
-
y1: { default: "#A13928", [DARK]: "#D0644E" },
|
|
87
|
-
y2: { default: "#B3402E", [DARK]: "#DD7059" },
|
|
88
|
-
y3: { default: "#BC4F3C", [DARK]: "#E17E68" },
|
|
89
|
-
y4: { default: "#C55E4A", [DARK]: "#E58C77" },
|
|
90
|
-
hi: { default: "#E39781", [DARK]: "#F4BCA9" },
|
|
91
|
-
shadow: {
|
|
92
|
-
default: "drop-shadow(0 1px 1.5px rgba(74, 23, 13, 0.3))",
|
|
93
|
-
[DARK]: "drop-shadow(0 1px 2px rgba(0, 0, 0, 0.45))",
|
|
94
|
-
},
|
|
95
|
-
});
|
|
96
|
-
export const yarnSubtle = stylex.defineVars({
|
|
97
|
-
un: { default: "#CFE2D9", [DARK]: "#141F1B" },
|
|
98
|
-
sh: { default: "#CBDFD5", [DARK]: "#17231E" },
|
|
99
|
-
y0: { default: "#DFEBE5", [DARK]: "#1D2E27" },
|
|
100
|
-
y1: { default: "#E3EEE8", [DARK]: "#20312B" },
|
|
101
|
-
y2: { default: "#E7F0EB", [DARK]: "#22352E" },
|
|
102
|
-
y3: { default: "#ECF3EF", [DARK]: "#263A32" },
|
|
103
|
-
y4: { default: "#F1F7F3", [DARK]: "#2A4038" },
|
|
104
|
-
hi: { default: "#FFFFFF", [DARK]: "#3E5F50" },
|
|
34
|
+
layer1: { default: "#EFEFF2", [DARK]: "#000000" },
|
|
35
|
+
layer2: { default: "#FFFFFF", [DARK]: "#161618" },
|
|
36
|
+
layer3: { default: "#F2F2F5", [DARK]: "#242426" },
|
|
37
|
+
layer4: { default: "#E6E6EA", [DARK]: "#323234" },
|
|
38
|
+
layer5: { default: "#D9D9DE", [DARK]: "#3F3F42" },
|
|
105
39
|
});
|
|
106
40
|
export const shadow = stylex.defineVars({
|
|
107
41
|
popover: {
|
|
@@ -112,9 +46,12 @@ export const shadow = stylex.defineVars({
|
|
|
112
46
|
default: "0 1px 2px rgba(35, 33, 29, 0.08)",
|
|
113
47
|
[DARK]: "0 1px 2px rgba(0, 0, 0, 0.3)",
|
|
114
48
|
},
|
|
49
|
+
pop: "0 10px 36px rgba(0, 0, 0, 0.22)",
|
|
50
|
+
sheet: "0 16px 50px rgba(0, 0, 0, 0.24)",
|
|
51
|
+
dock: "0 12px 40px rgba(0, 0, 0, 0.14)",
|
|
115
52
|
});
|
|
116
53
|
export const font = stylex.defineVars({
|
|
117
|
-
display: "'
|
|
54
|
+
display: "'Geist Variable', system-ui, sans-serif",
|
|
118
55
|
body: "'Geist Variable', system-ui, sans-serif",
|
|
119
56
|
mono: "'Geist Mono Variable', ui-monospace, monospace",
|
|
120
57
|
});
|
|
@@ -158,3 +95,38 @@ export const motion = stylex.defineVars({
|
|
|
158
95
|
// 新元件不要挑它,滑動類一律 240ms easeOut。保留只是因為它已經在發佈的 API 裡。
|
|
159
96
|
spring: "cubic-bezier(0.34, 1.56, 0.64, 1)",
|
|
160
97
|
});
|
|
98
|
+
// The flat language's own scales: four text sizes, the corner each kind of control wears,
|
|
99
|
+
// the ink washes hover paints with, and the four surfaces `color` has no name for.
|
|
100
|
+
export const type = stylex.defineVars({
|
|
101
|
+
t1: "11px",
|
|
102
|
+
t2: "13px",
|
|
103
|
+
t3: "15px",
|
|
104
|
+
t4: "17px",
|
|
105
|
+
body: "1.6",
|
|
106
|
+
tight: "1.35",
|
|
107
|
+
snug: "1.45",
|
|
108
|
+
});
|
|
109
|
+
export const tone = stylex.defineVars({
|
|
110
|
+
layer6: { default: "#CDCDD3", [DARK]: "#4C4C50" },
|
|
111
|
+
faint: { default: "#86868B", [DARK]: "#8E8E93" },
|
|
112
|
+
railLayer2: { default: "#FFFFFF", [DARK]: "#242426" },
|
|
113
|
+
railLayer3: { default: "#E6E6EA", [DARK]: "#323234" },
|
|
114
|
+
});
|
|
115
|
+
export const corner = stylex.defineVars({
|
|
116
|
+
card: "12px",
|
|
117
|
+
ib: "0.6rem",
|
|
118
|
+
md: "0.5rem",
|
|
119
|
+
btn: "0.55rem",
|
|
120
|
+
sm: "0.4rem",
|
|
121
|
+
xs: "0.3rem",
|
|
122
|
+
});
|
|
123
|
+
export const ink = stylex.defineVars({
|
|
124
|
+
n4: "color-mix(in srgb, currentColor 4%, transparent)",
|
|
125
|
+
n5: "color-mix(in srgb, currentColor 5%, transparent)",
|
|
126
|
+
n6: "color-mix(in srgb, currentColor 6%, transparent)",
|
|
127
|
+
n8: "color-mix(in srgb, currentColor 8%, transparent)",
|
|
128
|
+
n10: "color-mix(in srgb, currentColor 10%, transparent)",
|
|
129
|
+
n12: "color-mix(in srgb, currentColor 12%, transparent)",
|
|
130
|
+
n14: "color-mix(in srgb, currentColor 14%, transparent)",
|
|
131
|
+
n18: "color-mix(in srgb, currentColor 18%, transparent)",
|
|
132
|
+
});
|
package/package.json
CHANGED
package/dist/lib/paths.d.ts
DELETED
package/dist/lib/paths.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export const UNWEAVE_PATH = buildUnweave();
|
|
2
|
-
function buildUnweave(width = 320, amplitude = 1.4, period = 34, mid = 3, step = 10) {
|
|
3
|
-
let d = "";
|
|
4
|
-
for (let x = 0; x <= width; x += step) {
|
|
5
|
-
d += `${d ? "L" : "M"}${x},${(mid + amplitude * Math.sin(x / period)).toFixed(1)}`;
|
|
6
|
-
}
|
|
7
|
-
return d;
|
|
8
|
-
}
|
|
9
|
-
// Handoff receipt — two loose ends knotted together when the row opens.
|
|
10
|
-
export const KNOT_LEAD = "M1 7h13";
|
|
11
|
-
export const KNOT_LOOP = "M1 8 C3 3 8.5 2 10 5.5 C11.5 9 6.5 11 5 7.5 C3.8 4.7 8 2.5 14 7.5";
|
package/dist/lib/progress.d.ts
DELETED
package/dist/lib/progress.js
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
// yarn determinate — how a ball of yarn is actually wound: a bundle of parallel
|
|
2
|
-
// great-circle arcs in one direction, then the next bundle turned, and an
|
|
3
|
-
// outline circle to finish.
|
|
4
|
-
export const BALL_STRANDS = (() => {
|
|
5
|
-
const radius = 9.3;
|
|
6
|
-
const centre = 12;
|
|
7
|
-
const families = [25, 70, 115, 160].map((deg) => (deg * Math.PI) / 180);
|
|
8
|
-
const offsets = [-6.6, -3.4, -0.8, 1.8, 4.4, 7];
|
|
9
|
-
const strands = [];
|
|
10
|
-
for (const theta of families) {
|
|
11
|
-
const normal = [Math.cos(theta), Math.sin(theta)];
|
|
12
|
-
const along = [-Math.sin(theta), Math.cos(theta)];
|
|
13
|
-
for (const offset of offsets) {
|
|
14
|
-
const half = Math.sqrt(radius * radius - offset * offset);
|
|
15
|
-
const mx = centre + normal[0] * offset;
|
|
16
|
-
const my = centre + normal[1] * offset;
|
|
17
|
-
const bulge = offset * 0.62;
|
|
18
|
-
const cx = mx + normal[0] * bulge;
|
|
19
|
-
const cy = my + normal[1] * bulge;
|
|
20
|
-
strands.push(`M${(mx - along[0] * half).toFixed(2)},${(my - along[1] * half).toFixed(2)} Q${cx.toFixed(2)},${cy.toFixed(2)} ${(mx + along[0] * half).toFixed(2)},${(my + along[1] * half).toFixed(2)}`);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
strands.push(`M${centre},${centre - radius} A${radius},${radius} 0 1 1 ${centre - 0.01},${centre - radius}`);
|
|
24
|
-
return strands;
|
|
25
|
-
})();
|
|
26
|
-
export function ballDashOffset(percent, index) {
|
|
27
|
-
return 100 - Math.max(0, Math.min(100, percent * BALL_STRANDS.length - index * 100));
|
|
28
|
-
}
|
package/dist/lib/silk.d.ts
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
export interface SilkPalette {
|
|
2
|
-
un: string;
|
|
3
|
-
sh: string;
|
|
4
|
-
y0: string;
|
|
5
|
-
y1: string;
|
|
6
|
-
y2: string;
|
|
7
|
-
y3: string;
|
|
8
|
-
y4: string;
|
|
9
|
-
hi: string;
|
|
10
|
-
}
|
|
11
|
-
export interface SilkOptions {
|
|
12
|
-
palette: SilkPalette;
|
|
13
|
-
/** "press" = 全套(帶動+窩+掃光);"hover" = 僅展示面(帶動+光澤帶) */
|
|
14
|
-
mode: "press" | "hover";
|
|
15
|
-
/** 疏織(ghost):無底紗無縫隙紗,面紗行距 2.1、粗 0.6–1.0 */
|
|
16
|
-
ghost?: boolean;
|
|
17
|
-
/** 光澤帶亮度上限(primary/danger 0.75,secondary/ghost 與卡面 0.5) */
|
|
18
|
-
bandMax?: number;
|
|
19
|
-
/** 面紗行距(大面積依 guide 降密度) */
|
|
20
|
-
pitch?: number;
|
|
21
|
-
hiCount?: number;
|
|
22
|
-
hiSpan?: number;
|
|
23
|
-
/** 圓角 clip(px);省略 = 不 clip(交給 host 的 overflow) */
|
|
24
|
-
radius?: number;
|
|
25
|
-
/** 織一塊固定高度的長布(host 高度變化不重織,只有寬度變了才重織);
|
|
26
|
-
* 動態每幀只更新可視高度內的紗 */
|
|
27
|
-
fixedHeight?: number;
|
|
28
|
-
}
|
|
29
|
-
export declare class SilkBody {
|
|
30
|
-
private host;
|
|
31
|
-
private svg;
|
|
32
|
-
private opts;
|
|
33
|
-
private reduced;
|
|
34
|
-
private id;
|
|
35
|
-
private w;
|
|
36
|
-
private h;
|
|
37
|
-
private built;
|
|
38
|
-
private live;
|
|
39
|
-
private sheen;
|
|
40
|
-
private bandG;
|
|
41
|
-
private sheenG;
|
|
42
|
-
private pointer;
|
|
43
|
-
private pressed;
|
|
44
|
-
private gx;
|
|
45
|
-
private gy;
|
|
46
|
-
private tension;
|
|
47
|
-
private tenV;
|
|
48
|
-
private pressX;
|
|
49
|
-
private pressY;
|
|
50
|
-
private bx;
|
|
51
|
-
private bandOp;
|
|
52
|
-
private sweep;
|
|
53
|
-
private raf;
|
|
54
|
-
private ro;
|
|
55
|
-
private rebuild;
|
|
56
|
-
private removers;
|
|
57
|
-
constructor(host: HTMLElement, svg: SVGSVGElement, options: SilkOptions);
|
|
58
|
-
destroy(): void;
|
|
59
|
-
private disabled;
|
|
60
|
-
private build;
|
|
61
|
-
private liveD;
|
|
62
|
-
private track;
|
|
63
|
-
private release;
|
|
64
|
-
private tick;
|
|
65
|
-
private wake;
|
|
66
|
-
}
|
package/dist/lib/silk.js
DELETED
|
@@ -1,337 +0,0 @@
|
|
|
1
|
-
// 織體動態引擎(TEXTURE-GUIDE §3–4):lib/weave.ts 的動態孿生。
|
|
2
|
-
// weave.ts 給固定尺寸小控件在 module scope 預織;這裡給尺寸不定、要回應觸點的面
|
|
3
|
-
// (button 全套:帶動 + 窩 + 掃光;卡面「僅展示」子集:hover 帶動 + 光澤帶)。
|
|
4
|
-
// 幾何公式與 weave.ts 完全同源(mulberry32(10075)、共享波場、四層紗)。
|
|
5
|
-
// client-only:在 ref callback 裡 new、cleanup 裡 destroy()(useEffect 禁用,
|
|
6
|
-
// 見 Button.tsx 的用法)。
|
|
7
|
-
import { WEAVE_SEED } from "./weave.js";
|
|
8
|
-
const NS = "http://www.w3.org/2000/svg";
|
|
9
|
-
let uid = 0;
|
|
10
|
-
function mulberry32(seed) {
|
|
11
|
-
let a = seed >>> 0;
|
|
12
|
-
return () => {
|
|
13
|
-
a |= 0;
|
|
14
|
-
a = (a + 0x6d2b79f5) | 0;
|
|
15
|
-
let t = Math.imul(a ^ (a >>> 15), 1 | a);
|
|
16
|
-
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
17
|
-
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
function el(name, attrs, parent) {
|
|
21
|
-
const node = document.createElementNS(NS, name);
|
|
22
|
-
for (const [k, v] of Object.entries(attrs))
|
|
23
|
-
node.setAttribute(k, String(v));
|
|
24
|
-
parent?.append(node);
|
|
25
|
-
return node;
|
|
26
|
-
}
|
|
27
|
-
const STEP = 4;
|
|
28
|
-
export class SilkBody {
|
|
29
|
-
host;
|
|
30
|
-
svg;
|
|
31
|
-
opts;
|
|
32
|
-
reduced;
|
|
33
|
-
id;
|
|
34
|
-
w = 0;
|
|
35
|
-
h = 0;
|
|
36
|
-
built = false;
|
|
37
|
-
live = [];
|
|
38
|
-
sheen = [];
|
|
39
|
-
bandG;
|
|
40
|
-
sheenG;
|
|
41
|
-
pointer = null;
|
|
42
|
-
pressed = false;
|
|
43
|
-
gx = 0;
|
|
44
|
-
gy = 0;
|
|
45
|
-
tension = 0;
|
|
46
|
-
tenV = 0;
|
|
47
|
-
pressX = 0;
|
|
48
|
-
pressY = 0;
|
|
49
|
-
bx = 0;
|
|
50
|
-
bandOp = 0;
|
|
51
|
-
sweep = null;
|
|
52
|
-
raf = 0;
|
|
53
|
-
ro;
|
|
54
|
-
rebuild = 0;
|
|
55
|
-
removers = [];
|
|
56
|
-
constructor(host, svg, options) {
|
|
57
|
-
this.host = host;
|
|
58
|
-
this.svg = svg;
|
|
59
|
-
this.opts = {
|
|
60
|
-
bandMax: 0.75,
|
|
61
|
-
pitch: options.ghost ? 2.1 : 1.2,
|
|
62
|
-
hiCount: options.ghost ? 2 : 5,
|
|
63
|
-
hiSpan: 0.6,
|
|
64
|
-
...options,
|
|
65
|
-
};
|
|
66
|
-
this.reduced = matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
67
|
-
this.id = `silk${uid++}`;
|
|
68
|
-
this.build();
|
|
69
|
-
this.ro = new ResizeObserver(() => {
|
|
70
|
-
clearTimeout(this.rebuild);
|
|
71
|
-
this.rebuild = window.setTimeout(() => this.build(), this.opts.fixedHeight ? 120 : 0);
|
|
72
|
-
});
|
|
73
|
-
this.ro.observe(host);
|
|
74
|
-
const on = (target, type, fn) => {
|
|
75
|
-
target.addEventListener(type, fn);
|
|
76
|
-
this.removers.push(() => target.removeEventListener(type, fn));
|
|
77
|
-
};
|
|
78
|
-
on(host, "pointerenter", (e) => this.track(e, true));
|
|
79
|
-
on(host, "pointermove", (e) => this.track(e));
|
|
80
|
-
on(host, "pointerleave", () => {
|
|
81
|
-
this.pointer = null;
|
|
82
|
-
this.wake();
|
|
83
|
-
});
|
|
84
|
-
if (this.opts.mode === "press") {
|
|
85
|
-
on(host, "pointerdown", (e) => {
|
|
86
|
-
if (this.disabled())
|
|
87
|
-
return;
|
|
88
|
-
this.pressed = true;
|
|
89
|
-
this.track(e);
|
|
90
|
-
this.pressX = this.gx;
|
|
91
|
-
this.pressY = this.gy;
|
|
92
|
-
this.wake();
|
|
93
|
-
});
|
|
94
|
-
on(window, "pointerup", () => this.release());
|
|
95
|
-
on(host, "keydown", (e) => {
|
|
96
|
-
if ((e.key === " " || e.key === "Enter") && !this.disabled()) {
|
|
97
|
-
this.pointer = { x: this.w / 2, y: this.h / 2, vx: 0, vy: 0, speed: 0 };
|
|
98
|
-
this.gx = this.bx = this.pressX = this.w / 2;
|
|
99
|
-
this.gy = this.pressY = this.h / 2;
|
|
100
|
-
this.pressed = true;
|
|
101
|
-
this.wake();
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
on(host, "keyup", () => {
|
|
105
|
-
this.release();
|
|
106
|
-
this.pointer = null;
|
|
107
|
-
});
|
|
108
|
-
on(host, "blur", () => {
|
|
109
|
-
this.pointer = null;
|
|
110
|
-
this.wake();
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
destroy() {
|
|
115
|
-
this.ro.disconnect();
|
|
116
|
-
clearTimeout(this.rebuild);
|
|
117
|
-
for (const remove of this.removers)
|
|
118
|
-
remove();
|
|
119
|
-
if (this.raf)
|
|
120
|
-
cancelAnimationFrame(this.raf);
|
|
121
|
-
this.raf = 0;
|
|
122
|
-
}
|
|
123
|
-
disabled() {
|
|
124
|
-
return this.host.matches(":disabled, [aria-disabled=true]");
|
|
125
|
-
}
|
|
126
|
-
build() {
|
|
127
|
-
const w = this.host.clientWidth;
|
|
128
|
-
const h = this.opts.fixedHeight ?? this.host.clientHeight;
|
|
129
|
-
if (w === 0 || (this.built && w === this.w && h === this.h))
|
|
130
|
-
return;
|
|
131
|
-
this.w = w;
|
|
132
|
-
this.h = h;
|
|
133
|
-
this.built = true;
|
|
134
|
-
const rng = mulberry32(WEAVE_SEED);
|
|
135
|
-
const rand = (a, b) => a + rng() * (b - a);
|
|
136
|
-
const W1 = rand(Math.max(120, w * 0.9), w * 1.8);
|
|
137
|
-
const A1 = rand(1.0, 1.8);
|
|
138
|
-
const P1 = rand(0, 6.28);
|
|
139
|
-
const K1 = rand(0.05, 0.11);
|
|
140
|
-
const W2 = rand(w * 1.6, w * 3);
|
|
141
|
-
const A2 = rand(0.5, 0.9);
|
|
142
|
-
const P2 = rand(0, 6.28);
|
|
143
|
-
const K2 = rand(0.03, 0.07);
|
|
144
|
-
const field = (x, y) => A1 * Math.sin((x / W1) * 6.283 + P1 + y * K1) + A2 * Math.sin((x / W2) * 6.283 + P2 + y * K2);
|
|
145
|
-
const staticD = (y0) => {
|
|
146
|
-
let d = "";
|
|
147
|
-
for (let x = -4; x <= w + 4; x += STEP)
|
|
148
|
-
d += `${d ? "L" : "M"}${x},${(y0 + field(x, y0)).toFixed(2)}`;
|
|
149
|
-
return d;
|
|
150
|
-
};
|
|
151
|
-
const p = this.opts.palette;
|
|
152
|
-
this.svg.setAttribute("viewBox", `0 0 ${w} ${h}`);
|
|
153
|
-
if (this.opts.fixedHeight) {
|
|
154
|
-
this.svg.setAttribute("width", String(w));
|
|
155
|
-
this.svg.setAttribute("height", String(h));
|
|
156
|
-
}
|
|
157
|
-
this.svg.replaceChildren();
|
|
158
|
-
const defs = el("defs", {}, this.svg);
|
|
159
|
-
let clip = {};
|
|
160
|
-
if (this.opts.radius != null) {
|
|
161
|
-
el("clipPath", { id: `${this.id}-clip` }, defs).append(el("rect", { x: 0, y: 0, width: w, height: this.host.clientHeight, rx: this.opts.radius }, null));
|
|
162
|
-
clip = { "clip-path": `url(#${this.id}-clip)` };
|
|
163
|
-
}
|
|
164
|
-
const grad = el("linearGradient", { id: `${this.id}-band` }, defs);
|
|
165
|
-
el("stop", { offset: "0", "stop-color": "#fff", "stop-opacity": "0" }, grad);
|
|
166
|
-
el("stop", { offset: ".5", "stop-color": "#fff", "stop-opacity": ".85" }, grad);
|
|
167
|
-
el("stop", { offset: "1", "stop-color": "#fff", "stop-opacity": "0" }, grad);
|
|
168
|
-
const mask = el("mask", { id: `${this.id}-bandm` }, defs);
|
|
169
|
-
this.bandG = el("g", {}, mask);
|
|
170
|
-
el("rect", {
|
|
171
|
-
x: -w * 0.3,
|
|
172
|
-
y: -h,
|
|
173
|
-
width: w * 0.6,
|
|
174
|
-
height: h * 3,
|
|
175
|
-
fill: `url(#${this.id}-band)`,
|
|
176
|
-
transform: "rotate(-14)",
|
|
177
|
-
}, this.bandG);
|
|
178
|
-
const body = el("g", { ...clip, fill: "none", "stroke-linecap": "round" }, this.svg);
|
|
179
|
-
if (!this.opts.ghost) {
|
|
180
|
-
const under = el("g", { stroke: p.un }, body);
|
|
181
|
-
for (let y = -0.8; y < h + 2.6; y += 1.5)
|
|
182
|
-
el("path", { d: staticD(y), "stroke-width": "3.4" }, under);
|
|
183
|
-
const seams = el("g", { stroke: p.sh, opacity: ".5" }, body);
|
|
184
|
-
for (let y = 1.6; y < h + 1; y += 2.4)
|
|
185
|
-
el("path", { d: staticD(y), "stroke-width": rand(0.5, 0.8).toFixed(2) }, seams);
|
|
186
|
-
}
|
|
187
|
-
this.live = [];
|
|
188
|
-
const addLive = (y0, node) => {
|
|
189
|
-
const base = [];
|
|
190
|
-
for (let x = -4; x <= w + 4; x += STEP)
|
|
191
|
-
base.push(field(x, y0));
|
|
192
|
-
this.live.push({ y: y0, node, base, disp: 0, vel: 0 });
|
|
193
|
-
};
|
|
194
|
-
const buckets = [p.y0, p.y1, p.y2, p.y3, p.y4];
|
|
195
|
-
for (let y = 0.6; y < h + 1; y += this.opts.pitch + rand(-0.15, 0.15)) {
|
|
196
|
-
const bucket = Math.min(4, Math.max(0, Math.round(4.2 - (y / h) * 3 + rand(-0.9, 0.9))));
|
|
197
|
-
addLive(y, el("path", {
|
|
198
|
-
d: staticD(y),
|
|
199
|
-
"stroke-width": rand(0.6, this.opts.ghost ? 1.0 : 1.4).toFixed(2),
|
|
200
|
-
stroke: buckets[bucket],
|
|
201
|
-
}, body));
|
|
202
|
-
}
|
|
203
|
-
const hiG = el("g", { stroke: p.hi, opacity: ".45" }, body);
|
|
204
|
-
for (let i = 0; i < this.opts.hiCount; i++) {
|
|
205
|
-
const y = rand(2, h * this.opts.hiSpan);
|
|
206
|
-
addLive(y, el("path", { d: staticD(y), "stroke-width": rand(0.4, 0.7).toFixed(2) }, hiG));
|
|
207
|
-
}
|
|
208
|
-
this.sheenG = el("g", {
|
|
209
|
-
...clip,
|
|
210
|
-
fill: "none",
|
|
211
|
-
"stroke-linecap": "round",
|
|
212
|
-
stroke: p.hi,
|
|
213
|
-
mask: `url(#${this.id}-bandm)`,
|
|
214
|
-
opacity: 0,
|
|
215
|
-
}, this.svg);
|
|
216
|
-
this.sheen = [];
|
|
217
|
-
for (let i = 0; i < this.live.length; i += 2) {
|
|
218
|
-
const src = this.live[i];
|
|
219
|
-
const node = el("path", { d: staticD(src.y), "stroke-width": "0.9", "stroke-opacity": ".6" }, this.sheenG);
|
|
220
|
-
this.sheen.push({ src, node });
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
// 一條紗當下的 d:靜態 base × 局部繃緊 + 凹陷(窩)+ 帶動位移(§4.1)
|
|
224
|
-
liveD(yarn) {
|
|
225
|
-
const sT = 2 * (this.w * 0.35) ** 2;
|
|
226
|
-
const sD = 2 * (this.w * 0.22) ** 2;
|
|
227
|
-
const sX = 2 * (this.w * 0.2) ** 2;
|
|
228
|
-
const ten = Math.max(-0.4, this.tension);
|
|
229
|
-
const dy = this.pressY - yarn.y;
|
|
230
|
-
const funnel = ten * dy * 0.5 * Math.exp(-(dy ** 2) / 338);
|
|
231
|
-
let d = "";
|
|
232
|
-
for (let i = 0, x = -4; x <= this.w + 4; x += STEP, i++) {
|
|
233
|
-
const ampl = 1 - 0.5 * ten * Math.exp(-((x - this.pressX) ** 2) / sT);
|
|
234
|
-
const y = yarn.y +
|
|
235
|
-
yarn.base[i] * ampl +
|
|
236
|
-
funnel * Math.exp(-((x - this.pressX) ** 2) / sD) +
|
|
237
|
-
yarn.disp * Math.exp(-((x - this.gx) ** 2) / sX);
|
|
238
|
-
d += `${d ? "L" : "M"}${x},${y.toFixed(2)}`;
|
|
239
|
-
}
|
|
240
|
-
return d;
|
|
241
|
-
}
|
|
242
|
-
track(e, enter) {
|
|
243
|
-
const r = this.host.getBoundingClientRect();
|
|
244
|
-
const x = e.clientX - r.left;
|
|
245
|
-
const y = e.clientY - r.top;
|
|
246
|
-
const p = this.pointer;
|
|
247
|
-
const vx = p ? x - p.x : 0;
|
|
248
|
-
const vy = p ? y - p.y : 0;
|
|
249
|
-
const speed = Math.hypot(vx, vy);
|
|
250
|
-
this.pointer = { x, y, vx, vy, speed: p ? p.speed * 0.6 + speed * 0.4 : 0 };
|
|
251
|
-
if (enter) {
|
|
252
|
-
this.bx = x;
|
|
253
|
-
this.gx = x;
|
|
254
|
-
this.gy = y;
|
|
255
|
-
}
|
|
256
|
-
this.wake();
|
|
257
|
-
}
|
|
258
|
-
release() {
|
|
259
|
-
if (!this.pressed)
|
|
260
|
-
return;
|
|
261
|
-
this.pressed = false;
|
|
262
|
-
// 指標已拖出 → 不觸發 click,視覺安靜鬆開;過衝+掃光是「確認觸發」的專屬語言
|
|
263
|
-
if (this.pointer == null) {
|
|
264
|
-
this.wake();
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
this.tenV -= 0.035;
|
|
268
|
-
if (!this.reduced)
|
|
269
|
-
this.sweep = { from: this.pressX, dir: (this.pointer.vx ?? 1) >= 0 ? 1 : -1, t0: performance.now() };
|
|
270
|
-
this.wake();
|
|
271
|
-
}
|
|
272
|
-
tick(now) {
|
|
273
|
-
const p = this.pointer;
|
|
274
|
-
if (p != null) {
|
|
275
|
-
this.gx += (p.x - this.gx) * 0.25;
|
|
276
|
-
this.gy += (p.y - this.gy) * 0.25;
|
|
277
|
-
p.speed *= 0.92;
|
|
278
|
-
}
|
|
279
|
-
let pressDrift = false;
|
|
280
|
-
if (this.pressed && p != null) {
|
|
281
|
-
const dx = this.gx - this.pressX;
|
|
282
|
-
const dyP = this.gy - this.pressY;
|
|
283
|
-
this.pressX += dx * 0.18;
|
|
284
|
-
this.pressY += dyP * 0.18;
|
|
285
|
-
pressDrift = Math.abs(dx) + Math.abs(dyP) > 0.05;
|
|
286
|
-
}
|
|
287
|
-
let moving = false;
|
|
288
|
-
const vh = (this.opts.fixedHeight ? this.host.clientHeight : this.h) + 10;
|
|
289
|
-
const vy = p != null && !this.pressed ? Math.max(-1.8, Math.min(1.8, (p.vy ?? 0) * 0.28)) : 0;
|
|
290
|
-
for (const yarn of this.live) {
|
|
291
|
-
if (yarn.y > vh)
|
|
292
|
-
continue;
|
|
293
|
-
const target = p != null ? vy * Math.exp(-((yarn.y - this.gy) ** 2) / 200) : 0;
|
|
294
|
-
yarn.vel += (target - yarn.disp) * 0.13 - yarn.vel * 0.24;
|
|
295
|
-
yarn.disp += yarn.vel;
|
|
296
|
-
if (Math.abs(yarn.disp) > 1e-3 || Math.abs(yarn.vel) > 1e-3)
|
|
297
|
-
moving = true;
|
|
298
|
-
}
|
|
299
|
-
if (p != null)
|
|
300
|
-
p.vy = 0;
|
|
301
|
-
this.tenV += ((this.pressed && p != null ? 1 : 0) - this.tension) * 0.07 - this.tenV * 0.16;
|
|
302
|
-
this.tension += this.tenV;
|
|
303
|
-
const tensionActive = Math.abs(this.tension) > 1e-3 || Math.abs(this.tenV) > 1e-3 || pressDrift;
|
|
304
|
-
if (moving || tensionActive) {
|
|
305
|
-
for (const yarn of this.live)
|
|
306
|
-
if (yarn.y <= vh)
|
|
307
|
-
yarn.node.setAttribute("d", this.liveD(yarn));
|
|
308
|
-
for (const s of this.sheen)
|
|
309
|
-
if (s.src.y <= vh)
|
|
310
|
-
s.node.setAttribute("d", this.liveD(s.src));
|
|
311
|
-
}
|
|
312
|
-
let bandT = 0;
|
|
313
|
-
if (this.sweep != null) {
|
|
314
|
-
const t = (now - this.sweep.t0) / 640;
|
|
315
|
-
if (t >= 1)
|
|
316
|
-
this.sweep = null;
|
|
317
|
-
else {
|
|
318
|
-
const ease = 1 - (1 - t) ** 3;
|
|
319
|
-
this.bx = this.sweep.from + this.sweep.dir * ease * this.w * 0.9;
|
|
320
|
-
bandT = 0.9 * (1 - t);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
if (this.sweep == null && p != null && !this.disabled()) {
|
|
324
|
-
this.bx += (p.x - this.bx) * 0.16;
|
|
325
|
-
bandT = this.pressed ? 0.15 : Math.min(this.opts.bandMax, 0.3 + p.speed / 7);
|
|
326
|
-
}
|
|
327
|
-
this.bandOp += (bandT - this.bandOp) * 0.15;
|
|
328
|
-
this.bandG.setAttribute("transform", `translate(${this.bx.toFixed(1)},0)`);
|
|
329
|
-
this.sheenG.setAttribute("opacity", this.bandOp.toFixed(3));
|
|
330
|
-
const active = p != null || this.pressed || this.sweep != null || moving || tensionActive || this.bandOp > 0.005;
|
|
331
|
-
this.raf = active ? requestAnimationFrame((n) => this.tick(n)) : 0;
|
|
332
|
-
}
|
|
333
|
-
wake() {
|
|
334
|
-
if (!this.raf && !this.reduced && this.built)
|
|
335
|
-
this.raf = requestAnimationFrame((n) => this.tick(n));
|
|
336
|
-
}
|
|
337
|
-
}
|
package/dist/lib/svgId.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function useSvgId(prefix: string): string;
|
package/dist/lib/svgId.js
DELETED