bsmnt 0.1.0 → 0.1.2
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 +16 -0
- package/CLAUDE.md +2 -3
- package/README.md +1 -2
- package/bun.lock +1 -1
- package/docs/architecture.drawio +0 -16
- package/docs/architecture.mermaid +1 -7
- package/package.json +42 -42
- package/packages/cli/bin/index.js +0 -4
- package/packages/cli/src/commands/add-integration.js +1 -14
- package/packages/cli/src/commands/create.js +28 -5
- package/packages/create-basement-app/src/mergers/config.js +4 -26
- package/packages/create-basement-app/src/mergers/index.js +2 -5
- package/packages/create-basement-app/templates/webgl/components/webgl/canvas/index.tsx +3 -8
- package/packages/create-basement-app/templates/webgl/components/webgl/components/scene/index.tsx +11 -3
- package/packages/create-basement-app/templates/webgl/lib/renderer.ts +7 -0
- package/packages/create-basement-app/templates/webgl/package.json +2 -2
- package/packages/create-basement-app/integrations/basehub/config.js +0 -21
- package/packages/create-basement-app/integrations/basehub/files/README.md +0 -3
- package/packages/create-basement-app/templates/webgpu/.biome/plugins/README.md +0 -21
- package/packages/create-basement-app/templates/webgpu/.biome/plugins/no-anchor-element.grit +0 -12
- package/packages/create-basement-app/templates/webgpu/.biome/plugins/no-relative-parent-imports.grit +0 -10
- package/packages/create-basement-app/templates/webgpu/.biome/plugins/no-unnecessary-forwardref.grit +0 -9
- package/packages/create-basement-app/templates/webgpu/.cursor/rules/README.md +0 -184
- package/packages/create-basement-app/templates/webgpu/.cursor/rules/architecture.mdc +0 -437
- package/packages/create-basement-app/templates/webgpu/.cursor/rules/components.mdc +0 -436
- package/packages/create-basement-app/templates/webgpu/.cursor/rules/integrations.mdc +0 -447
- package/packages/create-basement-app/templates/webgpu/.cursor/rules/main.mdc +0 -278
- package/packages/create-basement-app/templates/webgpu/.cursor/rules/styling.mdc +0 -433
- package/packages/create-basement-app/templates/webgpu/.editorconfig +0 -40
- package/packages/create-basement-app/templates/webgpu/.env.example +0 -81
- package/packages/create-basement-app/templates/webgpu/.gitattributes +0 -19
- package/packages/create-basement-app/templates/webgpu/.github/PULL_REQUEST_TEMPLATE.md +0 -14
- package/packages/create-basement-app/templates/webgpu/.github/workflows/lighthouse-to-slack.yml +0 -136
- package/packages/create-basement-app/templates/webgpu/.vscode/extensions.json +0 -20
- package/packages/create-basement-app/templates/webgpu/.vscode/settings.json +0 -105
- package/packages/create-basement-app/templates/webgpu/README.md +0 -221
- package/packages/create-basement-app/templates/webgpu/_gitignore +0 -67
- package/packages/create-basement-app/templates/webgpu/app/favicon.ico +0 -0
- package/packages/create-basement-app/templates/webgpu/app/layout.tsx +0 -104
- package/packages/create-basement-app/templates/webgpu/app/page.tsx +0 -275
- package/packages/create-basement-app/templates/webgpu/app/robots.ts +0 -15
- package/packages/create-basement-app/templates/webgpu/app/sitemap.ts +0 -16
- package/packages/create-basement-app/templates/webgpu/biome.json +0 -250
- package/packages/create-basement-app/templates/webgpu/components/basement.svg +0 -1
- package/packages/create-basement-app/templates/webgpu/components/layout/footer/index.tsx +0 -27
- package/packages/create-basement-app/templates/webgpu/components/layout/header/index.tsx +0 -11
- package/packages/create-basement-app/templates/webgpu/components/layout/theme/index.tsx +0 -66
- package/packages/create-basement-app/templates/webgpu/components/layout/wrapper/index.tsx +0 -65
- package/packages/create-basement-app/templates/webgpu/components/ui/README.md +0 -77
- package/packages/create-basement-app/templates/webgpu/components/ui/image/README.md +0 -37
- package/packages/create-basement-app/templates/webgpu/components/ui/image/index.tsx +0 -224
- package/packages/create-basement-app/templates/webgpu/components/ui/link/index.tsx +0 -146
- package/packages/create-basement-app/templates/webgpu/lib/README.md +0 -33
- package/packages/create-basement-app/templates/webgpu/lib/hooks/index.ts +0 -12
- package/packages/create-basement-app/templates/webgpu/lib/hooks/use-device-detection.ts +0 -81
- package/packages/create-basement-app/templates/webgpu/lib/hooks/use-media-breakpoint.ts +0 -15
- package/packages/create-basement-app/templates/webgpu/lib/hooks/use-prefetch.ts +0 -74
- package/packages/create-basement-app/templates/webgpu/lib/integrations/.gitkeep +0 -0
- package/packages/create-basement-app/templates/webgpu/lib/scripts/dev.ts +0 -52
- package/packages/create-basement-app/templates/webgpu/lib/scripts/generate-component.ts +0 -322
- package/packages/create-basement-app/templates/webgpu/lib/scripts/generate-page.ts +0 -193
- package/packages/create-basement-app/templates/webgpu/lib/scripts/generate.ts +0 -79
- package/packages/create-basement-app/templates/webgpu/lib/scripts/utils.ts +0 -246
- package/packages/create-basement-app/templates/webgpu/lib/store/app.ts +0 -11
- package/packages/create-basement-app/templates/webgpu/lib/store/index.ts +0 -11
- package/packages/create-basement-app/templates/webgpu/lib/styles/README.md +0 -64
- package/packages/create-basement-app/templates/webgpu/lib/styles/cn.ts +0 -7
- package/packages/create-basement-app/templates/webgpu/lib/styles/colors.ts +0 -63
- package/packages/create-basement-app/templates/webgpu/lib/styles/config.ts +0 -34
- package/packages/create-basement-app/templates/webgpu/lib/styles/css/global.css +0 -85
- package/packages/create-basement-app/templates/webgpu/lib/styles/css/index.css +0 -6
- package/packages/create-basement-app/templates/webgpu/lib/styles/css/reset.css +0 -166
- package/packages/create-basement-app/templates/webgpu/lib/styles/css/root.css +0 -68
- package/packages/create-basement-app/templates/webgpu/lib/styles/css/tailwind.css +0 -132
- package/packages/create-basement-app/templates/webgpu/lib/styles/easings.ts +0 -21
- package/packages/create-basement-app/templates/webgpu/lib/styles/fonts.ts +0 -28
- package/packages/create-basement-app/templates/webgpu/lib/styles/index.ts +0 -12
- package/packages/create-basement-app/templates/webgpu/lib/styles/layout.mjs +0 -27
- package/packages/create-basement-app/templates/webgpu/lib/styles/scripts/README.md +0 -29
- package/packages/create-basement-app/templates/webgpu/lib/styles/scripts/generate-root.ts +0 -57
- package/packages/create-basement-app/templates/webgpu/lib/styles/scripts/generate-tailwind.ts +0 -162
- package/packages/create-basement-app/templates/webgpu/lib/styles/scripts/postcss-functions.mjs +0 -168
- package/packages/create-basement-app/templates/webgpu/lib/styles/scripts/setup-styles.ts +0 -24
- package/packages/create-basement-app/templates/webgpu/lib/styles/scripts/utils.ts +0 -20
- package/packages/create-basement-app/templates/webgpu/lib/styles/typography.ts +0 -36
- package/packages/create-basement-app/templates/webgpu/lib/utils/README.md +0 -40
- package/packages/create-basement-app/templates/webgpu/lib/utils/css.d.ts +0 -21
- package/packages/create-basement-app/templates/webgpu/lib/utils/easings.ts +0 -240
- package/packages/create-basement-app/templates/webgpu/lib/utils/fetch.ts +0 -84
- package/packages/create-basement-app/templates/webgpu/lib/utils/math.test.ts +0 -221
- package/packages/create-basement-app/templates/webgpu/lib/utils/math.ts +0 -236
- package/packages/create-basement-app/templates/webgpu/lib/utils/metadata.ts +0 -126
- package/packages/create-basement-app/templates/webgpu/lib/utils/strings.test.ts +0 -166
- package/packages/create-basement-app/templates/webgpu/lib/utils/strings.ts +0 -246
- package/packages/create-basement-app/templates/webgpu/lib/utils/types.d.ts +0 -15
- package/packages/create-basement-app/templates/webgpu/lib/utils/viewport.test.ts +0 -256
- package/packages/create-basement-app/templates/webgpu/lib/utils/viewport.ts +0 -193
- package/packages/create-basement-app/templates/webgpu/next.config.ts +0 -142
- package/packages/create-basement-app/templates/webgpu/package.json +0 -69
- package/packages/create-basement-app/templates/webgpu/postcss.config.mjs +0 -42
- package/packages/create-basement-app/templates/webgpu/public/fonts/geist/Geist-Mono.woff2 +0 -0
- package/packages/create-basement-app/templates/webgpu/tsconfig.json +0 -43
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for math utilities
|
|
3
|
-
*
|
|
4
|
-
* Run with: bun test lib/utils/math.test.ts
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { describe, expect, it } from "bun:test"
|
|
8
|
-
import {
|
|
9
|
-
clamp,
|
|
10
|
-
degToRad,
|
|
11
|
-
distance,
|
|
12
|
-
lerp,
|
|
13
|
-
mapRange,
|
|
14
|
-
modulo,
|
|
15
|
-
normalize,
|
|
16
|
-
radToDeg,
|
|
17
|
-
roundTo,
|
|
18
|
-
truncate,
|
|
19
|
-
} from "./math"
|
|
20
|
-
|
|
21
|
-
describe("clamp", () => {
|
|
22
|
-
it("should return input when within bounds", () => {
|
|
23
|
-
expect(clamp(0, 50, 100)).toBe(50)
|
|
24
|
-
expect(clamp(-10, 0, 10)).toBe(0)
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
it("should return min when input is below min", () => {
|
|
28
|
-
expect(clamp(0, -5, 100)).toBe(0)
|
|
29
|
-
expect(clamp(10, 5, 20)).toBe(10)
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
it("should return max when input is above max", () => {
|
|
33
|
-
expect(clamp(0, 150, 100)).toBe(100)
|
|
34
|
-
expect(clamp(10, 25, 20)).toBe(20)
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
it("should handle edge cases", () => {
|
|
38
|
-
expect(clamp(0, 0, 100)).toBe(0)
|
|
39
|
-
expect(clamp(0, 100, 100)).toBe(100)
|
|
40
|
-
})
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
describe("lerp", () => {
|
|
44
|
-
it("should return start when amount is 0", () => {
|
|
45
|
-
expect(lerp(0, 100, 0)).toBe(0)
|
|
46
|
-
expect(lerp(-50, 50, 0)).toBe(-50)
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
it("should return end when amount is 1", () => {
|
|
50
|
-
expect(lerp(0, 100, 1)).toBe(100)
|
|
51
|
-
expect(lerp(-50, 50, 1)).toBe(50)
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
it("should return midpoint when amount is 0.5", () => {
|
|
55
|
-
expect(lerp(0, 100, 0.5)).toBe(50)
|
|
56
|
-
expect(lerp(-100, 100, 0.5)).toBe(0)
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
it("should handle intermediate values", () => {
|
|
60
|
-
expect(lerp(0, 100, 0.25)).toBe(25)
|
|
61
|
-
expect(lerp(0, 100, 0.75)).toBe(75)
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
it("should extrapolate beyond 0-1 range", () => {
|
|
65
|
-
expect(lerp(0, 100, 1.5)).toBe(150)
|
|
66
|
-
expect(lerp(0, 100, -0.5)).toBe(-50)
|
|
67
|
-
})
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
describe("mapRange", () => {
|
|
71
|
-
it("should map value from one range to another", () => {
|
|
72
|
-
expect(mapRange(0, 100, 50, 0, 1)).toBe(0.5)
|
|
73
|
-
expect(mapRange(0, 1000, 500, 0, 1)).toBe(0.5)
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
it("should handle inverted output ranges", () => {
|
|
77
|
-
expect(mapRange(0, 100, 50, 1, 0)).toBe(0.5)
|
|
78
|
-
expect(mapRange(0, 100, 0, 1, 0)).toBe(1)
|
|
79
|
-
expect(mapRange(0, 100, 100, 1, 0)).toBe(0)
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
it("should not clamp by default", () => {
|
|
83
|
-
expect(mapRange(0, 100, 150, 0, 1)).toBe(1.5)
|
|
84
|
-
expect(mapRange(0, 100, -50, 0, 1)).toBe(-0.5)
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
it("should clamp when shouldClamp is true", () => {
|
|
88
|
-
expect(mapRange(0, 100, 150, 0, 1, true)).toBe(1)
|
|
89
|
-
expect(mapRange(0, 100, -50, 0, 1, true)).toBe(0)
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
it("should clamp inverted ranges correctly", () => {
|
|
93
|
-
expect(mapRange(0, 100, 150, 1, 0, true)).toBe(0)
|
|
94
|
-
expect(mapRange(0, 100, -50, 1, 0, true)).toBe(1)
|
|
95
|
-
})
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
describe("truncate", () => {
|
|
99
|
-
it("should truncate to specified decimal places", () => {
|
|
100
|
-
expect(truncate(1.23456789, 2)).toBe(1.23)
|
|
101
|
-
expect(truncate(1.23456789, 3)).toBe(1.235)
|
|
102
|
-
expect(truncate(1.23456789, 4)).toBe(1.2346)
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
it("should handle 0 decimal places", () => {
|
|
106
|
-
expect(truncate(1.23456789, 0)).toBe(1)
|
|
107
|
-
expect(truncate(3.9, 0)).toBe(4)
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
it("should handle already truncated numbers", () => {
|
|
111
|
-
expect(truncate(3.14, 2)).toBe(3.14)
|
|
112
|
-
expect(truncate(5, 2)).toBe(5)
|
|
113
|
-
})
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
describe("modulo", () => {
|
|
117
|
-
it("should work like JavaScript % for positive numbers", () => {
|
|
118
|
-
expect(modulo(5, 3)).toBe(2)
|
|
119
|
-
expect(modulo(10, 4)).toBe(2)
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
it("should handle negative dividends correctly (true modulo)", () => {
|
|
123
|
-
expect(modulo(-1, 3)).toBe(2)
|
|
124
|
-
expect(modulo(-5, 3)).toBe(1)
|
|
125
|
-
expect(modulo(-10, 4)).toBe(2)
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
it("should return input when divisor is 0", () => {
|
|
129
|
-
expect(modulo(5, 0)).toBe(5)
|
|
130
|
-
expect(modulo(-5, 0)).toBe(-5)
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
it("should return NaN for negative divisors", () => {
|
|
134
|
-
expect(modulo(5, -3)).toBeNaN()
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
it("should wrap array indices correctly", () => {
|
|
138
|
-
const length = 5
|
|
139
|
-
expect(modulo(-1, length)).toBe(4)
|
|
140
|
-
expect(modulo(5, length)).toBe(0)
|
|
141
|
-
expect(modulo(7, length)).toBe(2)
|
|
142
|
-
})
|
|
143
|
-
})
|
|
144
|
-
|
|
145
|
-
describe("roundTo", () => {
|
|
146
|
-
it("should round to the nearest multiple", () => {
|
|
147
|
-
expect(roundTo(23, 10)).toBe(20)
|
|
148
|
-
expect(roundTo(27, 10)).toBe(30)
|
|
149
|
-
expect(roundTo(25, 10)).toBe(30)
|
|
150
|
-
})
|
|
151
|
-
|
|
152
|
-
it("should handle decimal multiples", () => {
|
|
153
|
-
expect(roundTo(0.23, 0.1)).toBeCloseTo(0.2, 10)
|
|
154
|
-
expect(roundTo(0.27, 0.1)).toBeCloseTo(0.3, 10)
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
it("should handle exact multiples", () => {
|
|
158
|
-
expect(roundTo(20, 10)).toBe(20)
|
|
159
|
-
expect(roundTo(0.5, 0.5)).toBe(0.5)
|
|
160
|
-
})
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
describe("degToRad", () => {
|
|
164
|
-
it("should convert degrees to radians", () => {
|
|
165
|
-
expect(degToRad(0)).toBe(0)
|
|
166
|
-
expect(degToRad(180)).toBeCloseTo(Math.PI, 10)
|
|
167
|
-
expect(degToRad(90)).toBeCloseTo(Math.PI / 2, 10)
|
|
168
|
-
expect(degToRad(360)).toBeCloseTo(Math.PI * 2, 10)
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
it("should handle negative degrees", () => {
|
|
172
|
-
expect(degToRad(-90)).toBeCloseTo(-Math.PI / 2, 10)
|
|
173
|
-
})
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
describe("radToDeg", () => {
|
|
177
|
-
it("should convert radians to degrees", () => {
|
|
178
|
-
expect(radToDeg(0)).toBe(0)
|
|
179
|
-
expect(radToDeg(Math.PI)).toBeCloseTo(180, 10)
|
|
180
|
-
expect(radToDeg(Math.PI / 2)).toBeCloseTo(90, 10)
|
|
181
|
-
expect(radToDeg(Math.PI * 2)).toBeCloseTo(360, 10)
|
|
182
|
-
})
|
|
183
|
-
|
|
184
|
-
it("should handle negative radians", () => {
|
|
185
|
-
expect(radToDeg(-Math.PI / 2)).toBeCloseTo(-90, 10)
|
|
186
|
-
})
|
|
187
|
-
})
|
|
188
|
-
|
|
189
|
-
describe("distance", () => {
|
|
190
|
-
it("should calculate distance between two points", () => {
|
|
191
|
-
expect(distance(0, 0, 3, 4)).toBe(5)
|
|
192
|
-
expect(distance(0, 0, 0, 0)).toBe(0)
|
|
193
|
-
})
|
|
194
|
-
|
|
195
|
-
it("should handle negative coordinates", () => {
|
|
196
|
-
expect(distance(-3, -4, 0, 0)).toBe(5)
|
|
197
|
-
expect(distance(-1, -1, 1, 1)).toBeCloseTo(Math.sqrt(8), 10)
|
|
198
|
-
})
|
|
199
|
-
|
|
200
|
-
it("should be symmetric", () => {
|
|
201
|
-
expect(distance(0, 0, 3, 4)).toBe(distance(3, 4, 0, 0))
|
|
202
|
-
})
|
|
203
|
-
})
|
|
204
|
-
|
|
205
|
-
describe("normalize", () => {
|
|
206
|
-
it("should normalize value to 0-1 range", () => {
|
|
207
|
-
expect(normalize(0, 100, 50)).toBe(0.5)
|
|
208
|
-
expect(normalize(0, 100, 0)).toBe(0)
|
|
209
|
-
expect(normalize(0, 100, 100)).toBe(1)
|
|
210
|
-
})
|
|
211
|
-
|
|
212
|
-
it("should handle non-zero min values", () => {
|
|
213
|
-
expect(normalize(50, 100, 75)).toBe(0.5)
|
|
214
|
-
expect(normalize(-100, 100, 0)).toBe(0.5)
|
|
215
|
-
})
|
|
216
|
-
|
|
217
|
-
it("should extrapolate outside range", () => {
|
|
218
|
-
expect(normalize(0, 100, 150)).toBe(1.5)
|
|
219
|
-
expect(normalize(0, 100, -50)).toBe(-0.5)
|
|
220
|
-
})
|
|
221
|
-
})
|
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Math Utilities
|
|
3
|
-
*
|
|
4
|
-
* Pure mathematical functions with no side effects.
|
|
5
|
-
* These are the building blocks for animations, layouts, and data transformations.
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* ```ts
|
|
9
|
-
* import { clamp, lerp, mapRange } from '@/utils/math'
|
|
10
|
-
*
|
|
11
|
-
* // Constrain a value
|
|
12
|
-
* const bounded = clamp(0, value, 100)
|
|
13
|
-
*
|
|
14
|
-
* // Interpolate between values
|
|
15
|
-
* const mid = lerp(0, 100, 0.5) // 50
|
|
16
|
-
*
|
|
17
|
-
* // Map scroll position to opacity
|
|
18
|
-
* const opacity = mapRange(0, 500, scrollY, 0, 1, true)
|
|
19
|
-
* ```
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Constrains a value between a minimum and maximum.
|
|
24
|
-
*
|
|
25
|
-
* @param min - Lower bound
|
|
26
|
-
* @param input - Value to constrain
|
|
27
|
-
* @param max - Upper bound
|
|
28
|
-
* @returns The clamped value
|
|
29
|
-
*
|
|
30
|
-
* @example
|
|
31
|
-
* ```ts
|
|
32
|
-
* clamp(0, -5, 100) // 0
|
|
33
|
-
* clamp(0, 50, 100) // 50
|
|
34
|
-
* clamp(0, 150, 100) // 100
|
|
35
|
-
* ```
|
|
36
|
-
*/
|
|
37
|
-
export function clamp(min: number, input: number, max: number): number {
|
|
38
|
-
return Math.max(min, Math.min(input, max))
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Linear interpolation between two values.
|
|
43
|
-
*
|
|
44
|
-
* @param start - Starting value
|
|
45
|
-
* @param end - Ending value
|
|
46
|
-
* @param amount - Interpolation factor (0 = start, 1 = end)
|
|
47
|
-
* @returns The interpolated value
|
|
48
|
-
*
|
|
49
|
-
* @example
|
|
50
|
-
* ```ts
|
|
51
|
-
* lerp(0, 100, 0) // 0
|
|
52
|
-
* lerp(0, 100, 0.5) // 50
|
|
53
|
-
* lerp(0, 100, 1) // 100
|
|
54
|
-
* lerp(0, 100, 0.25) // 25
|
|
55
|
-
* ```
|
|
56
|
-
*/
|
|
57
|
-
export function lerp(start: number, end: number, amount: number): number {
|
|
58
|
-
return (1 - amount) * start + amount * end
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Maps a value from one range to another.
|
|
63
|
-
*
|
|
64
|
-
* @param inMin - Input range minimum
|
|
65
|
-
* @param inMax - Input range maximum
|
|
66
|
-
* @param input - Value to map
|
|
67
|
-
* @param outMin - Output range minimum
|
|
68
|
-
* @param outMax - Output range maximum
|
|
69
|
-
* @param shouldClamp - Whether to clamp output to range (default: false)
|
|
70
|
-
* @returns The mapped value
|
|
71
|
-
*
|
|
72
|
-
* @example
|
|
73
|
-
* ```ts
|
|
74
|
-
* // Map scroll (0-1000) to opacity (0-1)
|
|
75
|
-
* mapRange(0, 1000, 500, 0, 1) // 0.5
|
|
76
|
-
*
|
|
77
|
-
* // Map with clamping (won't exceed bounds)
|
|
78
|
-
* mapRange(0, 100, 150, 0, 1, true) // 1
|
|
79
|
-
*
|
|
80
|
-
* // Inverted ranges work too
|
|
81
|
-
* mapRange(0, 100, 50, 1, 0) // 0.5
|
|
82
|
-
* ```
|
|
83
|
-
*/
|
|
84
|
-
export function mapRange(
|
|
85
|
-
inMin: number,
|
|
86
|
-
inMax: number,
|
|
87
|
-
input: number,
|
|
88
|
-
outMin: number,
|
|
89
|
-
outMax: number,
|
|
90
|
-
shouldClamp = false
|
|
91
|
-
): number {
|
|
92
|
-
const result =
|
|
93
|
-
((input - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin
|
|
94
|
-
|
|
95
|
-
if (!shouldClamp) return result
|
|
96
|
-
|
|
97
|
-
const isInverted = outMin > outMax
|
|
98
|
-
return isInverted
|
|
99
|
-
? clamp(outMax, result, outMin)
|
|
100
|
-
: clamp(outMin, result, outMax)
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Truncates a number to a specified number of decimal places.
|
|
105
|
-
*
|
|
106
|
-
* @param value - Number to truncate
|
|
107
|
-
* @param decimals - Number of decimal places
|
|
108
|
-
* @returns The truncated number
|
|
109
|
-
*
|
|
110
|
-
* @example
|
|
111
|
-
* ```ts
|
|
112
|
-
* truncate(3.14159, 2) // 3.14
|
|
113
|
-
* truncate(3.14159, 0) // 3
|
|
114
|
-
* ```
|
|
115
|
-
*/
|
|
116
|
-
export function truncate(value: number, decimals: number): number {
|
|
117
|
-
return Number.parseFloat(value.toFixed(decimals))
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* True modulo operation (handles negative numbers correctly).
|
|
122
|
-
*
|
|
123
|
-
* JavaScript's % operator is remainder, not modulo.
|
|
124
|
-
* This function returns a value in [0, d) for positive divisors.
|
|
125
|
-
*
|
|
126
|
-
* @param n - Dividend
|
|
127
|
-
* @param d - Divisor (must be positive)
|
|
128
|
-
* @returns The modulo result
|
|
129
|
-
*
|
|
130
|
-
* @example
|
|
131
|
-
* ```ts
|
|
132
|
-
* // JavaScript remainder vs true modulo
|
|
133
|
-
* -1 % 3 // -1 (remainder)
|
|
134
|
-
* modulo(-1, 3) // 2 (modulo)
|
|
135
|
-
*
|
|
136
|
-
* // Useful for wrapping array indices
|
|
137
|
-
* modulo(index - 1, array.length)
|
|
138
|
-
* ```
|
|
139
|
-
*/
|
|
140
|
-
export function modulo(n: number, d: number): number {
|
|
141
|
-
if (d === 0) return n
|
|
142
|
-
if (d < 0) return Number.NaN
|
|
143
|
-
return ((n % d) + d) % d
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Rounds a number to the nearest multiple.
|
|
148
|
-
*
|
|
149
|
-
* @param value - Number to round
|
|
150
|
-
* @param multiple - Multiple to round to
|
|
151
|
-
* @returns The rounded value
|
|
152
|
-
*
|
|
153
|
-
* @example
|
|
154
|
-
* ```ts
|
|
155
|
-
* roundTo(23, 10) // 20
|
|
156
|
-
* roundTo(27, 10) // 30
|
|
157
|
-
* roundTo(0.23, 0.1) // 0.2
|
|
158
|
-
* ```
|
|
159
|
-
*/
|
|
160
|
-
export function roundTo(value: number, multiple: number): number {
|
|
161
|
-
return Math.round(value / multiple) * multiple
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Converts degrees to radians.
|
|
166
|
-
*
|
|
167
|
-
* @param degrees - Angle in degrees
|
|
168
|
-
* @returns Angle in radians
|
|
169
|
-
*
|
|
170
|
-
* @example
|
|
171
|
-
* ```ts
|
|
172
|
-
* degToRad(180) // Math.PI
|
|
173
|
-
* degToRad(90) // Math.PI / 2
|
|
174
|
-
* ```
|
|
175
|
-
*/
|
|
176
|
-
export function degToRad(degrees: number): number {
|
|
177
|
-
return (degrees * Math.PI) / 180
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Converts radians to degrees.
|
|
182
|
-
*
|
|
183
|
-
* @param radians - Angle in radians
|
|
184
|
-
* @returns Angle in degrees
|
|
185
|
-
*
|
|
186
|
-
* @example
|
|
187
|
-
* ```ts
|
|
188
|
-
* radToDeg(Math.PI) // 180
|
|
189
|
-
* radToDeg(Math.PI / 2) // 90
|
|
190
|
-
* ```
|
|
191
|
-
*/
|
|
192
|
-
export function radToDeg(radians: number): number {
|
|
193
|
-
return (radians * 180) / Math.PI
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Calculates the distance between two 2D points.
|
|
198
|
-
*
|
|
199
|
-
* @param x1 - First point X
|
|
200
|
-
* @param y1 - First point Y
|
|
201
|
-
* @param x2 - Second point X
|
|
202
|
-
* @param y2 - Second point Y
|
|
203
|
-
* @returns The distance
|
|
204
|
-
*
|
|
205
|
-
* @example
|
|
206
|
-
* ```ts
|
|
207
|
-
* distance(0, 0, 3, 4) // 5
|
|
208
|
-
* ```
|
|
209
|
-
*/
|
|
210
|
-
export function distance(
|
|
211
|
-
x1: number,
|
|
212
|
-
y1: number,
|
|
213
|
-
x2: number,
|
|
214
|
-
y2: number
|
|
215
|
-
): number {
|
|
216
|
-
return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Normalizes a value to a 0-1 range.
|
|
221
|
-
*
|
|
222
|
-
* @param min - Range minimum
|
|
223
|
-
* @param max - Range maximum
|
|
224
|
-
* @param value - Value to normalize
|
|
225
|
-
* @returns Normalized value (0-1)
|
|
226
|
-
*
|
|
227
|
-
* @example
|
|
228
|
-
* ```ts
|
|
229
|
-
* normalize(0, 100, 50) // 0.5
|
|
230
|
-
* normalize(0, 100, 0) // 0
|
|
231
|
-
* normalize(0, 100, 100) // 1
|
|
232
|
-
* ```
|
|
233
|
-
*/
|
|
234
|
-
export function normalize(min: number, max: number, value: number): number {
|
|
235
|
-
return (value - min) / (max - min)
|
|
236
|
-
}
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import type { Metadata } from "next"
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Metadata Generation Utilities
|
|
5
|
-
*
|
|
6
|
-
* Helpers to generate consistent metadata across pages,
|
|
7
|
-
* reducing duplication and ensuring SEO best practices.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
interface GenerateMetadataOptions {
|
|
11
|
-
title?: string
|
|
12
|
-
description?: string
|
|
13
|
-
keywords?: string[]
|
|
14
|
-
image?: {
|
|
15
|
-
url?: string
|
|
16
|
-
width?: number
|
|
17
|
-
height?: number
|
|
18
|
-
alt?: string
|
|
19
|
-
}
|
|
20
|
-
url?: string
|
|
21
|
-
siteName?: string
|
|
22
|
-
noIndex?: boolean
|
|
23
|
-
type?: "website" | "article"
|
|
24
|
-
publishedTime?: string
|
|
25
|
-
modifiedTime?: string
|
|
26
|
-
authors?: string[]
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const APP_BASE_URL =
|
|
30
|
-
process.env.NEXT_PUBLIC_BASE_URL ?? "https://localhost:3000"
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Generate complete metadata object for pages
|
|
34
|
-
*
|
|
35
|
-
* @example
|
|
36
|
-
* ```ts
|
|
37
|
-
* export async function generateMetadata({ params }) {
|
|
38
|
-
* const page = await fetchPage(params.slug)
|
|
39
|
-
*
|
|
40
|
-
* return generatePageMetadata({
|
|
41
|
-
* title: page.metadata?.title || page.title,
|
|
42
|
-
* description: page.metadata?.description,
|
|
43
|
-
* image: { url: page.metadata?.image?.asset?.url },
|
|
44
|
-
* url: `/page/${params.slug}`,
|
|
45
|
-
* noIndex: page.metadata?.noIndex,
|
|
46
|
-
* })
|
|
47
|
-
* }
|
|
48
|
-
* ```
|
|
49
|
-
*/
|
|
50
|
-
export function generatePageMetadata(
|
|
51
|
-
options: GenerateMetadataOptions
|
|
52
|
-
): Metadata {
|
|
53
|
-
const {
|
|
54
|
-
title,
|
|
55
|
-
description,
|
|
56
|
-
keywords,
|
|
57
|
-
image,
|
|
58
|
-
url,
|
|
59
|
-
siteName = "Satūs",
|
|
60
|
-
noIndex = false,
|
|
61
|
-
type = "website",
|
|
62
|
-
publishedTime,
|
|
63
|
-
modifiedTime,
|
|
64
|
-
authors,
|
|
65
|
-
} = options
|
|
66
|
-
|
|
67
|
-
const fullUrl = url ? `${APP_BASE_URL}${url}` : APP_BASE_URL
|
|
68
|
-
const imageUrl = image?.url || "/opengraph-image.jpg"
|
|
69
|
-
const imageWidth = image?.width || 1200
|
|
70
|
-
const imageHeight = image?.height || 630
|
|
71
|
-
const imageAlt = image?.alt || title || siteName
|
|
72
|
-
|
|
73
|
-
const metadata: Metadata = {
|
|
74
|
-
metadataBase: new URL(APP_BASE_URL),
|
|
75
|
-
title,
|
|
76
|
-
description,
|
|
77
|
-
keywords,
|
|
78
|
-
alternates: {
|
|
79
|
-
canonical: url || "/",
|
|
80
|
-
},
|
|
81
|
-
openGraph: {
|
|
82
|
-
title,
|
|
83
|
-
description,
|
|
84
|
-
url: fullUrl,
|
|
85
|
-
siteName,
|
|
86
|
-
locale: "en_US",
|
|
87
|
-
type,
|
|
88
|
-
images: [
|
|
89
|
-
{
|
|
90
|
-
url: imageUrl,
|
|
91
|
-
width: imageWidth,
|
|
92
|
-
height: imageHeight,
|
|
93
|
-
alt: imageAlt,
|
|
94
|
-
},
|
|
95
|
-
],
|
|
96
|
-
...(publishedTime && { publishedTime }),
|
|
97
|
-
...(modifiedTime && { modifiedTime }),
|
|
98
|
-
...(authors && { authors }),
|
|
99
|
-
},
|
|
100
|
-
twitter: {
|
|
101
|
-
card: "summary_large_image",
|
|
102
|
-
title,
|
|
103
|
-
description,
|
|
104
|
-
images: [
|
|
105
|
-
{
|
|
106
|
-
url: imageUrl,
|
|
107
|
-
width: imageWidth,
|
|
108
|
-
height: imageHeight,
|
|
109
|
-
alt: imageAlt,
|
|
110
|
-
},
|
|
111
|
-
],
|
|
112
|
-
},
|
|
113
|
-
other: {
|
|
114
|
-
"fb:app_id": process.env.NEXT_PUBLIC_FACEBOOK_APP_ID || "",
|
|
115
|
-
},
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (noIndex) {
|
|
119
|
-
metadata.robots = {
|
|
120
|
-
index: false,
|
|
121
|
-
follow: false,
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return metadata
|
|
126
|
-
}
|