Kiosk UI/UX overhaul: dark landscape mode with hero countdowns and full-width layout

Redesign the landscape orientation for kiosk readability at 3-10m distance:

- Add dark kiosk background (#1a1a2e) with high-contrast light text
- Replace 2-column grid with 5-row full-width stacking layout
- Add compact weather bar (temp + sunrise/sunset) replacing full widget
- Enlarge countdown to 2em hero size in landscape
- Replace time ranges with next 2-3 absolute departure times
- Add 3-tier urgency colors: Nu (green), 1-2min (red), 3-5min (orange)
- Make site headers full-width blue gradient bars in landscape
- Tighten card spacing (65px min-height, 8px gap) for 4-stop visibility
- Add scrolling news ticker with /api/ticker fallback messages
- Fix daylight bar from position:fixed to relative in landscape grid
- Hide background overlay in landscape for maximum contrast
- Fix weather-section HTML missing closing div tags

All changes scoped behind body.landscape CSS selectors; other orientations unaffected.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-15 19:12:08 +01:00
parent 6565661740
commit 60e41c2cc4
9 changed files with 421 additions and 58 deletions

View File

@@ -0,0 +1,84 @@
/**
* NewsTicker - Scrolling news/announcement ticker for landscape kiosk mode
* Fetches from /api/ticker with fallback to hardcoded messages
*/
class NewsTicker {
constructor(options = {}) {
this.options = {
containerId: options.containerId || 'news-ticker',
fetchUrl: options.fetchUrl || '/api/ticker',
refreshInterval: options.refreshInterval || 5 * 60 * 1000, // 5 minutes
fallbackMessages: options.fallbackMessages || [
'Välkommen till Ambassaderna',
'Håll dörren stängd',
'Tvättstugan stänger kl 22:00'
],
...options
};
this.container = null;
this.contentEl = null;
this.messages = [];
this.refreshTimer = null;
this.init();
}
init() {
this.container = document.getElementById(this.options.containerId);
if (!this.container) return;
this.contentEl = this.container.querySelector('.ticker-content');
if (!this.contentEl) {
this.contentEl = document.createElement('div');
this.contentEl.className = 'ticker-content';
this.container.appendChild(this.contentEl);
}
this.fetchMessages();
this.refreshTimer = setInterval(() => this.fetchMessages(), this.options.refreshInterval);
}
async fetchMessages() {
try {
const response = await fetch(this.options.fetchUrl);
if (response.ok) {
const data = await response.json();
if (Array.isArray(data.messages) && data.messages.length > 0) {
this.messages = data.messages;
} else {
this.messages = this.options.fallbackMessages;
}
} else {
this.messages = this.options.fallbackMessages;
}
} catch {
this.messages = this.options.fallbackMessages;
}
this.render();
}
render() {
if (!this.contentEl || this.messages.length === 0) return;
const separator = ' \u2022 '; // bullet separator
const text = this.messages.join(separator);
// Duplicate text for seamless infinite scroll loop
this.contentEl.textContent = text + separator + text;
}
stop() {
if (this.refreshTimer) {
clearInterval(this.refreshTimer);
this.refreshTimer = null;
}
}
}
// ES module export
export { NewsTicker };
// Keep window reference for backward compatibility
window.NewsTicker = NewsTicker;