910 lines
26 KiB
Svelte
910 lines
26 KiB
Svelte
<script>
|
|
import { selectedIndex, pushScreen } from '../lib/store.js';
|
|
import {
|
|
heroWelcomeText,
|
|
heroGuestText,
|
|
welcomePrefix,
|
|
welcomeSuffix,
|
|
resortName,
|
|
roomNumber,
|
|
heroStatusBarBg,
|
|
heroHeaderBg,
|
|
heroAccentLineColor,
|
|
heroWelcomePrefixColor,
|
|
heroWelcomeTextColor,
|
|
heroGuestTextColor,
|
|
enableHeaderBarOpacity,
|
|
borderTrimColor,
|
|
navElementsColor,
|
|
heroBgStart,
|
|
heroBgMid,
|
|
heroBgEnd,
|
|
navBarStart,
|
|
navBarMid,
|
|
navBarEnd,
|
|
} from '../lib/configStore.js';
|
|
import { onMount } from 'svelte';
|
|
|
|
let currentTime = new Date();
|
|
let weather = null;
|
|
let weatherError = false;
|
|
let inactivityTimeout;
|
|
let hasInteracted = false;
|
|
let currentNavIndex = -1;
|
|
|
|
// Subscribe to selectedIndex changes to keep currentNavIndex in sync
|
|
const unsubscribe = selectedIndex.subscribe(val => {
|
|
currentNavIndex = val;
|
|
});
|
|
|
|
onMount(() => {
|
|
// Update time every second
|
|
const interval = setInterval(() => {
|
|
currentTime = new Date();
|
|
}, 1000);
|
|
|
|
// Fetch weather data
|
|
fetchWeather();
|
|
|
|
// Add document-level keyboard listener for arrow keys
|
|
const handleKeyDown = (e) => {
|
|
if (e.key === 'ArrowRight' || e.key === 'ArrowDown' || e.key === 'ArrowLeft' || e.key === 'ArrowUp') {
|
|
e.preventDefault();
|
|
// Cycle forward through items
|
|
const newIndex = (currentNavIndex + 1) % navItems.length;
|
|
selectedIndex.set(newIndex);
|
|
handleNavHover();
|
|
} else if (e.key === 'Enter') {
|
|
e.preventDefault();
|
|
if (currentNavIndex >= 0 && currentNavIndex < navItems.length) {
|
|
handleSelect(navItems[currentNavIndex]);
|
|
}
|
|
}
|
|
};
|
|
|
|
document.addEventListener('keydown', handleKeyDown);
|
|
|
|
return () => {
|
|
clearInterval(interval);
|
|
document.removeEventListener('keydown', handleKeyDown);
|
|
unsubscribe();
|
|
};
|
|
});
|
|
|
|
function resetInactivityTimeout() {
|
|
if (inactivityTimeout) clearTimeout(inactivityTimeout);
|
|
inactivityTimeout = setTimeout(() => {
|
|
selectedIndex.set(-1);
|
|
}, 20000); // 20 seconds
|
|
}
|
|
|
|
function handleNavHover() {
|
|
hasInteracted = true;
|
|
resetInactivityTimeout();
|
|
}
|
|
|
|
async function fetchWeather() {
|
|
try {
|
|
// Try to get user's location and fetch weather
|
|
if (navigator.geolocation) {
|
|
navigator.geolocation.getCurrentPosition(
|
|
async (position) => {
|
|
const { latitude, longitude } = position.coords;
|
|
console.log('Location:', latitude, longitude);
|
|
const response = await fetch(
|
|
`https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t=temperature_2m,weather_code&temperature_unit=fahrenheit&timezone=auto`
|
|
);
|
|
const data = await response.json();
|
|
console.log('API Response:', data);
|
|
if (data.current) {
|
|
weather = {
|
|
temp: Math.round(data.current.temperature_2m),
|
|
code: data.current.weather_code,
|
|
};
|
|
console.log('Weather Code:', data.current.weather_code, 'Temp:', data.current.temperature_2m + '°F');
|
|
}
|
|
},
|
|
(error) => {
|
|
console.log('Geolocation error:', error);
|
|
weatherError = true;
|
|
}
|
|
);
|
|
} else {
|
|
console.log('Geolocation not supported');
|
|
}
|
|
} catch (e) {
|
|
console.log('Fetch error:', e);
|
|
weatherError = true;
|
|
}
|
|
}
|
|
|
|
function getWeatherIcon(code) {
|
|
// Returns weather icon type for SVG rendering
|
|
if (code === 0) return 'sun';
|
|
if (code === 1 || code === 2) return 'cloud-sun';
|
|
if (code === 3) return 'cloud';
|
|
if (code === 45 || code === 48) return 'cloud-fog';
|
|
if (code === 51 || code === 53 || code === 55) return 'cloud-rain';
|
|
if (code === 61 || code === 63 || code === 65) return 'cloud-rain';
|
|
if (code === 71 || code === 73 || code === 75) return 'cloud-snow';
|
|
return 'cloud-sun';
|
|
}
|
|
|
|
function formatTime(date) {
|
|
return date.toLocaleTimeString('en-US', {
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
hour12: true,
|
|
});
|
|
}
|
|
|
|
function getOrdinalSuffix(day) {
|
|
if (day > 3 && day < 21) return 'TH';
|
|
switch (day % 10) {
|
|
case 1: return 'ST';
|
|
case 2: return 'ND';
|
|
case 3: return 'RD';
|
|
default: return 'TH';
|
|
}
|
|
}
|
|
|
|
function formatDate(date) {
|
|
const weekday = date.toLocaleDateString('en-US', { weekday: 'long' });
|
|
const month = date.toLocaleDateString('en-US', { month: 'long' });
|
|
const day = date.getDate();
|
|
const year = date.getFullYear();
|
|
const suffix = getOrdinalSuffix(day);
|
|
return `${weekday}, ${month} ${day}${suffix} ${year}`;
|
|
}
|
|
|
|
const navItems = [
|
|
{ id: 'plex', label: 'Watch Plex', icon: 'play' },
|
|
{ id: 'youtube', label: 'Watch YouTube', icon: 'play-circle' },
|
|
{ id: 'restaurants', label: 'Restaurants', icon: 'utensils' },
|
|
{ id: 'attractions', label: 'Entertainment', icon: 'compass' },
|
|
];
|
|
|
|
function handleSelect(item) {
|
|
if (item.id === 'plex') {
|
|
// Launch Plex via control service
|
|
const ws = new WebSocket(import.meta.env.VITE_WS_URL || 'ws://localhost:3001');
|
|
ws.onopen = () => {
|
|
ws.send(JSON.stringify({ type: 'launch-plex' }));
|
|
ws.close();
|
|
};
|
|
} else if (item.id === 'youtube') {
|
|
// Launch YouTube via control service
|
|
const ws = new WebSocket(import.meta.env.VITE_WS_URL || 'ws://localhost:3001');
|
|
ws.onopen = () => {
|
|
ws.send(JSON.stringify({ type: 'launch-youtube' }));
|
|
ws.close();
|
|
};
|
|
} else if (item.id === 'restaurants' || item.id === 'attractions') {
|
|
pushScreen(item.id);
|
|
}
|
|
}
|
|
|
|
function handleNavigation(direction) {
|
|
const currentIdx = $selectedIndex === -1 ? -1 : $selectedIndex;
|
|
const newIndex =
|
|
direction === 'right' || direction === 'down'
|
|
? (currentIdx + 1) % navItems.length
|
|
: (currentIdx - 1 + navItems.length) % navItems.length;
|
|
selectedIndex.set(newIndex);
|
|
}
|
|
</script>
|
|
|
|
<div class="home-container">
|
|
<!-- Background Video is handled globally in App.svelte -->
|
|
|
|
<!-- Top Status Bar -->
|
|
<div class="status-bar" style="background-color: {$heroStatusBarBg};">
|
|
<div class="resort-name">{$resortName}</div>
|
|
<div class="room-number">{$roomNumber}</div>
|
|
</div>
|
|
|
|
<!-- Accent Line -->
|
|
<div class="accent-line" style="background-color: {$heroAccentLineColor};"></div>
|
|
|
|
<!-- Header Bar -->
|
|
<div class="header-bar" style="background: linear-gradient(135deg, {$heroHeaderBg} 0%, {$enableHeaderBarOpacity ? $heroHeaderBg + 'dd' : $heroHeaderBg} 50%, {$heroHeaderBg} 100%);">
|
|
<div class="header-left">
|
|
<div class="welcome" style="color: {$heroWelcomePrefixColor};">{$welcomePrefix}</div>
|
|
<div class="guest-name" style="color: {$heroGuestTextColor};">{$heroGuestText}</div>
|
|
</div>
|
|
<div class="header-right">
|
|
<div class="time-date">
|
|
<div class="time-line">
|
|
<div class="weather-info">
|
|
{#if weather}
|
|
<svg class="weather-icon" viewBox="0 0 24 24" fill="currentColor">
|
|
{#if getWeatherIcon(weather.code) === 'sun'}
|
|
<!-- Improved Sun Icon -->
|
|
<circle cx="12" cy="12" r="5"></circle>
|
|
<!-- Top ray -->
|
|
<line x1="12" y1="1" x2="12" y2="3.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none"></line>
|
|
<!-- Bottom ray -->
|
|
<line x1="12" y1="20.5" x2="12" y2="23" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none"></line>
|
|
<!-- Right ray -->
|
|
<line x1="20.5" y1="12" x2="23" y2="12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none"></line>
|
|
<!-- Left ray -->
|
|
<line x1="1" y1="12" x2="3.5" y2="12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none"></line>
|
|
<!-- Top-right ray -->
|
|
<line x1="17.66" y1="6.34" x2="19.78" y2="4.22" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none"></line>
|
|
<!-- Top-left ray -->
|
|
<line x1="6.34" y1="6.34" x2="4.22" y2="4.22" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none"></line>
|
|
<!-- Bottom-right ray -->
|
|
<line x1="17.66" y1="17.66" x2="19.78" y2="19.78" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none"></line>
|
|
<!-- Bottom-left ray -->
|
|
<line x1="6.34" y1="17.66" x2="4.22" y2="19.78" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none"></line>
|
|
{:else if getWeatherIcon(weather.code) === 'cloud-sun'}
|
|
<!-- Sun rays -->
|
|
<circle cx="5" cy="6" r="3" stroke="currentColor" stroke-width="1.5" fill="none"></circle>
|
|
<line x1="5" y1="2" x2="5" y2="0.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"></line>
|
|
<line x1="5" y1="10" x2="5" y2="11.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"></line>
|
|
<line x1="1" y1="6" x2="-0.5" y2="6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"></line>
|
|
<line x1="9" y1="6" x2="10.5" y2="6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"></line>
|
|
<!-- Cloud -->
|
|
<path d="M12 15a4 4 0 0 1 4-4h1a5 5 0 0 1 5 5v4H8v-4a4 4 0 0 1 4-4z" stroke="currentColor" stroke-width="1.5" fill="none"></path>
|
|
{:else if getWeatherIcon(weather.code) === 'cloud'}
|
|
<path d="M18 10h-1.26A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z"></path>
|
|
{:else if getWeatherIcon(weather.code) === 'cloud-fog'}
|
|
<path d="M18 10h-1.26A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z"></path>
|
|
<line x1="2" y1="20" x2="18" y2="20" stroke-width="1.5"></line>
|
|
<line x1="2" y1="17" x2="16" y2="17" stroke-width="1.5"></line>
|
|
{:else if getWeatherIcon(weather.code) === 'cloud-rain'}
|
|
<path d="M18 10h-1.26A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z"></path>
|
|
<line x1="8" y1="19" x2="8" y2="21" stroke-width="1.5"></line>
|
|
<line x1="8" y1="13" x2="8" y2="15" stroke-width="1.5"></line>
|
|
<line x1="14" y1="19" x2="14" y2="21" stroke-width="1.5"></line>
|
|
<line x1="14" y1="13" x2="14" y2="15" stroke-width="1.5"></line>
|
|
<line x1="20" y1="19" x2="20" y2="21" stroke-width="1.5"></line>
|
|
<line x1="20" y1="13" x2="20" y2="15" stroke-width="1.5"></line>
|
|
{:else if getWeatherIcon(weather.code) === 'cloud-snow'}
|
|
<path d="M18 10h-1.26A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z"></path>
|
|
<g stroke-width="1.2">
|
|
<path d="M8 19L6 20m0-3l2 1m0 0l-2 1m2-1l2 1"></path>
|
|
<path d="M14 19l-2 1m0-3l2 1m0 0l-2 1m2-1l2 1"></path>
|
|
<path d="M20 19l-2 1m0-3l2 1m0 0l-2 1m2-1l2 1"></path>
|
|
</g>
|
|
{:else}
|
|
<path d="M18 10h-1.26A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z"></path>
|
|
{/if}
|
|
</svg>
|
|
<span class="weather-temp">{weather.temp}°F</span>
|
|
{:else}
|
|
<!-- Default cloud icon while loading or on error -->
|
|
<svg class="weather-icon" viewBox="0 0 24 24" fill="currentColor">
|
|
<path d="M18 10h-1.26A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z"></path>
|
|
</svg>
|
|
<span class="weather-temp">--°F</span>
|
|
{/if}
|
|
</div>
|
|
<div class="time">{formatTime(currentTime)}</div>
|
|
</div>
|
|
<div class="date">{formatDate(currentTime)}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Hero Image Section -->
|
|
<div class="hero-section">
|
|
<div class="hero-image" style="background: linear-gradient(135deg, {$heroBgStart} 0%, {$heroBgMid} 50%, {$heroBgEnd} 100%);"></div>
|
|
</div>
|
|
|
|
<!-- Border Above Nav -->
|
|
<div class="border-above-nav" style="background-color: {$borderTrimColor};"></div>
|
|
|
|
<!-- Bottom Navigation Bar -->
|
|
<div class="nav-bar" style="background: linear-gradient(to bottom, {$navBarStart} 0%, {$navBarMid} 50%, {$navBarEnd} 100%);">
|
|
{#each navItems as item, index (item.id)}
|
|
<button
|
|
class="nav-item"
|
|
class:active={hasInteracted && index === $selectedIndex}
|
|
on:click={() => handleSelect(item)}
|
|
on:mouseenter={() => {
|
|
handleNavHover();
|
|
selectedIndex.set(index);
|
|
}}
|
|
on:keydown={(e) => {
|
|
if (e.key === 'Enter') handleSelect(item);
|
|
}}
|
|
style="color: {$navElementsColor};"
|
|
>
|
|
<div class="nav-icon" style="color: {$navElementsColor};">
|
|
<svg viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="1.5">
|
|
{#if item.icon === 'play'}
|
|
<!-- Plex Logo -->
|
|
<rect x="2" y="3" width="6" height="18"></rect>
|
|
<polygon points="10 3 10 21 20 12"></polygon>
|
|
{:else if item.icon === 'play-circle'}
|
|
<!-- YouTube Play Button -->
|
|
<rect x="2" y="3" width="20" height="18" rx="3" ry="3" fill="currentColor"></rect>
|
|
<polygon points="9 8 9 16 16 12" fill="white"></polygon>
|
|
{:else if item.icon === 'utensils'}
|
|
<!-- Fork and Knife -->
|
|
<g stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M4 3v15M4 8h-1.5M4 11h-1.5M8 3v15M8 8h1.5M8 11h1.5M16 4l2 2M20 8l-6 12M16 8l2 2M18 13l2-2"></path>
|
|
</g>
|
|
{:else if item.icon === 'compass'}
|
|
<circle cx="12" cy="12" r="10" fill="none"></circle>
|
|
<path d="M12 6v6l4 2" fill="none"></path>
|
|
{/if}
|
|
</svg>
|
|
</div>
|
|
<div class="nav-label">{item.label}</div>
|
|
</button>
|
|
{/each}
|
|
</div>
|
|
|
|
<!-- Bottom Trim -->
|
|
<div class="bottom-trim" style="background-color: {$borderTrimColor};"></div>
|
|
|
|
<!-- Weather Icon Test Grid -->
|
|
<div class="weather-test">
|
|
<div class="weather-test-item">
|
|
<strong>Sun (0)</strong>
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
<circle cx="12" cy="12" r="5"></circle>
|
|
<line x1="12" y1="1" x2="12" y2="3"></line>
|
|
<line x1="12" y1="21" x2="12" y2="23"></line>
|
|
<line x1="4.22" y1="4.22" x2="2.81" y2="2.81"></line>
|
|
<line x1="19.78" y1="19.78" x2="21.19" y2="21.19"></line>
|
|
<line x1="1" y1="12" x2="3" y2="12"></line>
|
|
<line x1="21" y1="12" x2="23" y2="12"></line>
|
|
<line x1="4.22" y1="19.78" x2="2.81" y2="21.19"></line>
|
|
<line x1="19.78" y1="4.22" x2="21.19" y2="2.81"></line>
|
|
</svg>
|
|
</div>
|
|
<div class="weather-test-item">
|
|
<strong>Cloud-Sun (1,2)</strong>
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
<circle cx="5" cy="6" r="3"></circle>
|
|
<line x1="5" y1="2" x2="5" y2="0.5"></line>
|
|
<line x1="5" y1="10" x2="5" y2="11.5"></line>
|
|
<line x1="1" y1="6" x2="-0.5" y2="6"></line>
|
|
<line x1="9" y1="6" x2="10.5" y2="6"></line>
|
|
<path d="M12 15a4 4 0 0 1 4-4h1a5 5 0 0 1 5 5v4H8v-4a4 4 0 0 1 4-4z"></path>
|
|
</svg>
|
|
</div>
|
|
<div class="weather-test-item">
|
|
<strong>Cloud (3)</strong>
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
<path d="M18 10h-1.26A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z"></path>
|
|
</svg>
|
|
</div>
|
|
<div class="weather-test-item">
|
|
<strong>Cloud-Fog (45,48)</strong>
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
<path d="M18 10h-1.26A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z"></path>
|
|
<line x1="2" y1="20" x2="18" y2="20"></line>
|
|
<line x1="2" y1="17" x2="16" y2="17"></line>
|
|
</svg>
|
|
</div>
|
|
<div class="weather-test-item">
|
|
<strong>Rain (51-75)</strong>
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
<path d="M18 10h-1.26A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z"></path>
|
|
<line x1="8" y1="19" x2="8" y2="21"></line>
|
|
<line x1="14" y1="19" x2="14" y2="21"></line>
|
|
</svg>
|
|
</div>
|
|
<div class="weather-test-item">
|
|
<strong>Snow (71-75)</strong>
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
<path d="M18 10h-1.26A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z"></path>
|
|
<text x="8" y="22" font-size="10">❄</text>
|
|
<text x="14" y="22" font-size="10">❄</text>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
@font-face {
|
|
font-family: 'Waltograph';
|
|
src: url('/fonts/waltograph/Waltograph/waltograph42.ttf') format('truetype');
|
|
}
|
|
|
|
@font-face {
|
|
font-family: 'Mickey Mouse';
|
|
src: url('/fonts/mickey-mouse-font/MickeyMousePersonalUseRegular-mLRAG.otf') format('opentype');
|
|
}
|
|
|
|
.home-container {
|
|
width: 100%;
|
|
height: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
background: #000;
|
|
box-sizing: border-box;
|
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
color: white;
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.background-video {
|
|
position: absolute;
|
|
left: 0;
|
|
top: 0;
|
|
width: 100vw;
|
|
height: 100vh;
|
|
object-fit: cover;
|
|
z-index: 10;
|
|
pointer-events: none;
|
|
}
|
|
|
|
/* Top Status Bar */
|
|
.status-bar {
|
|
background: #1B4965;
|
|
height: 9.26vh;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: flex-end;
|
|
padding: 0 3rem 1.2rem 3rem;
|
|
box-sizing: border-box;
|
|
position: relative;
|
|
z-index: 10;
|
|
}
|
|
|
|
.resort-name {
|
|
font-family: 'Mickey Mouse', serif;
|
|
font-size: 1.75rem;
|
|
font-weight: 700;
|
|
color: #FFFFFF;
|
|
letter-spacing: 0.15em;
|
|
}
|
|
|
|
.room-number {
|
|
font-size: 1.15rem;
|
|
font-weight: 700;
|
|
color: #FFFFFF;
|
|
letter-spacing: 0.05em;
|
|
}
|
|
|
|
/* Accent Line */
|
|
.accent-line {
|
|
height: 0.65vh;
|
|
background: #FF6F61;
|
|
position: relative;
|
|
z-index: 10;
|
|
}
|
|
|
|
/* Header Bar */
|
|
.header-bar {
|
|
background: linear-gradient(135deg, #1E8E9F 0%, #2EC4B6 50%, #1E8E9F 100%);
|
|
height: 14.35vh;
|
|
padding: 0 3rem;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
|
box-sizing: border-box;
|
|
position: relative;
|
|
z-index: 10;
|
|
}
|
|
|
|
.header-left {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.3rem;
|
|
}
|
|
|
|
.welcome {
|
|
font-size: 1rem;
|
|
color: #FF6F61;
|
|
letter-spacing: 0.12em;
|
|
margin-top: 0.2rem;
|
|
}
|
|
|
|
.guest-name {
|
|
font-size: 2.4rem;
|
|
font-weight: 700;
|
|
color: #fff;
|
|
letter-spacing: -0.01em;
|
|
margin-top: 0.2rem;
|
|
text-shadow: 0 2px 10px rgba(212, 175, 55, 0.2);
|
|
}
|
|
|
|
.header-right {
|
|
display: flex;
|
|
gap: 2.5rem;
|
|
align-items: flex-start;
|
|
text-align: right;
|
|
}
|
|
|
|
.weather-info {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.6rem;
|
|
}
|
|
|
|
.weather-icon {
|
|
width: 2.2rem;
|
|
height: 2.2rem;
|
|
color: #fff;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.weather-temp {
|
|
font-weight: 700;
|
|
color: #fff;
|
|
font-size: 2.2rem;
|
|
letter-spacing: -0.01em;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
|
|
.time-date {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.3rem;
|
|
text-align: right;
|
|
}
|
|
|
|
.time-line {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: flex-end;
|
|
gap: 1.5rem;
|
|
}
|
|
|
|
.time {
|
|
font-size: 2.2rem;
|
|
font-weight: 700;
|
|
color: #fff;
|
|
letter-spacing: -0.01em;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
.date {
|
|
font-size: 1.35rem;
|
|
color: rgba(255, 255, 255, 0.65);
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.08em;
|
|
}
|
|
|
|
/* Hero Section */
|
|
.hero-section {
|
|
height: 60.1vh;
|
|
position: relative;
|
|
overflow: hidden;
|
|
background: linear-gradient(135deg, #1B6B7A 0%, #2EC4B6 50%, #0F4C5C 100%);
|
|
}
|
|
|
|
.hero-image {
|
|
width: 100%;
|
|
height: 100%;
|
|
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1920 1080"><defs><linearGradient id="grad1" x1="0%25" y1="0%25" x2="100%25" y2="100%25"><stop offset="0%25" style="stop-color:%231a3a52;stop-opacity:1" /><stop offset="50%25" style="stop-color:%230f2540;stop-opacity:1" /><stop offset="100%25" style="stop-color:%23051018;stop-opacity:1" /></linearGradient></defs><rect width="1920" height="1080" fill="url(%23grad1)"/></svg>');
|
|
background-size: cover;
|
|
background-position: center;
|
|
animation: subtle-zoom 40s ease-in-out infinite;
|
|
position: relative;
|
|
}
|
|
|
|
.hero-image::after {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background: radial-gradient(circle at 30% 20%, rgba(212, 175, 55, 0.05) 0%, transparent 50%);
|
|
pointer-events: none;
|
|
}
|
|
|
|
@keyframes subtle-zoom {
|
|
0%,
|
|
100% {
|
|
transform: scale(1);
|
|
}
|
|
50% {
|
|
transform: scale(1.015);
|
|
}
|
|
}
|
|
|
|
@keyframes gentle-float {
|
|
0%,
|
|
100% {
|
|
transform: translateY(0);
|
|
}
|
|
50% {
|
|
transform: translateY(-8px);
|
|
}
|
|
}
|
|
|
|
@keyframes fade-in {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(-10px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
/* Weather Icon Test Grid */
|
|
.weather-test {
|
|
display: none;
|
|
position: fixed;
|
|
bottom: 160px;
|
|
left: 10px;
|
|
right: 10px;
|
|
grid-template-columns: repeat(6, 1fr);
|
|
gap: 10px;
|
|
background: rgba(0, 0, 0, 0.9);
|
|
padding: 10px;
|
|
border: 2px solid #d4af37;
|
|
z-index: 100;
|
|
max-height: 120px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.weather-test-item {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 8px;
|
|
border: 1px solid #d4af37;
|
|
border-radius: 4px;
|
|
font-size: 0.75rem;
|
|
color: white;
|
|
}
|
|
|
|
.weather-test-item svg {
|
|
width: 40px;
|
|
height: 40px;
|
|
margin: 5px 0;
|
|
color: #d4af37;
|
|
}
|
|
|
|
/* Border Above Nav */
|
|
.border-above-nav {
|
|
height: 0.93vh;
|
|
background: #1B4965;
|
|
position: relative;
|
|
z-index: 10;
|
|
}
|
|
|
|
/* Bottom Trim */
|
|
.bottom-trim {
|
|
height: 2.31vh;
|
|
background: #1B4965;
|
|
position: relative;
|
|
z-index: 10;
|
|
}
|
|
|
|
/* Bottom Navigation Bar */
|
|
.nav-bar {
|
|
background: linear-gradient(to bottom, #F4E1C1 0%, #EFDBAB 50%, #EAD5A0 100%);
|
|
height: 12.5vh;
|
|
padding: 1.75rem 3rem;
|
|
display: flex;
|
|
justify-content: space-around;
|
|
align-items: center;
|
|
gap: 1.5rem;
|
|
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.2);
|
|
box-sizing: border-box;
|
|
position: relative;
|
|
z-index: 10;
|
|
}
|
|
|
|
.nav-item {
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
padding: 0.9rem 1.2rem;
|
|
background: transparent;
|
|
border: none;
|
|
border-radius: 0.75rem;
|
|
color: #000;
|
|
cursor: pointer;
|
|
font-family: inherit;
|
|
font-size: 0.75rem;
|
|
font-weight: 700;
|
|
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
flex: 0 1 auto;
|
|
position: relative;
|
|
transform: scale(1);
|
|
}
|
|
|
|
.nav-item:hover {
|
|
transform: scale(1.5);
|
|
}
|
|
|
|
.nav-item.active {
|
|
transform: scale(1.5);
|
|
}
|
|
|
|
.nav-icon {
|
|
width: 2rem;
|
|
height: 2rem;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: #1B4965;
|
|
flex-shrink: 0;
|
|
margin-top: 0.1rem;
|
|
}
|
|
|
|
.nav-icon svg {
|
|
width: 100%;
|
|
height: 100%;
|
|
stroke-linecap: round;
|
|
stroke-linejoin: round;
|
|
}
|
|
|
|
.nav-label {
|
|
font-size: 0.8rem;
|
|
text-align: left;
|
|
font-weight: 700;
|
|
letter-spacing: 0.02em;
|
|
max-width: 3.5rem;
|
|
line-height: 1.15;
|
|
white-space: normal;
|
|
color: #1B4965;
|
|
}
|
|
|
|
@keyframes bounce-in {
|
|
0% {
|
|
transform: scale(0.8);
|
|
}
|
|
50% {
|
|
transform: scale(1.2);
|
|
}
|
|
100% {
|
|
transform: scale(1);
|
|
}
|
|
}
|
|
|
|
@media (max-width: 1400px) {
|
|
.status-bar {
|
|
padding: 0.9rem 2.5rem;
|
|
}
|
|
|
|
.header-bar {
|
|
padding: 1.5rem 2.5rem;
|
|
}
|
|
|
|
.header-right {
|
|
gap: 3rem;
|
|
}
|
|
|
|
.guest-name {
|
|
font-size: 1.7rem;
|
|
}
|
|
|
|
.nav-bar {
|
|
padding: 1.5rem 2.5rem;
|
|
}
|
|
|
|
.nav-item {
|
|
flex: 0 1 140px;
|
|
padding: 0.8rem 1.4rem;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 1024px) {
|
|
.status-bar {
|
|
padding: 0.8rem 2rem;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.header-bar {
|
|
padding: 1.5rem 2rem;
|
|
}
|
|
|
|
.header-right {
|
|
gap: 2.5rem;
|
|
}
|
|
|
|
.guest-name {
|
|
font-size: 1.5rem;
|
|
}
|
|
|
|
.weather-info {
|
|
padding: 0.6rem 1.2rem;
|
|
}
|
|
|
|
.weather-icon {
|
|
font-size: 2rem;
|
|
}
|
|
|
|
.nav-bar {
|
|
padding: 1.25rem 2rem;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.nav-item {
|
|
flex: 0 1 130px;
|
|
padding: 0.7rem 1.2rem;
|
|
}
|
|
|
|
.nav-icon {
|
|
font-size: 1.8rem;
|
|
}
|
|
|
|
.nav-label {
|
|
font-size: 0.8rem;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.status-bar {
|
|
padding: 0.7rem 1.5rem;
|
|
border-bottom: 2px solid rgba(212, 175, 55, 0.3);
|
|
}
|
|
|
|
.resort-name {
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
.room-number {
|
|
font-size: 0.85rem;
|
|
padding: 0.3rem 0.8rem;
|
|
}
|
|
|
|
.header-bar {
|
|
padding: 1rem 1.5rem;
|
|
flex-direction: column;
|
|
gap: 1rem;
|
|
text-align: center;
|
|
}
|
|
|
|
.header-left {
|
|
gap: 0.2rem;
|
|
}
|
|
|
|
.guest-name {
|
|
font-size: 1.3rem;
|
|
}
|
|
|
|
.header-right {
|
|
width: 100%;
|
|
justify-content: space-around;
|
|
gap: 1.5rem;
|
|
}
|
|
|
|
.weather-info {
|
|
padding: 0.5rem 1rem;
|
|
gap: 0.6rem;
|
|
}
|
|
|
|
.weather-icon {
|
|
font-size: 1.6rem;
|
|
}
|
|
|
|
.weather-temp {
|
|
font-size: 1.1rem;
|
|
}
|
|
|
|
.time {
|
|
font-size: 1.4rem;
|
|
}
|
|
|
|
.nav-bar {
|
|
padding: 1rem 1.5rem;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.nav-item {
|
|
flex: 0 1 auto;
|
|
padding: 0.6rem 0.9rem;
|
|
gap: 0.4rem;
|
|
}
|
|
|
|
.nav-icon {
|
|
font-size: 1.4rem;
|
|
}
|
|
|
|
.nav-label {
|
|
font-size: 0.7rem;
|
|
}
|
|
}
|
|
</style>
|