@kolkrabbi/kol-component 0.7.0 → 0.9.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.
Files changed (131) hide show
  1. package/README.md +8 -0
  2. package/package.json +2 -5
  3. package/src/atoms/RotaryDial.jsx +35 -21
  4. package/src/hooks/colorMath.js +214 -0
  5. package/src/hooks/useEyedropper.js +124 -0
  6. package/src/index.js +15 -18
  7. package/src/molecules/ButtonGroup.jsx +45 -0
  8. package/src/molecules/ColorInputRow.jsx +146 -126
  9. package/src/molecules/PaletteHarmonyWheel.jsx +192 -0
  10. package/src/molecules/Slider.jsx +29 -14
  11. package/src/molecules/SplitToolButton.jsx +133 -0
  12. package/src/organisms/ContentFilters.jsx +2 -2
  13. package/src/organisms/GalleryCarousel.jsx +2 -1
  14. package/src/organisms/LoaderOverlay.jsx +11 -14
  15. package/src/organisms/MediaTileGallery.jsx +57 -0
  16. package/src/organisms/MediaViewer.jsx +89 -64
  17. package/src/atoms/PriceDisplay.jsx +0 -34
  18. package/src/atoms/TextPressure.jsx +0 -331
  19. package/src/atoms/TypeSample.jsx +0 -50
  20. package/src/atoms/TypeSpecCard.jsx +0 -42
  21. package/src/chess/ChessPiece.jsx +0 -191
  22. package/src/chess/apparatus/AlternativeControlsMock.jsx +0 -460
  23. package/src/chess/apparatus/ChessAnalysisLayout.jsx +0 -24
  24. package/src/chess/apparatus/ChessBoard.jsx +0 -210
  25. package/src/chess/apparatus/ChessBoardFullscreen.jsx +0 -49
  26. package/src/chess/apparatus/ChessBoardWithControls.jsx +0 -41
  27. package/src/chess/apparatus/ChessBoardWithSidebar.jsx +0 -122
  28. package/src/chess/apparatus/ChessSidebar.jsx +0 -131
  29. package/src/chess/apparatus/GameArchiveTable.jsx +0 -493
  30. package/src/chess/apparatus/GameSelector.jsx +0 -47
  31. package/src/chess/apparatus/NotationPanel.jsx +0 -70
  32. package/src/chess/apparatus/PlaybackControls.jsx +0 -52
  33. package/src/chess/apparatus/VariationTree.jsx +0 -137
  34. package/src/chess/assets/chess-extra-set/set-1/bishop-1.svg +0 -6
  35. package/src/chess/assets/chess-extra-set/set-1/bishop.svg +0 -6
  36. package/src/chess/assets/chess-extra-set/set-1/king-1.svg +0 -9
  37. package/src/chess/assets/chess-extra-set/set-1/king.svg +0 -7
  38. package/src/chess/assets/chess-extra-set/set-1/knight-1.svg +0 -8
  39. package/src/chess/assets/chess-extra-set/set-1/knight.svg +0 -7
  40. package/src/chess/assets/chess-extra-set/set-1/pawn-1.svg +0 -3
  41. package/src/chess/assets/chess-extra-set/set-1/pawn.svg +0 -3
  42. package/src/chess/assets/chess-extra-set/set-1/queen-1.svg +0 -11
  43. package/src/chess/assets/chess-extra-set/set-1/queen.svg +0 -6
  44. package/src/chess/assets/chess-extra-set/set-1/rook-1.svg +0 -7
  45. package/src/chess/assets/chess-extra-set/set-1/rook.svg +0 -11
  46. package/src/chess/assets/chess-extra-set/set-2/bishop-1.svg +0 -4
  47. package/src/chess/assets/chess-extra-set/set-2/bishop.svg +0 -4
  48. package/src/chess/assets/chess-extra-set/set-2/king-1.svg +0 -4
  49. package/src/chess/assets/chess-extra-set/set-2/king.svg +0 -4
  50. package/src/chess/assets/chess-extra-set/set-2/knight-1.svg +0 -5
  51. package/src/chess/assets/chess-extra-set/set-2/knight.svg +0 -5
  52. package/src/chess/assets/chess-extra-set/set-2/pawn-1.svg +0 -4
  53. package/src/chess/assets/chess-extra-set/set-2/pawn.svg +0 -4
  54. package/src/chess/assets/chess-extra-set/set-2/queen-1.svg +0 -4
  55. package/src/chess/assets/chess-extra-set/set-2/queen.svg +0 -4
  56. package/src/chess/assets/chess-extra-set/set-2/rook-1.svg +0 -5
  57. package/src/chess/assets/chess-extra-set/set-2/rook.svg +0 -5
  58. package/src/chess/assets/chess-extra-set/set-3/bishop-1.svg +0 -10
  59. package/src/chess/assets/chess-extra-set/set-3/bishop.svg +0 -3
  60. package/src/chess/assets/chess-extra-set/set-3/king-1.svg +0 -12
  61. package/src/chess/assets/chess-extra-set/set-3/king.svg +0 -3
  62. package/src/chess/assets/chess-extra-set/set-3/knight-1.svg +0 -3
  63. package/src/chess/assets/chess-extra-set/set-3/knight.svg +0 -10
  64. package/src/chess/assets/chess-extra-set/set-3/pawn-1.svg +0 -10
  65. package/src/chess/assets/chess-extra-set/set-3/pawn.svg +0 -10
  66. package/src/chess/assets/chess-extra-set/set-3/queen-1.svg +0 -10
  67. package/src/chess/assets/chess-extra-set/set-3/queen.svg +0 -3
  68. package/src/chess/assets/chess-extra-set/set-3/rook-1.svg +0 -3
  69. package/src/chess/assets/chess-extra-set/set-3/rook.svg +0 -11
  70. package/src/chess/assets/chess-vector-set/ABoard.svg +0 -164
  71. package/src/chess/assets/chess-vector-set/BBiskup.svg +0 -12
  72. package/src/chess/assets/chess-vector-set/BKing.svg +0 -15
  73. package/src/chess/assets/chess-vector-set/BKnight.svg +0 -13
  74. package/src/chess/assets/chess-vector-set/BPawn.svg +0 -7
  75. package/src/chess/assets/chess-vector-set/BQueen.svg +0 -6
  76. package/src/chess/assets/chess-vector-set/BRook.svg +0 -12
  77. package/src/chess/assets/chess-vector-set/WBiskup.svg +0 -14
  78. package/src/chess/assets/chess-vector-set/WKing.svg +0 -13
  79. package/src/chess/assets/chess-vector-set/WKnight.svg +0 -10
  80. package/src/chess/assets/chess-vector-set/WPawn.svg +0 -8
  81. package/src/chess/assets/chess-vector-set/WQueen.svg +0 -17
  82. package/src/chess/assets/chess-vector-set/WRook.svg +0 -13
  83. package/src/chess/context/ChessControlsContext.jsx +0 -379
  84. package/src/chess/dashboards/ChessHero.jsx +0 -303
  85. package/src/chess/index.js +0 -32
  86. package/src/chess/utils/parsePgnTree.js +0 -97
  87. package/src/dashboards/cards/DashAlertCard.jsx +0 -44
  88. package/src/dashboards/cards/DashChartCard.jsx +0 -44
  89. package/src/dashboards/cards/DashFeaturedCard.jsx +0 -42
  90. package/src/dashboards/cards/DashListCard.jsx +0 -87
  91. package/src/dashboards/cards/DashMetricCard.jsx +0 -59
  92. package/src/dashboards/cards/DashSlotCard.jsx +0 -45
  93. package/src/dashboards/cards/DashStackedBarCard.jsx +0 -70
  94. package/src/dashboards/cards/DashTableCard.jsx +0 -31
  95. package/src/dashboards/cards/_shared/CardHeader.jsx +0 -15
  96. package/src/dashboards/cards/_shared/LegendRow.jsx +0 -25
  97. package/src/dashboards/charts/Candlestick.jsx +0 -79
  98. package/src/dashboards/charts/DonutChart.jsx +0 -87
  99. package/src/dashboards/charts/Heatmap.jsx +0 -79
  100. package/src/dashboards/charts/Histogram.jsx +0 -56
  101. package/src/dashboards/charts/LineChart.jsx +0 -157
  102. package/src/dashboards/charts/ScatterPlot.jsx +0 -93
  103. package/src/dashboards/charts/Sparkline.jsx +0 -63
  104. package/src/dashboards/index.js +0 -28
  105. package/src/dashboards/layout/DashboardGrid.jsx +0 -30
  106. package/src/dashboards/layout/GridCard.jsx +0 -44
  107. package/src/dashboards/shared/DashTooltip.jsx +0 -6
  108. package/src/dashboards/shared/formatMetric.js +0 -26
  109. package/src/dashboards/shared/useChartTooltip.js +0 -25
  110. package/src/dashboards/shared/useCountUp.js +0 -107
  111. package/src/molecules/ArticleCard.jsx +0 -178
  112. package/src/molecules/WorkListItem.jsx +0 -83
  113. package/src/molecules/foundry/SpecimenSectionHeader.jsx +0 -89
  114. package/src/organisms/ArticleHeader.jsx +0 -95
  115. package/src/organisms/ColorLoader.jsx +0 -155
  116. package/src/organisms/DiagonalMarqueeRiver.jsx +0 -138
  117. package/src/organisms/ParallaxShelf.jsx +0 -141
  118. package/src/organisms/PortableTextRenderer.jsx +0 -115
  119. package/src/organisms/ProductDetailLayout.jsx +0 -189
  120. package/src/organisms/ScrollDriftGallery.jsx +0 -214
  121. package/src/organisms/StackHero.jsx +0 -83
  122. package/src/organisms/WorkCard.jsx +0 -120
  123. package/src/organisms/WorkViewToggle.jsx +0 -170
  124. package/src/organisms/foundry/FontPreviewSection.jsx +0 -187
  125. package/src/organisms/foundry/FoundryCharacterSets.jsx +0 -113
  126. package/src/organisms/foundry/GlyphMetricsGrid.jsx +0 -335
  127. package/src/organisms/foundry/TypefaceHero.jsx +0 -107
  128. package/src/organisms/foundry/TypefaceStyleSection.jsx +0 -163
  129. package/src/organisms/foundry/VariableFontSection.jsx +0 -158
  130. package/src/organisms/foundry/glyphData.js +0 -30
  131. package/src/organisms/foundry/index.js +0 -21
@@ -1,331 +0,0 @@
1
- import { useEffect, useRef, useState } from 'react'
2
- import usePrefersReducedMotion from '../hooks/usePrefersReducedMotion.js'
3
-
4
- // Variable-font axis rest values — where every glyph sits with no pointer near.
5
- const DEFAULTS = { wdth: 100, wght: 400, ital: 0, alpha: 1 }
6
- const STATIC_VARIATION = "'wght' 400, 'wdth' 100, 'ital' 0.00"
7
-
8
- /**
9
- * TextPressure — a single line of variable-font text whose glyphs deform
10
- * toward the pointer: characters nearest the cursor grow widest / heaviest /
11
- * most italic, and the effect falls off linearly with distance. A
12
- * requestAnimationFrame loop lerps a smoothed follower toward the raw cursor
13
- * (container-scoped listeners) and writes per-glyph `font-variation-settings`
14
- * each frame. On mouse-leave the glyphs coast back to their rest axis values
15
- * (`returnToDefault`) or freeze in place, then the loop auto-stops until the
16
- * pointer returns. Ported from the react-bits / JuanFuentes effect, collapsed
17
- * from three source variants into one.
18
- *
19
- * ── Font contract ──────────────────────────────────────────────────────────
20
- * The effect only *moves* if the resolved font is a VARIABLE font exposing the
21
- * `wght` / `wdth` / `ital` axes. The KOL variable font is a CONSUMER asset —
22
- * the design system does not ship one. Two ways to supply it:
23
- * • `fontUrl` given → an `@font-face` is injected at runtime for `fontFamily`
24
- * (e.g. fontFamily="TG Root-Tune" fontUrl="/fonts/TGRoot-TuneVF.ttf").
25
- * • `fontUrl` omitted → the already-loaded `fontFamily` is used as-is; the
26
- * default `var(--kol-font-family-sans)` animates only if that theme font is
27
- * itself variable — otherwise the text renders correctly but static.
28
- *
29
- * Reduced motion → no listeners, no rAF; glyphs render static at the rest axis
30
- * values (wght 400 / wdth 100 / ital 0). Casing is authored by the caller —
31
- * this component never text-transforms.
32
- *
33
- * @param {string} text the line; split into one <span> per character
34
- * @param {string} fontFamily applied family + injected @font-face family
35
- * @param {string} [fontUrl] variable-font URL → injects @font-face when set
36
- * @param {boolean} width animate the `wdth` axis (5–150)
37
- * @param {boolean} weight animate the `wght` axis (100–900)
38
- * @param {boolean} italic animate the `ital` axis (0–1)
39
- * @param {boolean} alpha animate per-glyph opacity (0–1)
40
- * @param {boolean} flex distribute glyphs with justify-between
41
- * @param {boolean} stroke add an outlined ghost layer behind each glyph
42
- * @param {boolean} scale vertically scale the line to fill the container
43
- * @param {string} textColor glyph color (KOL token / CSS color)
44
- * @param {string} strokeColor stroke color when `stroke` is on
45
- * @param {string} className extra classes on the <h1>
46
- * @param {number} minFontSize floor for the responsive font size
47
- * @param {number} [maxFontSize] cap for the responsive font size
48
- * @param {boolean} returnToDefault true: coast back to rest on leave; false: freeze
49
- */
50
- export default function TextPressure({
51
- text = 'Compressa',
52
- fontFamily = 'var(--kol-font-family-sans)',
53
- fontUrl,
54
-
55
- width = true,
56
- weight = true,
57
- italic = true,
58
- alpha = false,
59
-
60
- flex = true,
61
- stroke = false,
62
- scale = false,
63
-
64
- textColor = 'var(--kol-fg-emphasis)',
65
- strokeColor = 'var(--kol-accent-primary)',
66
- className = '',
67
-
68
- minFontSize = 24,
69
- maxFontSize = null,
70
- returnToDefault = true,
71
- }) {
72
- const containerRef = useRef(null)
73
- const titleRef = useRef(null)
74
- const spansRef = useRef([])
75
-
76
- const mouseRef = useRef({ x: 0, y: 0 })
77
- const cursorRef = useRef({ x: 0, y: 0 })
78
- const currentValuesRef = useRef([])
79
- const velocityRef = useRef([])
80
-
81
- const [fontSize, setFontSize] = useState(minFontSize)
82
- const [scaleY, setScaleY] = useState(1)
83
- const [lineHeight, setLineHeight] = useState(1)
84
- const [isHovering, setIsHovering] = useState(false)
85
-
86
- const reducedMotion = usePrefersReducedMotion()
87
- const chars = text.split('')
88
-
89
- const dist = (a, b) => {
90
- const dx = b.x - a.x
91
- const dy = b.y - a.y
92
- return Math.sqrt(dx * dx + dy * dy)
93
- }
94
-
95
- // Container-scoped pointer tracking (never window). Skipped for reduced motion.
96
- useEffect(() => {
97
- if (reducedMotion) return undefined
98
- const container = containerRef.current
99
- if (!container) return undefined
100
-
101
- const handleMouseMove = (e) => {
102
- cursorRef.current.x = e.clientX
103
- cursorRef.current.y = e.clientY
104
- }
105
- const handleTouchMove = (e) => {
106
- const t = e.touches[0]
107
- cursorRef.current.x = t.clientX
108
- cursorRef.current.y = t.clientY
109
- }
110
- const handleMouseEnter = () => setIsHovering(true)
111
- const handleMouseLeave = () => setIsHovering(false)
112
-
113
- container.addEventListener('mousemove', handleMouseMove)
114
- container.addEventListener('touchmove', handleTouchMove, { passive: false })
115
- container.addEventListener('mouseenter', handleMouseEnter)
116
- container.addEventListener('mouseleave', handleMouseLeave)
117
-
118
- const { left, top, width: w, height: h } = container.getBoundingClientRect()
119
- mouseRef.current.x = left + w / 2
120
- mouseRef.current.y = top + h / 2
121
- cursorRef.current.x = mouseRef.current.x
122
- cursorRef.current.y = mouseRef.current.y
123
-
124
- return () => {
125
- container.removeEventListener('mousemove', handleMouseMove)
126
- container.removeEventListener('touchmove', handleTouchMove)
127
- container.removeEventListener('mouseenter', handleMouseEnter)
128
- container.removeEventListener('mouseleave', handleMouseLeave)
129
- }
130
- }, [reducedMotion])
131
-
132
- // Responsive sizing (layout, not motion — runs regardless of reduced motion).
133
- useEffect(() => {
134
- const setSize = () => {
135
- if (!containerRef.current || !titleRef.current) return
136
- const { width: containerW, height: containerH } = containerRef.current.getBoundingClientRect()
137
-
138
- let newFontSize = containerW / (chars.length / 2)
139
- newFontSize = Math.max(newFontSize, minFontSize)
140
- if (maxFontSize) newFontSize = Math.min(newFontSize, maxFontSize)
141
-
142
- setFontSize(newFontSize)
143
- setScaleY(1)
144
- setLineHeight(1)
145
-
146
- requestAnimationFrame(() => {
147
- if (!titleRef.current) return
148
- const textRect = titleRef.current.getBoundingClientRect()
149
- if (scale && textRect.height > 0) {
150
- const yRatio = containerH / textRect.height
151
- setScaleY(yRatio)
152
- setLineHeight(yRatio)
153
- }
154
- })
155
- }
156
-
157
- setSize()
158
- window.addEventListener('resize', setSize)
159
- return () => window.removeEventListener('resize', setSize)
160
- // eslint-disable-next-line react-hooks/exhaustive-deps
161
- }, [scale, text, minFontSize, maxFontSize])
162
-
163
- // The pressure loop. Skipped entirely for reduced motion (glyphs stay static).
164
- useEffect(() => {
165
- if (reducedMotion) return undefined
166
- let rafId
167
-
168
- const animate = () => {
169
- if (isHovering) {
170
- mouseRef.current.x += (cursorRef.current.x - mouseRef.current.x) / 15
171
- mouseRef.current.y += (cursorRef.current.y - mouseRef.current.y) / 15
172
-
173
- if (titleRef.current && containerRef.current) {
174
- const containerRect = containerRef.current.getBoundingClientRect()
175
- const maxDist = containerRect.width / 2
176
-
177
- spansRef.current.forEach((span, i) => {
178
- if (!span) return
179
- const rect = span.getBoundingClientRect()
180
- const charCenter = { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 }
181
- const d = dist(mouseRef.current, charCenter)
182
-
183
- const getAttr = (distance, minVal, maxVal) => {
184
- const val = maxVal - Math.abs((maxVal * distance) / maxDist)
185
- return Math.max(minVal, val + minVal)
186
- }
187
-
188
- const wdth = width ? Math.floor(getAttr(d, 5, 150)) : 100
189
- const wght = weight ? Math.floor(getAttr(d, 100, 900)) : 400
190
- const italVal = italic ? getAttr(d, 0, 1) : 0
191
- const alphaVal = alpha ? getAttr(d, 0, 1) : 1
192
-
193
- const prev = currentValuesRef.current[i]
194
- if (prev && !returnToDefault) {
195
- velocityRef.current[i] = {
196
- wdth: wdth - prev.wdth,
197
- wght: wght - prev.wght,
198
- ital: italVal - prev.ital,
199
- alpha: alphaVal - prev.alpha,
200
- }
201
- }
202
- currentValuesRef.current[i] = { wdth, wght, ital: italVal, alpha: alphaVal }
203
-
204
- span.style.opacity = alphaVal
205
- span.style.fontVariationSettings = `'wght' ${wght}, 'wdth' ${wdth}, 'ital' ${italVal.toFixed(2)}`
206
- })
207
- }
208
- } else {
209
- // Coast back to rest (returnToDefault) or settle into a frozen pose.
210
- let allSettled = true
211
-
212
- spansRef.current.forEach((span, i) => {
213
- if (!span) return
214
- const current = currentValuesRef.current[i] || { ...DEFAULTS }
215
- if (!velocityRef.current[i]) velocityRef.current[i] = { wdth: 0, wght: 0, ital: 0, alpha: 0 }
216
- const velocity = velocityRef.current[i]
217
-
218
- if (returnToDefault) {
219
- const springStrength = 0.05
220
- const damping = 0.88
221
- velocity.wdth += (DEFAULTS.wdth - current.wdth) * springStrength
222
- velocity.wght += (DEFAULTS.wght - current.wght) * springStrength
223
- velocity.ital += (DEFAULTS.ital - current.ital) * springStrength
224
- velocity.alpha += (DEFAULTS.alpha - current.alpha) * springStrength
225
- velocity.wdth *= damping
226
- velocity.wght *= damping
227
- velocity.ital *= damping
228
- velocity.alpha *= damping
229
-
230
- const newWdth = current.wdth + velocity.wdth
231
- const newWght = current.wght + velocity.wght
232
- const newItal = current.ital + velocity.ital
233
- const newAlpha = current.alpha + velocity.alpha
234
-
235
- if (
236
- Math.abs(newWdth - DEFAULTS.wdth) > 0.5 ||
237
- Math.abs(newWght - DEFAULTS.wght) > 0.5 ||
238
- Math.abs(velocity.wdth) > 0.1 ||
239
- Math.abs(velocity.wght) > 0.1
240
- ) {
241
- allSettled = false
242
- }
243
-
244
- currentValuesRef.current[i] = { wdth: newWdth, wght: newWght, ital: newItal, alpha: newAlpha }
245
- span.style.opacity = newAlpha
246
- span.style.fontVariationSettings = `'wght' ${Math.round(newWght)}, 'wdth' ${Math.round(newWdth)}, 'ital' ${newItal.toFixed(2)}`
247
- } else {
248
- const damping = 0.75
249
- velocity.wdth *= damping
250
- velocity.wght *= damping
251
- velocity.ital *= damping
252
- velocity.alpha *= damping
253
-
254
- const newWdth = current.wdth + velocity.wdth
255
- const newWght = current.wght + velocity.wght
256
- const newItal = current.ital + velocity.ital
257
- const newAlpha = current.alpha + velocity.alpha
258
-
259
- if (
260
- Math.abs(velocity.wdth) > 0.01 ||
261
- Math.abs(velocity.wght) > 0.01 ||
262
- Math.abs(velocity.ital) > 0.0001 ||
263
- Math.abs(velocity.alpha) > 0.0001
264
- ) {
265
- allSettled = false
266
- }
267
-
268
- currentValuesRef.current[i] = { wdth: newWdth, wght: newWght, ital: newItal, alpha: newAlpha }
269
- span.style.opacity = newAlpha
270
- span.style.fontVariationSettings = `'wght' ${Math.round(newWght)}, 'wdth' ${Math.round(newWdth)}, 'ital' ${newItal.toFixed(2)}`
271
- }
272
- })
273
-
274
- if (allSettled) {
275
- cancelAnimationFrame(rafId)
276
- return
277
- }
278
- }
279
-
280
- rafId = requestAnimationFrame(animate)
281
- }
282
-
283
- animate()
284
- return () => cancelAnimationFrame(rafId)
285
- }, [width, weight, italic, alpha, chars.length, isHovering, returnToDefault, reducedMotion])
286
-
287
- const titleClass = [
288
- 'kol-text-pressure-title m-0 w-full max-w-full overflow-hidden box-border text-center whitespace-nowrap select-none origin-top font-thin',
289
- flex ? 'flex justify-between' : '',
290
- stroke ? 'kol-text-pressure--stroke' : '',
291
- className,
292
- ]
293
- .filter(Boolean)
294
- .join(' ')
295
-
296
- return (
297
- <div
298
- ref={containerRef}
299
- className="kol-text-pressure relative flex h-full w-full max-w-full items-center justify-center overflow-hidden"
300
- >
301
- {fontUrl && (
302
- <style>{`@font-face { font-family: '${fontFamily}'; src: url('${fontUrl}'); font-style: normal; }`}</style>
303
- )}
304
-
305
- <h1
306
- ref={titleRef}
307
- className={titleClass}
308
- style={{
309
- fontFamily,
310
- fontSize,
311
- lineHeight,
312
- transform: `scale(1, ${scaleY})`,
313
- color: textColor,
314
- '--kol-text-pressure-stroke': strokeColor,
315
- }}
316
- >
317
- {chars.map((char, i) => (
318
- <span
319
- key={i}
320
- ref={(el) => (spansRef.current[i] = el)}
321
- data-char={char}
322
- className="kol-text-pressure-char inline-block"
323
- style={{ fontVariationSettings: STATIC_VARIATION }}
324
- >
325
- {char}
326
- </span>
327
- ))}
328
- </h1>
329
- </div>
330
- )
331
- }
@@ -1,50 +0,0 @@
1
- /**
2
- * TypeSample — a single labeled type-specimen block: an optional mono caption
3
- * over one paragraph whose typography is driven entirely by props via inline
4
- * style. The atomic unit of the type-specimen kit — stack several to show a
5
- * scale, a weight range, or a family; adjacent samples get a hairline
6
- * separator (`.kol-type-sample + .kol-type-sample`).
7
- *
8
- * Presentational — arbitrary px values are the point, so props map to inline
9
- * style rather than fixed kol-* size stops. Label and children render
10
- * verbatim as authored.
11
- *
12
- * @param {string} family font family name (wrapped as `"family", sans-serif`); defaults to the KOL sans token
13
- * @param {number} weight font-weight
14
- * @param {boolean} italic italic sample
15
- * @param {number} size font-size in px
16
- * @param {number} lineHeight line-height in px; unitless 1.2 when unset
17
- * @param {string} label mono caption above the sample (omit to hide)
18
- * @param {ReactNode} children the specimen text itself
19
- */
20
- export default function TypeSample({
21
- family,
22
- weight = 400,
23
- italic = false,
24
- size = 32,
25
- lineHeight,
26
- label,
27
- children,
28
- }) {
29
- return (
30
- <div className="kol-type-sample py-6">
31
- {label && (
32
- <p className="kol-helper-12 tracking-wider text-meta m-0 mb-3">
33
- {label}
34
- </p>
35
- )}
36
- <p
37
- className="kol-type-sample-body m-0 text-auto"
38
- style={{
39
- fontFamily: family ? `"${family}", sans-serif` : 'var(--kol-font-family-sans)',
40
- fontWeight: weight,
41
- fontStyle: italic ? 'italic' : 'normal',
42
- fontSize: `${size}px`,
43
- lineHeight: lineHeight ? `${lineHeight}px` : '1.2',
44
- }}
45
- >
46
- {children}
47
- </p>
48
- </div>
49
- )
50
- }
@@ -1,42 +0,0 @@
1
- /**
2
- * TypeSpecCard — a two-column type-spec row: a left meta panel of key/value
3
- * pairs (font metrics) beside a live sample slot on the right, with an
4
- * optional corner label. The "data-sheet" member of the type-specimen kit —
5
- * pairs the numeric spec of a type style with a rendered example of it
6
- * (often a TypeSample passed as children, composed at the call site).
7
- *
8
- * Single column on mobile; `240px + fluid` two-column at lg. `meta` is
9
- * arbitrary tuples — no fixed metric schema. Sample paragraphs get 16px
10
- * rhythm via `.kol-type-spec-sample p` (Preflight zeroes <p> margins).
11
- *
12
- * @param {string} label corner caption, absolute top-left (omit to hide)
13
- * @param {Array<[string, ReactNode]>} meta key/value rows in the left panel
14
- * @param {ReactNode} children the live type sample on the right
15
- */
16
- export default function TypeSpecCard({ label, meta = [], children }) {
17
- return (
18
- <div className="kol-type-spec relative py-12 border-t border-fg-08">
19
- {label && (
20
- <span className="kol-type-spec-label kol-helper-12 tracking-widest text-meta absolute top-4 left-0">
21
- {label}
22
- </span>
23
- )}
24
- <div className="kol-type-spec-row grid grid-cols-1 gap-6 lg:grid-cols-[240px_minmax(0,1fr)] lg:gap-12 items-start pt-6">
25
- <div className="kol-type-spec-meta flex flex-col">
26
- {meta.map(([key, value]) => (
27
- <div
28
- key={key}
29
- className="kol-type-spec-meta-row grid grid-cols-[auto_minmax(0,1fr)] gap-4 items-baseline py-2 border-b border-[var(--kol-fg-04)] last:border-b-0"
30
- >
31
- <span className="kol-helper-10 text-meta">{key}</span>
32
- <span className="kol-helper-10 text-strong text-right [overflow-wrap:anywhere]">{value}</span>
33
- </div>
34
- ))}
35
- </div>
36
- <div className="kol-type-spec-sample min-w-0">
37
- {children}
38
- </div>
39
- </div>
40
- </div>
41
- )
42
- }
@@ -1,191 +0,0 @@
1
- // Load default chess vector set
2
- const svgModules = import.meta.glob('./assets/chess-vector-set/*.svg', {
3
- eager: true,
4
- query: '?raw',
5
- import: 'default'
6
- })
7
-
8
- // Load extra chess sets (set-1, set-2, set-3)
9
- const extraSet1Modules = import.meta.glob('./assets/chess-extra-set/set-1/*.svg', {
10
- eager: true,
11
- query: '?raw',
12
- import: 'default'
13
- })
14
-
15
- const extraSet2Modules = import.meta.glob('./assets/chess-extra-set/set-2/*.svg', {
16
- eager: true,
17
- query: '?raw',
18
- import: 'default'
19
- })
20
-
21
- const extraSet3Modules = import.meta.glob('./assets/chess-extra-set/set-3/*.svg', {
22
- eager: true,
23
- query: '?raw',
24
- import: 'default'
25
- })
26
-
27
- const PIECE_KEY_MAP = {
28
- white: {
29
- king: 'WKing',
30
- queen: 'WQueen',
31
- rook: 'WRook',
32
- bishop: 'WBiskup',
33
- knight: 'WKnight',
34
- pawn: 'WPawn'
35
- },
36
- black: {
37
- king: 'BKing',
38
- queen: 'BQueen',
39
- rook: 'BRook',
40
- bishop: 'BBiskup',
41
- knight: 'BKnight',
42
- pawn: 'BPawn'
43
- }
44
- }
45
-
46
- // Map for extra sets (use simpler naming convention)
47
- const EXTRA_SET_PIECE_MAP = {
48
- white: {
49
- king: 'king-1',
50
- queen: 'queen-1',
51
- rook: 'rook-1',
52
- bishop: 'bishop-1',
53
- knight: 'knight-1',
54
- pawn: 'pawn-1'
55
- },
56
- black: {
57
- king: 'king',
58
- queen: 'queen',
59
- rook: 'rook',
60
- bishop: 'bishop',
61
- knight: 'knight',
62
- pawn: 'pawn'
63
- }
64
- }
65
-
66
- const PIECE_CACHE = Object.entries(svgModules).reduce((acc, [path, svgContent]) => {
67
- const fileName = path.split('/').pop() || ''
68
- const key = fileName.replace('.svg', '')
69
- acc[key] = svgContent
70
- return acc
71
- }, {})
72
-
73
- // Cache for extra sets
74
- const EXTRA_SET_1_CACHE = Object.entries(extraSet1Modules).reduce((acc, [path, svgContent]) => {
75
- const fileName = path.split('/').pop() || ''
76
- const key = fileName.replace('.svg', '')
77
- acc[key] = svgContent
78
- return acc
79
- }, {})
80
-
81
- const EXTRA_SET_2_CACHE = Object.entries(extraSet2Modules).reduce((acc, [path, svgContent]) => {
82
- const fileName = path.split('/').pop() || ''
83
- const key = fileName.replace('.svg', '')
84
- acc[key] = svgContent
85
- return acc
86
- }, {})
87
-
88
- const EXTRA_SET_3_CACHE = Object.entries(extraSet3Modules).reduce((acc, [path, svgContent]) => {
89
- const fileName = path.split('/').pop() || ''
90
- const key = fileName.replace('.svg', '')
91
- acc[key] = svgContent
92
- return acc
93
- }, {})
94
-
95
- const normalizeSize = (value) => {
96
- if (typeof value === 'number') {
97
- return `${value}px`
98
- }
99
- if (typeof value === 'string') {
100
- return value
101
- }
102
- return '64px'
103
- }
104
-
105
- const applySizeToMarkup = (markup) => {
106
- let updated = markup
107
- updated = updated.replace(/width="[^"]*"/i, 'width="100%"')
108
- updated = updated.replace(/height="[^"]*"/i, 'height="100%"')
109
- return updated
110
- }
111
-
112
- const ChessPieceBase = ({
113
- size = 64,
114
- className = '',
115
- style = {},
116
- children
117
- }) => {
118
- const dimension = normalizeSize(size)
119
-
120
- return (
121
- <div
122
- className={`relative inline-flex aspect-square items-center justify-center ${className}`}
123
- style={{
124
- width: dimension,
125
- height: dimension,
126
- ...style
127
- }}
128
- >
129
- {children}
130
- </div>
131
- )
132
- }
133
-
134
- const ChessPiece = ({
135
- piece,
136
- color = 'white',
137
- size = 64,
138
- className = '',
139
- style = {},
140
- pieceSet = 'default'
141
- }) => {
142
- const normalizedColor = color === 'black' ? 'black' : 'white'
143
-
144
- let pieceKey
145
- let cache
146
-
147
- // Select piece set and cache
148
- if (pieceSet === 'set-1') {
149
- pieceKey = EXTRA_SET_PIECE_MAP[normalizedColor]?.[piece]
150
- cache = EXTRA_SET_1_CACHE
151
- } else if (pieceSet === 'set-2') {
152
- pieceKey = EXTRA_SET_PIECE_MAP[normalizedColor]?.[piece]
153
- cache = EXTRA_SET_2_CACHE
154
- } else if (pieceSet === 'set-3') {
155
- pieceKey = EXTRA_SET_PIECE_MAP[normalizedColor]?.[piece]
156
- cache = EXTRA_SET_3_CACHE
157
- } else {
158
- // Default set
159
- pieceKey = PIECE_KEY_MAP[normalizedColor]?.[piece]
160
- cache = PIECE_CACHE
161
- }
162
-
163
- const markup = pieceKey ? cache[pieceKey] : null
164
-
165
- if (!markup) {
166
- console.warn(`ChessPiece: No asset found for ${normalizedColor} ${piece} in set ${pieceSet}`)
167
- return null
168
- }
169
-
170
- return (
171
- <ChessPieceBase
172
- size={size}
173
- className={`text-current ${className}`}
174
- style={style}
175
- >
176
- <span
177
- className="block h-full w-full"
178
- style={{
179
- display: 'inline-flex',
180
- alignItems: 'center',
181
- justifyContent: 'center'
182
- }}
183
- dangerouslySetInnerHTML={{ __html: applySizeToMarkup(markup) }}
184
- />
185
- </ChessPieceBase>
186
- )
187
- }
188
-
189
- ChessPiece.Base = ChessPieceBase
190
-
191
- export default ChessPiece