Compare commits
12 Commits
f2e5f23b18
...
ba0cdbab64
| Author | SHA1 | Date | |
|---|---|---|---|
| ba0cdbab64 | |||
| 57cd9809e0 | |||
| 5f60ed88c8 | |||
| 4a6012b097 | |||
| f0b04a7a0d | |||
| 84ce6efb2d | |||
| 92166cea6e | |||
| 98441bc906 | |||
| 3c9ae03cb6 | |||
| 1b1460fd45 | |||
| 2b7fc6b016 | |||
| 1e776c1c9a |
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"orientation": "landscape",
|
"orientation": "landscape",
|
||||||
"darkMode": "auto",
|
"darkMode": "off",
|
||||||
"backgroundImage": "https://images.unsplash.com/photo-1509356843151-3e7d96241e11?q=80&w=1000",
|
"backgroundImage": "https://images.unsplash.com/photo-1509356843151-3e7d96241e11?q=80&w=1000",
|
||||||
"backgroundOpacity": 0.45,
|
"backgroundOpacity": 0.25,
|
||||||
"sites": [
|
"sites": [
|
||||||
{
|
{
|
||||||
"id": "1411",
|
"id": "1411",
|
||||||
@@ -23,6 +23,16 @@
|
|||||||
"id": "1110",
|
"id": "1110",
|
||||||
"name": "Radiohuset",
|
"name": "Radiohuset",
|
||||||
"enabled": true
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1007",
|
||||||
|
"name": "Cityterminalen (på Kungsbron)",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "9636",
|
||||||
|
"name": "Bråvallavägen",
|
||||||
|
"enabled": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"combineSameDirection": true
|
"combineSameDirection": true
|
||||||
|
|||||||
@@ -1,57 +1,25 @@
|
|||||||
/* Clock styles - Modern entryway kiosk design with blue glow - Ribbon banner */
|
/* Clock styles - Subtle header clock */
|
||||||
.clock-container {
|
.clock-container {
|
||||||
background: linear-gradient(135deg, #003366 0%, #004080 50%, #0059b3 100%);
|
background: linear-gradient(135deg, #003366 0%, #004d80 100%);
|
||||||
color: var(--color-surface);
|
color: white;
|
||||||
padding: 8px 20px;
|
padding: 6px 20px;
|
||||||
border-radius: 8px;
|
border-radius: 4px;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
box-shadow: 0 0 20px rgba(0, 89, 179, 0.6),
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||||
0 0 40px rgba(0, 89, 179, 0.4),
|
|
||||||
0 4px 12px rgba(0, 0, 0, 0.15),
|
|
||||||
inset 0 1px 0 rgba(255, 255, 255, 0.3);
|
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
transition: box-shadow 0.3s ease;
|
|
||||||
border: 2px solid rgba(255, 255, 255, 0.2);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 15px;
|
gap: 15px;
|
||||||
}
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
transition: box-shadow 0.3s ease;
|
||||||
.clock-container::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background: linear-gradient(135deg, rgba(255, 255, 255, 0.15) 0%, rgba(255, 255, 255, 0.05) 50%, rgba(255, 255, 255, 0.1) 100%);
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.clock-container:hover {
|
|
||||||
box-shadow: 0 0 30px rgba(0, 89, 179, 0.8),
|
|
||||||
0 0 60px rgba(0, 89, 179, 0.5),
|
|
||||||
0 4px 12px rgba(0, 0, 0, 0.15),
|
|
||||||
inset 0 1px 0 rgba(255, 255, 255, 0.3);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body.dark-mode .clock-container {
|
body.dark-mode .clock-container {
|
||||||
background: linear-gradient(135deg, #001a33 0%, #002d5c 50%, #004080 100%);
|
background: linear-gradient(135deg, #001a33 0%, #002d5c 100%);
|
||||||
box-shadow: 0 0 25px rgba(0, 89, 179, 0.7),
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.4);
|
||||||
0 0 50px rgba(0, 89, 179, 0.4),
|
|
||||||
0 4px 12px rgba(0, 0, 0, 0.3),
|
|
||||||
inset 0 1px 0 rgba(255, 255, 255, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
body.dark-mode .clock-container:hover {
|
|
||||||
box-shadow: 0 0 35px rgba(0, 89, 179, 0.9),
|
|
||||||
0 0 70px rgba(0, 89, 179, 0.6),
|
|
||||||
0 4px 12px rgba(0, 0, 0, 0.3),
|
|
||||||
inset 0 1px 0 rgba(255, 255, 255, 0.2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Configuration button styles */
|
/* Configuration button styles */
|
||||||
@@ -88,6 +56,12 @@ body.dark-mode .clock-container:hover {
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.landscape .config-button {
|
||||||
|
background-color: rgba(255, 255, 255, 0.15);
|
||||||
|
opacity: 0.7;
|
||||||
|
bottom: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Configuration modal styles */
|
/* Configuration modal styles */
|
||||||
.config-modal {
|
.config-modal {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -350,7 +324,7 @@ body.dark-mode .site-search-result div:first-child {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body.dark-mode .site-search-result div:last-child {
|
body.dark-mode .site-search-result div:last-child {
|
||||||
color: var(--color-text-muted-dark);
|
color: var(--color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
#search-site-button {
|
#search-site-button {
|
||||||
@@ -469,10 +443,20 @@ body.dark-mode #config-cancel-button:hover {
|
|||||||
background-color: var(--color-text-muted);
|
background-color: var(--color-text-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Tabular figures for all numeric displays */
|
||||||
|
.clock-time,
|
||||||
|
.countdown-large,
|
||||||
|
.next-departures,
|
||||||
|
#custom-weather .temperature,
|
||||||
|
#custom-weather .forecast-hour .temp {
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
font-family: var(--font-numbers);
|
||||||
|
}
|
||||||
|
|
||||||
.clock-time {
|
.clock-time {
|
||||||
font-size: 2.2em;
|
font-size: 2.2em;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif;
|
font-family: var(--font-numbers);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
letter-spacing: 1px;
|
letter-spacing: 1px;
|
||||||
@@ -488,7 +472,7 @@ body.dark-mode #config-cancel-button:hover {
|
|||||||
.clock-date {
|
.clock-date {
|
||||||
font-size: 2.2em;
|
font-size: 2.2em;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif;
|
font-family: var(--font-primary);
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
opacity: 0.98;
|
opacity: 0.98;
|
||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.5px;
|
||||||
@@ -673,65 +657,27 @@ body.dark-mode .departure-card {
|
|||||||
|
|
||||||
/* Direction arrow indicator */
|
/* Direction arrow indicator */
|
||||||
.direction-arrow-box {
|
.direction-arrow-box {
|
||||||
width: 32px;
|
width: 28px;
|
||||||
height: 32px;
|
height: 28px;
|
||||||
border-radius: 4px;
|
border-radius: 50%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
font-size: 1.4em;
|
font-size: 1.1em;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
border: none;
|
||||||
border: 1.5px solid rgba(255, 255, 255, 0.3);
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.direction-arrow-box.left {
|
.direction-arrow-box.left {
|
||||||
background: repeating-linear-gradient(
|
background: rgba(255, 140, 0, 0.2);
|
||||||
45deg,
|
color: #ff9800;
|
||||||
#fff5e6,
|
|
||||||
#fff5e6 4px,
|
|
||||||
#ffe6cc 4px,
|
|
||||||
#ffe6cc 8px
|
|
||||||
);
|
|
||||||
color: #ff6600;
|
|
||||||
border-color: #ff6600;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.direction-arrow-box.right {
|
.direction-arrow-box.right {
|
||||||
background: repeating-linear-gradient(
|
background: rgba(79, 195, 247, 0.2);
|
||||||
45deg,
|
|
||||||
#e6f2ff,
|
|
||||||
#e6f2ff 4px,
|
|
||||||
#cce6ff 4px,
|
|
||||||
#cce6ff 8px
|
|
||||||
);
|
|
||||||
color: #0066cc;
|
|
||||||
border-color: #0066cc;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.dark-mode .direction-arrow-box.left {
|
|
||||||
background: repeating-linear-gradient(
|
|
||||||
45deg,
|
|
||||||
#664422,
|
|
||||||
#664422 4px,
|
|
||||||
#553311 4px,
|
|
||||||
#553311 8px
|
|
||||||
);
|
|
||||||
color: #ff8800;
|
|
||||||
border-color: #ff8800;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.dark-mode .direction-arrow-box.right {
|
|
||||||
background: repeating-linear-gradient(
|
|
||||||
45deg,
|
|
||||||
#223366,
|
|
||||||
#223366 4px,
|
|
||||||
#112255 4px,
|
|
||||||
#112255 8px
|
|
||||||
);
|
|
||||||
color: var(--color-accent);
|
color: var(--color-accent);
|
||||||
border-color: var(--color-accent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Destination text */
|
/* Destination text */
|
||||||
@@ -755,20 +701,10 @@ body.dark-mode .direction-destination {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
gap: 3px;
|
gap: 2px;
|
||||||
min-width: 110px;
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
max-width: 110px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Time display */
|
|
||||||
.time-display {
|
|
||||||
display: flex;
|
|
||||||
align-items: baseline;
|
|
||||||
gap: 4px;
|
|
||||||
white-space: nowrap;
|
|
||||||
font-size: 0.95em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Pulse animation for urgent and now states */
|
/* Pulse animation for urgent and now states */
|
||||||
@keyframes pulse-glow {
|
@keyframes pulse-glow {
|
||||||
@@ -838,19 +774,7 @@ body.landscape .countdown-large.soon {
|
|||||||
text-shadow: 0 0 8px #f39c12, 0 0 12px #f39c12, 0 0 16px rgba(243, 156, 18, 0.8);
|
text-shadow: 0 0 8px #f39c12, 0 0 12px #f39c12, 0 0 16px rgba(243, 156, 18, 0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Time range (legacy) */
|
/* Next departures */
|
||||||
.time-range {
|
|
||||||
font-size: 0.85em;
|
|
||||||
color: var(--color-text-muted);
|
|
||||||
font-weight: 500;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.dark-mode .time-range {
|
|
||||||
color: var(--color-text-muted-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Next departures (replaces time-range) */
|
|
||||||
.next-departures {
|
.next-departures {
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
color: var(--color-text-muted);
|
color: var(--color-text-muted);
|
||||||
@@ -1128,15 +1052,16 @@ body.landscape .site-header {
|
|||||||
|
|
||||||
body.landscape .site-name {
|
body.landscape .site-name {
|
||||||
display: block;
|
display: block;
|
||||||
background: linear-gradient(135deg, #0061a1 0%, #003d66 100%);
|
background: linear-gradient(90deg, rgba(0, 97, 161, 0.45), rgba(200, 30, 50, 0.45));
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 1px;
|
letter-spacing: 2px;
|
||||||
padding: 6px 16px;
|
padding: 5px 16px;
|
||||||
border-radius: 4px;
|
border-radius: 2px;
|
||||||
box-shadow: 0 2px 8px rgba(0, 97, 161, 0.4);
|
box-shadow: none;
|
||||||
font-size: 0.85em;
|
font-size: 0.95em;
|
||||||
|
border-left: 4px solid var(--color-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Compact weather bar - hidden by default, shown in landscape */
|
/* Compact weather bar - hidden by default, shown in landscape */
|
||||||
@@ -1149,7 +1074,7 @@ body.landscape #compact-weather-bar {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
background: rgba(0, 0, 0, 0.5);
|
background: var(--color-bar-bg);
|
||||||
color: #ddd;
|
color: #ddd;
|
||||||
padding: 6px 20px;
|
padding: 6px 20px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
@@ -1173,13 +1098,7 @@ body.landscape #compact-weather-bar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body.landscape #news-ticker {
|
body.landscape #news-ticker {
|
||||||
display: block;
|
display: none;
|
||||||
overflow: hidden;
|
|
||||||
background: var(--ticker-bg);
|
|
||||||
height: var(--ticker-height);
|
|
||||||
line-height: var(--ticker-height);
|
|
||||||
border-radius: 4px;
|
|
||||||
position: relative;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#news-ticker .ticker-content {
|
#news-ticker .ticker-content {
|
||||||
@@ -1222,7 +1141,7 @@ body.landscape #news-ticker {
|
|||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: #191970;
|
background-color: var(--color-daylight-night);
|
||||||
background-image: var(--daylight-gradient, none);
|
background-image: var(--daylight-gradient, none);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1236,13 +1155,28 @@ body.landscape #news-ticker {
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#daylight-hours-bar .sun-icon {
|
#daylight-hours-bar .sun-icon,
|
||||||
|
#daylight-hours-bar .moon-icon {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
display: block;
|
display: block;
|
||||||
|
animation: celestial-pulse 3s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
#daylight-hours-bar .sun-icon {
|
||||||
filter: drop-shadow(0 0 4px rgba(255, 215, 0, 0.8));
|
filter: drop-shadow(0 0 4px rgba(255, 215, 0, 0.8));
|
||||||
text-shadow: 0 0 8px rgba(255, 215, 0, 0.6);
|
text-shadow: 0 0 8px rgba(255, 215, 0, 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#daylight-hours-bar .moon-icon {
|
||||||
|
filter: drop-shadow(0 0 4px rgba(180, 200, 255, 0.8));
|
||||||
|
text-shadow: 0 0 8px rgba(180, 200, 255, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes celestial-pulse {
|
||||||
|
0%, 100% { transform: scale(1); opacity: 0.85; }
|
||||||
|
50% { transform: scale(1.3); opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
/* Landscape: daylight bar sits in grid instead of fixed overlay */
|
/* Landscape: daylight bar sits in grid instead of fixed overlay */
|
||||||
body.landscape #daylight-hours-bar {
|
body.landscape #daylight-hours-bar {
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -1459,18 +1393,6 @@ body.dark-mode .config-search-input:focus-visible {
|
|||||||
color: #ccc;
|
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
|
Responsive Design - Departure cards
|
||||||
======================================== */
|
======================================== */
|
||||||
@@ -1501,14 +1423,11 @@ body.dark-mode .time-range {
|
|||||||
max-width: 90px;
|
max-width: 90px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.time-display {
|
|
||||||
font-size: 0.85em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.direction-arrow-box {
|
.direction-arrow-box {
|
||||||
width: 26px;
|
width: 24px;
|
||||||
height: 26px;
|
height: 24px;
|
||||||
font-size: 1.1em;
|
font-size: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Weather responsive */
|
/* Weather responsive */
|
||||||
@@ -1594,13 +1513,9 @@ body.dark-mode .time-range {
|
|||||||
max-width: 75px;
|
max-width: 75px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.time-range {
|
|
||||||
font-size: 0.75em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.direction-arrow-box {
|
.direction-arrow-box {
|
||||||
width: 22px;
|
width: 20px;
|
||||||
height: 22px;
|
height: 20px;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,13 +34,24 @@
|
|||||||
--ticker-height: 36px;
|
--ticker-height: 36px;
|
||||||
--ticker-speed: 30s;
|
--ticker-speed: 30s;
|
||||||
--ticker-bg: rgba(0, 0, 0, 0.85);
|
--ticker-bg: rgba(0, 0, 0, 0.85);
|
||||||
|
--font-primary: 'Segoe UI', 'Roboto', 'Helvetica Neue', Arial, sans-serif;
|
||||||
|
--font-numbers: 'Segoe UI', 'Roboto Mono', 'SF Mono', 'Consolas', monospace;
|
||||||
|
--color-bg-kiosk: #1a1a2e;
|
||||||
|
--color-surface-kiosk: rgba(255, 255, 255, 0.08);
|
||||||
|
--color-surface-kiosk-hover: rgba(255, 255, 255, 0.12);
|
||||||
|
--color-text-secondary: #bbb;
|
||||||
|
--color-text-tertiary: #888;
|
||||||
|
--color-bar-bg: rgba(0, 0, 0, 0.5);
|
||||||
|
--color-daylight-night: #191970;
|
||||||
|
--color-daylight-dawn: #FF6B35;
|
||||||
|
--color-daylight-day: #FFEB3B;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ========================================
|
/* ========================================
|
||||||
Base Styles
|
Base Styles
|
||||||
======================================== */
|
======================================== */
|
||||||
body {
|
body {
|
||||||
font-family: Arial, sans-serif;
|
font-family: var(--font-primary);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
background-color: var(--color-bg);
|
background-color: var(--color-bg);
|
||||||
@@ -116,6 +127,7 @@ body {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ========================================
|
/* ========================================
|
||||||
@@ -150,7 +162,7 @@ body.normal .departure-container {
|
|||||||
body.landscape {
|
body.landscape {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
padding: 10px 20px 0 20px;
|
padding: 10px 20px 0 20px;
|
||||||
background-color: #1a1a2e;
|
background-color: var(--color-bg-kiosk);
|
||||||
color: var(--color-text-light);
|
color: var(--color-text-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,7 +174,7 @@ body.landscape #background-overlay {
|
|||||||
body.landscape #content-wrapper {
|
body.landscape #content-wrapper {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: auto auto 1fr auto auto;
|
grid-template-rows: auto auto 1fr auto auto;
|
||||||
gap: var(--kiosk-gap);
|
gap: 4px;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
max-height: 100vh;
|
max-height: 100vh;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -173,9 +185,11 @@ body.landscape .clock-container {
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Compact weather bar sits in row 2 */
|
/* Compact weather bar in row 2 */
|
||||||
body.landscape #compact-weather-bar {
|
body.landscape #compact-weather-bar {
|
||||||
grid-row: 2;
|
grid-row: 2;
|
||||||
|
font-size: 0.85em;
|
||||||
|
padding: 2px 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.landscape .main-content-grid {
|
body.landscape .main-content-grid {
|
||||||
@@ -185,15 +199,62 @@ body.landscape .main-content-grid {
|
|||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hide the full weather widget in landscape */
|
/* Weather forecast strip at bottom - row 4 */
|
||||||
body.landscape .weather-section {
|
body.landscape .weather-section {
|
||||||
|
grid-row: 4;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.landscape .weather-container {
|
||||||
|
overflow: hidden;
|
||||||
|
max-height: none;
|
||||||
|
position: static;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.landscape #custom-weather {
|
||||||
|
background: var(--color-bar-bg);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.landscape #custom-weather .current-weather {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.landscape #custom-weather .forecast {
|
||||||
|
display: flex;
|
||||||
|
gap: 0;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.landscape #custom-weather .forecast-hour {
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 2px 8px;
|
||||||
|
min-width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.landscape #custom-weather .forecast-hour .time {
|
||||||
|
font-size: 0.65em;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.landscape #custom-weather .forecast-hour .icon img {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.landscape #custom-weather .forecast-hour .temp {
|
||||||
|
font-size: 0.65em;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.landscape #custom-weather .attribution {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.landscape .departure-container {
|
body.landscape .departure-container {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
|
gap: 12px;
|
||||||
gap: var(--kiosk-gap);
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
padding-right: 4px;
|
padding-right: 4px;
|
||||||
@@ -201,46 +262,52 @@ body.landscape .departure-container {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.landscape .weather-container {
|
body.landscape .departure-column {
|
||||||
overflow-y: auto;
|
flex: 1;
|
||||||
overflow-x: hidden;
|
min-width: 0;
|
||||||
max-height: 100%;
|
display: flex;
|
||||||
position: sticky;
|
flex-direction: column;
|
||||||
top: 0;
|
gap: 4px;
|
||||||
align-self: start;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.landscape .departure-card {
|
|
||||||
min-height: 65px;
|
|
||||||
background-color: rgba(255, 255, 255, 0.08);
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
|
||||||
}
|
|
||||||
|
|
||||||
body.landscape .line-number-box {
|
|
||||||
min-width: 90px;
|
|
||||||
width: 90px;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.landscape .line-number-large {
|
|
||||||
font-size: 3.5em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body.landscape .site-container {
|
body.landscape .site-container {
|
||||||
margin-bottom: 6px;
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.landscape .departure-card {
|
||||||
|
min-height: 0;
|
||||||
|
background-color: var(--color-surface-kiosk);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||||
|
border-radius: 3px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.landscape .line-number-box {
|
||||||
|
min-width: 48px;
|
||||||
|
width: 48px;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.landscape .line-number-large {
|
||||||
|
font-size: 1.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.landscape .transport-mode-icon .transport-icon {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.landscape .site-container {
|
||||||
|
margin-bottom: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.landscape .site-header {
|
body.landscape .site-header {
|
||||||
font-size: 1em;
|
font-size: 0.8em;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin-bottom: 6px;
|
margin-bottom: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* News ticker sits in row 4 */
|
/* Daylight bar in row 5 */
|
||||||
body.landscape #news-ticker {
|
|
||||||
grid-row: 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Daylight bar sits in row 5 */
|
|
||||||
body.landscape #daylight-hours-bar {
|
body.landscape #daylight-hours-bar {
|
||||||
grid-row: 5;
|
grid-row: 5;
|
||||||
}
|
}
|
||||||
@@ -254,37 +321,47 @@ body.landscape .countdown-large {
|
|||||||
color: var(--color-text-light);
|
color: var(--color-text-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
body.landscape .time-range,
|
|
||||||
body.landscape .next-departures {
|
body.landscape .next-departures {
|
||||||
color: #bbb;
|
color: var(--color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tighter card spacing in landscape */
|
/* Compact card spacing in landscape */
|
||||||
body.landscape .directions-wrapper {
|
body.landscape .directions-wrapper {
|
||||||
padding: 4px 8px;
|
padding: 3px 6px;
|
||||||
gap: 2px;
|
gap: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.landscape .direction-row {
|
body.landscape .direction-row {
|
||||||
min-height: 28px;
|
min-height: 30px;
|
||||||
gap: 6px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hero countdown in landscape */
|
body.landscape .direction-destination {
|
||||||
|
font-size: 1.4em;
|
||||||
|
color: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.landscape .direction-arrow-box {
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
font-size: 0.85em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Countdown in landscape */
|
||||||
body.landscape .countdown-large {
|
body.landscape .countdown-large {
|
||||||
font-size: var(--kiosk-countdown-size);
|
font-size: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.landscape .times-container {
|
body.landscape .times-container {
|
||||||
min-width: 130px;
|
min-width: 90px;
|
||||||
max-width: 160px;
|
max-width: 140px;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.landscape .next-departures {
|
body.landscape .next-departures {
|
||||||
font-size: 0.7em;
|
font-size: 0.75em;
|
||||||
color: #bbb;
|
color: var(--color-text-secondary);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ========================================
|
/* ========================================
|
||||||
|
|||||||
@@ -119,16 +119,16 @@ class DeparturesManager {
|
|||||||
if (diffMinutes <= 0) {
|
if (diffMinutes <= 0) {
|
||||||
return 'Nu';
|
return 'Nu';
|
||||||
} else if (diffMinutes === 1) {
|
} else if (diffMinutes === 1) {
|
||||||
return 'In 1 minute';
|
return 'Om 1 minut';
|
||||||
} else if (diffMinutes < 60) {
|
} else if (diffMinutes < 60) {
|
||||||
return `In ${diffMinutes} minutes`;
|
return `Om ${diffMinutes} minuter`;
|
||||||
} else {
|
} else {
|
||||||
const hours = Math.floor(diffMinutes / 60);
|
const hours = Math.floor(diffMinutes / 60);
|
||||||
const minutes = diffMinutes % 60;
|
const minutes = diffMinutes % 60;
|
||||||
if (minutes === 0) {
|
if (minutes === 0) {
|
||||||
return `In ${hours} hour${hours > 1 ? 's' : ''}`;
|
return `Om ${hours} timm${hours > 1 ? 'ar' : 'e'}`;
|
||||||
} else {
|
} else {
|
||||||
return `In ${hours} hour${hours > 1 ? 's' : ''} and ${minutes} minute${minutes > 1 ? 's' : ''}`;
|
return `Om ${hours} timm${hours > 1 ? 'ar' : 'e'} och ${minutes} minut${minutes > 1 ? 'er' : ''}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -244,6 +244,8 @@ class DeparturesManager {
|
|||||||
return { countdownText, countdownClass };
|
return { countdownText, countdownClass };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Legacy single-site display methods (fallback if API returns data.departures instead of data.sites) ---
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a departure card element (legacy format)
|
* Create a departure card element (legacy format)
|
||||||
* @param {Object} departure - Departure object
|
* @param {Object} departure - Departure object
|
||||||
@@ -359,33 +361,25 @@ class DeparturesManager {
|
|||||||
timesContainer.className = 'times-container';
|
timesContainer.className = 'times-container';
|
||||||
|
|
||||||
const firstDeparture = direction.departures[0];
|
const firstDeparture = direction.departures[0];
|
||||||
const secondDeparture = direction.departures[1];
|
|
||||||
|
|
||||||
if (firstDeparture) {
|
if (firstDeparture) {
|
||||||
const displayTime = firstDeparture.display;
|
|
||||||
const departureTime = this.getDepartureTime(firstDeparture);
|
|
||||||
const timeDisplay = DeparturesManager.formatDateTime(departureTime);
|
|
||||||
|
|
||||||
const { countdownText, countdownClass } = this.getCountdownInfo(firstDeparture);
|
const { countdownText, countdownClass } = this.getCountdownInfo(firstDeparture);
|
||||||
|
|
||||||
const timeDisplayElement = document.createElement('div');
|
// Primary countdown - big and prominent
|
||||||
timeDisplayElement.className = 'time-display';
|
|
||||||
|
|
||||||
const countdownSpan = document.createElement('span');
|
const countdownSpan = document.createElement('span');
|
||||||
countdownSpan.className = `countdown-large ${countdownClass}`;
|
countdownSpan.className = `countdown-large ${countdownClass}`;
|
||||||
countdownSpan.textContent = countdownText;
|
countdownSpan.textContent = countdownText;
|
||||||
|
timesContainer.appendChild(countdownSpan);
|
||||||
|
|
||||||
timeDisplayElement.appendChild(countdownSpan);
|
// Next departures - separate line, excludes current departure
|
||||||
|
const upcomingTimes = direction.departures.slice(1, 4)
|
||||||
// Show next 2-3 absolute times as small text
|
|
||||||
const nextTimesSpan = document.createElement('span');
|
|
||||||
nextTimesSpan.className = 'next-departures';
|
|
||||||
const upcomingTimes = direction.departures.slice(0, 3)
|
|
||||||
.map(d => DeparturesManager.formatDateTime(this.getDepartureTime(d)));
|
.map(d => DeparturesManager.formatDateTime(this.getDepartureTime(d)));
|
||||||
nextTimesSpan.textContent = upcomingTimes.join(' ');
|
if (upcomingTimes.length > 0) {
|
||||||
timeDisplayElement.appendChild(nextTimesSpan);
|
const nextTimesDiv = document.createElement('div');
|
||||||
|
nextTimesDiv.className = 'next-departures';
|
||||||
timesContainer.appendChild(timeDisplayElement);
|
nextTimesDiv.textContent = 'Sedan: ' + upcomingTimes.join(' ');
|
||||||
|
timesContainer.appendChild(nextTimesDiv);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
directionRow.appendChild(timesContainer);
|
directionRow.appendChild(timesContainer);
|
||||||
@@ -397,8 +391,11 @@ class DeparturesManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Legacy single-site display methods (continued) ---
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display departures in the UI
|
* Display departures in the UI (legacy single-site path)
|
||||||
|
* Called when API returns data.departures instead of data.sites.
|
||||||
* @param {Array} departures - Array of departure objects
|
* @param {Array} departures - Array of departure objects
|
||||||
*/
|
*/
|
||||||
displayDepartures(departures) {
|
displayDepartures(departures) {
|
||||||
@@ -422,7 +419,7 @@ class DeparturesManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update existing cards or add new ones
|
* Update existing cards or add new ones (legacy single-site path)
|
||||||
* @param {Array} newDepartures - Array of new departure objects
|
* @param {Array} newDepartures - Array of new departure objects
|
||||||
*/
|
*/
|
||||||
updateExistingCards(newDepartures) {
|
updateExistingCards(newDepartures) {
|
||||||
@@ -468,7 +465,7 @@ class DeparturesManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update card content
|
* Update card content (legacy single-site path)
|
||||||
* @param {HTMLElement} card - Card element
|
* @param {HTMLElement} card - Card element
|
||||||
* @param {Object} departure - Departure object
|
* @param {Object} departure - Departure object
|
||||||
*/
|
*/
|
||||||
@@ -490,6 +487,8 @@ class DeparturesManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- End of legacy single-site display methods ---
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add highlight effect to element
|
* Add highlight effect to element
|
||||||
* @param {HTMLElement} element - Element to highlight
|
* @param {HTMLElement} element - Element to highlight
|
||||||
@@ -509,32 +508,80 @@ class DeparturesManager {
|
|||||||
const config = this.getConfig();
|
const config = this.getConfig();
|
||||||
const enabledSites = config.sites.filter(site => site.enabled);
|
const enabledSites = config.sites.filter(site => site.enabled);
|
||||||
|
|
||||||
this.container.innerHTML = '';
|
// Build new content off-DOM first, then swap in one operation
|
||||||
|
const fragment = document.createDocumentFragment();
|
||||||
|
|
||||||
|
// Build site containers
|
||||||
|
const siteElements = [];
|
||||||
sites.forEach(site => {
|
sites.forEach(site => {
|
||||||
const siteConfig = enabledSites.find(s => s.id === site.siteId);
|
const siteConfig = enabledSites.find(s => s.id === site.siteId);
|
||||||
if (!siteConfig) return;
|
if (!siteConfig) return;
|
||||||
|
|
||||||
|
// Skip sites that returned empty departures (API hiccup)
|
||||||
|
// but keep sites with explicit errors so user sees feedback
|
||||||
|
if (site.data && site.data.departures && site.data.departures.length === 0 && !site.error) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const siteContainer = document.createElement('div');
|
const siteContainer = document.createElement('div');
|
||||||
siteContainer.className = 'site-container';
|
siteContainer.className = 'site-container';
|
||||||
|
|
||||||
const siteHeader = document.createElement('div');
|
const siteHeader = document.createElement('div');
|
||||||
siteHeader.className = 'site-header';
|
siteHeader.className = 'site-header';
|
||||||
siteHeader.innerHTML = `<span class="site-name">${site.siteName || siteConfig.name}</span>`;
|
const siteName = document.createElement('span');
|
||||||
|
siteName.className = 'site-name';
|
||||||
|
siteName.textContent = site.siteName || siteConfig.name;
|
||||||
|
siteHeader.appendChild(siteName);
|
||||||
siteContainer.appendChild(siteHeader);
|
siteContainer.appendChild(siteHeader);
|
||||||
|
|
||||||
if (site.data && site.data.departures) {
|
let cardCount = 0;
|
||||||
|
if (site.data && site.data.departures && site.data.departures.length > 0) {
|
||||||
const lineGroups = this.groupDeparturesByLineNumber(site.data.departures);
|
const lineGroups = this.groupDeparturesByLineNumber(site.data.departures);
|
||||||
this.displayGroupedDeparturesByLine(lineGroups, siteContainer);
|
this.displayGroupedDeparturesByLine(lineGroups, siteContainer);
|
||||||
|
cardCount = Object.keys(lineGroups).length;
|
||||||
} else if (site.error) {
|
} else if (site.error) {
|
||||||
const errorElement = document.createElement('div');
|
const errorElement = document.createElement('div');
|
||||||
errorElement.className = 'error';
|
errorElement.className = 'error';
|
||||||
errorElement.textContent = `Error loading departures for ${site.siteName}: ${site.error}`;
|
errorElement.textContent = `Error loading departures for ${site.siteName}: ${site.error}`;
|
||||||
siteContainer.appendChild(errorElement);
|
siteContainer.appendChild(errorElement);
|
||||||
|
cardCount = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.container.appendChild(siteContainer);
|
siteElements.push({ element: siteContainer, weight: cardCount + 1 });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// If no sites have data at all, keep existing display (don't flash empty)
|
||||||
|
if (siteElements.length === 0) return;
|
||||||
|
|
||||||
|
// In landscape mode, distribute sites into balanced columns
|
||||||
|
if (document.body.classList.contains('landscape') && siteElements.length > 1) {
|
||||||
|
const numCols = Math.min(3, siteElements.length);
|
||||||
|
const columns = [];
|
||||||
|
const weights = [];
|
||||||
|
for (let i = 0; i < numCols; i++) {
|
||||||
|
const col = document.createElement('div');
|
||||||
|
col.className = 'departure-column';
|
||||||
|
columns.push(col);
|
||||||
|
weights.push(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Greedy: assign each site to the lightest column
|
||||||
|
siteElements.forEach(({ element, weight }) => {
|
||||||
|
const minIdx = weights.indexOf(Math.min(...weights));
|
||||||
|
columns[minIdx].appendChild(element);
|
||||||
|
weights[minIdx] += weight;
|
||||||
|
});
|
||||||
|
|
||||||
|
columns.forEach(col => fragment.appendChild(col));
|
||||||
|
} else {
|
||||||
|
siteElements.forEach(({ element }) => {
|
||||||
|
fragment.appendChild(element);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap old content for new in one operation (no flash)
|
||||||
|
this.container.textContent = '';
|
||||||
|
this.container.appendChild(fragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -578,13 +625,14 @@ class DeparturesManager {
|
|||||||
console.error('Error fetching departures:', error);
|
console.error('Error fetching departures:', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.container.innerHTML = `
|
// On transient errors, keep existing data on screen.
|
||||||
<div class="error">
|
// Only show error if we have no data at all yet.
|
||||||
<p>Failed to load departures. Please try again later.</p>
|
if (!this.container.children.length) {
|
||||||
<p>Error: ${error.message}</p>
|
const errorDiv = document.createElement('div');
|
||||||
<p>Make sure the Node.js server is running: <code>node server.js</code></p>
|
errorDiv.className = 'error';
|
||||||
</div>
|
errorDiv.textContent = `Failed to load departures: ${error.message}`;
|
||||||
`;
|
this.container.appendChild(errorDiv);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,24 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
class WeatherManager {
|
class WeatherManager {
|
||||||
|
static WEATHER_CONDITIONS_SV = {
|
||||||
|
'Clear': 'Klart',
|
||||||
|
'Clouds': 'Molnigt',
|
||||||
|
'Rain': 'Regn',
|
||||||
|
'Drizzle': 'Duggregn',
|
||||||
|
'Thunderstorm': 'Åska',
|
||||||
|
'Snow': 'Snö',
|
||||||
|
'Mist': 'Dimma',
|
||||||
|
'Fog': 'Dimma',
|
||||||
|
'Haze': 'Dis',
|
||||||
|
'Smoke': 'Rök',
|
||||||
|
'Dust': 'Damm',
|
||||||
|
'Sand': 'Sand',
|
||||||
|
'Ash': 'Aska',
|
||||||
|
'Squall': 'Byar',
|
||||||
|
'Tornado': 'Tornado'
|
||||||
|
};
|
||||||
|
|
||||||
constructor(options = {}) {
|
constructor(options = {}) {
|
||||||
// Default options
|
// Default options
|
||||||
// Get API key from options, window (injected by server from .env), or fallback
|
// Get API key from options, window (injected by server from .env), or fallback
|
||||||
@@ -297,7 +315,7 @@ class WeatherManager {
|
|||||||
|
|
||||||
const conditionElement = document.querySelector('#custom-weather .weather-icon div');
|
const conditionElement = document.querySelector('#custom-weather .weather-icon div');
|
||||||
if (conditionElement) {
|
if (conditionElement) {
|
||||||
conditionElement.textContent = this.weatherData.condition;
|
conditionElement.textContent = WeatherManager.WEATHER_CONDITIONS_SV[this.weatherData.condition] || this.weatherData.condition;
|
||||||
}
|
}
|
||||||
|
|
||||||
const iconElement = document.querySelector('#custom-weather .weather-icon img');
|
const iconElement = document.querySelector('#custom-weather .weather-icon img');
|
||||||
@@ -364,7 +382,7 @@ class WeatherManager {
|
|||||||
if (sunTimesElement && this.sunTimes) {
|
if (sunTimesElement && this.sunTimes) {
|
||||||
const sunriseTime = this.formatTime(this.sunTimes.today.sunrise);
|
const sunriseTime = this.formatTime(this.sunTimes.today.sunrise);
|
||||||
const sunsetTime = this.formatTime(this.sunTimes.today.sunset);
|
const sunsetTime = this.formatTime(this.sunTimes.today.sunset);
|
||||||
sunTimesElement.textContent = `☀️ Sunrise: ${sunriseTime} | 🌙 Sunset: ${sunsetTime}`;
|
sunTimesElement.textContent = `☀️ Soluppgång: ${sunriseTime} | 🌙 Solnedgång: ${sunsetTime}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update daylight hours bar
|
// Update daylight hours bar
|
||||||
@@ -381,7 +399,7 @@ class WeatherManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Render compact weather bar for landscape mode
|
* Render compact weather bar for landscape mode
|
||||||
* Shows: [icon] temp condition | Sunrise HH:MM | Sunset HH:MM
|
* Shows: [icon] temp condition 💨 wind | ☀️ Sol ↑ HH:MM 🌙 Sol ↓ HH:MM
|
||||||
*/
|
*/
|
||||||
renderCompactWeatherBar() {
|
renderCompactWeatherBar() {
|
||||||
const bar = document.getElementById('compact-weather-bar');
|
const bar = document.getElementById('compact-weather-bar');
|
||||||
@@ -399,13 +417,21 @@ class WeatherManager {
|
|||||||
const strong = document.createElement('strong');
|
const strong = document.createElement('strong');
|
||||||
strong.textContent = `${this.weatherData.temperature}\u00B0C`;
|
strong.textContent = `${this.weatherData.temperature}\u00B0C`;
|
||||||
tempSpan.appendChild(strong);
|
tempSpan.appendChild(strong);
|
||||||
tempSpan.appendChild(document.createTextNode(` ${this.weatherData.condition || ''}`));
|
const conditionSv = WeatherManager.WEATHER_CONDITIONS_SV[this.weatherData.condition] || this.weatherData.condition;
|
||||||
|
tempSpan.appendChild(document.createTextNode(` ${conditionSv || ''}`));
|
||||||
bar.appendChild(tempSpan);
|
bar.appendChild(tempSpan);
|
||||||
|
|
||||||
const sep1 = document.createElement('span');
|
// Wind speed
|
||||||
sep1.className = 'weather-bar-sep';
|
if (this.weatherData.wind) {
|
||||||
sep1.textContent = '|';
|
const windSpan = document.createElement('span');
|
||||||
bar.appendChild(sep1);
|
windSpan.textContent = `💨 ${this.weatherData.wind.speed} km/h`;
|
||||||
|
bar.appendChild(windSpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sep = document.createElement('span');
|
||||||
|
sep.className = 'weather-bar-sep';
|
||||||
|
sep.textContent = '|';
|
||||||
|
bar.appendChild(sep);
|
||||||
|
|
||||||
let sunriseStr = '--:--';
|
let sunriseStr = '--:--';
|
||||||
let sunsetStr = '--:--';
|
let sunsetStr = '--:--';
|
||||||
@@ -413,19 +439,9 @@ class WeatherManager {
|
|||||||
sunriseStr = this.formatTime(this.sunTimes.today.sunrise);
|
sunriseStr = this.formatTime(this.sunTimes.today.sunrise);
|
||||||
sunsetStr = this.formatTime(this.sunTimes.today.sunset);
|
sunsetStr = this.formatTime(this.sunTimes.today.sunset);
|
||||||
}
|
}
|
||||||
|
const sunSpan = document.createElement('span');
|
||||||
const sunriseSpan = document.createElement('span');
|
sunSpan.textContent = `☀️ Sol ↑ ${sunriseStr} 🌙 Sol ↓ ${sunsetStr}`;
|
||||||
sunriseSpan.textContent = `\u2600\uFE0F Sunrise ${sunriseStr}`;
|
bar.appendChild(sunSpan);
|
||||||
bar.appendChild(sunriseSpan);
|
|
||||||
|
|
||||||
const sep2 = document.createElement('span');
|
|
||||||
sep2.className = 'weather-bar-sep';
|
|
||||||
sep2.textContent = '|';
|
|
||||||
bar.appendChild(sep2);
|
|
||||||
|
|
||||||
const sunsetSpan = document.createElement('span');
|
|
||||||
sunsetSpan.textContent = `\uD83C\uDF19 Sunset ${sunsetStr}`;
|
|
||||||
bar.appendChild(sunsetSpan);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -698,7 +714,17 @@ class WeatherManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Position current hour indicator
|
// Position current hour indicator
|
||||||
|
// On first render, skip transition so icon appears instantly at correct position
|
||||||
|
if (!this._daylightBarInitialized) {
|
||||||
|
indicatorElement.style.transition = 'none';
|
||||||
indicatorElement.style.left = `${currentPos}%`;
|
indicatorElement.style.left = `${currentPos}%`;
|
||||||
|
// Force reflow, then re-enable transition for smooth minute-to-minute movement
|
||||||
|
indicatorElement.offsetLeft;
|
||||||
|
indicatorElement.style.transition = '';
|
||||||
|
this._daylightBarInitialized = true;
|
||||||
|
} else {
|
||||||
|
indicatorElement.style.left = `${currentPos}%`;
|
||||||
|
}
|
||||||
|
|
||||||
// Debug logging
|
// Debug logging
|
||||||
console.log('Daylight bar positions:', {
|
console.log('Daylight bar positions:', {
|
||||||
|
|||||||
@@ -78,8 +78,7 @@ document.addEventListener('DOMContentLoaded', async function() {
|
|||||||
lastUpdatedId: 'last-updated'
|
lastUpdatedId: 'last-updated'
|
||||||
});
|
});
|
||||||
|
|
||||||
// Initialize NewsTicker (visible in landscape mode only via CSS)
|
// NewsTicker disabled - ticker removed from UI
|
||||||
window.newsTicker = new NewsTicker();
|
|
||||||
|
|
||||||
// Set up event listeners
|
// Set up event listeners
|
||||||
document.addEventListener('darkModeChanged', event => {
|
document.addEventListener('darkModeChanged', event => {
|
||||||
|
|||||||
Reference in New Issue
Block a user