1
0
Fork 0

Dark mode for contrast and more bug squashing

This commit is contained in:
Atridad Lahiji 2025-03-25 00:43:08 -06:00
parent fe0cc3482e
commit 84eef1beb2
Signed by: atridad
SSH key fingerprint: SHA256:LGomp8Opq0jz+7kbwNcdfTcuaLRb5Nh0k5AchDDb438
7 changed files with 244 additions and 136 deletions

View file

@ -2,7 +2,7 @@
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
overflow: hidden; overflow: hidden;
background-color: white; background-color: #000000;
position: relative; position: relative;
} }
@ -11,14 +11,14 @@
top: 20px; top: 20px;
left: 20px; left: 20px;
transform: none; transform: none;
color: #213547; color: #ffffff;
background-color: white; background-color: rgba(40, 40, 40, 0.8);
padding: 12px 20px; padding: 12px 20px;
border-radius: 8px; border-radius: 8px;
font-family: Inter, system-ui, Arial, sans-serif; font-family: Inter, system-ui, Arial, sans-serif;
font-size: 14px; font-size: 14px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
border: 1px solid #ddd; border: 1px solid #444;
z-index: 100; z-index: 100;
display: flex; display: flex;
gap: 12px; gap: 12px;
@ -43,27 +43,27 @@
width: calc(100vw / 0.01); /* Width based on minimum zoom (0.01) */ width: calc(100vw / 0.01); /* Width based on minimum zoom (0.01) */
height: calc((100vh - 100px) / 0.01); /* Height based on minimum zoom minus UI height */ height: calc((100vh - 100px) / 0.01); /* Height based on minimum zoom minus UI height */
transform-origin: 0 0; transform-origin: 0 0;
background-color: white; background-color: #000000;
will-change: transform; will-change: transform;
contain: layout paint; contain: layout paint;
} }
.target { .target {
position: absolute; position: absolute;
background-color: #444444; /* Darker gray for better visibility */ background-color: #666666; /* Light gray for better contrast on black */
border-radius: 50%; border-radius: 50%;
transition: background-color 0.2s ease; transition: background-color 0.2s ease;
box-shadow: 0 0 15px rgba(0, 0, 0, 0.3); box-shadow: 0 0 15px rgba(100, 100, 100, 0.4);
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
will-change: transform; will-change: transform;
contain: layout; contain: layout;
border: 4px solid rgba(0, 0, 0, 0.4); /* Darker border for better contrast */ border: 4px solid rgba(180, 180, 180, 0.6); /* Lighter border for contrast */
} }
.target.active { .target.active {
background-color: #22aa22; /* Darker green for better visibility */ background-color: #22cc22; /* Bright green for better visibility */
box-shadow: 0 0 30px rgba(34, 170, 34, 0.8); box-shadow: 0 0 30px rgba(34, 204, 34, 0.8);
border: 4px solid white; border: 4px solid #ffffff;
z-index: 10; z-index: 10;
} }
@ -75,7 +75,7 @@ body,
width: 100%; width: 100%;
height: 100%; height: 100%;
overflow: hidden; overflow: hidden;
background-color: white; background-color: #000000;
} }
.controls { .controls {
@ -94,13 +94,14 @@ body,
display: flex; display: flex;
align-items: center; align-items: center;
gap: 6px; gap: 6px;
background: white; background: rgba(40, 40, 40, 0.8);
border: 1px solid #ddd; border: 1px solid #444;
padding: 8px 16px; padding: 8px 16px;
border-radius: 8px; border-radius: 8px;
cursor: pointer; cursor: pointer;
font-size: 14px; font-size: 14px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); color: #ffffff;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
transition: all 0.2s ease; transition: all 0.2s ease;
} }
@ -108,7 +109,7 @@ body,
.rounds-toggle:hover, .rounds-toggle:hover,
.export-button:hover, .export-button:hover,
.reset-button:hover { .reset-button:hover {
background: #f5f5f5; background: #444444;
border-color: #646cff; border-color: #646cff;
} }
@ -118,17 +119,17 @@ body,
} }
.export-button:disabled:hover { .export-button:disabled:hover {
background: white; background: rgba(40, 40, 40, 0.8);
border-color: #ddd; border-color: #444;
} }
.reset-button { .reset-button {
border-color: #ffcdd2; border-color: #aa3333;
} }
.reset-button:hover { .reset-button:hover {
background: #fff5f5; background: #442222;
border-color: #ef5350; border-color: #ff4444;
} }
/* Preset Selection */ /* Preset Selection */
@ -144,8 +145,9 @@ body,
.preset-button { .preset-button {
padding: 8px 16px; padding: 8px 16px;
background: white; background: #333333;
border: 1px solid #ddd; color: #ffffff;
border: 1px solid #555;
border-radius: 8px; border-radius: 8px;
cursor: pointer; cursor: pointer;
transition: all 0.2s ease; transition: all 0.2s ease;
@ -154,32 +156,14 @@ body,
.preset-button.active { .preset-button.active {
background: #646cff; background: #646cff;
color: white; color: white;
border-color: #646cff; border-color: #8088ff;
} }
.preset-button:hover:not(.active) { .preset-button:hover:not(.active) {
background: #f5f5f5; background: #444444;
border-color: #646cff; border-color: #646cff;
} }
/* Mode Selection */
.mode-selection {
margin-bottom: 20px;
}
.radio-group {
display: flex;
flex-direction: column;
gap: 10px;
}
.radio-group label {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
}
/* Break Screen */ /* Break Screen */
.break-screen { .break-screen {
position: fixed; position: fixed;
@ -187,7 +171,7 @@ body,
left: 0; left: 0;
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
background-color: rgba(0, 0, 0, 0.5); background-color: rgba(0, 0, 0, 0.7);
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@ -195,17 +179,18 @@ body,
} }
.break-container { .break-container {
background-color: white; background-color: #222222;
color: #ffffff;
padding: 30px; padding: 30px;
border-radius: 10px; border-radius: 10px;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.2); box-shadow: 0 5px 20px rgba(0, 0, 0, 0.5);
max-width: 500px; max-width: 500px;
text-align: center; text-align: center;
} }
.break-container h2 { .break-container h2 {
margin-top: 0; margin-top: 0;
color: #213547; color: #ffffff;
} }
.break-container button { .break-container button {
@ -221,7 +206,7 @@ body,
} }
.break-container button:hover { .break-container button:hover {
background-color: #4e57cc; background-color: #7a82ff;
} }
/* Resize Warning Popup */ /* Resize Warning Popup */
@ -231,7 +216,7 @@ body,
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
z-index: 2000; z-index: 2000;
background-color: rgba(0, 0, 0, 0.5); background-color: rgba(0, 0, 0, 0.7);
width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex; display: flex;
@ -241,17 +226,18 @@ body,
} }
.resize-warning-content { .resize-warning-content {
background-color: white; background-color: #222222;
color: #ffffff;
padding: 20px 30px; padding: 20px 30px;
border-radius: 10px; border-radius: 10px;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.2); box-shadow: 0 5px 20px rgba(0, 0, 0, 0.5);
max-width: 400px; max-width: 400px;
text-align: center; text-align: center;
} }
.resize-warning-content h3 { .resize-warning-content h3 {
margin-top: 0; margin-top: 0;
color: #213547; color: #ffffff;
} }
.resize-warning-content button { .resize-warning-content button {
@ -267,7 +253,7 @@ body,
} }
.resize-warning-content button:hover { .resize-warning-content button:hover {
background-color: #4e57cc; background-color: #7a82ff;
} }
@keyframes fadeIn { @keyframes fadeIn {
@ -292,26 +278,26 @@ body,
padding: 20px; padding: 20px;
border-radius: 12px; border-radius: 12px;
color: white; color: white;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); box-shadow: 0 4px 15px rgba(0, 0, 0, 0.5);
text-align: center; text-align: center;
transition: background-color 0.3s ease; transition: background-color 0.3s ease;
} }
.preset-indicator.preset-A { .preset-indicator.preset-A {
background-color: #4285f4; /* Blue for Preset A */ background-color: rgba(66, 133, 244, 0.9); /* Blue for Preset A */
} }
.preset-indicator.preset-B { .preset-indicator.preset-B {
background-color: #34a853; /* Green for Preset B */ background-color: rgba(52, 168, 83, 0.9); /* Green for Preset B */
} }
.preset-indicator.preset-C { .preset-indicator.preset-C {
background-color: #ea4335; /* Red for Preset C */ background-color: rgba(234, 67, 53, 0.9); /* Red for Preset C */
} }
.preset-indicator.preset-D { .preset-indicator.preset-D {
background-color: #fbbc05; /* Yellow for Preset D */ background-color: rgba(251, 188, 5, 0.9); /* Yellow for Preset D */
} }
/* Preset Transition Effect */ /* Preset Transition Effect */
@ -334,7 +320,7 @@ body,
font-size: 36px; font-size: 36px;
font-weight: bold; font-weight: bold;
color: white; color: white;
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.5); text-shadow: 0 2px 8px rgba(0, 0, 0, 0.8);
padding: 30px 60px; padding: 30px 60px;
border-radius: 16px; border-radius: 16px;
text-align: center; text-align: center;
@ -342,19 +328,19 @@ body,
} }
.preset-transition-overlay.preset-A .preset-transition-content { .preset-transition-overlay.preset-A .preset-transition-content {
background-color: rgba(66, 133, 244, 0.85); /* Blue for Preset A */ background-color: rgba(66, 133, 244, 0.9); /* Blue for Preset A */
} }
.preset-transition-overlay.preset-B .preset-transition-content { .preset-transition-overlay.preset-B .preset-transition-content {
background-color: rgba(52, 168, 83, 0.85); /* Green for Preset B */ background-color: rgba(52, 168, 83, 0.9); /* Green for Preset B */
} }
.preset-transition-overlay.preset-C .preset-transition-content { .preset-transition-overlay.preset-C .preset-transition-content {
background-color: rgba(234, 67, 53, 0.85); /* Red for Preset C */ background-color: rgba(234, 67, 53, 0.9); /* Red for Preset C */
} }
.preset-transition-overlay.preset-D .preset-transition-content { .preset-transition-overlay.preset-D .preset-transition-content {
background-color: rgba(251, 188, 5, 0.85); /* Yellow for Preset D */ background-color: rgba(251, 188, 5, 0.9); /* Yellow for Preset D */
} }
@keyframes fadeInOut { @keyframes fadeInOut {

View file

@ -4,7 +4,7 @@
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
background: rgba(0, 0, 0, 0.5); background: rgba(0, 0, 0, 0.7);
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@ -12,15 +12,16 @@
} }
.instructions-panel { .instructions-panel {
background: white; background: #222222;
border-radius: 12px; border-radius: 12px;
padding: 30px; padding: 30px;
max-width: 600px; max-width: 600px;
width: 90%; width: 90%;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
position: relative; position: relative;
max-height: 90vh; max-height: 90vh;
overflow-y: auto; overflow-y: auto;
color: #ffffff;
} }
.instructions-header { .instructions-header {
@ -33,18 +34,18 @@
.instructions-header h2 { .instructions-header h2 {
margin: 0; margin: 0;
font-size: 1.5em; font-size: 1.5em;
color: #213547; color: #ffffff;
} }
.instructions-content { .instructions-content {
color: #4a5568; color: #cccccc;
line-height: 1.6; line-height: 1.6;
} }
.instructions-content h3 { .instructions-content h3 {
margin-top: 25px; margin-top: 25px;
margin-bottom: 15px; margin-bottom: 15px;
color: #213547; color: #ffffff;
} }
.instructions-content ul { .instructions-content ul {
@ -81,20 +82,21 @@
.keyboard-shortcut { .keyboard-shortcut {
display: inline-block; display: inline-block;
background: #f1f5f9; background: #333333;
padding: 2px 6px; padding: 2px 6px;
border-radius: 4px; border-radius: 4px;
font-family: monospace; font-family: monospace;
margin: 0 2px; margin: 0 2px;
color: #ffffff;
} }
/* Mode Selection Styles */ /* Mode Selection Styles */
.mode-selection { .mode-selection {
background: #f8fafc; background: #333333;
padding: 20px; padding: 20px;
border-radius: 8px; border-radius: 8px;
margin: 20px 0; margin: 20px 0;
border: 1px solid #e2e8f0; border: 1px solid #444444;
} }
.mode-selection .radio-group { .mode-selection .radio-group {
@ -107,9 +109,9 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 15px; padding: 15px;
border: 1px solid #e2e8f0; border: 1px solid #444444;
border-radius: 8px; border-radius: 8px;
background: white; background: #222222;
cursor: pointer; cursor: pointer;
transition: all 0.2s; transition: all 0.2s;
position: relative; position: relative;
@ -117,25 +119,26 @@
.mode-selection label:hover { .mode-selection label:hover {
border-color: #646cff; border-color: #646cff;
box-shadow: 0 2px 8px rgba(100, 108, 255, 0.1); box-shadow: 0 2px 8px rgba(100, 108, 255, 0.3);
} }
.mode-selection input[type="radio"] { .mode-selection input[type="radio"] {
position: absolute; position: absolute;
top: 15px; top: 15px;
left: 15px; left: 15px;
accent-color: #646cff;
} }
.mode-label { .mode-label {
font-weight: bold; font-weight: bold;
color: #213547; color: #ffffff;
margin-left: 25px; margin-left: 25px;
font-size: 16px; font-size: 16px;
} }
.mode-description { .mode-description {
margin: 8px 0 0 25px; margin: 8px 0 0 25px;
color: #64748b; color: #cccccc;
font-size: 14px; font-size: 14px;
} }
@ -144,11 +147,11 @@
} }
.mode-selection input[type="radio"]:checked ~ .mode-description { .mode-selection input[type="radio"]:checked ~ .mode-description {
color: #4a5568; color: #dddddd;
} }
.warning-message { .warning-message {
background-color: #fff8e1; background-color: #332211;
border-left: 4px solid #ffb300; border-left: 4px solid #ffb300;
padding: 15px; padding: 15px;
border-radius: 4px; border-radius: 4px;
@ -156,7 +159,7 @@
} }
.warning-message h3 { .warning-message h3 {
color: #e65100; color: #ffb300;
margin-top: 0; margin-top: 0;
margin-bottom: 10px; margin-bottom: 10px;
display: flex; display: flex;
@ -165,5 +168,5 @@
.warning-message p { .warning-message p {
margin: 0; margin: 0;
color: #333; color: #dddddd;
} }

View file

@ -2,16 +2,16 @@
position: fixed; position: fixed;
top: 80px; top: 80px;
left: 20px; left: 20px;
background: white; background: #222222;
border: 1px solid #ddd; border: 1px solid #444;
border-radius: 8px; border-radius: 8px;
padding: 20px; padding: 20px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
z-index: 1000; z-index: 1000;
width: 300px; width: 300px;
max-height: calc(100vh - 120px); max-height: calc(100vh - 120px);
overflow-y: auto; overflow-y: auto;
color: #213547; color: #ffffff;
} }
.rounds-header { .rounds-header {
@ -21,16 +21,16 @@
margin-bottom: 20px; margin-bottom: 20px;
position: sticky; position: sticky;
top: 0; top: 0;
background: white; background: #222222;
padding-bottom: 10px; padding-bottom: 10px;
border-bottom: 1px solid #eee; border-bottom: 1px solid #444;
} }
.rounds-header h2 { .rounds-header h2 {
margin: 0; margin: 0;
font-size: 1.2em; font-size: 1.2em;
font-weight: 600; font-weight: 600;
color: #213547; color: #ffffff;
} }
.rounds-header button { .rounds-header button {
@ -40,6 +40,7 @@
cursor: pointer; cursor: pointer;
padding: 0 8px; padding: 0 8px;
box-shadow: none; box-shadow: none;
color: #ffffff;
} }
.rounds-header button:hover { .rounds-header button:hover {
@ -55,27 +56,27 @@
.round-item { .round-item {
padding: 12px; padding: 12px;
border: 1px solid #eee; border: 1px solid #444;
border-radius: 6px; border-radius: 6px;
background: #f9f9f9; background: #333333;
transition: all 0.2s ease; transition: all 0.2s ease;
} }
.round-item:hover { .round-item:hover {
border-color: #ddd; border-color: #555;
background: white; background: #3a3a3a;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05); box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
} }
.round-number { .round-number {
font-weight: 600; font-weight: 600;
margin-bottom: 8px; margin-bottom: 8px;
color: #213547; color: #ffffff;
} }
.round-stats { .round-stats {
font-size: 0.9em; font-size: 0.9em;
color: #666; color: #cccccc;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 4px; gap: 4px;

View file

@ -2,28 +2,36 @@
position: fixed; position: fixed;
top: 80px; top: 80px;
right: 20px; right: 20px;
background: white; background: #222222;
border: 1px solid #ddd; border: 1px solid #444;
border-radius: 8px; border-radius: 8px;
padding: 20px; padding: 15px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
z-index: 1000; z-index: 1000;
width: 300px; width: 260px;
color: #213547; color: #ffffff;
max-height: 80vh;
overflow-y: auto;
}
/* Override all child backgrounds except for specific elements */
.settings-panel * {
background-color: #222222;
color: #ffffff;
} }
.settings-header { .settings-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 20px; margin-bottom: 15px;
} }
.settings-header h2 { .settings-header h2 {
margin: 0; margin: 0;
font-size: 1.2em; font-size: 1.2em;
font-weight: 600; font-weight: 600;
color: #213547; color: #ffffff;
} }
.settings-header button { .settings-header button {
@ -33,6 +41,7 @@
cursor: pointer; cursor: pointer;
padding: 0 8px; padding: 0 8px;
box-shadow: none; box-shadow: none;
color: #ffffff;
} }
.settings-header button:hover { .settings-header button:hover {
@ -43,14 +52,14 @@
.slider-row { .slider-row {
display: flex; display: flex;
align-items: center; align-items: center;
margin: 10px 0; margin: 8px 0;
gap: 10px; gap: 10px;
} }
.slider-row label { .slider-row label {
width: 120px; width: 100px;
font-size: 14px; font-size: 14px;
color: #213547; color: #ffffff;
} }
.slider-row input[type="range"] { .slider-row input[type="range"] {
@ -58,14 +67,14 @@
} }
.slider-row .value-input { .slider-row .value-input {
width: 50px; width: 40px;
text-align: center; text-align: center;
font-size: 14px; font-size: 14px;
color: #213547; color: #ffffff;
padding: 4px; padding: 4px;
border: 1px solid #ddd; border: 1px solid #444;
border-radius: 4px; border-radius: 4px;
background: white; background: #333333;
} }
.slider-row .value-input:focus { .slider-row .value-input:focus {
@ -75,13 +84,13 @@
} }
.slider-row .value-input:disabled { .slider-row .value-input:disabled {
background: #f5f5f5; background: #444;
color: #999; color: #999;
cursor: not-allowed; cursor: not-allowed;
} }
.acceleration-toggle { .acceleration-toggle {
margin-bottom: 20px; margin-bottom: 15px;
} }
.acceleration-toggle label { .acceleration-toggle label {
@ -90,12 +99,93 @@
gap: 8px; gap: 8px;
cursor: pointer; cursor: pointer;
font-size: 14px; font-size: 14px;
color: #213547; color: #ffffff;
} }
.sliders-container h3 { .sliders-container h3 {
margin: 0 0 15px 0; margin: 0 0 12px 0;
font-size: 1em; font-size: 1em;
font-weight: 600; font-weight: 600;
color: #213547; color: #ffffff;
}
/* Mode selection styling */
.mode-selection {
margin-bottom: 15px;
background: #222222;
color: #ffffff;
}
.mode-selection h3 {
margin: 0 0 12px 0;
font-size: 1em;
font-weight: 600;
color: #ffffff;
}
.mode-selection .radio-group {
display: flex;
flex-direction: column;
gap: 8px;
color: #ffffff;
background: #222222;
}
/* Completely restyle radio buttons */
.radio-group label {
display: flex;
align-items: center;
gap: 10px;
cursor: pointer;
font-size: 14px;
color: #ffffff;
background: #222222;
border: none;
padding: 5px;
border-radius: 4px;
}
.radio-group input[type="radio"] {
accent-color: #646cff;
width: 16px;
height: 16px;
min-width: 16px;
margin: 0;
padding: 0;
}
/* Preset Selection */
.preset-selection {
margin-bottom: 15px;
background: #222222;
}
.preset-selection h3 {
margin: 0 0 12px 0;
font-size: 1em;
font-weight: 600;
color: #ffffff;
}
.preset-buttons {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.preset-button {
padding: 5px 10px;
background: #333333;
color: #ffffff;
border: 1px solid #555;
border-radius: 5px;
cursor: pointer;
transition: all 0.2s ease;
font-size: 13px;
}
.preset-button.active {
background: #646cff;
color: white;
border-color: #8088ff;
} }

View file

@ -102,7 +102,7 @@ export const Settings: React.FC<SettingsProps> = ({
checked={mode === 'freestyle'} checked={mode === 'freestyle'}
onChange={() => handleModeChange('freestyle')} onChange={() => handleModeChange('freestyle')}
/> />
Freestyle Mode <span>Freestyle Mode</span>
</label> </label>
<label> <label>
<input <input
@ -112,7 +112,7 @@ export const Settings: React.FC<SettingsProps> = ({
checked={mode === 'study'} checked={mode === 'study'}
onChange={() => handleModeChange('study')} onChange={() => handleModeChange('study')}
/> />
Study Mode (25 targets per preset) <span>Study Mode (25 targets per preset)</span>
</label> </label>
</div> </div>
</div> </div>

View file

@ -10,24 +10,36 @@ export const calculateZoomDelta = (
export const calculateInitialViewport = (initialZoom: number): ViewportOffset => { export const calculateInitialViewport = (initialZoom: number): ViewportOffset => {
// When fully zoomed out, we want to see the entire content area // When fully zoomed out, we want to see the entire content area
// The target distribution is sized to match the viewport at minimum zoom // Account for safety margins and UI exclusion zones
// Account for the UI exclusion zone at the top
const windowWidth = window.innerWidth; const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight; const windowHeight = window.innerHeight;
// Same UI margin as in target generation // Same UI margins as in target generation
const UI_TOP_MARGIN = 100; const UI_TOP_MARGIN = 100;
const UI_BOTTOM_MARGIN = 50; // Added bottom margin
// Center the viewport on the target area // We need to account for the safety margin used in target generation
const areaWidth = windowWidth / CONSTANTS.ZOOM_LEVELS.min; const MAX_RADIUS = CONSTANTS.TARGET_MAX_RADIUS;
const areaHeight = (windowHeight - UI_TOP_MARGIN) / CONSTANTS.ZOOM_LEVELS.min; const safetyMargin = MAX_RADIUS * 1.5; // Increased from 1.2 to 1.5
// Calculate the full target area including margins
const minZoom = CONSTANTS.ZOOM_LEVELS.min;
const areaWidth = windowWidth / minZoom;
const areaHeight = (windowHeight - UI_TOP_MARGIN - UI_BOTTOM_MARGIN) / minZoom;
// Account for the UI exclusion zone in Y offset // Account for the UI exclusion zone in Y offset
const startY = UI_TOP_MARGIN / CONSTANTS.ZOOM_LEVELS.min; const startY = UI_TOP_MARGIN / minZoom;
// Center viewport on the usable area
const usableWidth = areaWidth - (safetyMargin * 2);
const usableHeight = areaHeight - (safetyMargin * 2);
const usableCenterX = safetyMargin + (usableWidth / 2);
const usableCenterY = startY + safetyMargin + (usableHeight / 2);
// Position viewport so that the center of the usable area is in the middle of the screen
return { return {
x: (areaWidth - windowWidth / initialZoom) / 2, x: usableCenterX - (windowWidth / initialZoom / 2),
y: startY + (areaHeight - windowHeight / initialZoom) / 2 y: usableCenterY - (windowHeight / initialZoom / 2)
}; };
}; };

View file

@ -11,14 +11,15 @@ export const generateTargets = () => {
const windowWidth = window.innerWidth; const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight; const windowHeight = window.innerHeight;
// Define UI exclusion zone (top area where UI elements are located) // Define UI exclusion zones
const UI_TOP_MARGIN = 100; // pixels from top to exclude const UI_TOP_MARGIN = 100; // pixels from top to exclude
const UI_BOTTOM_MARGIN = 50; // pixels from bottom to exclude for any potential UI
// Base the target area on the minimum zoom level // Base the target area on the minimum zoom level
// This ensures when fully zoomed out, we see the entire grid // This ensures when fully zoomed out, we see the entire grid
const minZoom = CONSTANTS.ZOOM_LEVELS.min; const minZoom = CONSTANTS.ZOOM_LEVELS.min;
const areaWidth = windowWidth / minZoom; const areaWidth = windowWidth / minZoom;
const areaHeight = (windowHeight - UI_TOP_MARGIN) / minZoom; const areaHeight = (windowHeight - UI_TOP_MARGIN - UI_BOTTOM_MARGIN) / minZoom;
// Start Y position below the UI elements // Start Y position below the UI elements
const startY = UI_TOP_MARGIN / minZoom; const startY = UI_TOP_MARGIN / minZoom;
@ -28,18 +29,33 @@ export const generateTargets = () => {
const cellWidth = areaWidth / gridSize; const cellWidth = areaWidth / gridSize;
const cellHeight = areaHeight / gridSize; const cellHeight = areaHeight / gridSize;
// Increase safety margin to prevent any clipping
const safetyMargin = MAX_RADIUS * 1.5; // Increased from 1.2 to 1.5
// Adjust the usable area to prevent clipping at edges
const usableWidth = areaWidth - (safetyMargin * 2);
const usableHeight = areaHeight - (safetyMargin * 2);
// New starting point that accounts for target radius
const startUsableX = safetyMargin;
const startUsableY = startY + safetyMargin;
// Calculate adjusted cell dimensions for the usable area
const adjustedCellWidth = usableWidth / gridSize;
const adjustedCellHeight = usableHeight / gridSize;
for (let i = 0; i < CONSTANTS.TARGET_COUNT; i++) { for (let i = 0; i < CONSTANTS.TARGET_COUNT; i++) {
// Calculate grid position // Calculate grid position
const gridX = i % gridSize; const gridX = i % gridSize;
const gridY = Math.floor(i / gridSize); const gridY = Math.floor(i / gridSize);
// Add some randomness within each grid cell // Reduce randomness to keep targets more centered in their cells
const randomOffsetX = (Math.random() - 0.5) * cellWidth * 0.6; const randomOffsetX = (Math.random() - 0.5) * adjustedCellWidth * 0.4; // Reduced from 0.6
const randomOffsetY = (Math.random() - 0.5) * cellHeight * 0.6; const randomOffsetY = (Math.random() - 0.5) * adjustedCellHeight * 0.4; // Reduced from 0.6
// Calculate final position - add startY to push everything down below UI // Calculate final position - add margin to prevent edge clipping
const x = cellWidth * (gridX + 0.5) + randomOffsetX; const x = startUsableX + adjustedCellWidth * (gridX + 0.5) + randomOffsetX;
const y = startY + cellHeight * (gridY + 0.5) + randomOffsetY; const y = startUsableY + adjustedCellHeight * (gridY + 0.5) + randomOffsetY;
// Random radius // Random radius
const radius = MIN_RADIUS + Math.random() * (MAX_RADIUS - MIN_RADIUS); const radius = MIN_RADIUS + Math.random() * (MAX_RADIUS - MIN_RADIUS);