Skip to content

Commit 134364e

Browse files
Merge pull request #36 from SapienzaInteractiveGraphicsCourse/develop
Develop
2 parents 0510700 + 933f8bf commit 134364e

2 files changed

Lines changed: 276 additions & 0 deletions

File tree

src/styles/setup-menu.css

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,44 @@
2424
pointer-events: none;
2525
}
2626

27+
.setup-commands-button {
28+
position: absolute;
29+
top: clamp(14px, 2.2vh, 24px);
30+
right: clamp(14px, 3vw, 34px);
31+
z-index: 3;
32+
min-height: 38px;
33+
padding: 0 14px;
34+
border: 1px solid color-mix(in srgb, var(--setup-accent, #38bdf8) 56%, transparent);
35+
border-radius: 7px;
36+
color: #f8fafc;
37+
background:
38+
linear-gradient(135deg, color-mix(in srgb, var(--setup-accent, #38bdf8) 22%, transparent), transparent 58%),
39+
color-mix(in srgb, var(--setup-dark, #071923) 74%, rgba(15, 23, 42, 0.92));
40+
box-shadow:
41+
inset 0 1px 0 rgba(255, 255, 255, 0.16),
42+
0 12px 28px rgba(0, 0, 0, 0.26),
43+
0 0 20px color-mix(in srgb, var(--setup-accent, #38bdf8) 18%, transparent);
44+
font-family: "Arial Black", Impact, system-ui, sans-serif;
45+
font-size: 13px;
46+
letter-spacing: 0.02em;
47+
cursor: pointer;
48+
transition:
49+
border-color 160ms ease,
50+
box-shadow 160ms ease,
51+
color 160ms ease,
52+
transform 160ms ease;
53+
}
54+
55+
.setup-commands-button:hover {
56+
color: color-mix(in srgb, var(--setup-second, #facc15) 86%, white);
57+
border-color: color-mix(in srgb, var(--setup-second, #facc15) 72%, white);
58+
box-shadow:
59+
inset 0 1px 0 rgba(255, 255, 255, 0.2),
60+
0 14px 32px rgba(0, 0, 0, 0.3),
61+
0 0 22px color-mix(in srgb, var(--setup-accent, #38bdf8) 32%, transparent);
62+
transform: translateY(-1px) scale(1.04);
63+
}
64+
2765
.setup-menu[hidden] {
2866
display: none;
2967
}
@@ -487,14 +525,163 @@
487525
box-shadow: none;
488526
}
489527

528+
.setup-commands-modal {
529+
position: fixed;
530+
inset: 0;
531+
z-index: 8;
532+
display: grid;
533+
place-items: center;
534+
padding: 22px;
535+
background:
536+
radial-gradient(circle at 50% 40%, color-mix(in srgb, var(--setup-accent, #38bdf8) 14%, transparent), transparent 38%),
537+
rgba(2, 6, 23, 0.72);
538+
backdrop-filter: blur(6px);
539+
}
540+
541+
.setup-commands-modal[hidden] {
542+
display: none;
543+
}
544+
545+
.setup-commands-panel {
546+
width: min(560px, 100%);
547+
padding: 18px;
548+
border: 1px solid color-mix(in srgb, var(--setup-accent, #38bdf8) 58%, transparent);
549+
border-radius: 8px;
550+
color: #f8fafc;
551+
background:
552+
linear-gradient(135deg, color-mix(in srgb, var(--setup-accent, #38bdf8) 18%, transparent), transparent 54%),
553+
linear-gradient(180deg, rgba(255, 255, 255, 0.1), transparent),
554+
color-mix(in srgb, var(--setup-dark, #071923) 82%, #020617);
555+
box-shadow:
556+
inset 0 1px 0 rgba(255, 255, 255, 0.16),
557+
0 28px 76px rgba(0, 0, 0, 0.52),
558+
0 0 36px color-mix(in srgb, var(--setup-accent, #38bdf8) 24%, transparent);
559+
}
560+
561+
.setup-commands-header {
562+
display: flex;
563+
gap: 14px;
564+
align-items: start;
565+
justify-content: space-between;
566+
margin-bottom: 14px;
567+
}
568+
569+
.setup-commands-header span {
570+
color: color-mix(in srgb, var(--setup-accent, #38bdf8) 76%, white);
571+
font-size: 11px;
572+
font-weight: 900;
573+
text-transform: uppercase;
574+
}
575+
576+
.setup-commands-header h2 {
577+
margin: 2px 0 0;
578+
font-family: "Arial Black", Impact, system-ui, sans-serif;
579+
font-size: 32px;
580+
font-style: italic;
581+
line-height: 1;
582+
text-shadow:
583+
0 4px 0 rgba(0, 0, 0, 0.7),
584+
0 0 18px color-mix(in srgb, var(--setup-accent, #38bdf8) 38%, transparent);
585+
}
586+
587+
.setup-commands-close {
588+
min-height: 34px;
589+
padding: 0 12px;
590+
border: 1px solid color-mix(in srgb, var(--setup-second, #facc15) 54%, transparent);
591+
border-radius: 6px;
592+
color: color-mix(in srgb, var(--setup-second, #facc15) 82%, white);
593+
background: rgba(15, 23, 42, 0.58);
594+
font-weight: 900;
595+
cursor: pointer;
596+
transition: border-color 160ms ease, background 160ms ease, transform 160ms ease;
597+
}
598+
599+
.setup-commands-close:hover {
600+
border-color: color-mix(in srgb, var(--setup-second, #facc15) 82%, white);
601+
background: color-mix(in srgb, var(--setup-accent, #38bdf8) 14%, rgba(15, 23, 42, 0.72));
602+
transform: translateY(-1px);
603+
}
604+
605+
.setup-commands-grid {
606+
display: grid;
607+
grid-template-columns: repeat(2, minmax(0, 1fr));
608+
gap: 8px;
609+
}
610+
611+
.setup-command-row {
612+
display: grid;
613+
grid-template-columns: minmax(122px, auto) minmax(0, 1fr);
614+
gap: 10px;
615+
align-items: center;
616+
min-height: 42px;
617+
padding: 8px 10px;
618+
border: 1px solid rgba(148, 163, 184, 0.22);
619+
border-radius: 7px;
620+
background: rgba(15, 23, 42, 0.5);
621+
}
622+
623+
.setup-command-row kbd {
624+
display: inline-flex;
625+
align-items: center;
626+
justify-content: center;
627+
min-height: 25px;
628+
padding: 0 8px;
629+
border: 1px solid color-mix(in srgb, var(--setup-accent, #38bdf8) 44%, transparent);
630+
border-radius: 5px;
631+
color: color-mix(in srgb, var(--setup-second, #facc15) 82%, white);
632+
background:
633+
linear-gradient(180deg, rgba(255, 255, 255, 0.12), transparent),
634+
rgba(2, 6, 23, 0.54);
635+
font-family: "Arial Black", Impact, system-ui, sans-serif;
636+
font-size: 11px;
637+
font-weight: 900;
638+
line-height: 1;
639+
text-align: center;
640+
}
641+
642+
.setup-command-row span {
643+
color: #dbeafe;
644+
font-size: 13px;
645+
font-weight: 800;
646+
line-height: 1.2;
647+
}
648+
490649
.setup-option-card:focus-visible,
491650
.setup-carousel-arrow:focus-visible,
492651
.setup-progress-item:focus-visible,
493652
.setup-nav-button:focus-visible,
653+
.setup-commands-button:focus-visible,
654+
.setup-commands-close:focus-visible,
494655
.pre-race-color-option:focus-visible,
495656
.pre-race-start-button:focus-visible,
496657
.pause-button:focus-visible,
497658
.finish-button:focus-visible {
498659
outline: 2px solid #facc15;
499660
outline-offset: 2px;
500661
}
662+
663+
@media (max-width: 720px) {
664+
.setup-commands-button {
665+
top: 10px;
666+
right: 10px;
667+
min-height: 34px;
668+
padding: 0 10px;
669+
font-size: 11px;
670+
}
671+
672+
.setup-commands-panel {
673+
padding: 14px;
674+
}
675+
676+
.setup-commands-header h2 {
677+
font-size: 26px;
678+
}
679+
680+
.setup-commands-grid {
681+
grid-template-columns: 1fr;
682+
}
683+
684+
.setup-command-row {
685+
grid-template-columns: minmax(108px, auto) minmax(0, 1fr);
686+
}
687+
}

src/ui/setupMenu.js

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,13 @@ export function createSetupMenu({ onStart }) {
6161
menu.className = "setup-menu";
6262
menu.setAttribute("aria-label", "Race setup");
6363

64+
const commandsButton = document.createElement("button");
65+
commandsButton.className = "setup-commands-button";
66+
commandsButton.type = "button";
67+
commandsButton.textContent = "Commands";
68+
69+
const commandsDialog = createCommandsDialog();
70+
6471
const logo = document.createElement("img");
6572
logo.className = "setup-logo";
6673
logo.src = logoUrl;
@@ -96,15 +103,18 @@ export function createSetupMenu({ onStart }) {
96103

97104
nav.append(backButton, nextButton);
98105
panel.append(header, progress, stage, nav);
106+
menu.appendChild(commandsButton);
99107
menu.appendChild(logo);
100108
menu.appendChild(panel);
109+
menu.appendChild(commandsDialog);
101110

102111
function render() {
103112
const step = SETUP_STEPS[currentStepIndex];
104113
const selectedId = setup[step.key];
105114
const selectedOption = step.options.find((option) => option.id === selectedId) ?? step.options[0];
106115

107116
menu.dataset.setupStep = step.key;
117+
applyOptionTheme(menu, selectedOption.id);
108118
applyOptionTheme(panel, selectedOption.id);
109119
renderProgress(progress, setup, currentStepIndex);
110120
renderStage(stage, step, selectedId);
@@ -170,6 +180,25 @@ export function createSetupMenu({ onStart }) {
170180
onStart?.({ ...setup });
171181
});
172182

183+
commandsButton.addEventListener("click", () => {
184+
menuAudio.playUiConfirm();
185+
commandsDialog.hidden = false;
186+
});
187+
188+
commandsDialog.addEventListener("click", (event) => {
189+
const closeButton = event.target.closest("[data-commands-close]");
190+
const clickedBackdrop = event.target === commandsDialog;
191+
192+
if (!closeButton && !clickedBackdrop) {
193+
return;
194+
}
195+
196+
menuAudio.playUiSelect();
197+
closeCommandsDialog();
198+
});
199+
200+
window.addEventListener("keydown", handleCommandsKeydown);
201+
173202
function moveSelection(direction) {
174203
const step = SETUP_STEPS[currentStepIndex];
175204
const selectedIndex = step.options.findIndex((option) => option.id === setup[step.key]);
@@ -179,6 +208,19 @@ export function createSetupMenu({ onStart }) {
179208
render();
180209
}
181210

211+
function closeCommandsDialog() {
212+
commandsDialog.hidden = true;
213+
}
214+
215+
function handleCommandsKeydown(event) {
216+
if (event.key !== "Escape" || commandsDialog.hidden || menu.hidden) {
217+
return;
218+
}
219+
220+
event.preventDefault();
221+
closeCommandsDialog();
222+
}
223+
182224
render();
183225

184226
return {
@@ -192,11 +234,58 @@ export function createSetupMenu({ onStart }) {
192234
menu.hidden = true;
193235
},
194236
dispose: () => {
237+
window.removeEventListener("keydown", handleCommandsKeydown);
195238
menuAudio.dispose();
196239
}
197240
};
198241
}
199242

243+
function createCommandsDialog() {
244+
const dialog = document.createElement("div");
245+
dialog.className = "setup-commands-modal";
246+
dialog.hidden = true;
247+
dialog.setAttribute("role", "dialog");
248+
dialog.setAttribute("aria-modal", "true");
249+
dialog.setAttribute("aria-labelledby", "setup-commands-title");
250+
dialog.dataset.commandsClose = "backdrop";
251+
252+
dialog.innerHTML = `
253+
<div class="setup-commands-panel">
254+
<header class="setup-commands-header">
255+
<div>
256+
<span>Driving guide</span>
257+
<h2 id="setup-commands-title">Commands</h2>
258+
</div>
259+
<button class="setup-commands-close" type="button" data-commands-close="button" aria-label="Close commands">Close</button>
260+
</header>
261+
<div class="setup-commands-grid">
262+
${renderCommandRow("W / Arrow Up", "Accelerate")}
263+
${renderCommandRow("A / Arrow Left", "Turn left")}
264+
${renderCommandRow("S / Arrow Down", "Brake / Reverse")}
265+
${renderCommandRow("D / Arrow Right", "Turn right")}
266+
${renderCommandRow("Space", "Handbrake")}
267+
${renderCommandRow("C", "Change camera")}
268+
${renderCommandRow("L", "Toggle lights")}
269+
${renderCommandRow("R", "Restart race")}
270+
${renderCommandRow("Esc", "Pause menu")}
271+
${renderCommandRow("F1", "Toggle minimap")}
272+
${renderCommandRow("F2 / F3 / F4", "Debug views")}
273+
</div>
274+
</div>
275+
`;
276+
277+
return dialog;
278+
}
279+
280+
function renderCommandRow(keys, label) {
281+
return `
282+
<div class="setup-command-row">
283+
<kbd>${keys}</kbd>
284+
<span>${label}</span>
285+
</div>
286+
`;
287+
}
288+
200289
function renderProgress(container, setup, currentStepIndex) {
201290
const labels = getRaceSetupLabels(setup);
202291

0 commit comments

Comments
 (0)