Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 185 additions & 7 deletions static/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ input, select {
box-sizing: border-box;
background: var(--input-bg);
color: var(--text-color);
font-family: 'Space Mono', monospace;
transition: background 0.3s ease, border-color 0.3s ease, color 0.3s ease;
}

Expand Down Expand Up @@ -367,23 +368,55 @@ button.loading:hover {
flex: 1;
min-width: 200px;
margin-right: 0;
padding: 10px;
border: 2px solid var(--border-color);
border-radius: 8px;
font-size: 16px;
box-sizing: border-box;
background: var(--input-bg);
color: var(--text-color);
transition: background 0.3s ease, border-color 0.3s ease, color 0.3s ease;
font-family: 'Space Mono', monospace;
}

.location-search input:focus {
outline: none;
border-color: #667eea;
}

.location-search button {
padding: 8px 15px;
background: #667eea;
font-family: 'Space Mono', monospace;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 12px 24px;
border: none;
border-radius: 4px;
border-radius: 25px;
font-size: 16px;
font-weight: bold;
cursor: pointer;
white-space: nowrap;
width: auto;
min-width: auto;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
margin-top: 0;
}

.location-search button:hover {
background: #5a67d8;
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
background: linear-gradient(135deg, #5a67d8 0%, #6b46c1 100%);
}

.location-search button:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}

.location-search button:disabled:hover {
transform: none;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
}

#locationMap {
Expand Down Expand Up @@ -696,11 +729,20 @@ input[readonly] {

.other-genre-input input {
width: 100%;
padding: 8px;
padding: 10px;
border: 2px solid var(--border-color);
border-radius: 4px;
background: var(--input-bg);
border-radius: 8px;
font-size: 16px;
box-sizing: border-box;
background: var(--input-bg);
color: var(--text-color);
transition: background 0.3s ease, border-color 0.3s ease, color 0.3s ease;
font-family: 'Space Mono', monospace;
}

.other-genre-input input:focus {
outline: none;
border-color: #667eea;
}

.clear-data-container {
Expand Down Expand Up @@ -1048,4 +1090,140 @@ input[readonly] {
max-height: 0;
opacity: 0;
padding: 0;
}

/* ==========================================
Select2 Custom Styling for Timezone Dropdown
========================================== */

/* Main Select2 container */
.select2-container--default .select2-selection--single {
background: var(--input-bg);
border: 2px solid var(--border-color);
border-radius: 8px;
height: 45px;
padding: 5px 10px;
transition: background 0.3s ease, border-color 0.3s ease;
}

.select2-container--default .select2-selection--single:focus,
.select2-container--default.select2-container--open .select2-selection--single {
border-color: #667eea;
outline: none;
}

.select2-container--default .select2-selection--single .select2-selection__rendered {
color: var(--text-color);
line-height: 33px;
padding-left: 5px;
font-family: 'Space Mono', monospace;
font-size: 16px;
}

.select2-container--default .select2-selection--single .select2-selection__placeholder {
color: var(--text-secondary);
}

.select2-container--default .select2-selection--single .select2-selection__arrow {
height: 43px;
right: 5px;
}

/* Dropdown */
.select2-container--default .select2-dropdown {
background: var(--input-bg);
border: 2px solid var(--border-color);
border-radius: 8px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
margin-top: 5px;
max-width: 100%;
}

/* Search box in dropdown */
.select2-container--default .select2-search--dropdown .select2-search__field {
background: var(--input-bg);
border: 2px solid var(--border-color);
border-radius: 6px;
color: var(--text-color);
font-family: 'Space Mono', monospace;
padding: 8px;
margin: 8px;
}

.select2-container--default .select2-search--dropdown .select2-search__field:focus {
border-color: #667eea;
outline: none;
}

/* Dropdown results */
.select2-container--default .select2-results__option {
background: var(--input-bg);
color: var(--text-color);
padding: 10px 12px;
font-family: 'Space Mono', monospace;
font-size: 14px;
}

.select2-container--default .select2-results__option--highlighted[aria-selected] {
background: #667eea;
color: white;
}

.select2-container--default .select2-results__option[aria-selected=true] {
background: var(--section-bg);
color: var(--text-color);
}

.select2-container--default .select2-results__option[aria-selected=true]:hover {
background: #667eea;
color: white;
}

/* Option groups */
.select2-container--default .select2-results__group {
background: var(--section-bg);
color: var(--text-secondary);
font-weight: bold;
padding: 8px 12px;
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.5px;
border-top: 1px solid var(--border-color);
}

.select2-container--default .select2-results__option--group {
padding-left: 0;
}

.select2-container--default .select2-results__options--nested .select2-results__option {
padding-left: 20px;
}

/* Loading state */
.select2-container--default .select2-results__option--loading {
color: var(--text-secondary);
}

/* No results message */
.select2-container--default .select2-results__option--no-results {
color: var(--text-secondary);
background: var(--input-bg);
}

/* Width fix */
.select2-container {
width: 100% !important;
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The !important flag is used here to override Select2's default width. While this works, it's generally better to avoid !important as it makes styles harder to maintain and override in the future. Consider using a more specific selector or setting the width through Select2's initialization options in timezone-select.js instead.

Suggested change
width: 100% !important;
width: 100%;

Copilot uses AI. Check for mistakes.
}

.timezone-select {
width: 100%;
}

/* Dark mode adjustments */
.dark-mode .select2-container--default .select2-selection--single .select2-selection__arrow b {
border-color: var(--text-color) transparent transparent transparent;
}

.dark-mode .select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b {
border-color: transparent transparent var(--text-color) transparent;
}
26 changes: 26 additions & 0 deletions static/js/dropdown-button.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Dropdown button functionality
function toggleDropdown() {
const dropdown = document.getElementById('chartDropdown');
dropdown.classList.toggle('show');
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The toggleDropdown function doesn't check if the dropdown element exists before attempting to toggle its class. If the element is not found, this will throw a TypeError. Add a null check: if (dropdown) dropdown.classList.toggle('show'); to prevent errors on pages where the dropdown doesn't exist.

Suggested change
dropdown.classList.toggle('show');
if (dropdown) {
dropdown.classList.toggle('show');
}

Copilot uses AI. Check for mistakes.
}

// Close dropdown when clicking outside
document.addEventListener('click', function(event) {
const dropdown = document.getElementById('chartDropdown');
const toggleButton = document.querySelector('.dropdown-toggle');

Comment on lines +10 to +11
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused variable toggleButton.

Suggested change
const toggleButton = document.querySelector('.dropdown-toggle');

Copilot uses AI. Check for mistakes.
if (dropdown && !event.target.closest('.dropdown-button-wrapper')) {
dropdown.classList.remove('show');
}
});

// Prevent form submission when clicking the dropdown toggle
document.addEventListener('DOMContentLoaded', function() {
Comment on lines +8 to +18
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The click event listener is registered outside of DOMContentLoaded. While it will work since the actual DOM queries happen when the event fires, it's better practice to wrap all event listener registrations in DOMContentLoaded for consistency and to ensure the page structure is ready. This makes the code more maintainable and reduces potential issues if the script load order changes.

Suggested change
document.addEventListener('click', function(event) {
const dropdown = document.getElementById('chartDropdown');
const toggleButton = document.querySelector('.dropdown-toggle');
if (dropdown && !event.target.closest('.dropdown-button-wrapper')) {
dropdown.classList.remove('show');
}
});
// Prevent form submission when clicking the dropdown toggle
document.addEventListener('DOMContentLoaded', function() {
// Prevent form submission when clicking the dropdown toggle
document.addEventListener('DOMContentLoaded', function() {
// Close dropdown when clicking outside
document.addEventListener('click', function(event) {
const dropdown = document.getElementById('chartDropdown');
const toggleButton = document.querySelector('.dropdown-toggle');
if (dropdown && !event.target.closest('.dropdown-button-wrapper')) {
dropdown.classList.remove('show');
}
});

Copilot uses AI. Check for mistakes.
const toggleButton = document.querySelector('.dropdown-toggle');
if (toggleButton) {
toggleButton.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
});
}
});
47 changes: 43 additions & 4 deletions static/js/location-map.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Interactive map for location selection using Leaflet
let map;
let marker;
let currentTileLayer;

document.addEventListener('DOMContentLoaded', function() {
initMap();
Expand All @@ -23,6 +24,20 @@ document.addEventListener('DOMContentLoaded', function() {
}
});
}

// Listen for dark mode changes and update map tiles
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.attributeName === 'class') {
updateMapTiles();
}
});
});

observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['class']
});
});

function initMap() {
Expand All @@ -35,10 +50,8 @@ function initMap() {
// Make map globally available for form persistence
window.map = map;

// Add OpenStreetMap tiles
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
// Add appropriate tile layer based on current mode
updateMapTiles();

// Add click listener to map
map.on('click', function(e) {
Expand Down Expand Up @@ -143,6 +156,32 @@ function getTimezoneForLocation(lat, lng) {
}
}

function updateMapTiles() {
if (!map) return;

// Remove existing tile layer if present
if (currentTileLayer) {
map.removeLayer(currentTileLayer);
}

const isDarkMode = document.documentElement.classList.contains('dark-mode');

if (isDarkMode) {
// Dark mode: Use CartoDB Dark Matter tiles
currentTileLayer = L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', {
attribution: '© OpenStreetMap contributors © CARTO',
subdomains: 'abcd',
maxZoom: 19
}).addTo(map);
} else {
// Light mode: Use standard OpenStreetMap tiles
currentTileLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors',
maxZoom: 19
}).addTo(map);
}
}

// Search for locations using Nominatim (OpenStreetMap's geocoding service)
function searchLocation() {
const searchInput = document.getElementById('locationSearch');
Expand Down
12 changes: 9 additions & 3 deletions static/js/sparkles.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ function createSparkle() {
const sparkle = document.createElement('div');
sparkle.className = 'sparkle';

// Random sparkle characters
const sparkleChars = ['', '', '🌟', '💫', '🔮'];
sparkle.textContent = sparkleChars[Math.floor(Math.random() * sparkleChars.length)];
// Random star characters (matching star-trail)
const starChars = ['', '', '', '', '✪', '✫', '✬', '✭', '✮', '✯'];
sparkle.textContent = starChars[Math.floor(Math.random() * starChars.length)];

// Random position across the width
sparkle.style.left = Math.random() * 100 + '%';
Expand All @@ -20,6 +20,12 @@ function createSparkle() {
const size = Math.random() * 15 + 15;
sparkle.style.fontSize = size + 'px';

// Random color (matching star-trail)
const colors = ['#FFD700', '#FFA500', '#FF69B4', '#9370DB', '#00CED1', '#FFFFFF', '#FFB6C1'];
const color = colors[Math.floor(Math.random() * colors.length)];
sparkle.style.color = color;
sparkle.style.textShadow = `0 0 ${size/2}px ${color}`;

// Some sparkles get extra twinkle
if (Math.random() > 0.7) {
sparkle.classList.add('twinkle');
Expand Down
21 changes: 21 additions & 0 deletions static/js/timezone-select.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Initialize timezone selector with Select2
document.addEventListener('DOMContentLoaded', function() {
// Initialize Select2 on the timezone select element
$('#timezone_offset').select2({
placeholder: 'Select or search for a timezone...',
allowClear: false,
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Select2 initialization has allowClear: false which means users cannot clear their timezone selection once made. This could be problematic if a user wants to start over or made a mistake. Consider setting allowClear: true to improve user experience, especially since the field is required and validation will still ensure a value is selected on form submission.

Suggested change
allowClear: false,
allowClear: true,

Copilot uses AI. Check for mistakes.
width: '100%',
dropdownAutoWidth: false
});

// If there's a stored value in localStorage, restore it
var storedTimezone = localStorage.getItem('timezone_offset');
if (storedTimezone) {
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When restoring the timezone value from localStorage, there's no validation to ensure the stored value is actually one of the valid timezone options. If localStorage contains an invalid or outdated value, Select2 will fail silently. Consider adding validation by checking if the stored value exists as an option in the select element before attempting to restore it.

Suggested change
if (storedTimezone) {
if (
storedTimezone &&
$('#timezone_offset option[value="' + storedTimezone.replace(/"/g, '\\"') + '"]').length > 0
) {

Copilot uses AI. Check for mistakes.
$('#timezone_offset').val(storedTimezone).trigger('change');
}

// Save timezone to localStorage when changed
$('#timezone_offset').on('change', function() {
localStorage.setItem('timezone_offset', $(this).val());
});
});
Comment on lines +2 to +21
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The timezone-select.js script depends on jQuery being loaded, but there's no check to ensure jQuery is available before using it. If jQuery fails to load from the CDN for any reason, this script will throw an error and break the page. Add a check like if (typeof jQuery === 'undefined') or wrap the code in a try-catch block for better error handling.

Copilot uses AI. Check for mistakes.
4 changes: 4 additions & 0 deletions templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,12 @@
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Space+Mono:ital,wght@0,400;0,700;1,400;1,700&display=swap" rel="stylesheet">
<!-- Select2 for searchable dropdowns -->
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<!-- Dark mode script loaded early to prevent flash -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
Comment on lines +42 to +43
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jQuery and Select2 are loaded from CDNs in the head, blocking page rendering. These scripts should be loaded with 'defer' attribute or moved to the end of the body to improve initial page load performance. Since timezone-select.js uses DOMContentLoaded, deferring these scripts won't cause timing issues.

Suggested change
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js" defer></script>
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js" defer></script>

Copilot uses AI. Check for mistakes.
<script src="{{ url_for('static', filename='js/dark-mode.js') }}"></script>
<script src="{{ url_for('static', filename='js/sparkles.js') }}"></script>
<script src="{{ url_for('static', filename='js/star-trail.js') }}"></script>
Expand Down
Loading