diff --git a/index.html b/index.html
index c1ae1b3..f4ad34c 100644
--- a/index.html
+++ b/index.html
@@ -133,7 +133,7 @@
diff --git a/public/css/components.css b/public/css/components.css
index 4c68dfb..8044405 100644
--- a/public/css/components.css
+++ b/public/css/components.css
@@ -1291,6 +1291,272 @@ body.dark-mode .config-file-label {
margin-top: 10px;
}
+/* ========================================
+ Accessibility - Focus styles
+ ======================================== */
+.config-button:focus-visible {
+ outline: 2px solid var(--color-accent);
+ outline-offset: 2px;
+ opacity: 1;
+ background-color: var(--color-primary);
+}
+
+.config-tab:focus-visible {
+ outline: 2px solid var(--color-primary);
+ outline-offset: -2px;
+}
+
+body.dark-mode .config-tab:focus-visible {
+ outline-color: var(--color-accent);
+}
+
+.config-modal-close:focus-visible {
+ outline: 2px solid var(--color-surface);
+ outline-offset: 2px;
+}
+
+.config-modal-footer button:focus-visible,
+.config-option button:focus-visible,
+#search-site-button:focus-visible,
+#select-from-map-button:focus-visible {
+ outline: 2px solid var(--color-primary);
+ outline-offset: 2px;
+}
+
+body.dark-mode .config-modal-footer button:focus-visible,
+body.dark-mode .config-option button:focus-visible {
+ outline-color: var(--color-accent);
+}
+
+.config-option select:focus-visible,
+.config-option input[type="text"]:focus-visible,
+.config-search-input:focus-visible {
+ outline: 2px solid var(--color-primary);
+ outline-offset: -1px;
+ border-color: var(--color-primary);
+}
+
+body.dark-mode .config-option select:focus-visible,
+body.dark-mode .config-option input[type="text"]:focus-visible,
+body.dark-mode .config-search-input:focus-visible {
+ outline-color: var(--color-accent);
+ border-color: var(--color-accent);
+}
+
+/* Accessibility - Color contrast improvements */
+#custom-weather .sun-times {
+ color: #ccc;
+}
+
+.site-search-result div:last-child {
+ color: #777;
+}
+
+body.dark-mode .site-search-result div:last-child {
+ color: #bbb;
+}
+
+body.dark-mode .time-range {
+ color: #bbb;
+}
+
+/* ========================================
+ Responsive Design - Departure cards
+ ======================================== */
+@media (max-width: 768px) {
+ .departure-card {
+ min-height: 60px;
+ }
+
+ .line-number-box {
+ min-width: 42px;
+ width: 42px;
+ }
+
+ .line-number-large {
+ font-size: 1.3em;
+ }
+
+ .direction-destination {
+ font-size: 0.9em;
+ }
+
+ .countdown-large {
+ font-size: 1.1em;
+ }
+
+ .times-container {
+ min-width: 90px;
+ max-width: 90px;
+ }
+
+ .time-display {
+ font-size: 0.85em;
+ }
+
+ .direction-arrow-box {
+ width: 26px;
+ height: 26px;
+ font-size: 1.1em;
+ }
+
+ /* Weather responsive */
+ #custom-weather .current-weather {
+ flex-direction: column;
+ gap: 4px;
+ }
+
+ #custom-weather .location-info {
+ margin-right: 0;
+ }
+
+ #custom-weather .temperature {
+ font-size: 1.3em;
+ }
+
+ #custom-weather .forecast-hour {
+ min-width: 44px;
+ height: 80px;
+ padding: 4px 3px;
+ }
+
+ #custom-weather .forecast-hour .icon img {
+ width: 32px;
+ height: 32px;
+ }
+
+ /* Config modal responsive */
+ .config-modal-content {
+ width: 95%;
+ max-height: 95vh;
+ }
+
+ .config-modal-body {
+ max-height: calc(95vh - 180px);
+ }
+
+ .config-tabs {
+ flex-wrap: wrap;
+ }
+
+ .config-tab {
+ padding: 10px 12px;
+ font-size: 0.85em;
+ }
+}
+
+@media (max-width: 480px) {
+ .departure-card {
+ min-height: 50px;
+ }
+
+ .line-number-box {
+ min-width: 36px;
+ width: 36px;
+ padding: 2px;
+ }
+
+ .line-number-large {
+ font-size: 1.1em;
+ }
+
+ .transport-mode-icon .transport-icon {
+ width: 16px;
+ height: 16px;
+ }
+
+ .directions-wrapper {
+ padding: 4px 6px;
+ gap: 3px;
+ }
+
+ .direction-destination {
+ font-size: 0.8em;
+ }
+
+ .countdown-large {
+ font-size: 1em;
+ }
+
+ .times-container {
+ min-width: 75px;
+ max-width: 75px;
+ }
+
+ .time-range {
+ font-size: 0.75em;
+ }
+
+ .direction-arrow-box {
+ width: 22px;
+ height: 22px;
+ font-size: 0.9em;
+ }
+
+ /* Weather responsive */
+ #custom-weather {
+ padding: 6px;
+ }
+
+ #custom-weather .weather-icon img {
+ width: 36px;
+ height: 36px;
+ }
+
+ #custom-weather .temperature {
+ font-size: 1.1em;
+ }
+
+ #custom-weather .forecast-hour {
+ min-width: 38px;
+ height: 70px;
+ margin-right: 4px;
+ }
+
+ #custom-weather .forecast-hour .time {
+ font-size: 0.65em;
+ }
+
+ #custom-weather .forecast-hour .icon img {
+ width: 28px;
+ height: 28px;
+ }
+
+ #custom-weather .forecast-hour .temp {
+ font-size: 0.7em;
+ }
+
+ /* Config modal responsive */
+ .config-modal-header h2 {
+ font-size: 1.2em;
+ }
+
+ .config-modal-header {
+ padding: 10px 15px;
+ }
+
+ .config-modal-body {
+ padding: 15px;
+ }
+
+ .config-modal-footer {
+ padding: 10px 15px;
+ }
+
+ .config-tab {
+ padding: 8px 8px;
+ font-size: 0.8em;
+ }
+
+ .config-flex-row {
+ flex-wrap: wrap;
+ }
+
+ .config-flex-row-mb {
+ flex-wrap: wrap;
+ }
+}
+
/* Reduced motion preference */
@media (prefers-reduced-motion: reduce) {
.countdown-large.urgent,
diff --git a/public/css/main.css b/public/css/main.css
index dff081f..a22ed13 100644
--- a/public/css/main.css
+++ b/public/css/main.css
@@ -1,349 +1,354 @@
-/* Base styles */
- body {
- font-family: Arial, sans-serif;
- margin: 0 auto;
- padding: 20px;
- background-color: #f5f5f5;
- color: #333;
- transition: all 0.5s ease;
- }
-
- /* Auto-apply landscape layout for wide screens */
- @media (min-width: 1200px) {
- body:not(.vertical):not(.upsidedown):not(.vertical-reverse) {
- max-width: 100%;
- padding: 8px 12px 0 12px; /* Minimal padding to maximize space */
- padding-bottom: 0;
- }
-
- body:not(.vertical):not(.upsidedown):not(.vertical-reverse) #content-wrapper {
- display: grid;
- grid-template-rows: auto 1fr auto;
- gap: 8px; /* Reduced gap */
- height: 100vh;
- max-height: 100vh;
- overflow: hidden;
- }
-
- body:not(.vertical):not(.upsidedown):not(.vertical-reverse) .clock-container {
- grid-row: 1;
- margin-bottom: 0;
- padding: 6px 16px; /* Reduced padding */
- }
-
- body:not(.vertical):not(.upsidedown):not(.vertical-reverse) .main-content-grid {
- grid-row: 2;
- display: block;
- overflow-y: auto;
- overflow-x: hidden;
- min-height: 0;
- width: 100%;
- }
-
- body:not(.vertical):not(.upsidedown):not(.vertical-reverse) .departure-container {
- display: grid;
- grid-template-columns: repeat(4, 1fr); /* Fixed 4 columns to use all space */
- gap: 6px; /* Minimal gap */
- margin-bottom: 0;
- width: 100%;
- box-sizing: border-box;
- padding: 0; /* Remove any padding */
- }
-
- /* Ensure each column uses equal space */
- body:not(.vertical):not(.upsidedown):not(.vertical-reverse) .departure-container > * {
- min-width: 0; /* Allow flex shrinking */
- max-width: 100%; /* Prevent overflow */
- }
-
- /* Weather fixed at bottom */
- body:not(.vertical):not(.upsidedown):not(.vertical-reverse) .weather-section {
- grid-row: 3;
- position: sticky;
- bottom: 0;
- background-color: inherit;
- padding: 8px 0; /* Reduced padding */
- margin-top: 0;
- }
-
- body:not(.vertical):not(.upsidedown):not(.vertical-reverse) .weather-container {
- margin: 0;
- max-width: 100%;
- }
- }
-
- /* Dark mode styles */
- body.dark-mode {
- background-color: #222;
- color: #f5f5f5;
- }
-
- body.dark-mode .departure-card {
- background-color: #333;
- border-left-color: #0077cc;
- }
-
- body.dark-mode .config-modal-content {
- background-color: #333;
- color: #f5f5f5;
- }
-
- body.dark-mode .config-modal-body {
- background-color: #333;
- }
-
- body.dark-mode .config-modal-footer {
- background-color: #444;
- }
-
- body.dark-mode #config-cancel-button {
- background-color: #555;
- color: #f5f5f5;
- }
-
- body.dark-mode .time,
- body.dark-mode .destination {
- color: #f5f5f5;
- }
-
- body.dark-mode .direction,
- body.dark-mode .details,
- body.dark-mode .countdown,
- body.dark-mode .last-updated {
- color: #aaa;
- }
-
- body.dark-mode h2 {
- color: #0077cc;
- }
-
- body.dark-mode .sun-times {
- color: #aaa;
- }
-
- body.dark-mode .line-number {
- background-color: #0077cc;
- }
-
- /* Normal orientation */
- body.normal {
- max-width: 800px;
- }
- body.normal .departure-container {
- display: grid;
- grid-template-columns: 1fr;
- gap: 10px;
- }
-
- /* Landscape orientation - Optimized for wide screens */
- body.landscape {
- max-width: 100%;
- padding: 20px 40px;
- }
-
- /* Main content area: clock at top, then two-column layout below */
- body.landscape #content-wrapper {
- display: grid;
- grid-template-rows: auto 1fr;
- gap: 20px;
- height: 100vh;
- max-height: 100vh;
- overflow: hidden;
- }
-
- body.landscape .clock-container {
- grid-row: 1;
- margin-bottom: 0;
- }
-
- /* Main content grid: departures on left, weather on right */
- body.landscape .main-content-grid {
- grid-row: 2;
- display: grid;
- grid-template-columns: 1fr 380px;
- gap: 20px;
- overflow: hidden;
- min-height: 0;
- }
-
- /* Departures container: multi-column grid */
- body.landscape .departure-container {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(450px, 1fr));
- gap: 15px;
- overflow-y: auto;
- overflow-x: hidden;
- padding-right: 10px;
- min-height: 0;
- }
-
- /* Weather container: fixed width, scrollable */
- body.landscape .weather-container {
- overflow-y: auto;
- overflow-x: hidden;
- max-height: 100%;
- position: sticky;
- top: 0;
- align-self: start;
- }
-
- /* Better horizontal space usage in landscape */
- body.landscape .departure-card {
- min-height: 120px;
- }
-
- body.landscape .line-number-box {
- min-width: 120px;
- width: 120px;
- }
-
- body.landscape .line-number-large {
- font-size: 3.5em;
- }
-
- /* Site containers in landscape should be more compact */
- body.landscape .site-container {
- margin-bottom: 15px;
- }
-
- body.landscape .site-header {
- font-size: 1em;
- padding: 8px 12px;
- }
-
- /* Vertical orientation (90 degrees rotated) */
- body.vertical {
- max-width: 100%;
- height: 100vh;
- padding: 0;
- margin: 0;
- overflow: hidden;
- display: flex;
- justify-content: center;
- align-items: center;
- }
-
- body.vertical #content-wrapper {
- transform: rotate(90deg);
- transform-origin: center center;
- position: absolute;
- width: 100vh; /* Use viewport height for width */
- height: 100vw; /* Use viewport width for height */
- max-width: 800px; /* Limit width for better readability */
- padding: 20px;
- box-sizing: border-box;
- overflow-y: auto;
- background-color: transparent; /* Remove background color */
- left: 50%;
- top: 50%;
- margin-left: -50vh; /* Half of width */
- margin-top: -50vw; /* Half of height */
- }
-
- body.vertical .config-button {
- transform: rotate(-90deg);
- position: fixed;
- right: 10px;
- bottom: 10px; /* Changed from top to bottom */
- z-index: 1000;
- }
-
- body.vertical .departure-container {
- display: grid;
- grid-template-columns: 1fr;
- gap: 10px;
- }
-
- /* Upside down orientation (180 degrees rotated) */
- body.upsidedown {
- max-width: 100%;
- height: 100vh;
- padding: 0;
- margin: 0;
- overflow: hidden;
- display: flex;
- justify-content: center;
- align-items: center;
- }
-
- body.upsidedown #content-wrapper {
- transform: rotate(180deg);
- transform-origin: center center;
- position: absolute;
- width: 100%;
- max-width: 800px;
- padding: 20px;
- box-sizing: border-box;
- overflow-y: auto;
- background-color: transparent; /* Remove background color */
- left: 50%;
- top: 50%;
- margin-left: -400px; /* Half of max-width */
- margin-top: -50vh; /* Half of viewport height */
- }
-
- body.upsidedown .config-button {
- transform: rotate(-180deg);
- position: fixed;
- right: 10px;
- bottom: 10px; /* Changed from top to bottom */
- z-index: 1000;
- }
-
- body.upsidedown .departure-container {
- display: grid;
- grid-template-columns: 1fr;
- gap: 10px;
- }
-
- /* Vertical reverse orientation (270 degrees rotated) */
- body.vertical-reverse {
- max-width: 100%;
- height: 100vh;
- padding: 0;
- margin: 0;
- overflow: hidden;
- display: flex;
- justify-content: center;
- align-items: center;
- }
-
- body.vertical-reverse #content-wrapper {
- transform: rotate(270deg);
- transform-origin: center center;
- position: absolute;
- width: 100vh; /* Use viewport height for width */
- height: 100vw; /* Use viewport width for height */
- max-width: none; /* Remove max-width limitation */
- padding: 20px;
- box-sizing: border-box;
- overflow: visible; /* Show all content */
- background-color: transparent; /* Remove background color to show background image */
- left: 50%;
- top: 50%;
- margin-left: -50vh; /* Half of width */
- margin-top: -50vw; /* Half of height */
- }
-
- body.vertical-reverse .config-button {
- transform: rotate(-270deg);
- position: fixed;
- right: 10px;
- bottom: 10px; /* Changed from top to bottom */
- z-index: 1000;
- }
-
- body.vertical-reverse .departure-container {
- display: grid;
- grid-template-columns: 1fr;
- gap: 10px;
- width: 100%; /* Ensure full width */
- }
-
- /* Mode indicators - using a class instead of pseudo-element */
- .mode-indicator {
- font-size: 0.7em;
- color: #666;
- font-weight: normal;
- display: inline;
- }
+/* ========================================
+ CSS Custom Properties
+ ======================================== */
+:root {
+ --color-primary: #0061a1;
+ --color-primary-dark: #004d80;
+ --color-primary-light: #0077cc;
+ --color-accent: #4fc3f7;
+ --color-bg: #f5f5f5;
+ --color-bg-dark: #222;
+ --color-text: #333;
+ --color-text-light: #f5f5f5;
+ --color-text-muted: #666;
+ --color-text-muted-dark: #aaa;
+ --color-urgent: #c41e3a;
+ --color-urgent-dark: #ff6b6b;
+ --color-now: #00a651;
+ --color-now-dark: #4ecdc4;
+ --color-border: #ddd;
+ --color-border-dark: #555;
+ --color-surface: white;
+ --color-surface-dark: #333;
+ --color-surface-darker: #444;
+ --radius-sm: 4px;
+ --radius-md: 6px;
+ --radius-lg: 8px;
+ --shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.1);
+ --shadow-md: 0 4px 8px rgba(0, 0, 0, 0.2);
+ --gradient-blue: linear-gradient(135deg, #0061a1 0%, #004d80 100%);
+}
+
+/* ========================================
+ Base Styles
+ ======================================== */
+body {
+ font-family: Arial, sans-serif;
+ margin: 0;
+ padding: 0;
+ background-color: var(--color-bg);
+ color: var(--color-text);
+ transition: background-color 0.5s ease, color 0.5s ease;
+ height: 100vh;
+ overflow: hidden;
+}
+
+/* For normal orientation on narrow screens, add padding */
+@media (max-width: 1199px) {
+ body.normal {
+ padding: 20px;
+ }
+}
+
+/* Auto-apply wide layout for normal orientation on large screens */
+@media (min-width: 1200px) {
+ body.normal {
+ max-width: 100%;
+ padding: 8px 12px 0 12px;
+ }
+
+ body.normal #content-wrapper {
+ display: grid;
+ grid-template-rows: auto 1fr auto;
+ gap: 8px;
+ height: 100vh;
+ max-height: 100vh;
+ overflow: hidden;
+ }
+
+ body.normal .clock-container {
+ grid-row: 1;
+ margin-bottom: 0;
+ padding: 6px 16px;
+ }
+
+ body.normal .main-content-grid {
+ grid-row: 2;
+ display: block;
+ overflow-y: auto;
+ overflow-x: hidden;
+ min-height: 0;
+ width: 100%;
+ }
+
+ body.normal .departure-container {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 6px;
+ margin-bottom: 0;
+ width: 100%;
+ box-sizing: border-box;
+ padding: 0;
+ }
+
+ body.normal .departure-container > * {
+ min-width: 0;
+ max-width: 100%;
+ }
+
+ body.normal .weather-section {
+ grid-row: 3;
+ position: sticky;
+ bottom: 35px;
+ background-color: inherit;
+ padding: 8px 0 0 0;
+ margin-top: 0;
+ }
+
+ body.normal .weather-container {
+ margin: 0;
+ max-width: 100%;
+ }
+}
+
+/* ========================================
+ Dark Mode - Layout-level overrides only
+ ======================================== */
+body.dark-mode {
+ background-color: var(--color-bg-dark);
+ color: var(--color-text-light);
+}
+
+body.dark-mode .departure-card {
+ background-color: var(--color-surface-dark);
+ border-left-color: var(--color-primary-light);
+}
+
+/* ========================================
+ Orientation: Normal
+ ======================================== */
+body.normal {
+ max-width: 800px;
+}
+
+body.normal .departure-container {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 10px;
+}
+
+/* ========================================
+ Orientation: Landscape
+ ======================================== */
+body.landscape {
+ max-width: 100%;
+ padding: 20px 40px;
+}
+
+body.landscape #content-wrapper {
+ display: grid;
+ grid-template-rows: auto 1fr;
+ gap: 20px;
+ height: 100vh;
+ max-height: 100vh;
+ overflow: hidden;
+}
+
+body.landscape .clock-container {
+ grid-row: 1;
+ margin-bottom: 0;
+}
+
+body.landscape .main-content-grid {
+ grid-row: 2;
+ display: grid;
+ grid-template-columns: 1fr 380px;
+ gap: 20px;
+ overflow: hidden;
+ min-height: 0;
+}
+
+body.landscape .departure-container {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(450px, 1fr));
+ gap: 15px;
+ overflow-y: auto;
+ overflow-x: hidden;
+ padding-right: 10px;
+ min-height: 0;
+}
+
+body.landscape .weather-container {
+ overflow-y: auto;
+ overflow-x: hidden;
+ max-height: 100%;
+ position: sticky;
+ top: 0;
+ align-self: start;
+}
+
+body.landscape .departure-card {
+ min-height: 120px;
+}
+
+body.landscape .line-number-box {
+ min-width: 120px;
+ width: 120px;
+}
+
+body.landscape .line-number-large {
+ font-size: 3.5em;
+}
+
+body.landscape .site-container {
+ margin-bottom: 15px;
+}
+
+body.landscape .site-header {
+ font-size: 1em;
+ padding: 8px 12px;
+}
+
+/* ========================================
+ Orientation: Vertical (90deg)
+ ======================================== */
+body.vertical {
+ max-width: 100%;
+ height: 100vh;
+ padding: 0;
+ margin: 0;
+ overflow: hidden;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+body.vertical #content-wrapper {
+ transform: rotate(90deg);
+ transform-origin: center center;
+ position: absolute;
+ width: 100vh;
+ height: 100vw;
+ max-width: 800px;
+ padding: 20px;
+ box-sizing: border-box;
+ overflow-y: auto;
+ background-color: transparent;
+ left: 50%;
+ top: 50%;
+ margin-left: -50vh;
+ margin-top: -50vw;
+}
+
+body.vertical .config-button {
+ transform: rotate(-90deg);
+ position: fixed;
+ right: 10px;
+ bottom: 10px;
+ z-index: 1000;
+}
+
+body.vertical .departure-container {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 10px;
+}
+
+/* ========================================
+ Orientation: Upside Down (180deg)
+ ======================================== */
+body.upsidedown {
+ max-width: 100%;
+ height: 100vh;
+ padding: 0;
+ margin: 0;
+ overflow: hidden;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+body.upsidedown #content-wrapper {
+ transform: rotate(180deg);
+ transform-origin: center center;
+ position: absolute;
+ width: 100%;
+ max-width: 800px;
+ padding: 20px;
+ box-sizing: border-box;
+ overflow-y: auto;
+ background-color: transparent;
+ left: 50%;
+ top: 50%;
+ transform: rotate(180deg) translate(50%, 50%);
+}
+
+body.upsidedown .config-button {
+ transform: rotate(-180deg);
+ position: fixed;
+ right: 10px;
+ bottom: 10px;
+ z-index: 1000;
+}
+
+body.upsidedown .departure-container {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 10px;
+}
+
+/* ========================================
+ Orientation: Vertical Reverse (270deg)
+ ======================================== */
+body.vertical-reverse {
+ max-width: 100%;
+ height: 100vh;
+ padding: 0;
+ margin: 0;
+ overflow: hidden;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+body.vertical-reverse #content-wrapper {
+ transform: rotate(270deg);
+ transform-origin: center center;
+ position: absolute;
+ width: 100vh;
+ height: 100vw;
+ max-width: none;
+ padding: 20px;
+ box-sizing: border-box;
+ overflow: visible;
+ background-color: transparent;
+ left: 50%;
+ top: 50%;
+ margin-left: -50vh;
+ margin-top: -50vw;
+}
+
+body.vertical-reverse .config-button {
+ transform: rotate(-270deg);
+ position: fixed;
+ right: 10px;
+ bottom: 10px;
+ z-index: 1000;
+}
+
+body.vertical-reverse .departure-container {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 10px;
+ width: 100%;
+}
+
+/* ========================================
+ Mode Indicator
+ ======================================== */
+.mode-indicator {
+ font-size: 0.7em;
+ color: var(--color-text-muted);
+ font-weight: normal;
+ display: inline;
+}
diff --git a/public/js/components/ConfigManager.js b/public/js/components/ConfigManager.js
index 6c7f6b5..04d3718 100644
--- a/public/js/components/ConfigManager.js
+++ b/public/js/components/ConfigManager.js
@@ -68,6 +68,9 @@ class ConfigManager {
buttonContainer.id = this.options.configButtonId;
buttonContainer.className = 'config-button';
buttonContainer.title = 'Settings';
+ buttonContainer.setAttribute('role', 'button');
+ buttonContainer.setAttribute('aria-label', 'Open settings');
+ buttonContainer.setAttribute('tabindex', '0');
buttonContainer.innerHTML = `