From 84eef1beb29d747d40658f67d59f329fda6627a8 Mon Sep 17 00:00:00 2001 From: Atridad Lahiji Date: Tue, 25 Mar 2025 00:43:08 -0600 Subject: [PATCH] Dark mode for contrast and more bug squashing --- src/App.css | 116 ++++++++++++--------------- src/components/Instructions.css | 39 +++++----- src/components/RoundsHistory.css | 29 +++---- src/components/Settings.css | 130 ++++++++++++++++++++++++++----- src/components/Settings.tsx | 4 +- src/utils/interaction.ts | 30 ++++--- src/utils/target.ts | 32 ++++++-- 7 files changed, 244 insertions(+), 136 deletions(-) diff --git a/src/App.css b/src/App.css index b3235e9..04ffb6d 100644 --- a/src/App.css +++ b/src/App.css @@ -2,7 +2,7 @@ width: 100vw; height: 100vh; overflow: hidden; - background-color: white; + background-color: #000000; position: relative; } @@ -11,14 +11,14 @@ top: 20px; left: 20px; transform: none; - color: #213547; - background-color: white; + color: #ffffff; + background-color: rgba(40, 40, 40, 0.8); padding: 12px 20px; border-radius: 8px; font-family: Inter, system-ui, Arial, sans-serif; font-size: 14px; - box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); - border: 1px solid #ddd; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3); + border: 1px solid #444; z-index: 100; display: flex; gap: 12px; @@ -43,27 +43,27 @@ 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 */ transform-origin: 0 0; - background-color: white; + background-color: #000000; will-change: transform; contain: layout paint; } .target { position: absolute; - background-color: #444444; /* Darker gray for better visibility */ + background-color: #666666; /* Light gray for better contrast on black */ border-radius: 50%; 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%); will-change: transform; 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 { - background-color: #22aa22; /* Darker green for better visibility */ - box-shadow: 0 0 30px rgba(34, 170, 34, 0.8); - border: 4px solid white; + background-color: #22cc22; /* Bright green for better visibility */ + box-shadow: 0 0 30px rgba(34, 204, 34, 0.8); + border: 4px solid #ffffff; z-index: 10; } @@ -75,7 +75,7 @@ body, width: 100%; height: 100%; overflow: hidden; - background-color: white; + background-color: #000000; } .controls { @@ -94,13 +94,14 @@ body, display: flex; align-items: center; gap: 6px; - background: white; - border: 1px solid #ddd; + background: rgba(40, 40, 40, 0.8); + border: 1px solid #444; padding: 8px 16px; border-radius: 8px; cursor: pointer; 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; } @@ -108,7 +109,7 @@ body, .rounds-toggle:hover, .export-button:hover, .reset-button:hover { - background: #f5f5f5; + background: #444444; border-color: #646cff; } @@ -118,17 +119,17 @@ body, } .export-button:disabled:hover { - background: white; - border-color: #ddd; + background: rgba(40, 40, 40, 0.8); + border-color: #444; } .reset-button { - border-color: #ffcdd2; + border-color: #aa3333; } .reset-button:hover { - background: #fff5f5; - border-color: #ef5350; + background: #442222; + border-color: #ff4444; } /* Preset Selection */ @@ -144,8 +145,9 @@ body, .preset-button { padding: 8px 16px; - background: white; - border: 1px solid #ddd; + background: #333333; + color: #ffffff; + border: 1px solid #555; border-radius: 8px; cursor: pointer; transition: all 0.2s ease; @@ -154,32 +156,14 @@ body, .preset-button.active { background: #646cff; color: white; - border-color: #646cff; + border-color: #8088ff; } .preset-button:hover:not(.active) { - background: #f5f5f5; + background: #444444; 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 { position: fixed; @@ -187,7 +171,7 @@ body, left: 0; width: 100vw; height: 100vh; - background-color: rgba(0, 0, 0, 0.5); + background-color: rgba(0, 0, 0, 0.7); display: flex; justify-content: center; align-items: center; @@ -195,17 +179,18 @@ body, } .break-container { - background-color: white; + background-color: #222222; + color: #ffffff; padding: 30px; 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; text-align: center; } .break-container h2 { margin-top: 0; - color: #213547; + color: #ffffff; } .break-container button { @@ -221,7 +206,7 @@ body, } .break-container button:hover { - background-color: #4e57cc; + background-color: #7a82ff; } /* Resize Warning Popup */ @@ -231,7 +216,7 @@ body, left: 50%; transform: translate(-50%, -50%); z-index: 2000; - background-color: rgba(0, 0, 0, 0.5); + background-color: rgba(0, 0, 0, 0.7); width: 100%; height: 100%; display: flex; @@ -241,17 +226,18 @@ body, } .resize-warning-content { - background-color: white; + background-color: #222222; + color: #ffffff; padding: 20px 30px; 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; text-align: center; } .resize-warning-content h3 { margin-top: 0; - color: #213547; + color: #ffffff; } .resize-warning-content button { @@ -267,7 +253,7 @@ body, } .resize-warning-content button:hover { - background-color: #4e57cc; + background-color: #7a82ff; } @keyframes fadeIn { @@ -292,26 +278,26 @@ body, padding: 20px; border-radius: 12px; color: white; - text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); - box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); + text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5); + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.5); text-align: center; transition: background-color 0.3s ease; } .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 { - background-color: #34a853; /* Green for Preset B */ + background-color: rgba(52, 168, 83, 0.9); /* Green for Preset B */ } .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 { - background-color: #fbbc05; /* Yellow for Preset D */ + background-color: rgba(251, 188, 5, 0.9); /* Yellow for Preset D */ } /* Preset Transition Effect */ @@ -334,7 +320,7 @@ body, font-size: 36px; font-weight: bold; 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; border-radius: 16px; text-align: center; @@ -342,19 +328,19 @@ body, } .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 { - 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 { - 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 { - 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 { diff --git a/src/components/Instructions.css b/src/components/Instructions.css index ae50ae0..5d08eb3 100644 --- a/src/components/Instructions.css +++ b/src/components/Instructions.css @@ -4,7 +4,7 @@ left: 0; right: 0; bottom: 0; - background: rgba(0, 0, 0, 0.5); + background: rgba(0, 0, 0, 0.7); display: flex; justify-content: center; align-items: center; @@ -12,15 +12,16 @@ } .instructions-panel { - background: white; + background: #222222; border-radius: 12px; padding: 30px; max-width: 600px; 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; max-height: 90vh; overflow-y: auto; + color: #ffffff; } .instructions-header { @@ -33,18 +34,18 @@ .instructions-header h2 { margin: 0; font-size: 1.5em; - color: #213547; + color: #ffffff; } .instructions-content { - color: #4a5568; + color: #cccccc; line-height: 1.6; } .instructions-content h3 { margin-top: 25px; margin-bottom: 15px; - color: #213547; + color: #ffffff; } .instructions-content ul { @@ -81,20 +82,21 @@ .keyboard-shortcut { display: inline-block; - background: #f1f5f9; + background: #333333; padding: 2px 6px; border-radius: 4px; font-family: monospace; margin: 0 2px; + color: #ffffff; } /* Mode Selection Styles */ .mode-selection { - background: #f8fafc; + background: #333333; padding: 20px; border-radius: 8px; margin: 20px 0; - border: 1px solid #e2e8f0; + border: 1px solid #444444; } .mode-selection .radio-group { @@ -107,9 +109,9 @@ display: flex; flex-direction: column; padding: 15px; - border: 1px solid #e2e8f0; + border: 1px solid #444444; border-radius: 8px; - background: white; + background: #222222; cursor: pointer; transition: all 0.2s; position: relative; @@ -117,25 +119,26 @@ .mode-selection label:hover { 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"] { position: absolute; top: 15px; left: 15px; + accent-color: #646cff; } .mode-label { font-weight: bold; - color: #213547; + color: #ffffff; margin-left: 25px; font-size: 16px; } .mode-description { margin: 8px 0 0 25px; - color: #64748b; + color: #cccccc; font-size: 14px; } @@ -144,11 +147,11 @@ } .mode-selection input[type="radio"]:checked ~ .mode-description { - color: #4a5568; + color: #dddddd; } .warning-message { - background-color: #fff8e1; + background-color: #332211; border-left: 4px solid #ffb300; padding: 15px; border-radius: 4px; @@ -156,7 +159,7 @@ } .warning-message h3 { - color: #e65100; + color: #ffb300; margin-top: 0; margin-bottom: 10px; display: flex; @@ -165,5 +168,5 @@ .warning-message p { margin: 0; - color: #333; + color: #dddddd; } \ No newline at end of file diff --git a/src/components/RoundsHistory.css b/src/components/RoundsHistory.css index c644b0a..ced572e 100644 --- a/src/components/RoundsHistory.css +++ b/src/components/RoundsHistory.css @@ -2,16 +2,16 @@ position: fixed; top: 80px; left: 20px; - background: white; - border: 1px solid #ddd; + background: #222222; + border: 1px solid #444; border-radius: 8px; 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; width: 300px; max-height: calc(100vh - 120px); overflow-y: auto; - color: #213547; + color: #ffffff; } .rounds-header { @@ -21,16 +21,16 @@ margin-bottom: 20px; position: sticky; top: 0; - background: white; + background: #222222; padding-bottom: 10px; - border-bottom: 1px solid #eee; + border-bottom: 1px solid #444; } .rounds-header h2 { margin: 0; font-size: 1.2em; font-weight: 600; - color: #213547; + color: #ffffff; } .rounds-header button { @@ -40,6 +40,7 @@ cursor: pointer; padding: 0 8px; box-shadow: none; + color: #ffffff; } .rounds-header button:hover { @@ -55,27 +56,27 @@ .round-item { padding: 12px; - border: 1px solid #eee; + border: 1px solid #444; border-radius: 6px; - background: #f9f9f9; + background: #333333; transition: all 0.2s ease; } .round-item:hover { - border-color: #ddd; - background: white; - box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05); + border-color: #555; + background: #3a3a3a; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2); } .round-number { font-weight: 600; margin-bottom: 8px; - color: #213547; + color: #ffffff; } .round-stats { font-size: 0.9em; - color: #666; + color: #cccccc; display: flex; flex-direction: column; gap: 4px; diff --git a/src/components/Settings.css b/src/components/Settings.css index c371320..dcd417d 100644 --- a/src/components/Settings.css +++ b/src/components/Settings.css @@ -2,28 +2,36 @@ position: fixed; top: 80px; right: 20px; - background: white; - border: 1px solid #ddd; + background: #222222; + border: 1px solid #444; border-radius: 8px; - padding: 20px; - box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + padding: 15px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3); z-index: 1000; - width: 300px; - color: #213547; + width: 260px; + 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 { display: flex; justify-content: space-between; align-items: center; - margin-bottom: 20px; + margin-bottom: 15px; } .settings-header h2 { margin: 0; font-size: 1.2em; font-weight: 600; - color: #213547; + color: #ffffff; } .settings-header button { @@ -33,6 +41,7 @@ cursor: pointer; padding: 0 8px; box-shadow: none; + color: #ffffff; } .settings-header button:hover { @@ -43,14 +52,14 @@ .slider-row { display: flex; align-items: center; - margin: 10px 0; + margin: 8px 0; gap: 10px; } .slider-row label { - width: 120px; + width: 100px; font-size: 14px; - color: #213547; + color: #ffffff; } .slider-row input[type="range"] { @@ -58,14 +67,14 @@ } .slider-row .value-input { - width: 50px; + width: 40px; text-align: center; font-size: 14px; - color: #213547; + color: #ffffff; padding: 4px; - border: 1px solid #ddd; + border: 1px solid #444; border-radius: 4px; - background: white; + background: #333333; } .slider-row .value-input:focus { @@ -75,13 +84,13 @@ } .slider-row .value-input:disabled { - background: #f5f5f5; + background: #444; color: #999; cursor: not-allowed; } .acceleration-toggle { - margin-bottom: 20px; + margin-bottom: 15px; } .acceleration-toggle label { @@ -90,12 +99,93 @@ gap: 8px; cursor: pointer; font-size: 14px; - color: #213547; + color: #ffffff; } .sliders-container h3 { - margin: 0 0 15px 0; + margin: 0 0 12px 0; font-size: 1em; 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; } \ No newline at end of file diff --git a/src/components/Settings.tsx b/src/components/Settings.tsx index 5beb783..28ef109 100644 --- a/src/components/Settings.tsx +++ b/src/components/Settings.tsx @@ -102,7 +102,7 @@ export const Settings: React.FC = ({ checked={mode === 'freestyle'} onChange={() => handleModeChange('freestyle')} /> - Freestyle Mode + Freestyle Mode diff --git a/src/utils/interaction.ts b/src/utils/interaction.ts index e580cac..625ad92 100644 --- a/src/utils/interaction.ts +++ b/src/utils/interaction.ts @@ -10,24 +10,36 @@ export const calculateZoomDelta = ( export const calculateInitialViewport = (initialZoom: number): ViewportOffset => { // 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 the UI exclusion zone at the top + // Account for safety margins and UI exclusion zones const windowWidth = window.innerWidth; 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_BOTTOM_MARGIN = 50; // Added bottom margin - // Center the viewport on the target area - const areaWidth = windowWidth / CONSTANTS.ZOOM_LEVELS.min; - const areaHeight = (windowHeight - UI_TOP_MARGIN) / CONSTANTS.ZOOM_LEVELS.min; + // We need to account for the safety margin used in target generation + const MAX_RADIUS = CONSTANTS.TARGET_MAX_RADIUS; + 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 - 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 { - x: (areaWidth - windowWidth / initialZoom) / 2, - y: startY + (areaHeight - windowHeight / initialZoom) / 2 + x: usableCenterX - (windowWidth / initialZoom / 2), + y: usableCenterY - (windowHeight / initialZoom / 2) }; }; diff --git a/src/utils/target.ts b/src/utils/target.ts index e5a187f..a22de34 100644 --- a/src/utils/target.ts +++ b/src/utils/target.ts @@ -11,14 +11,15 @@ export const generateTargets = () => { const windowWidth = window.innerWidth; 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_BOTTOM_MARGIN = 50; // pixels from bottom to exclude for any potential UI // Base the target area on the minimum zoom level // This ensures when fully zoomed out, we see the entire grid const minZoom = CONSTANTS.ZOOM_LEVELS.min; 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 const startY = UI_TOP_MARGIN / minZoom; @@ -28,18 +29,33 @@ export const generateTargets = () => { const cellWidth = areaWidth / 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++) { // Calculate grid position const gridX = i % gridSize; const gridY = Math.floor(i / gridSize); - // Add some randomness within each grid cell - const randomOffsetX = (Math.random() - 0.5) * cellWidth * 0.6; - const randomOffsetY = (Math.random() - 0.5) * cellHeight * 0.6; + // Reduce randomness to keep targets more centered in their cells + const randomOffsetX = (Math.random() - 0.5) * adjustedCellWidth * 0.4; // Reduced from 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 - const x = cellWidth * (gridX + 0.5) + randomOffsetX; - const y = startY + cellHeight * (gridY + 0.5) + randomOffsetY; + // Calculate final position - add margin to prevent edge clipping + const x = startUsableX + adjustedCellWidth * (gridX + 0.5) + randomOffsetX; + const y = startUsableY + adjustedCellHeight * (gridY + 0.5) + randomOffsetY; // Random radius const radius = MIN_RADIUS + Math.random() * (MAX_RADIUS - MIN_RADIUS);