Keyboard
Esc
F1
F2
F3
F4
F5
F6
F7
F8
F9
F10
F11
F12
`
1
2
3
4
5
6
7
8
9
0
-
=
Backspace
Tab
Q
W
E
R
T
Y
U
I
O
P
[
]
\
Caps
A
S
D
F
G
H
J
K
L
;
'
Enter
Shift
Z
X
C
V
B
N
M
,
.
/
Shift
Ctrl
Alt
Space
Alt
Ctrl
Installation Guide
1
Install Dependencies
Lucide React
npm install lucide-react
2
Copy Component Code
Keyboard.tsx
1"use client"
2import type React from "react"
3import { useEffect, useState } from "react"
4import { Command, ArrowUp, ArrowDown, ArrowLeft, ArrowRight, ChevronsUp, Menu } from "lucide-react"
5
6interface KeyObject {
7 label?: string
8 code?: string
9 size: number
10 spacer?: boolean
11 type?: string
12 icon?: string
13}
14interface KeyboardRow {
15 function?: boolean
16 keys: KeyObject[]
17 nav?: KeyObject[]
18}
19interface InteractiveKeyboardProps {
20 layout?: "standard" | "compact"
21 showFunctionKeys?: boolean
22 showNavigationCluster?: boolean
23 activeKeys?: string[]
24 activeKeyGlowColor?: string
25 activeKeyGlowIntensity?: number
26 theme?: "cyberpunk" | "minimal" | "retro" | "mechanical" | "neon" | "pastel"
27 keyColor?: string
28 keyTextColor?: string
29 accentColor?: string
30 keyPressedColor?: string
31 keyPressAnimationDuration?: number
32 onKeyPress?: (code: string, key?: string) => void
33 onKeyRelease?: (code: string, key?: string) => void
34 className?: string
35 allowPhysicalKeyboard?: boolean
36 perspective?: number
37 rotateX?: number
38 [key: string]: unknown
39}
40
41interface KeyStyleProps {
42 background: string
43 color: string
44 boxShadow: string
45 textShadow?: string
46 border: string
47 fontFamily?: string
48 fontWeight?: string | number
49 fontSize?: string
50 borderRadius?: string
51 letterSpacing?: string
52 transform?: string
53 transition?: string
54 height?: string
55 marginBottom?: string
56 padding?: string
57 width?: string
58}
59
60interface ThemeStyles {
61 keyboard: {
62 background: string
63 boxShadow: string
64 border: string
65 borderRadius?: string
66 marginBottom?: string
67 padding?: string
68 width?: string
69 }
70 key: KeyStyleProps
71 keyPressed: KeyStyleProps
72 keyHover: KeyStyleProps
73 keyActive?: KeyStyleProps
74 specialKey?: KeyStyleProps
75 functionKey?: KeyStyleProps
76 modifierKey?: KeyStyleProps
77 spaceKey?: KeyStyleProps
78 arrowKey?: KeyStyleProps
79}
80
81const InteractiveKeyboard: React.FC<InteractiveKeyboardProps> = ({
82 layout = "standard",
83 showFunctionKeys = true,
84 showNavigationCluster = true,
85 activeKeys = [],
86 activeKeyGlowColor = "#6366f1",
87 activeKeyGlowIntensity = 0.8,
88 theme = "cyberpunk",
89 keyColor = "#2a2a2a",
90 keyTextColor = "#ffffff",
91 accentColor = "#6366f1",
92 keyPressedColor = "#333333",
93 keyPressAnimationDuration = 150,
94 onKeyPress = () => {},
95 onKeyRelease = () => {},
96 className = "",
97 allowPhysicalKeyboard = true,
98 perspective = 1000,
99 rotateX = 10,
100 ...props
101}) => {
102 const [pressedKeys, setPressedKeys] = useState<Set<string>>(new Set())
103 const getKeyboardLayout = (): KeyboardRow[] => {
104 switch (layout) {
105 case "compact":
106 return getStandardLayout().filter((row) => !row.function)
107 case "standard":
108 default:
109 return getStandardLayout()
110 }
111 }
112
113 const getStandardLayout = (): KeyboardRow[] => {
114 return [
115 {
116 function: true,
117 keys: [
118 { label: "Esc", code: "Escape", size: 1 },
119 { spacer: true, size: 1 },
120 { label: "F1", code: "F1", size: 1 },
121 { label: "F2", code: "F2", size: 1 },
122 { label: "F3", code: "F3", size: 1 },
123 { label: "F4", code: "F4", size: 1 },
124 { spacer: true, size: 0.5 },
125 { label: "F5", code: "F5", size: 1 },
126 { label: "F6", code: "F6", size: 1 },
127 { label: "F7", code: "F7", size: 1 },
128 { label: "F8", code: "F8", size: 1 },
129 { spacer: true, size: 0.5 },
130 { label: "F9", code: "F9", size: 1 },
131 { label: "F10", code: "F10", size: 1 },
132 { label: "F11", code: "F11", size: 1 },
133 { label: "F12", code: "F12", size: 1 },
134 ],
135 nav: [
136 { spacer: true, size: 0.5 },
137 { type: "light", size: 0.5, code: "light1" },
138 { type: "light", size: 0.5, code: "light2" },
139 { type: "light", size: 0.5, code: "light3" },
140 ],
141 },
142 {
143 keys: [
144 { label: "`", code: "Backquote", size: 1 },
145 { label: "1", code: "Digit1", size: 1 },
146 { label: "2", code: "Digit2", size: 1 },
147 { label: "3", code: "Digit3", size: 1 },
148 { label: "4", code: "Digit4", size: 1 },
149 { label: "5", code: "Digit5", size: 1 },
150 { label: "6", code: "Digit6", size: 1 },
151 { label: "7", code: "Digit7", size: 1 },
152 { label: "8", code: "Digit8", size: 1 },
153 { label: "9", code: "Digit9", size: 1 },
154 { label: "0", code: "Digit0", size: 1 },
155 { label: "-", code: "Minus", size: 1 },
156 { label: "=", code: "Equal", size: 1 },
157 { label: "Backspace", code: "Backspace", size: 2 },
158 ],
159 nav: [
160 { label: "Del", code: "Delete", size: 1 },
161 { label: "End", code: "End", size: 1 },
162 { label: "PgDn", code: "PageDown", size: 1 },
163 ],
164 },
165 {
166 keys: [
167 { label: "Tab", code: "Tab", size: 1.5 },
168 { label: "Q", code: "KeyQ", size: 1 },
169 { label: "W", code: "KeyW", size: 1 },
170 { label: "E", code: "KeyE", size: 1 },
171 { label: "R", code: "KeyR", size: 1 },
172 { label: "T", code: "KeyT", size: 1 },
173 { label: "Y", code: "KeyY", size: 1 },
174 { label: "U", code: "KeyU", size: 1 },
175 { label: "I", code: "KeyI", size: 1 },
176 { label: "O", code: "KeyO", size: 1 },
177 { label: "P", code: "KeyP", size: 1 },
178 { label: "[", code: "BracketLeft", size: 1 },
179 { label: "]", code: "BracketRight", size: 1 },
180 { label: "\\", code: "Backslash", size: 1.5 },
181 ],
182 nav: [
183 { label: "Ins", code: "Insert", size: 1 },
184 { label: "Home", code: "Home", size: 1 },
185 { label: "PgUp", code: "PageUp", size: 1 },
186 ],
187 },
188 {
189 keys: [
190 { label: "Caps", code: "CapsLock", size: 1.75, icon: "capslock" },
191 { label: "A", code: "KeyA", size: 1 },
192 { label: "S", code: "KeyS", size: 1 },
193 { label: "D", code: "KeyD", size: 1 },
194 { label: "F", code: "KeyF", size: 1 },
195 { label: "G", code: "KeyG", size: 1 },
196 { label: "H", code: "KeyH", size: 1 },
197 { label: "J", code: "KeyJ", size: 1 },
198 { label: "K", code: "KeyK", size: 1 },
199 { label: "L", code: "KeyL", size: 1 },
200 { label: ";", code: "Semicolon", size: 1 },
201 { label: "'", code: "Quote", size: 1 },
202 { label: "Enter", code: "Enter", size: 2.25 },
203 ],
204 nav: [
205 { label: "Print", code: "PrintScreen", size: 1 },
206 { label: "Scroll", code: "ScrollLock", size: 1 },
207 { label: "Pause", code: "Pause", size: 1 },
208 ],
209 },
210 {
211 keys: [
212 { label: "Shift", code: "ShiftLeft", size: 2.25 },
213 { label: "Z", code: "KeyZ", size: 1 },
214 { label: "X", code: "KeyX", size: 1 },
215 { label: "C", code: "KeyC", size: 1 },
216 { label: "V", code: "KeyV", size: 1 },
217 { label: "B", code: "KeyB", size: 1 },
218 { label: "N", code: "KeyN", size: 1 },
219 { label: "M", code: "KeyM", size: 1 },
220 { label: ",", code: "Comma", size: 1 },
221 { label: ".", code: "Period", size: 1 },
222 { label: "/", code: "Slash", size: 1 },
223 { label: "Shift", code: "ShiftRight", size: 2.75 },
224 ],
225 nav: [
226 { spacer: true, size: 1 },
227 { label: "", code: "ArrowUp", size: 1, icon: "arrowup" },
228 { spacer: true, size: 1 },
229 ],
230 },
231 {
232 keys: [
233 { label: "Ctrl", code: "ControlLeft", size: 1.25 },
234 { label: "", code: "MetaLeft", size: 1.25, icon: "windows" },
235 { label: "Alt", code: "AltLeft", size: 1.25 },
236 { label: "Space", code: "Space", size: 6.25 },
237 { label: "Alt", code: "AltRight", size: 1.25 },
238 { label: "", code: "MetaRight", size: 1.25, icon: "windows" },
239 { label: "", code: "ContextMenu", size: 1.25, icon: "menu" },
240 { label: "Ctrl", code: "ControlRight", size: 1.25 },
241 ],
242 nav: [
243 { label: "", code: "ArrowLeft", size: 1, icon: "arrowleft" },
244 { label: "", code: "ArrowDown", size: 1, icon: "arrowdown" },
245 { label: "", code: "ArrowRight", size: 1, icon: "arrowright" },
246 ],
247 },
248 ]
249 }
250
251 useEffect(() => {
252 if (!allowPhysicalKeyboard) return
253
254 const handleKeyDown = (e: KeyboardEvent) => {
255 setPressedKeys((prev) => {
256 const newSet = new Set(prev)
257 newSet.add(e.code)
258 return newSet
259 })
260
261 onKeyPress(e.code, e.key)
262 }
263 const handleKeyUp = (e: KeyboardEvent) => {
264 setPressedKeys((prev) => {
265 const newSet = new Set(prev)
266 newSet.delete(e.code)
267 return newSet
268 })
269 onKeyRelease(e.code, e.key)
270 }
271 if (allowPhysicalKeyboard) {
272 window.addEventListener("keydown", handleKeyDown)
273 window.addEventListener("keyup", handleKeyUp)
274 }
275 return () => {
276 window.removeEventListener("keydown", handleKeyDown)
277 window.removeEventListener("keyup", handleKeyUp)
278 }
279 }, [allowPhysicalKeyboard, onKeyPress, onKeyRelease])
280
281 const handleKeyDown = (code: string) => {
282 setPressedKeys((prev) => {
283 const newSet = new Set(prev)
284 newSet.add(code)
285 return newSet
286 })
287
288 onKeyPress(code)
289 }
290
291 const handleKeyUp = (code: string) => {
292 setPressedKeys((prev) => {
293 const newSet = new Set(prev)
294 newSet.delete(code)
295 return newSet
296 })
297
298 onKeyRelease(code)
299 }
300
301 const getThemeStyles = (): ThemeStyles => {
302 switch (theme) {
303 case "minimal":
304 return {
305 keyboard: {
306 background: "linear-gradient(to bottom, #ffffff, #f8f9fa)",
307 boxShadow: "0 10px 30px rgba(0, 0, 0, 0.08), 0 6px 10px rgba(0, 0, 0, 0.05)",
308 border: "1px solid rgba(0, 0, 0, 0.06)",
309 borderRadius: "10px",
310 },
311 key: {
312 background: "linear-gradient(to bottom, #ffffff, #f7f7f9)",
313 color: "#333333",
314 boxShadow: "0 2px 3px rgba(0, 0, 0, 0.06), inset 0 1px 0 rgba(255, 255, 255, 0.8)",
315 textShadow: "none",
316 border: "1px solid rgba(0, 0, 0, 0.08)",
317 fontFamily: "system-ui, -apple-system, sans-serif",
318 fontWeight: "500",
319 fontSize: "11px",
320 borderRadius: "5px",
321 transition: "all 0.15s ease",
322 },
323 keyPressed: {
324 background: "linear-gradient(to bottom, #f0f0f0, #e8e8e8)",
325 boxShadow: "inset 0 1px 2px rgba(0, 0, 0, 0.08)",
326 color: accentColor,
327 border: "1px solid rgba(0, 0, 0, 0.12)",
328 transform: "translateY(1px)",
329 fontSize: "11px",
330 fontWeight: "500",
331 fontFamily: "system-ui, -apple-system, sans-serif",
332 textShadow: "none",
333 borderRadius: "5px",
334 transition: "all 0.05s ease",
335 },
336 keyHover: {
337 background: "linear-gradient(to bottom, #ffffff, #f9f9f9)",
338 boxShadow: "0 2px 3px rgba(0, 0, 0, 0.08), inset 0 1px 0 rgba(255, 255, 255, 1)",
339 color: accentColor,
340 border: "1px solid rgba(0, 0, 0, 0.1)",
341 fontSize: "11px",
342 fontWeight: "500",
343 fontFamily: "system-ui, -apple-system, sans-serif",
344 textShadow: "none",
345 borderRadius: "5px",
346 transition: "all 0.15s ease",
347 },
348 keyActive: {
349 background: "linear-gradient(to bottom, #ffffff, #f5f5f7)",
350 boxShadow: `0 0 10px ${activeKeyGlowColor}, inset 0 1px 0 rgba(255, 255, 255, 0.8)`,
351 color: activeKeyGlowColor,
352 border: `1px solid ${activeKeyGlowColor}`,
353 fontSize: "11px",
354 fontWeight: "500",
355 fontFamily: "system-ui, -apple-system, sans-serif",
356 textShadow: `0 0 5px ${activeKeyGlowColor}`,
357 borderRadius: "5px",
358 transition: "all 0.1s ease",
359 },
360 specialKey: {
361 background: "linear-gradient(to bottom, #f8f8fa, #eff0f2)",
362 color: "#555555",
363 boxShadow: "0 2px 3px rgba(0, 0, 0, 0.06), inset 0 1px 0 rgba(255, 255, 255, 0.8)",
364 border: "1px solid rgba(0, 0, 0, 0.08)",
365 fontSize: "10px",
366 fontWeight: "500",
367 fontFamily: "system-ui, -apple-system, sans-serif",
368 textShadow: "none",
369 borderRadius: "5px",
370 transition: "all 0.15s ease",
371 },
372 functionKey: {
373 background: "linear-gradient(to bottom, #f2f2f4, #eaeaec)",
374 color: "#666666",
375 fontSize: "9px",
376 boxShadow: "0 2px 3px rgba(0, 0, 0, 0.06), inset 0 1px 0 rgba(255, 255, 255, 0.8)",
377 border: "1px solid rgba(0, 0, 0, 0.08)",
378 fontWeight: "500",
379 fontFamily: "system-ui, -apple-system, sans-serif",
380 textShadow: "none",
381 borderRadius: "5px",
382 transition: "all 0.15s ease",
383 },
384 modifierKey: {
385 background: "linear-gradient(to bottom, #f2f2f4, #eaeaec)",
386 color: "#555555",
387 boxShadow: "0 2px 3px rgba(0, 0, 0, 0.06), inset 0 1px 0 rgba(255, 255, 255, 0.8)",
388 border: "1px solid rgba(0, 0, 0, 0.08)",
389 fontSize: "10px",
390 fontWeight: "500",
391 fontFamily: "system-ui, -apple-system, sans-serif",
392 textShadow: "none",
393 borderRadius: "5px",
394 transition: "all 0.15s ease",
395 },
396 spaceKey: {
397 background: "linear-gradient(to bottom, #ffffff, #f8f8f8)",
398 color: "#333333",
399 boxShadow: "0 2px 3px rgba(0, 0, 0, 0.06), inset 0 1px 0 rgba(255, 255, 255, 0.8)",
400 border: "1px solid rgba(0, 0, 0, 0.08)",
401 fontSize: "11px",
402 fontWeight: "500",
403 fontFamily: "system-ui, -apple-system, sans-serif",
404 textShadow: "none",
405 borderRadius: "5px",
406 transition: "all 0.15s ease",
407 },
408 arrowKey: {
409 background: "linear-gradient(to bottom, #f2f2f4, #eaeaec)",
410 color: "#555555",
411 boxShadow: "0 2px 3px rgba(0, 0, 0, 0.06), inset 0 1px 0 rgba(255, 255, 255, 0.8)",
412 border: "1px solid rgba(0, 0, 0, 0.08)",
413 fontSize: "11px",
414 fontWeight: "500",
415 fontFamily: "system-ui, -apple-system, sans-serif",
416 textShadow: "none",
417 borderRadius: "5px",
418 transition: "all 0.15s ease",
419 },
420 }
421
422 case "retro":
423 return {
424 keyboard: {
425 background: "linear-gradient(to bottom, #f5f0e8, #e8e0d0)",
426 boxShadow: "0 8px 20px rgba(120, 100, 80, 0.2), 0 4px 8px rgba(120, 100, 80, 0.1), inset 0 1px 0 #fff",
427 border: "2px solid #d0c0a0",
428 borderRadius: "8px",
429 padding: "8px",
430 },
431 key: {
432 background: "linear-gradient(to bottom, #fff8e8, #f0e8d8)",
433 color: "#705030",
434 boxShadow: "0 4px 0 #c0b090, inset 0 1px 0 #fffaf0",
435 textShadow: "none",
436 border: "1px solid #d0c0a0",
437 fontFamily: '"Courier New", monospace',
438 fontWeight: "600",
439 fontSize: "11px",
440 borderRadius: "6px",
441 transition: "all 0.12s ease-out",
442 marginBottom: "4px",
443 },
444 keyPressed: {
445 background: "linear-gradient(to bottom, #e8d8c0, #d8c8b0)",
446 boxShadow: "0 0 0 #c0b090, inset 0 1px 2px rgba(120, 100, 80, 0.2)",
447 color: "#604020",
448 textShadow: "none",
449 border: "1px solid #c0b090",
450 transform: "translateY(4px)",
451 fontSize: "11px",
452 fontWeight: "600",
453 fontFamily: '"Courier New", monospace',
454 borderRadius: "6px",
455 marginBottom: "0px",
456 transition: "all 0.05s ease",
457 },
458 keyHover: {
459 background: "linear-gradient(to bottom, #fffaf0, #f8f0e0)",
460 boxShadow: "0 4px 0 #c0b090, inset 0 1px 0 #fffaf0",
461 color: "#604020",
462 border: "1px solid #c0b090",
463 fontSize: "11px",
464 fontWeight: "600",
465 fontFamily: '"Courier New", monospace',
466 textShadow: "none",
467 borderRadius: "6px",
468 transition: "all 0.12s ease-out",
469 marginBottom: "4px",
470 },
471 keyActive: {
472 background: "linear-gradient(to bottom, #fff8e8, #f0e8d8)",
473 boxShadow: `0 4px 0 #c0b090, 0 0 15px ${activeKeyGlowColor}, inset 0 1px 0 #fffaf0`,
474 color: activeKeyGlowColor,
475 textShadow: `0 0 5px ${activeKeyGlowColor}`,
476 border: `1px solid ${activeKeyGlowColor}`,
477 fontSize: "11px",
478 fontWeight: "600",
479 fontFamily: '"Courier New", monospace',
480 borderRadius: "6px",
481 transition: "all 0.12s ease-out",
482 marginBottom: "4px",
483 },
484 specialKey: {
485 background: "linear-gradient(to bottom, #f0e8d8, #e0d0c0)",
486 color: "#604020",
487 boxShadow: "0 4px 0 #c0b090, inset 0 1px 0 #fffaf0",
488 border: "1px solid #d0c0a0",
489 fontSize: "10px",
490 fontWeight: "600",
491 fontFamily: '"Courier New", monospace',
492 textShadow: "none",
493 borderRadius: "6px",
494 transition: "all 0.12s ease-out",
495 marginBottom: "4px",
496 },
497 functionKey: {
498 background: "linear-gradient(to bottom, #e8d8c0, #d8c8b0)",
499 color: "#604020",
500 fontSize: "9px",
501 boxShadow: "0 4px 0 #c0b090, inset 0 1px 0 #fffaf0",
502 border: "1px solid #d0c0a0",
503 fontWeight: "600",
504 fontFamily: '"Courier New", monospace',
505 textShadow: "none",
506 borderRadius: "6px",
507 transition: "all 0.12s ease-out",
508 marginBottom: "4px",
509 },
510 modifierKey: {
511 background: "linear-gradient(to bottom, #e8d8c0, #d8c8b0)",
512 color: "#604020",
513 boxShadow: "0 4px 0 #c0b090, inset 0 1px 0 #fffaf0",
514 border: "1px solid #d0c0a0",
515 fontSize: "10px",
516 fontWeight: "600",
517 fontFamily: '"Courier New", monospace',
518 textShadow: "none",
519 borderRadius: "6px",
520 transition: "all 0.12s ease-out",
521 marginBottom: "4px",
522 },
523 spaceKey: {
524 background: "linear-gradient(to bottom, #fff8e8, #f0e8d8)",
525 color: "#705030",
526 boxShadow: "0 4px 0 #c0b090, inset 0 1px 0 #fffaf0",
527 border: "1px solid #d0c0a0",
528 fontSize: "11px",
529 fontWeight: "600",
530 fontFamily: '"Courier New", monospace',
531 textShadow: "none",
532 borderRadius: "6px",
533 transition: "all 0.12s ease-out",
534 marginBottom: "4px",
535 },
536 arrowKey: {
537 background: "linear-gradient(to bottom, #e8d8c0, #d8c8b0)",
538 color: "#604020",
539 boxShadow: "0 4px 0 #c0b090, inset 0 1px 0 #fffaf0",
540 border: "1px solid #d0c0a0",
541 fontSize: "11px",
542 fontWeight: "600",
543 fontFamily: '"Courier New", monospace',
544 textShadow: "none",
545 borderRadius: "6px",
546 transition: "all 0.12s ease-out",
547 marginBottom: "4px",
548 },
549 }
550
551 case "cyberpunk":
552 return {
553 keyboard: {
554 background: `linear-gradient(145deg, ${adjustColorBrightness(
555 keyColor,
556 -5,
557 )}, ${adjustColorBrightness(keyColor, -15)})`,
558 boxShadow: `0 15px 40px rgba(0, 0, 0, 0.3), 0 10px 20px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.05), inset 0 -1px 0 rgba(0, 0, 0, 0.5)`,
559 border: `1px solid ${adjustColorBrightness(keyColor, -20)}`,
560 borderRadius: "12px",
561 },
562 key: {
563 background: `linear-gradient(145deg, ${adjustColorBrightness(keyColor, 10)}, ${keyColor})`,
564 color: keyTextColor,
565 boxShadow: `0 3px 0 ${adjustColorBrightness(
566 keyColor,
567 -20,
568 )}, 0 0 10px rgba(0, 0, 0, 0.2), inset 0 1px 1px rgba(255, 255, 255, 0.1)`,
569 textShadow: `0 0 5px rgba(255, 255, 255, 0.3)`,
570 border: `1px solid ${adjustColorBrightness(keyColor, -10)}`,
571 fontFamily: '"Inter", "SF Pro Display", system-ui, sans-serif',
572 fontWeight: "600",
573 fontSize: "11px",
574 borderRadius: "6px",
575 letterSpacing: "0.5px",
576 transition: "all 0.15s cubic-bezier(0.23, 1, 0.32, 1)",
577 },
578 keyPressed: {
579 background: `linear-gradient(145deg, ${keyPressedColor}, ${adjustColorBrightness(keyPressedColor, -10)})`,
580 boxShadow: `0 0 0 ${adjustColorBrightness(keyColor, -20)}, 0 0 15px rgba(${hexToRgb(
581 accentColor,
582 )}, 0.5), inset 0 1px 2px rgba(0, 0, 0, 0.3)`,
583 color: accentColor,
584 textShadow: `0 0 8px rgba(${hexToRgb(accentColor)}, 0.7)`,
585 border: `1px solid ${adjustColorBrightness(keyPressedColor, 10)}`,
586 transform: "translateY(2px)",
587 fontSize: "11px",
588 fontWeight: "600",
589 fontFamily: '"Inter", "SF Pro Display", system-ui, sans-serif',
590 borderRadius: "6px",
591 letterSpacing: "0.5px",
592 transition: "all 0.08s cubic-bezier(0.23, 1, 0.32, 1)",
593 },
594 keyHover: {
595 background: `linear-gradient(145deg, ${adjustColorBrightness(
596 keyColor,
597 15,
598 )}, ${adjustColorBrightness(keyColor, 5)})`,
599 boxShadow: `0 3px 0 ${adjustColorBrightness(keyColor, -20)}, 0 0 12px rgba(${hexToRgb(
600 accentColor,
601 )}, 0.3), inset 0 1px 1px rgba(255, 255, 255, 0.15)`,
602 color: accentColor,
603 textShadow: `0 0 8px rgba(${hexToRgb(accentColor)}, 0.5)`,
604 border: `1px solid ${adjustColorBrightness(keyColor, 0)}`,
605 fontSize: "11px",
606 fontWeight: "600",
607 fontFamily: '"Inter", "SF Pro Display", system-ui, sans-serif',
608 borderRadius: "6px",
609 letterSpacing: "0.5px",
610 transition: "all 0.15s cubic-bezier(0.23, 1, 0.32, 1)",
611 },
612 keyActive: {
613 background: `linear-gradient(145deg, ${adjustColorBrightness(keyColor, 10)}, ${keyColor})`,
614 boxShadow: `0 3px 0 ${adjustColorBrightness(keyColor, -20)}, 0 0 20px rgba(${hexToRgb(
615 activeKeyGlowColor,
616 )}, ${activeKeyGlowIntensity}), inset 0 1px 1px rgba(255, 255, 255, 0.1)`,
617 color: activeKeyGlowColor,
618 textShadow: `0 0 10px rgba(${hexToRgb(activeKeyGlowColor)}, 0.9)`,
619 border: `1px solid ${activeKeyGlowColor}`,
620 fontSize: "11px",
621 fontWeight: "600",
622 fontFamily: '"Inter", "SF Pro Display", system-ui, sans-serif',
623 borderRadius: "6px",
624 letterSpacing: "0.5px",
625 transition: "all 0.15s cubic-bezier(0.23, 1, 0.32, 1)",
626 },
627 specialKey: {
628 background: `linear-gradient(145deg, ${adjustColorBrightness(
629 keyColor,
630 0,
631 )}, ${adjustColorBrightness(keyColor, -10)})`,
632 color: adjustColorBrightness(keyTextColor, -10),
633 boxShadow: `0 3px 0 ${adjustColorBrightness(
634 keyColor,
635 -20,
636 )}, 0 0 10px rgba(0, 0, 0, 0.2), inset 0 1px 1px rgba(255, 255, 255, 0.1)`,
637 border: `1px solid ${adjustColorBrightness(keyColor, -10)}`,
638 fontSize: "10px",
639 fontWeight: "600",
640 fontFamily: '"Inter", "SF Pro Display", system-ui, sans-serif',
641 textShadow: `0 0 5px rgba(255, 255, 255, 0.3)`,
642 borderRadius: "6px",
643 letterSpacing: "0.5px",
644 transition: "all 0.15s cubic-bezier(0.23, 1, 0.32, 1)",
645 },
646 functionKey: {
647 background: `linear-gradient(145deg, ${adjustColorBrightness(
648 keyColor,
649 -5,
650 )}, ${adjustColorBrightness(keyColor, -15)})`,
651 color: adjustColorBrightness(keyTextColor, -15),
652 fontSize: "9px",
653 boxShadow: `0 3px 0 ${adjustColorBrightness(
654 keyColor,
655 -20,
656 )}, 0 0 10px rgba(0, 0, 0, 0.2), inset 0 1px 1px rgba(255, 255, 255, 0.1)`,
657 border: `1px solid ${adjustColorBrightness(keyColor, -10)}`,
658 fontWeight: "600",
659 fontFamily: '"Inter", "SF Pro Display", system-ui, sans-serif',
660 textShadow: `0 0 5px rgba(255, 255, 255, 0.3)`,
661 borderRadius: "6px",
662 letterSpacing: "0.5px",
663 transition: "all 0.15s cubic-bezier(0.23, 1, 0.32, 1)",
664 },
665 modifierKey: {
666 background: `linear-gradient(145deg, ${adjustColorBrightness(
667 keyColor,
668 -5,
669 )}, ${adjustColorBrightness(keyColor, -15)})`,
670 color: adjustColorBrightness(keyTextColor, -5),
671 boxShadow: `0 3px 0 ${adjustColorBrightness(
672 keyColor,
673 -20,
674 )}, 0 0 10px rgba(0, 0, 0, 0.2), inset 0 1px 1px rgba(255, 255, 255, 0.1)`,
675 border: `1px solid ${adjustColorBrightness(keyColor, -10)}`,
676 fontSize: "10px",
677 fontWeight: "600",
678 fontFamily: '"Inter", "SF Pro Display", system-ui, sans-serif',
679 textShadow: `0 0 5px rgba(255, 255, 255, 0.3)`,
680 borderRadius: "6px",
681 letterSpacing: "0.5px",
682 transition: "all 0.15s cubic-bezier(0.23, 1, 0.32, 1)",
683 },
684 spaceKey: {
685 background: `linear-gradient(145deg, ${adjustColorBrightness(
686 keyColor,
687 5,
688 )}, ${adjustColorBrightness(keyColor, -5)})`,
689 color: keyTextColor,
690 boxShadow: `0 3px 0 ${adjustColorBrightness(
691 keyColor,
692 -20,
693 )}, 0 0 10px rgba(0, 0, 0, 0.2), inset 0 1px 1px rgba(255, 255, 255, 0.1)`,
694 border: `1px solid ${adjustColorBrightness(keyColor, -10)}`,
695 fontSize: "11px",
696 fontWeight: "600",
697 fontFamily: '"Inter", "SF Pro Display", system-ui, sans-serif',
698 textShadow: `0 0 5px rgba(255, 255, 255, 0.3)`,
699 borderRadius: "6px",
700 letterSpacing: "0.5px",
701 transition: "all 0.15s cubic-bezier(0.23, 1, 0.32, 1)",
702 },
703 arrowKey: {
704 background: `linear-gradient(145deg, ${adjustColorBrightness(
705 keyColor,
706 -5,
707 )}, ${adjustColorBrightness(keyColor, -15)})`,
708 color: accentColor,
709 boxShadow: `0 3px 0 ${adjustColorBrightness(
710 keyColor,
711 -20,
712 )}, 0 0 10px rgba(0, 0, 0, 0.2), inset 0 1px 1px rgba(255, 255, 255, 0.1)`,
713 border: `1px solid ${adjustColorBrightness(keyColor, -10)}`,
714 fontSize: "11px",
715 fontWeight: "600",
716 fontFamily: '"Inter", "SF Pro Display", system-ui, sans-serif',
717 textShadow: `0 0 5px rgba(${hexToRgb(accentColor)}, 0.5)`,
718 borderRadius: "6px",
719 letterSpacing: "0.5px",
720 transition: "all 0.15s cubic-bezier(0.23, 1, 0.32, 1)",
721 },
722 }
723 case "neon":
724 return {
725 keyboard: {
726 background: "linear-gradient(to bottom, #121212, #1a1a1a)",
727 boxShadow:
728 "0 15px 40px rgba(0, 0, 0, 0.4), 0 10px 20px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.05)",
729 border: "1px solid #333",
730 borderRadius: "12px",
731 padding: "10px",
732 },
733 key: {
734 background: "linear-gradient(to bottom, #222222, #111111)",
735 color: "#00ffcc",
736 boxShadow: "0 3px 0 #000, 0 0 8px rgba(0, 0, 0, 0.3), inset 0 1px 1px rgba(255, 255, 255, 0.05)",
737 textShadow: "0 0 8px rgba(0, 255, 204, 0.7)",
738 border: "1px solid #333",
739 fontFamily: '"Orbitron", sans-serif',
740 fontWeight: "500",
741 fontSize: "11px",
742 borderRadius: "6px",
743 transition: "all 0.15s ease",
744 },
745 keyPressed: {
746 background: "linear-gradient(to bottom, #111111, #0a0a0a)",
747 boxShadow: "0 0 0 #000, 0 0 15px rgba(0, 255, 204, 0.7), inset 0 1px 2px rgba(0, 0, 0, 0.5)",
748 color: "#ffffff",
749 textShadow: "0 0 10px rgba(0, 255, 204, 1)",
750 border: "1px solid #00ffcc",
751 transform: "translateY(3px)",
752 fontSize: "11px",
753 fontWeight: "500",
754 fontFamily: '"Orbitron", sans-serif',
755 borderRadius: "6px",
756 transition: "all 0.05s ease",
757 },
758 keyHover: {
759 background: "linear-gradient(to bottom, #2a2a2a, #191919)",
760 boxShadow: "0 3px 0 #000, 0 0 12px rgba(0, 255, 204, 0.4), inset 0 1px 1px rgba(255, 255, 255, 0.1)",
761 color: "#ffffff",
762 textShadow: "0 0 8px rgba(0, 255, 204, 0.8)",
763 border: "1px solid #00aa88",
764 fontSize: "11px",
765 fontWeight: "500",
766 fontFamily: '"Orbitron", sans-serif',
767 borderRadius: "6px",
768 transition: "all 0.15s ease",
769 },
770 keyActive: {
771 background: "linear-gradient(to bottom, #222222, #111111)",
772 boxShadow: "0 3px 0 #000, 0 0 20px rgba(0, 255, 204, 0.8), inset 0 1px 1px rgba(255, 255, 255, 0.05)",
773 color: "#ffffff",
774 textShadow: "0 0 15px rgba(0, 255, 204, 1)",
775 border: "1px solid #00ffcc",
776 fontSize: "11px",
777 fontWeight: "500",
778 fontFamily: '"Orbitron", sans-serif',
779 borderRadius: "6px",
780 transition: "all 0.15s ease",
781 },
782 specialKey: {
783 background: "linear-gradient(to bottom, #191919, #0d0d0d)",
784 color: "#ff3399",
785 boxShadow: "0 3px 0 #000, 0 0 8px rgba(0, 0, 0, 0.3), inset 0 1px 1px rgba(255, 255, 255, 0.05)",
786 border: "1px solid #333",
787 fontSize: "10px",
788 fontWeight: "500",
789 fontFamily: '"Orbitron", sans-serif',
790 textShadow: "0 0 8px rgba(255, 51, 153, 0.7)",
791 borderRadius: "6px",
792 transition: "all 0.15s ease",
793 },
794 functionKey: {
795 background: "linear-gradient(to bottom, #191919, #0d0d0d)",
796 color: "#3399ff",
797 fontSize: "9px",
798 boxShadow: "0 3px 0 #000, 0 0 8px rgba(0, 0, 0, 0.3), inset 0 1px 1px rgba(255, 255, 255, 0.05)",
799 border: "1px solid #333",
800 fontWeight: "500",
801 fontFamily: '"Orbitron", sans-serif',
802 textShadow: "0 0 8px rgba(51, 153, 255, 0.7)",
803 borderRadius: "6px",
804 transition: "all 0.15s ease",
805 },
806 modifierKey: {
807 background: "linear-gradient(to bottom, #191919, #0d0d0d)",
808 color: "#ffcc00",
809 boxShadow: "0 3px 0 #000, 0 0 8px rgba(0, 0, 0, 0.3), inset 0 1px 1px rgba(255, 255, 255, 0.05)",
810 border: "1px solid #333",
811 fontSize: "10px",
812 fontWeight: "500",
813 fontFamily: '"Orbitron", sans-serif',
814 textShadow: "0 0 8px rgba(255, 204, 0, 0.7)",
815 borderRadius: "6px",
816 transition: "all 0.15s ease",
817 },
818 spaceKey: {
819 background: "linear-gradient(to bottom, #222222, #111111)",
820 color: "#00ffcc",
821 boxShadow: "0 3px 0 #000, 0 0 8px rgba(0, 0, 0, 0.3), inset 0 1px 1px rgba(255, 255, 255, 0.05)",
822 border: "1px solid #333",
823 fontSize: "11px",
824 fontWeight: "500",
825 fontFamily: '"Orbitron", sans-serif',
826 textShadow: "0 0 8px rgba(0, 255, 204, 0.7)",
827 borderRadius: "6px",
828 transition: "all 0.15s ease",
829 },
830 arrowKey: {
831 background: "linear-gradient(to bottom, #191919, #0d0d0d)",
832 color: "#ff9900",
833 boxShadow: "0 3px 0 #000, 0 0 8px rgba(0, 0, 0, 0.3), inset 0 1px 1px rgba(255, 255, 255, 0.05)",
834 border: "1px solid #333",
835 fontSize: "11px",
836 fontWeight: "500",
837 fontFamily: '"Orbitron", sans-serif',
838 textShadow: "0 0 8px rgba(255, 153, 0, 0.7)",
839 borderRadius: "6px",
840 transition: "all 0.15s ease",
841 },
842 }
843
844 case "pastel":
845 return {
846 keyboard: {
847 background: "linear-gradient(to bottom, #f0e6f6, #e7ddf0)",
848 boxShadow: "0 12px 30px rgba(200, 180, 220, 0.3), 0 8px 15px rgba(200, 180, 220, 0.2)",
849 border: "1px solid #d8cceb",
850 borderRadius: "16px",
851 padding: "12px",
852 },
853 key: {
854 background: "linear-gradient(to bottom, #ffffff, #f5f0f9)",
855 color: "#7b6d8d",
856 boxShadow: "0 3px 0 #d8cceb, 0 0 5px rgba(0, 0, 0, 0.03), inset 0 1px 0 rgba(255, 255, 255, 1)",
857 textShadow: "none",
858 border: "1px solid #e2d7f0",
859 fontFamily: '"Quicksand", "Avenir Next", sans-serif',
860 fontWeight: "500",
861 fontSize: "11px",
862 borderRadius: "10px",
863 transition: "all 0.2s ease",
864 },
865 keyPressed: {
866 background: "linear-gradient(to bottom, #f0e6f9, #e7ddf5)",
867 boxShadow: "0 0 0 #d8cceb, inset 0 1px 2px rgba(0, 0, 0, 0.05)",
868 color: "#9370db",
869 textShadow: "none",
870 border: "1px solid #d8cceb",
871 transform: "translateY(3px)",
872 fontSize: "11px",
873 fontWeight: "500",
874 fontFamily: '"Quicksand", "Avenir Next", sans-serif',
875 borderRadius: "10px",
876 transition: "all 0.1s ease",
877 },
878 keyHover: {
879 background: "linear-gradient(to bottom, #ffffff, #f9f5fc)",
880 boxShadow: "0 3px 0 #d8cceb, 0 0 8px rgba(155, 122, 188, 0.2), inset 0 1px 0 rgba(255, 255, 255, 1)",
881 color: "#9370db",
882 border: "1px solid #d8cceb",
883 fontSize: "11px",
884 fontWeight: "500",
885 fontFamily: '"Quicksand", "Avenir Next", sans-serif',
886 textShadow: "none",
887 borderRadius: "10px",
888 transition: "all 0.2s ease",
889 },
890 keyActive: {
891 background: "linear-gradient(to bottom, #ffffff, #f5f0f9)",
892 boxShadow: "0 3px 0 #d8cceb, 0 0 12px rgba(155, 122, 188, 0.4), inset 0 1px 0 rgba(255, 255, 255, 1)",
893 color: "#9370db",
894 textShadow: "0 0 3px rgba(155, 122, 188, 0.3)",
895 border: "1px solid #b79ce8",
896 fontSize: "11px",
897 fontWeight: "500",
898 fontFamily: '"Quicksand", "Avenir Next", sans-serif',
899 borderRadius: "10px",
900 transition: "all 0.2s ease",
901 },
902 specialKey: {
903 background: "linear-gradient(to bottom, #f9f0fc, #f0e6f6)",
904 color: "#9382ab",
905 boxShadow: "0 3px 0 #d8cceb, 0 0 5px rgba(0, 0, 0, 0.03), inset 0 1px 0 rgba(255, 255, 255, 1)",
906 border: "1px solid #e2d7f0",
907 fontSize: "10px",
908 fontWeight: "500",
909 fontFamily: '"Quicksand", "Avenir Next", sans-serif',
910 textShadow: "none",
911 borderRadius: "10px",
912 transition: "all 0.2s ease",
913 },
914 functionKey: {
915 background: "linear-gradient(to bottom, #f0e6f6, #e7ddf0)",
916 color: "#a58cc4",
917 fontSize: "9px",
918 boxShadow: "0 3px 0 #d8cceb, 0 0 5px rgba(0, 0, 0, 0.03), inset 0 1px 0 rgba(255, 255, 255, 1)",
919 border: "1px solid #e2d7f0",
920 fontWeight: "500",
921 fontFamily: '"Quicksand", "Avenir Next", sans-serif',
922 textShadow: "none",
923 borderRadius: "10px",
924 transition: "all 0.2s ease",
925 },
926 modifierKey: {
927 background: "linear-gradient(to bottom, #f0e6f6, #e7ddf0)",
928 color: "#9382ab",
929 boxShadow: "0 3px 0 #d8cceb, 0 0 5px rgba(0, 0, 0, 0.03), inset 0 1px 0 rgba(255, 255, 255, 1)",
930 border: "1px solid #e2d7f0",
931 fontSize: "10px",
932 fontWeight: "500",
933 fontFamily: '"Quicksand", "Avenir Next", sans-serif',
934 textShadow: "none",
935 borderRadius: "10px",
936 transition: "all 0.2s ease",
937 },
938 spaceKey: {
939 background: "linear-gradient(to bottom, #ffffff, #f5f0f9)",
940 color: "#7b6d8d",
941 boxShadow: "0 3px 0 #d8cceb, 0 0 5px rgba(0, 0, 0, 0.03), inset 0 1px 0 rgba(255, 255, 255, 1)",
942 border: "1px solid #e2d7f0",
943 fontSize: "11px",
944 fontWeight: "500",
945 fontFamily: '"Quicksand", "Avenir Next", sans-serif',
946 textShadow: "none",
947 borderRadius: "10px",
948 transition: "all 0.2s ease",
949 },
950 arrowKey: {
951 background: "linear-gradient(to bottom, #f0e6f6, #e7ddf0)",
952 color: "#b79ce8",
953 boxShadow: "0 3px 0 #d8cceb, 0 0 5px rgba(0, 0, 0, 0.03), inset 0 1px 0 rgba(255, 255, 255, 1)",
954 border: "1px solid #e2d7f0",
955 fontSize: "11px",
956 fontWeight: "500",
957 fontFamily: '"Quicksand", "Avenir Next", sans-serif',
958 textShadow: "none",
959 borderRadius: "10px",
960 transition: "all 0.2s ease",
961 },
962 }
963
964 case "mechanical":
965 return {
966 keyboard: {
967 background: "linear-gradient(to bottom, #2c2c2c, #1a1a1a)",
968 boxShadow: "0 12px 30px rgba(0, 0, 0, 0.5), 0 8px 15px rgba(0, 0, 0, 0.4)",
969 border: "2px solid #000",
970 borderRadius: "8px",
971 padding: "10px",
972 },
973 key: {
974 background: "linear-gradient(to bottom, #363636, #222222)",
975 color: "#ddd",
976 boxShadow: "0 2px 0 #000, inset 0 1px 0 rgba(255, 255, 255, 0.1), inset 0 0 5px rgba(255, 255, 255, 0.05)",
977 textShadow: "none",
978 border: "1px solid #111",
979 fontFamily: '"IBM Plex Mono", monospace',
980 fontWeight: "600",
981 fontSize: "11px",
982 borderRadius: "5px",
983 transition: "all 0.08s ease",
984 height: "40px",
985 },
986 keyPressed: {
987 background: "linear-gradient(to bottom, #222222, #1a1a1a)",
988 boxShadow: "0 0 0 #000, inset 0 0 10px rgba(0, 0, 0, 0.8)",
989 color: "#fff",
990 textShadow: "none",
991 border: "1px solid #000",
992 transform: "translateY(2px)",
993 fontSize: "11px",
994 fontWeight: "600",
995 fontFamily: '"IBM Plex Mono", monospace',
996 borderRadius: "5px",
997 transition: "all 0.02s ease",
998 height: "40px",
999 },
1000 keyHover: {
1001 background: "linear-gradient(to bottom, #3a3a3a, #262626)",
1002 boxShadow: "0 2px 0 #000, inset 0 1px 0 rgba(255, 255, 255, 0.1), inset 0 0 5px rgba(255, 255, 255, 0.08)",
1003 color: "#fff",
1004 border: "1px solid #111",
1005 fontSize: "11px",
1006 fontWeight: "600",
1007 fontFamily: '"IBM Plex Mono", monospace',
1008 textShadow: "none",
1009 borderRadius: "5px",
1010 transition: "all 0.08s ease",
1011 height: "40px",
1012 },
1013 keyActive: {
1014 background: "linear-gradient(to bottom, #363636, #222222)",
1015 boxShadow: "0 2px 0 #000, inset 0 1px 0 rgba(255, 255, 255, 0.1), inset 0 0 5px rgba(255, 255, 255, 0.05)",
1016 color: "#ff7700",
1017 textShadow: "0 0 5px rgba(255, 119, 0, 0.5)",
1018 border: "1px solid #ff7700",
1019 fontSize: "11px",
1020 fontWeight: "600",
1021 fontFamily: '"IBM Plex Mono", monospace',
1022 borderRadius: "5px",
1023 transition: "all 0.08s ease",
1024 height: "40px",
1025 },
1026 specialKey: {
1027 background: "linear-gradient(to bottom, #2d2d2d, #1d1d1d)",
1028 color: "#aaa",
1029 boxShadow: "0 2px 0 #000, inset 0 1px 0 rgba(255, 255, 255, 0.1), inset 0 0 5px rgba(255, 255, 255, 0.05)",
1030 border: "1px solid #111",
1031 fontSize: "10px",
1032 fontWeight: "600",
1033 fontFamily: '"IBM Plex Mono", monospace',
1034 textShadow: "none",
1035 borderRadius: "5px",
1036 transition: "all 0.08s ease",
1037 height: "40px",
1038 },
1039 functionKey: {
1040 background: "linear-gradient(to bottom, #2a2a2a, #1a1a1a)",
1041 color: "#888",
1042 fontSize: "9px",
1043 boxShadow: "0 2px 0 #000, inset 0 1px 0 rgba(255, 255, 255, 0.1), inset 0 0 5px rgba(255, 255, 255, 0.05)",
1044 border: "1px solid #111",
1045 fontWeight: "600",
1046 fontFamily: '"IBM Plex Mono", monospace',
1047 textShadow: "none",
1048 borderRadius: "5px",
1049 transition: "all 0.08s ease",
1050 height: "30px",
1051 },
1052 modifierKey: {
1053 background: "linear-gradient(to bottom, #2d2d2d, #1d1d1d)",
1054 color: "#bbb",
1055 boxShadow: "0 2px 0 #000, inset 0 1px 0 rgba(255, 255, 255, 0.1), inset 0 0 5px rgba(255, 255, 255, 0.05)",
1056 border: "1px solid #111",
1057 fontSize: "10px",
1058 fontWeight: "600",
1059 fontFamily: '"IBM Plex Mono", monospace',
1060 textShadow: "none",
1061 borderRadius: "5px",
1062 transition: "all 0.08s ease",
1063 height: "40px",
1064 },
1065 spaceKey: {
1066 background: "linear-gradient(to bottom, #363636, #222222)",
1067 color: "#ddd",
1068 boxShadow: "0 2px 0 #000, inset 0 1px 0 rgba(255, 255, 255, 0.1), inset 0 0 5px rgba(255, 255, 255, 0.05)",
1069 border: "1px solid #111",
1070 fontSize: "11px",
1071 fontWeight: "600",
1072 fontFamily: '"IBM Plex Mono", monospace',
1073 textShadow: "none",
1074 borderRadius: "5px",
1075 transition: "all 0.08s ease",
1076 height: "40px",
1077 },
1078 arrowKey: {
1079 background: "linear-gradient(to bottom, #2d2d2d, #1d1d1d)",
1080 color: "#ff7700",
1081 boxShadow: "0 2px 0 #000, inset 0 1px 0 rgba(255, 255, 255, 0.1), inset 0 0 5px rgba(255, 255, 255, 0.05)",
1082 border: "1px solid #111",
1083 fontSize: "11px",
1084 fontWeight: "600",
1085 fontFamily: '"IBM Plex Mono", monospace',
1086 textShadow: "none",
1087 borderRadius: "5px",
1088 transition: "all 0.08s ease",
1089 height: "40px",
1090 },
1091 }
1092 }
1093 }
1094 const getKeyStyle = (key: KeyObject, isPressed: boolean, isActive: boolean) => {
1095 const size = key.size || 1
1096 const keyType = getKeyType(key)
1097 let baseStyle = { ...themeStyles.key }
1098 if (keyType === "special" && themeStyles.specialKey) {
1099 baseStyle = { ...baseStyle, ...themeStyles.specialKey }
1100 } else if (keyType === "function" && themeStyles.functionKey) {
1101 baseStyle = { ...baseStyle, ...themeStyles.functionKey }
1102 } else if (keyType === "modifier" && themeStyles.modifierKey) {
1103 baseStyle = { ...baseStyle, ...themeStyles.modifierKey }
1104 } else if (keyType === "space" && themeStyles.spaceKey) {
1105 baseStyle = { ...baseStyle, ...themeStyles.spaceKey }
1106 } else if (keyType === "arrow" && themeStyles.arrowKey) {
1107 baseStyle = { ...baseStyle, ...themeStyles.arrowKey }
1108 }
1109 if (isActive && themeStyles.keyActive) {
1110 baseStyle = { ...baseStyle, ...themeStyles.keyActive }
1111 }
1112 return {
1113 position: "relative" as const,
1114 width: `${calcKeyWidth(size)}px`,
1115 height: `${keyHeight}px`,
1116 ...(isPressed ? { ...baseStyle, ...themeStyles.keyPressed } : baseStyle),
1117 borderRadius: baseStyle.borderRadius || "4px",
1118 display: "flex" as const,
1119 justifyContent: "center" as const,
1120 alignItems: "center" as const,
1121 cursor: "pointer",
1122 userSelect: "none" as const,
1123 transition: `all ${keyPressAnimationDuration}ms cubic-bezier(0.2, 0.8, 0.2, 1)`,
1124 transform: isPressed ? themeStyles.keyPressed.transform || "translateY(2px)" : "translateY(0)",
1125 fontSize: baseStyle.fontSize || "11px",
1126 fontWeight: baseStyle.fontWeight || 500,
1127 fontFamily: baseStyle.fontFamily || "inherit",
1128 padding: "0",
1129 margin: "0",
1130 letterSpacing: baseStyle.letterSpacing || "normal",
1131 willChange: "transform, box-shadow",
1132 }
1133 }
1134 function adjustColorBrightness(hex: string, percent: number): string {
1135 const num = Number.parseInt(hex.replace("#", ""), 16)
1136 const amt = Math.round(2.55 * percent)
1137 const R = (num >> 16) + amt
1138 const G = ((num >> 8) & 0x00ff) + amt
1139 const B = (num & 0x0000ff) + amt
1140
1141 return (
1142 "#" +
1143 (
1144 0x1000000 +
1145 (R < 255 ? (R < 0 ? 0 : R) : 255) * 0x10000 +
1146 (G < 255 ? (G < 0 ? 0 : G) : 255) * 0x100 +
1147 (B < 255 ? (B < 0 ? 0 : B) : 255)
1148 )
1149 .toString(16)
1150 .slice(1)
1151 )
1152 }
1153
1154 function hexToRgb(hex: string): string {
1155 const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
1156 return result
1157 ? `${Number.parseInt(result[1], 16)}, ${Number.parseInt(result[2], 16)}, ${Number.parseInt(result[3], 16)}`
1158 : "0, 255, 255"
1159 }
1160 const getKeyType = (key: KeyObject): string => {
1161 if (!key.code) return "regular"
1162 if (key.type === "numpad") return "numpad"
1163 if (["Space"].includes(key.code)) return "space"
1164 if (["F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "Escape"].includes(key.code))
1165 return "function"
1166 if (
1167 [
1168 "ShiftLeft",
1169 "ShiftRight",
1170 "ControlLeft",
1171 "ControlRight",
1172 "AltLeft",
1173 "AltRight",
1174 "MetaLeft",
1175 "MetaRight",
1176 "CapsLock",
1177 ].includes(key.code)
1178 )
1179 return "modifier"
1180 if (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(key.code)) return "arrow"
1181 if (
1182 [
1183 "Backspace",
1184 "Tab",
1185 "Enter",
1186 "Delete",
1187 "Home",
1188 "End",
1189 "PageUp",
1190 "PageDown",
1191 "Insert",
1192 "PrintScreen",
1193 "ScrollLock",
1194 "Pause",
1195 "ContextMenu",
1196 ].includes(key.code)
1197 )
1198 return "special"
1199
1200 return "regular"
1201 }
1202 const isKeyActive = (code: string | undefined): boolean => {
1203 if (!code) return false
1204 return activeKeys.includes(code)
1205 }
1206
1207 const renderKeyIcon = (key: KeyObject) => {
1208 if (!key.icon) return null
1209
1210 switch (key.icon) {
1211 case "windows":
1212 return <Command className="h-3 w-3" />
1213 case "menu":
1214 return <Menu className="h-3 w-3" />
1215 case "capslock":
1216 return <ChevronsUp className="h-3 w-3 mr-1" />
1217 case "arrowup":
1218 return <ArrowUp className="h-3 w-3" />
1219 case "arrowdown":
1220 return <ArrowDown className="h-3 w-3" />
1221 case "arrowleft":
1222 return <ArrowLeft className="h-3 w-3" />
1223 case "arrowright":
1224 return <ArrowRight className="h-3 w-3" />
1225 default:
1226 return null
1227 }
1228 }
1229
1230 const themeStyles = getThemeStyles()
1231 const keyboardLayout = getKeyboardLayout()
1232 const keyUnit = 40
1233 const keySpacing = 6
1234 const keyHeight = 40
1235 const calcKeyWidth = (size: number): number => keyUnit * size + keySpacing * (size - 1)
1236 const keyboardStyle = {
1237 ...themeStyles.keyboard,
1238 display: "flex" as const,
1239 flexDirection: "column" as const,
1240 padding: "20px",
1241 borderRadius: themeStyles.keyboard.borderRadius || "10px",
1242 transform: `perspective(${perspective}px) rotateX(${rotateX}deg)`,
1243 position: "relative" as const,
1244 gap: `${keySpacing}px`,
1245 maxWidth: "fit-content",
1246 transition: "all 0.3s ease",
1247 }
1248 const calculateMainRowWidth = (row: KeyboardRow): number => {
1249 let totalWidth = 0
1250 for (const key of row.keys) {
1251 if (key.spacer) {
1252 totalWidth += calcKeyWidth(key.size)
1253 } else if (key.type === "light") {
1254 totalWidth += 8 + (key.size || 0.5) * 4
1255 } else {
1256 totalWidth += calcKeyWidth(key.size)
1257 }
1258 }
1259 if (row.keys.length > 0) {
1260 totalWidth += (row.keys.length - 1) * keySpacing
1261 }
1262 return totalWidth
1263 }
1264 const calculateMaxMainRowWidth = (): number => {
1265 let maxWidth = 0
1266 for (const row of keyboardLayout) {
1267 if (row.keys.length > 0) {
1268 const rowWidth = calculateMainRowWidth(row)
1269 maxWidth = Math.max(maxWidth, rowWidth)
1270 }
1271 }
1272 return maxWidth
1273 }
1274
1275 const maxMainRowWidth = calculateMaxMainRowWidth()
1276 return (
1277 <div
1278 className={`keyboard-container ${className}`}
1279 style={{
1280 display: "flex",
1281 justifyContent: "center",
1282 alignItems: "center",
1283 padding: "20px",
1284 }}
1285 {...props}
1286 >
1287 <div className="keyboard-wrapper" style={{ display: "flex", gap: "20px" }}>
1288 <div className="keyboard" style={keyboardStyle}>
1289 {keyboardLayout.map((row, rowIndex) => {
1290 if (row.function && !showFunctionKeys) return null
1291 const mainRowWidth = calculateMainRowWidth(row)
1292 return (
1293 <div
1294 key={`row-${rowIndex}`}
1295 className="keyboard-row"
1296 style={{
1297 display: "flex",
1298 gap: `${keySpacing}px`,
1299 position: "relative",
1300 marginTop: row.function && rowIndex === 0 ? "10px" : "0",
1301 justifyContent: "flex-start",
1302 }}
1303 >
1304 <div style={{ display: "flex", gap: `${keySpacing}px` }}>
1305 {row.keys.map((key, keyIndex) => {
1306 if (key.spacer) {
1307 return (
1308 <div
1309 key={`spacer-${rowIndex}-${keyIndex}`}
1310 style={{
1311 width: `${calcKeyWidth(key.size)}px`,
1312 height: `${keyHeight}px`,
1313 background: "transparent",
1314 }}
1315 />
1316 )
1317 }
1318
1319 if (key.type === "light") {
1320 return (
1321 <div
1322 key={`light-${rowIndex}-${keyIndex}`}
1323 style={{
1324 width: "8px",
1325 height: "8px",
1326 borderRadius: "50%",
1327 background: accentColor,
1328 boxShadow: `0 0 8px rgba(${hexToRgb(accentColor)}, 0.7)`,
1329 position: "relative",
1330 marginTop: "4px",
1331 marginLeft: keyIndex === 0 ? "0" : "6px",
1332 }}
1333 />
1334 )
1335 }
1336 const isPressed = pressedKeys.has(key.code || "")
1337 const isActive = isKeyActive(key.code)
1338 return (
1339 <div
1340 key={`key-${rowIndex}-${keyIndex}`}
1341 data-key={key.code}
1342 className={`key ${key.code} ${isActive ? "active" : ""}`}
1343 style={getKeyStyle(key, isPressed, isActive)}
1344 onMouseDown={() => key.code && handleKeyDown(key.code)}
1345 onMouseUp={() => key.code && handleKeyUp(key.code)}
1346 onMouseLeave={() => key.code && pressedKeys.has(key.code) && handleKeyUp(key.code)}
1347 onTouchStart={(e) => {
1348 e.preventDefault()
1349 }}
1350 onTouchEnd={() => key.code && handleKeyUp(key.code)}
1351 onMouseEnter={() => {
1352 const element = document.querySelector(`.key.${key.code}`) as HTMLElement
1353 if (element && !pressedKeys.has(key.code || "") && !isActive) {
1354 Object.assign(element.style, themeStyles.keyHover)
1355 }
1356 }}
1357 onMouseOut={() => {
1358 const element = document.querySelector(`.key.${key.code}`) as HTMLElement
1359 if (element && !pressedKeys.has(key.code || "") && !isActive) {
1360 const keyType = getKeyType(key)
1361 let baseStyle = { ...themeStyles.key }
1362
1363 if (keyType === "special" && themeStyles.specialKey) {
1364 baseStyle = {
1365 ...baseStyle,
1366 ...themeStyles.specialKey,
1367 }
1368 } else if (keyType === "function" && themeStyles.functionKey) {
1369 baseStyle = {
1370 ...baseStyle,
1371 ...themeStyles.functionKey,
1372 }
1373 } else if (keyType === "modifier" && themeStyles.modifierKey) {
1374 baseStyle = {
1375 ...baseStyle,
1376 ...themeStyles.modifierKey,
1377 }
1378 } else if (keyType === "space" && themeStyles.spaceKey) {
1379 baseStyle = {
1380 ...baseStyle,
1381 ...themeStyles.spaceKey,
1382 }
1383 } else if (keyType === "arrow" && themeStyles.arrowKey) {
1384 baseStyle = {
1385 ...baseStyle,
1386 ...themeStyles.arrowKey,
1387 }
1388 }
1389
1390 Object.assign(element.style, baseStyle)
1391 }
1392 }}
1393 >
1394 <div style={{ display: "flex", alignItems: "center", justifyContent: "center" }}>
1395 {renderKeyIcon(key)}
1396 {key.label && <span>{key.label}</span>}
1397 </div>
1398 </div>
1399 )
1400 })}
1401 </div>
1402 {showNavigationCluster && row.nav && row.nav.length > 0 && (
1403 <div
1404 className="nav-cluster"
1405 style={{
1406 display: "flex",
1407 gap: `${keySpacing}px`,
1408 marginLeft: `${Math.max(0, maxMainRowWidth - mainRowWidth + keySpacing * 2)}px`,
1409 }}
1410 >
1411 {row.nav.map((key, keyIndex) => {
1412 if (key.spacer) {
1413 return (
1414 <div
1415 key={`nav-spacer-${rowIndex}-${keyIndex}`}
1416 style={{
1417 width: `${calcKeyWidth(key.size)}px`,
1418 height: `${keyHeight}px`,
1419 background: "transparent",
1420 }}
1421 />
1422 )
1423 }
1424
1425 if (key.type === "light") {
1426 return (
1427 <div
1428 key={`nav-light-${rowIndex}-${keyIndex}`}
1429 style={{
1430 width: "8px",
1431 height: "8px",
1432 borderRadius: "50%",
1433 background: accentColor,
1434 boxShadow: `0 0 8px rgba(${hexToRgb(accentColor)}, 0.7)`,
1435 position: "relative",
1436 marginTop: "4px",
1437 marginLeft: keyIndex === 0 ? "0" : "6px",
1438 }}
1439 />
1440 )
1441 }
1442 const isPressed = pressedKeys.has(key.code || "")
1443 const isActive = isKeyActive(key.code)
1444 return (
1445 <div
1446 key={`nav-key-${rowIndex}-${keyIndex}`}
1447 data-key={key.code}
1448 className={`key ${key.code} ${isActive ? "active" : ""}`}
1449 style={getKeyStyle(key, isPressed, isActive)}
1450 onMouseDown={() => key.code && handleKeyDown(key.code)}
1451 onMouseUp={() => key.code && handleKeyUp(key.code)}
1452 onMouseLeave={() => key.code && pressedKeys.has(key.code) && handleKeyUp(key.code)}
1453 onTouchStart={(e) => {
1454 e.preventDefault()
1455 }}
1456 onTouchEnd={() => key.code && handleKeyUp(key.code)}
1457 onMouseEnter={() => {
1458 const element = document.querySelector(`.key.${key.code}`) as HTMLElement
1459 if (element && !pressedKeys.has(key.code || "") && !isActive) {
1460 Object.assign(element.style, themeStyles.keyHover)
1461 }
1462 }}
1463 onMouseOut={() => {
1464 const element = document.querySelector(`.key.${key.code}`) as HTMLElement
1465 if (element && !pressedKeys.has(key.code || "") && !isActive) {
1466 const keyType = getKeyType(key)
1467 let baseStyle = { ...themeStyles.key }
1468
1469 if (keyType === "special" && themeStyles.specialKey) {
1470 baseStyle = {
1471 ...baseStyle,
1472 ...themeStyles.specialKey,
1473 }
1474 } else if (keyType === "function" && themeStyles.functionKey) {
1475 baseStyle = {
1476 ...baseStyle,
1477 ...themeStyles.functionKey,
1478 }
1479 } else if (keyType === "modifier" && themeStyles.modifierKey) {
1480 baseStyle = {
1481 ...baseStyle,
1482 ...themeStyles.modifierKey,
1483 }
1484 } else if (keyType === "space" && themeStyles.spaceKey) {
1485 baseStyle = {
1486 ...baseStyle,
1487 ...themeStyles.spaceKey,
1488 }
1489 } else if (keyType === "arrow" && themeStyles.arrowKey) {
1490 baseStyle = {
1491 ...baseStyle,
1492 ...themeStyles.arrowKey,
1493 }
1494 }
1495 Object.assign(element.style, baseStyle)
1496 }
1497 }}
1498 >
1499 <div style={{ display: "flex", alignItems: "center", justifyContent: "center" }}>
1500 {renderKeyIcon(key)}
1501 {key.label && <span>{key.label}</span>}
1502 </div>
1503 </div>
1504 )
1505 })}
1506 </div>
1507 )}
1508 </div>
1509 )
1510 })}
1511 </div>
1512 </div>
1513 </div>
1514 )
1515}
1516
1517export default InteractiveKeyboard
1518
3
Final Steps
Update Import Paths
Make sure to update the import paths in the component code to match your project structure. For example, change @/components/ui/button
to match your UI components location.
Props
Name | Type | Default | Description |
---|---|---|---|
layout | string | standard | Keyboard layout (standard or compact) |
showFunctionKeys | boolean | true | Show function keys (F1-F12) row |
showNavigationCluster | boolean | true | Show navigation key cluster |
activeKeys | string[] | [] | Array of key codes to highlight as active |
activeKeyGlowColor | string | #6366f1 | Glow color for active keys |
activeKeyGlowIntensity | number | 0.8 | Intensity of glow effect for active keys (0-1) |
theme | string | cyberpunk | Keyboard theme (cyberpunk, minimal, retro, mechanical, neon, pastel) |
keyColor | string | #2a2a2a | Base color for keyboard keys |
keyTextColor | string | #ffffff | Text color for keyboard keys |
accentColor | string | #6366f1 | Accent color for special keys and highlights |
keyPressedColor | string | #333333 | Color for pressed keys |
keyPressAnimationDuration | number | 150 | Duration of key press animation in milliseconds |
onKeyPress | function | () => {} | Callback function when a key is pressed (code: string, key?: string) => void |
onKeyRelease | function | () => {} | Callback function when a key is released (code: string, key?: string) => void |
className | string | - | Additional CSS classes to apply |
allowPhysicalKeyboard | boolean | true | Allow physical keyboard integration |
perspective | number | 1000 | CSS perspective value for 3D effect |
rotateX | number | 10 | X-axis rotation angle in degrees |