Skip to content

Commit 52be2b9

Browse files
author
danil-nizamov
committed
added keyboard
1 parent fc63136 commit 52be2b9

File tree

4 files changed

+273
-4
lines changed

4 files changed

+273
-4
lines changed

client/config.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"keyboard": true
3+
}

client/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ <h2>Completed</h2>
6464
</svg>
6565
</button>
6666
</div>
67+
<div id="keyboard-container" class="keyboard-container"></div>
6768
</div>
6869
</div>
6970
</main>

client/typing-simulator.css

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,84 @@
159159
color: var(--Colors-Text-Body-Medium);
160160
margin: 0;
161161
}
162+
163+
/* Mini Keyboard Styles */
164+
.bespoke .keyboard-container {
165+
display: none;
166+
width: 100%;
167+
max-width: 900px;
168+
margin: var(--UI-Spacing-spacing-xl) auto;
169+
padding: var(--UI-Spacing-spacing-lg);
170+
background: transparent;
171+
}
172+
173+
.bespoke .keyboard-container.visible {
174+
display: block;
175+
}
176+
177+
.bespoke .keyboard {
178+
display: flex;
179+
flex-direction: column;
180+
gap: var(--UI-Spacing-spacing-xs);
181+
font-family: var(--body-family);
182+
font-size: 12px;
183+
user-select: none;
184+
}
185+
186+
.bespoke .keyboard-row {
187+
display: flex;
188+
justify-content: center;
189+
gap: var(--UI-Spacing-spacing-xs);
190+
}
191+
192+
.bespoke .keyboard-key {
193+
display: flex;
194+
align-items: center;
195+
justify-content: center;
196+
min-width: 28px;
197+
height: 32px;
198+
padding: 0 var(--UI-Spacing-spacing-sm);
199+
background: var(--Colors-Box-Background-Secondary);
200+
border: 1px solid var(--Colors-Stroke-Default);
201+
border-radius: var(--UI-Radius-radius-s);
202+
color: var(--Colors-Text-Body-Strongest);
203+
font-weight: 500;
204+
transition: all 0.15s ease;
205+
box-shadow: 0 1px 2px 0 var(--Colors-Shadow-Card);
206+
}
207+
208+
.bespoke .keyboard-key.space {
209+
min-width: 200px;
210+
}
211+
212+
.bespoke .keyboard-key.enter {
213+
min-width: 60px;
214+
}
215+
216+
.bespoke .keyboard-key.shift {
217+
min-width: 70px;
218+
}
219+
220+
.bespoke .keyboard-key.backspace {
221+
min-width: 70px;
222+
}
223+
224+
.bespoke .keyboard-key.tab {
225+
min-width: 50px;
226+
}
227+
228+
.bespoke .keyboard-key.caps {
229+
min-width: 60px;
230+
}
231+
232+
.bespoke .keyboard-key.active {
233+
background: color-mix(in srgb, var(--Colors-Primary-Default) 30%, transparent);
234+
border-color: var(--Colors-Primary-Default);
235+
transform: scale(0.95);
236+
}
237+
238+
.bespoke .keyboard-key.active-error {
239+
background: color-mix(in srgb, var(--Colors-Alert-Error-Default) 30%, transparent);
240+
border-color: var(--Colors-Alert-Error-Default);
241+
transform: scale(0.95);
242+
}

client/typing-simulator.js

Lines changed: 188 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
let completionScreen = null;
88
let restartButton = null;
99
let startOverButton = null;
10+
let keyboardContainer = null;
11+
let config = { keyboard: true };
1012

1113
// Character states: 'pending', 'correct', 'incorrect'
1214
const charStates = [];
@@ -16,13 +18,158 @@
1618
let totalErrors = 0;
1719
let totalInputs = 0;
1820

21+
// Keyboard state
22+
let keyboardEnabled = false;
23+
let activeKeyElement = null;
24+
let activeKeyTimeout = null;
25+
1926
function setStatus(msg) {
2027
const status = document.getElementById('status');
2128
if (status) {
2229
status.textContent = msg;
2330
}
2431
}
2532

33+
// Load configuration
34+
async function loadConfig() {
35+
try {
36+
const response = await fetch('./config.json');
37+
if (!response.ok) {
38+
console.warn('Config file not found, using defaults');
39+
return;
40+
}
41+
config = await response.json();
42+
} catch (error) {
43+
console.warn('Error loading config:', error);
44+
}
45+
}
46+
47+
// Keyboard layout definition
48+
const keyboardLayout = [
49+
['`', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', 'backspace'],
50+
['tab', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\\'],
51+
['caps', 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', "'", 'enter'],
52+
['shift', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', 'shift'],
53+
['space']
54+
];
55+
56+
// Map special keys to display names
57+
const keyDisplayNames = {
58+
'backspace': '⌫',
59+
'tab': 'Tab',
60+
'caps': 'Caps',
61+
'enter': 'Enter',
62+
'shift': 'Shift',
63+
'space': 'Space'
64+
};
65+
66+
// Get key element by character
67+
function getKeyElement(char) {
68+
if (!keyboardContainer) return null;
69+
70+
// Normalize character
71+
const normalizedChar = char.toLowerCase();
72+
73+
// Handle special keys
74+
if (char === ' ') {
75+
return keyboardContainer.querySelector('[data-key="space"]');
76+
}
77+
if (char === '\n' || char === '\r') {
78+
return keyboardContainer.querySelector('[data-key="enter"]');
79+
}
80+
if (char === '\t') {
81+
return keyboardContainer.querySelector('[data-key="tab"]');
82+
}
83+
84+
// Find regular key
85+
return keyboardContainer.querySelector(`[data-key="${normalizedChar}"]`);
86+
}
87+
88+
// Highlight a key on the keyboard
89+
function highlightKey(char, isError = false) {
90+
// Clear previous highlight
91+
if (activeKeyElement) {
92+
activeKeyElement.classList.remove('active', 'active-error');
93+
}
94+
95+
// Clear timeout if exists
96+
if (activeKeyTimeout) {
97+
clearTimeout(activeKeyTimeout);
98+
}
99+
100+
const keyElement = getKeyElement(char);
101+
if (keyElement) {
102+
activeKeyElement = keyElement;
103+
if (isError) {
104+
keyElement.classList.add('active-error');
105+
} else {
106+
keyElement.classList.add('active');
107+
}
108+
109+
// Remove highlight after animation
110+
activeKeyTimeout = setTimeout(() => {
111+
if (keyElement) {
112+
keyElement.classList.remove('active', 'active-error');
113+
}
114+
activeKeyElement = null;
115+
}, 200);
116+
}
117+
}
118+
119+
// Render the keyboard
120+
function renderKeyboard() {
121+
if (!keyboardContainer) return;
122+
123+
const keyboard = document.createElement('div');
124+
keyboard.className = 'keyboard';
125+
126+
keyboardLayout.forEach(row => {
127+
const rowElement = document.createElement('div');
128+
rowElement.className = 'keyboard-row';
129+
130+
row.forEach(key => {
131+
const keyElement = document.createElement('div');
132+
keyElement.className = 'keyboard-key';
133+
keyElement.setAttribute('data-key', key.toLowerCase());
134+
135+
// Add special class for certain keys
136+
if (key === 'space' || key === 'enter' || key === 'shift' ||
137+
key === 'backspace' || key === 'tab' || key === 'caps') {
138+
keyElement.classList.add(key);
139+
}
140+
141+
// Set display text
142+
if (keyDisplayNames[key]) {
143+
keyElement.textContent = keyDisplayNames[key];
144+
} else {
145+
keyElement.textContent = key.toUpperCase();
146+
}
147+
148+
rowElement.appendChild(keyElement);
149+
});
150+
151+
keyboard.appendChild(rowElement);
152+
});
153+
154+
keyboardContainer.innerHTML = '';
155+
keyboardContainer.appendChild(keyboard);
156+
}
157+
158+
// Initialize keyboard
159+
function initializeKeyboard() {
160+
keyboardContainer = document.getElementById('keyboard-container');
161+
if (!keyboardContainer) return;
162+
163+
keyboardEnabled = config.keyboard === true;
164+
165+
if (keyboardEnabled) {
166+
renderKeyboard();
167+
keyboardContainer.classList.add('visible');
168+
} else {
169+
keyboardContainer.classList.remove('visible');
170+
}
171+
}
172+
26173
async function loadText() {
27174
try {
28175
setStatus('Loading...');
@@ -159,11 +306,17 @@
159306

160307
totalInputs++; // Track total inputs
161308

162-
if (typedChar === expectedChar) {
163-
charStates[charIndex] = 'correct';
164-
} else {
309+
const isError = typedChar !== expectedChar;
310+
if (isError) {
165311
charStates[charIndex] = 'incorrect';
166312
totalErrors++; // Track total errors (even if later fixed)
313+
} else {
314+
charStates[charIndex] = 'correct';
315+
}
316+
317+
// Highlight keyboard key
318+
if (keyboardEnabled) {
319+
highlightKey(typedChar, isError);
167320
}
168321
}
169322
typedText = input;
@@ -177,6 +330,11 @@
177330
charStates[i] = 'pending';
178331
}
179332
}
333+
334+
// Highlight backspace key
335+
if (keyboardEnabled) {
336+
highlightKey('backspace', false);
337+
}
180338
}
181339

182340
renderText();
@@ -188,6 +346,16 @@
188346
e.preventDefault();
189347
}
190348

349+
// Highlight special keys that might not trigger input event
350+
if (keyboardEnabled) {
351+
if (e.key === 'Enter' || e.key === 'Return') {
352+
highlightKey('\n', false);
353+
} else if (e.key === 'Tab') {
354+
highlightKey('\t', false);
355+
e.preventDefault(); // Prevent tab from moving focus
356+
}
357+
}
358+
191359
// Allow all other keys to work normally
192360
// The input handler will process the changes
193361
}
@@ -206,6 +374,16 @@
206374
totalErrors = 0;
207375
totalInputs = 0;
208376

377+
// Clear keyboard highlights
378+
if (activeKeyElement) {
379+
activeKeyElement.classList.remove('active', 'active-error');
380+
activeKeyElement = null;
381+
}
382+
if (activeKeyTimeout) {
383+
clearTimeout(activeKeyTimeout);
384+
activeKeyTimeout = null;
385+
}
386+
209387
// Show typing container and hide completion screen
210388
const typingTextContainer = document.querySelector('.typing-text-container');
211389
if (typingTextContainer) {
@@ -350,7 +528,10 @@ Generated: ${new Date().toLocaleString()}
350528
}
351529
}
352530

353-
function initialize() {
531+
async function initialize() {
532+
// Load config first
533+
await loadConfig();
534+
354535
textContainer = document.getElementById('typing-text');
355536
hiddenInput = document.getElementById('hidden-input');
356537
completionScreen = document.getElementById('completion-screen');
@@ -362,6 +543,9 @@ Generated: ${new Date().toLocaleString()}
362543
return;
363544
}
364545

546+
// Initialize keyboard
547+
initializeKeyboard();
548+
365549
// Set up event listeners
366550
hiddenInput.addEventListener('input', handleInput);
367551
hiddenInput.addEventListener('keydown', handleKeyDown);

0 commit comments

Comments
 (0)