File size: 7,499 Bytes
924b98d
 
 
 
 
 
 
66242a7
 
 
 
a786a62
 
66242a7
 
 
 
 
924b98d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a786a62
 
66242a7
 
 
 
a786a62
 
924b98d
 
 
 
66242a7
a786a62
 
924b98d
66242a7
 
924b98d
66242a7
 
924b98d
 
 
66242a7
924b98d
 
66242a7
924b98d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4b19933
 
 
 
 
924b98d
 
 
 
 
 
 
 
 
4b19933
 
 
 
 
924b98d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a786a62
4b19933
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
/* ═══════════════════════════════════════════════════════
 *  SUNSET RACING β€” base styles
 *  Runtime UI (pause menu, touch buttons, toasts, attract
 *  overlay, countdown) is styled by js/game.js via injected
 *  <style> tags. This file holds the static surfaces only.
 * ═══════════════════════════════════════════════════════ */

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html, body {
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: #000;
  -webkit-font-smoothing: antialiased;
}

/* Subtle cinematic vignette β€” tightens the frame, adds depth
   behind the HUD without affecting the 3D canvas brightness. */
body::after {
  content: '';
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 100;
  background: radial-gradient(
    ellipse at center,
    transparent 55%,
    rgba(0, 0, 0, 0.38) 100%
  );
}

#game {
  display: block;
  width: 100%;
  height: 100%;
}

/* HUD canvases are positioned by js/hud.js via cssText;
   we only hint the browser about scaling. */
#hud-canvas,
#hud-overlay {
  image-rendering: auto;
}

/* Keyboard hint strip at the bottom of the screen. */
#controls {
  position: fixed;
  bottom: 12px;
  left: 50%;
  transform: translateX(-50%);
  font-family: 'Orbitron', sans-serif;
  font-weight: 700;
  font-size: 12px;
  color: #fff;
  opacity: 0.45;
  text-shadow: 0 1px 6px rgba(0, 0, 0, 0.9);
  pointer-events: none;
  letter-spacing: 3px;
  white-space: nowrap;
  text-transform: uppercase;
  z-index: 140;
  transition: opacity 0.3s ease;
}

#controls:hover { opacity: 0.75; }

/* Hide the keyboard hint when running on a touch device β€” the
   on-screen touch buttons serve as the control reference there,
   and the hint string otherwise collides with them. */
@media (hover: none) and (pointer: coarse) {
  #controls { display: none; }
}

/* On narrow viewports the hint wraps messily; shrink it. */
@media (max-width: 720px) {
  #controls {
    font-size: 10px;
    letter-spacing: 2px;
    white-space: normal;
    max-width: 92vw;
    text-align: center;
  }
}

/* Scale the HUD gauge, top overlay, and minimap down on phones so
   they no longer overlap the on-screen steering / pedal / nitro
   touch buttons. The minimap is bottom-right at 180Γ—180 and the
   right-cluster touch buttons live right: 24-212px Γ— bottom: 100-252px,
   so without this rule they collide directly. */
@media (max-width: 640px) {
  #hud-canvas {
    transform: scale(0.68);
    transform-origin: bottom left;
  }
  #hud-overlay {
    transform: scale(0.85);
    transform-origin: top left;
  }
  #minimap {
    transform: scale(0.62);
    transform-origin: bottom right;
    opacity: 0.7;
  }
}

/* ═══════════════════════════════════════════════════════
 *  Loading veil β€” shown while game.js initializes
 *  (three.js modules, scenery generation). Faded out on
 *  the first animation frame by js/game.js.
 * ═══════════════════════════════════════════════════════ */
#loading-veil {
  position: fixed;
  inset: 0;
  z-index: 900;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 28px;
  pointer-events: auto;
  background:
    radial-gradient(ellipse at 50% 120%, #ff6a3d 0%, #8a1e3c 32%, #2a0a2a 62%, #050510 100%);
  font-family: 'Orbitron', sans-serif;
  color: #fff;
  opacity: 1;
  transition: opacity 0.6s ease-out;
}

#loading-veil.hidden {
  opacity: 0;
  pointer-events: none;
}

#loading-veil .lv-title {
  font-weight: 900;
  font-size: clamp(32px, 9vw, 68px);
  letter-spacing: clamp(4px, 1vw, 10px);
  text-shadow:
    0 0 40px rgba(255, 160, 80, 0.8),
    0 4px 24px rgba(0, 0, 0, 0.9);
  text-align: center;
}

#loading-veil .lv-sub {
  font-weight: 700;
  font-size: clamp(11px, 2vw, 14px);
  letter-spacing: 4px;
  color: rgba(255, 255, 255, 0.82);
  text-shadow: 0 2px 8px rgba(0, 0, 0, 0.7);
}

#loading-veil .lv-dots {
  display: flex;
  gap: 10px;
}
#loading-veil .lv-dots span {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 0 12px rgba(255, 255, 255, 0.8);
  animation: lvDot 1.1s ease-in-out infinite;
}
#loading-veil .lv-dots span:nth-child(2) { animation-delay: 0.15s; }
#loading-veil .lv-dots span:nth-child(3) { animation-delay: 0.3s; }

@keyframes lvDot {
  0%, 100% { transform: translateY(0); opacity: 0.35; }
  50%      { transform: translateY(-8px); opacity: 1; }
}

/* ═══════════════════════════════════════════════════════
 *  Gold-toast keyframe for the "NEW BEST LAP!" celebration.
 *  The toast element gets the .toast-gold class applied in
 *  js/game.js, which pulls this animation in.
 * ═══════════════════════════════════════════════════════ */
@keyframes toastGoldIn {
  0%   { transform: translate(-50%, -50%) scale(0.6); opacity: 0; }
  22%  { transform: translate(-50%, -50%) scale(1.18); opacity: 1; }
  40%  { transform: translate(-50%, -50%) scale(1.00); opacity: 1; }
  85%  { opacity: 1; }
  100% { opacity: 0; }
}

/* ═══════════════════════════════════════════════════════
 *  Countdown pop-in β€” applied via inline animation from
 *  js/game.js each time the countdown digit changes so the
 *  "3 β†’ 2 β†’ 1 β†’ GO!" beats actually punch into view instead
 *  of just color-swapping. The transform is compounded with
 *  the existing `translate(-50%, -50%)` set on countdownEl.
 * ═══════════════════════════════════════════════════════ */
@keyframes countdownPop {
  0%   { transform: translate(-50%, -50%) scale(0.35); opacity: 0; filter: blur(6px); }
  35%  { transform: translate(-50%, -50%) scale(1.18); opacity: 1; filter: blur(0); }
  55%  { transform: translate(-50%, -50%) scale(0.96); opacity: 1; }
  100% { transform: translate(-50%, -50%) scale(1.00); opacity: 1; }
}

/* ═══════════════════════════════════════════════════════
 *  Accessibility: honor the user's "reduce motion" OS pref.
 *  We keep state transitions (fades) but strip the bouncy
 *  keyframes and vignette pulse so motion-sensitive players
 *  still get a usable, polished HUD.
 * ═══════════════════════════════════════════════════════ */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.08s !important;
  }
  #loading-veil .lv-dots span {
    animation: none !important;
    opacity: 0.7 !important;
    transform: none !important;
  }
}