- Umbrella tip now bigger (larger padding/font/icon) and there can be more than one chip shown at once (e.g. cold + rainy). - Added a softer 'You might want an umbrella' tier: shown when the existing strong threshold isn't met but any remaining hour today still has a real (>10%) rain chance -- previously that case showed nothing at all. - Added a jacket tip: shown when today's forecast high is below 12°C. Both new thresholds are named constants at the top of summary.js, easy to retune without touching the logic. summary.js's umbrella boolean is replaced by a tips[] array so future nudges (e.g. a UV/sun-cream tip, if we add UV index data) slot in the same way without another one-off boolean + span. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
375 lines
14 KiB
CSS
375 lines
14 KiB
CSS
/* Stack design system tokens — include verbatim in every app */
|
|
:root {
|
|
--navy: #1a1a2e;
|
|
--navy-dark: #0f0f20;
|
|
--gold: #c9a84c;
|
|
--gold-light: #e8c96d;
|
|
--surface: rgba(255,255,255,0.07);
|
|
--surface-2: rgba(255,255,255,0.08);
|
|
--text: rgba(255,255,255,0.88);
|
|
--text-muted: rgba(255,255,255,0.48);
|
|
--body-bg: #f4f5f7;
|
|
--card-bg: #ffffff;
|
|
--card-border: #e4e8ee;
|
|
--text-dark: #1e293b;
|
|
--text-mid: #64748b;
|
|
--shadow-sm: 0 1px 3px rgba(0,0,0,0.07), 0 1px 2px rgba(0,0,0,0.04);
|
|
--shadow-md: 0 4px 12px rgba(0,0,0,0.08);
|
|
--danger: #dc2626;
|
|
--radius: 10px;
|
|
--font: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
}
|
|
body { background: var(--body-bg); color: var(--text-dark); font-family: var(--font); }
|
|
|
|
/* App theme */
|
|
:root {
|
|
--app-primary: #0ea5e9;
|
|
--app-primary-light: #38bdf8;
|
|
/* A stronger surface than the shared --surface/--surface-2 tokens, for the
|
|
display's 3 main content cards (today block, next-day cards, daily cards) —
|
|
the shared tokens read as barely-there on a tablet under lobby lighting. */
|
|
--surface-strong: rgba(255, 255, 255, 0.13);
|
|
--surface-strong-border: rgba(255, 255, 255, 0.22);
|
|
}
|
|
|
|
*, *::before, *::after { box-sizing: border-box; }
|
|
html, body, #root { height: 100%; margin: 0; font-size: 14px; }
|
|
|
|
::-webkit-scrollbar { width: 4px; height: 4px; }
|
|
::-webkit-scrollbar-track { background: transparent; }
|
|
::-webkit-scrollbar-thumb { background: var(--card-border); border-radius: 2px; }
|
|
|
|
/* ── Reception display (full-screen, public, no chrome) ───────────────────
|
|
Fixed-viewport CSS grid, not intrinsic/flowing content — this is a kiosk
|
|
screen, not a responsive page, so every row is sized to guarantee the whole
|
|
thing always sums to exactly 100vh with no scrolling: header/now/label rows
|
|
are `auto` (their own intrinsic height), the two content rows are
|
|
`minmax(0, 1fr)` (share whatever's left, and — critically — `minmax(0, …)`
|
|
lets them shrink below their content's natural size instead of overflowing
|
|
the grid). Each `1fr` row's direct child then has `min-height: 0` +
|
|
`overflow: hidden` so it actually respects that shrunk size rather than
|
|
forcing the track to grow (the #1 CSS grid gotcha this pattern exists to
|
|
avoid). Landscape-first: designed to be wide and comfortably short. */
|
|
.wx-display {
|
|
height: 100vh;
|
|
width: 100vw;
|
|
overflow: hidden;
|
|
box-sizing: border-box;
|
|
background: linear-gradient(160deg, var(--navy-dark) 0%, var(--navy) 60%, #14304a 100%);
|
|
color: var(--text);
|
|
display: grid;
|
|
grid-template-rows: auto auto minmax(0, 1fr) auto minmax(0, 1fr) auto;
|
|
row-gap: clamp(8px, 1.6vh, 20px);
|
|
padding: clamp(14px, 2.2vh, 32px) clamp(20px, 3vw, 48px);
|
|
font-size: clamp(12px, 0.95vw, 16px);
|
|
}
|
|
|
|
.wx-display-header {
|
|
display: flex;
|
|
align-items: baseline;
|
|
justify-content: space-between;
|
|
flex-wrap: wrap;
|
|
gap: 12px;
|
|
}
|
|
.wx-clock { font-size: clamp(1.8rem, 4.2vw, 3.4rem); font-weight: 700; line-height: 1; letter-spacing: -0.02em; }
|
|
.wx-date { color: var(--text-muted); font-size: clamp(0.9rem, 1.4vw, 1.25rem); margin-top: 4px; }
|
|
.wx-hotel-name { color: var(--gold); font-size: clamp(1rem, 1.6vw, 1.4rem); font-weight: 600; letter-spacing: .03em; text-transform: uppercase; }
|
|
|
|
.wx-now {
|
|
display: flex;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
gap: clamp(10px, 1.8vw, 24px);
|
|
}
|
|
.wx-now-temp-block { display: flex; flex-direction: column; align-items: center; line-height: 1; }
|
|
.wx-now-temp { font-size: clamp(2.4rem, 5.5vw, 4.6rem); font-weight: 700; line-height: 1; }
|
|
.wx-now-temp-label {
|
|
margin-top: 2px;
|
|
font-size: clamp(0.65rem, 0.85vw, 0.85rem);
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
letter-spacing: .06em;
|
|
color: var(--text-muted);
|
|
}
|
|
.wx-now-meta { display: flex; flex-direction: column; gap: 4px; color: var(--text-muted); font-size: clamp(0.8rem, 1.05vw, 1.05rem); }
|
|
.wx-summary-headline { color: var(--text); font-weight: 600; font-size: clamp(0.95rem, 1.3vw, 1.3rem); }
|
|
.wx-summary-note { color: #fbbf24; }
|
|
/* Tip chips (umbrella/jacket/...) -- can be more than one at once (e.g. a cold
|
|
rainy day), so this wraps multiple .wx-tip pills rather than a single fixed
|
|
span. Sized up from the old single-tip version to read clearly at a glance
|
|
on the kiosk tablet. */
|
|
.wx-tips {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
gap: clamp(8px, 1vw, 12px);
|
|
margin-left: auto;
|
|
}
|
|
.wx-tip {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: clamp(8px, 1.2vh, 13px) clamp(14px, 1.7vw, 22px);
|
|
border-radius: 999px;
|
|
background: rgba(125, 211, 252, 0.14);
|
|
border: 1px solid rgba(125, 211, 252, 0.35);
|
|
color: #7dd3fc;
|
|
font-weight: 600;
|
|
font-size: clamp(0.9rem, 1.15vw, 1.15rem);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.wx-section-label {
|
|
font-size: clamp(0.7rem, 0.95vw, 0.9rem);
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
letter-spacing: .08em;
|
|
color: var(--text-muted);
|
|
}
|
|
.wx-section-label-row {
|
|
display: flex;
|
|
align-items: baseline;
|
|
justify-content: space-between;
|
|
flex-wrap: wrap;
|
|
gap: 8px;
|
|
}
|
|
|
|
/* Sunrise/sunset, shown next to a day's label */
|
|
.wx-astro { display: flex; gap: clamp(10px, 1.2vw, 18px); color: var(--text-muted); font-size: clamp(0.7rem, 0.85vw, 0.9rem); }
|
|
.wx-astro-item { display: inline-flex; align-items: center; gap: 4px; }
|
|
|
|
/* Rain% and wind, each their own line with a small leading icon — shown on hourly
|
|
and 3-hourly point cards. .wx-meta-lg (today's hourly cards) runs ~150% bigger
|
|
than the default (next-2-days cards), matching today's larger icon/temp. */
|
|
.wx-meta { display: flex; flex-direction: column; align-items: center; gap: 2px; font-size: clamp(0.6rem, 0.72vw, 0.72rem); white-space: nowrap; }
|
|
.wx-meta-lg { font-size: clamp(0.9rem, 1.08vw, 1.08rem); gap: 3px; }
|
|
.wx-meta-line { display: inline-flex; align-items: center; gap: 3px; }
|
|
.wx-meta-precip { color: #7dd3fc; }
|
|
.wx-meta-wind { color: var(--text-muted); }
|
|
|
|
/* Today — wrapped in its own block so its hourly cards read as one distinct
|
|
group, not a continuation of the "Coming up" cards below it. Fills its grid
|
|
row exactly (see the .wx-display comment above). */
|
|
.wx-today-block {
|
|
background: var(--surface-strong);
|
|
border: 1px solid var(--surface-strong-border);
|
|
border-radius: var(--radius);
|
|
padding: clamp(8px, 1.4vh, 16px);
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: clamp(6px, 1vh, 12px);
|
|
min-height: 0;
|
|
overflow: hidden;
|
|
}
|
|
.wx-hourly-row {
|
|
display: flex;
|
|
align-items: stretch;
|
|
gap: clamp(5px, 0.8vw, 12px);
|
|
overflow-x: auto;
|
|
overflow-y: hidden;
|
|
flex: 1;
|
|
min-height: 0;
|
|
}
|
|
.wx-hour-card {
|
|
flex: 1 0 clamp(74px, 7.4vw, 106px);
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 5px;
|
|
padding: clamp(6px, 1vh, 10px) 4px;
|
|
border-radius: var(--radius);
|
|
background: var(--surface-2);
|
|
}
|
|
.wx-hour-card.now { background: rgba(201,168,76,0.16); border: 1px solid rgba(201,168,76,0.4); }
|
|
.wx-hour-time { font-size: clamp(0.68rem, 0.82vw, 0.88rem); color: var(--text-muted); }
|
|
.wx-hour-temp { font-size: clamp(1.28rem, 1.5vw, 1.65rem); font-weight: 600; }
|
|
|
|
/* "Coming up" — tomorrow/day-after (3-hourly) above, days 4-7 (daily) below,
|
|
sharing one grid row between them so the pair always fits. */
|
|
.wx-coming-up {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: clamp(8px, 1.4vh, 18px);
|
|
min-height: 0;
|
|
overflow: hidden;
|
|
}
|
|
.wx-next-days {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: clamp(10px, 1.4vw, 20px);
|
|
flex: 1;
|
|
min-height: 0;
|
|
}
|
|
.wx-next-day-card {
|
|
background: var(--surface-strong);
|
|
border: 1px solid var(--surface-strong-border);
|
|
border-radius: var(--radius);
|
|
padding: clamp(8px, 1.2vh, 14px);
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 6px;
|
|
min-height: 0;
|
|
overflow: hidden;
|
|
}
|
|
.wx-next-day-title-row { display: flex; align-items: baseline; justify-content: space-between; flex-wrap: wrap; gap: 6px; }
|
|
.wx-next-day-title { font-weight: 700; font-size: clamp(0.85rem, 1.1vw, 1.1rem); }
|
|
.wx-next-day-points {
|
|
display: flex;
|
|
align-items: stretch;
|
|
gap: clamp(4px, 0.6vw, 8px);
|
|
overflow-x: auto;
|
|
overflow-y: hidden;
|
|
flex: 1;
|
|
min-height: 0;
|
|
}
|
|
.wx-point {
|
|
flex: 1 0 clamp(46px, 4.4vw, 60px);
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 3px;
|
|
}
|
|
.wx-point-time { font-size: clamp(0.6rem, 0.75vw, 0.75rem); color: var(--text-muted); }
|
|
.wx-point-temp { font-size: clamp(0.78rem, 0.95vw, 1rem); font-weight: 600; }
|
|
|
|
/* Days 4-7 — plain daily row, fixed (short) height, doesn't need to stretch */
|
|
.wx-daily-row {
|
|
display: grid;
|
|
grid-template-columns: repeat(4, 1fr);
|
|
gap: clamp(8px, 1.2vw, 16px);
|
|
flex: 0 0 auto;
|
|
}
|
|
|
|
.wx-day-card {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 6px;
|
|
padding: clamp(8px, 1.2vh, 14px) 6px;
|
|
border-radius: var(--radius);
|
|
background: var(--surface-strong);
|
|
border: 1px solid var(--surface-strong-border);
|
|
}
|
|
.wx-day-name { font-weight: 700; font-size: clamp(0.78rem, 0.92vw, 0.98rem); }
|
|
.wx-day-temps { display: flex; gap: 8px; font-size: clamp(0.75rem, 0.88vw, 0.95rem); }
|
|
.wx-day-max { font-weight: 600; }
|
|
.wx-day-min { color: var(--text-muted); }
|
|
|
|
.wx-updated { text-align: right; color: var(--text-muted); font-size: clamp(0.65rem, 0.75vw, 0.8rem); }
|
|
|
|
.wx-fallback-screen {
|
|
height: 100vh; width: 100vw; box-sizing: border-box;
|
|
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
gap: 12px; background: var(--navy-dark); color: var(--text-muted);
|
|
}
|
|
|
|
/* Every "Coming up" card opens the hourly-detail modal on tap — see DayModal. */
|
|
.wx-tappable { cursor: pointer; transition: background .12s, transform .08s; }
|
|
.wx-tappable:hover { background: var(--surface-2); }
|
|
.wx-tappable:active { transform: scale(0.97); }
|
|
|
|
/* ── Day-detail modal (opened by tapping a "Coming up" card) ────────────── */
|
|
.wx-modal-overlay {
|
|
position: fixed;
|
|
inset: 0;
|
|
background: rgba(15,15,32,0.72);
|
|
backdrop-filter: blur(2px);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 24px;
|
|
z-index: 500;
|
|
}
|
|
.wx-modal {
|
|
background: var(--navy);
|
|
border: 1px solid var(--surface-2);
|
|
border-radius: var(--radius);
|
|
box-shadow: var(--shadow-md);
|
|
width: min(92vw, 920px);
|
|
max-height: 85vh;
|
|
padding: clamp(18px, 2.6vh, 32px);
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 16px;
|
|
color: var(--text);
|
|
overflow: hidden;
|
|
}
|
|
.wx-modal-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
|
|
.wx-modal-title { font-size: clamp(1.3rem, 2.4vw, 2rem); font-weight: 700; color: var(--gold); }
|
|
.wx-modal-close {
|
|
background: none; border: none; color: var(--text-muted); cursor: pointer;
|
|
padding: 4px; display: flex; flex-shrink: 0;
|
|
}
|
|
.wx-modal-close:hover { color: var(--text); }
|
|
.wx-modal-note { color: var(--text-muted); font-size: clamp(0.8rem, 1vw, 0.95rem); margin: 0; }
|
|
.wx-modal-empty {
|
|
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
gap: 10px; color: var(--text-muted); padding: 40px 0;
|
|
}
|
|
.wx-modal-hourly {
|
|
display: flex;
|
|
align-items: stretch;
|
|
gap: clamp(8px, 1.2vw, 16px);
|
|
overflow-x: auto;
|
|
padding-bottom: 4px;
|
|
}
|
|
.wx-modal-point {
|
|
flex: 1 0 clamp(64px, 7vw, 90px);
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 6px;
|
|
padding: clamp(10px, 1.4vh, 16px) 6px;
|
|
border-radius: var(--radius);
|
|
background: var(--surface-2);
|
|
}
|
|
.wx-modal-point-time { font-size: clamp(0.72rem, 0.85vw, 0.9rem); color: var(--text-muted); }
|
|
.wx-modal-point-temp { font-size: clamp(1.1rem, 1.3vw, 1.4rem); font-weight: 600; }
|
|
|
|
/* Idle-timeout progress bar — depletes over the 20s auto-close window, restarting
|
|
(via the DayModal's resetTick key, which remounts this element) on interaction. */
|
|
.wx-modal-timeout-track { height: 3px; background: var(--surface-2); border-radius: 2px; overflow: hidden; flex-shrink: 0; }
|
|
.wx-modal-timeout-bar { height: 100%; background: var(--gold); animation: wx-timeout-deplete 20s linear forwards; }
|
|
@keyframes wx-timeout-deplete { from { width: 100%; } to { width: 0%; } }
|
|
|
|
/* ── Admin (authed) page ──────────────────────────────────────────────── */
|
|
.wx-admin-shell { min-height: 100vh; background: var(--body-bg); display: flex; justify-content: center; padding: 32px 16px; }
|
|
.wx-admin-panel { width: 100%; max-width: 560px; }
|
|
.wx-admin-panel h1 { font-size: 20px; margin: 0 0 4px; color: var(--text-dark); }
|
|
.wx-admin-sub { color: var(--text-mid); font-size: 13px; margin-bottom: 20px; }
|
|
.wx-status-row { display: flex; justify-content: space-between; align-items: center; padding: 10px 0; border-bottom: 1px solid var(--card-border); font-size: 13.5px; }
|
|
.wx-status-row:last-child { border-bottom: none; }
|
|
.wx-status-label { color: var(--text-mid); }
|
|
.wx-status-value { font-weight: 600; color: var(--text-dark); }
|
|
.wx-status-value.ok { color: #16a34a; }
|
|
.wx-status-value.err { color: var(--danger); }
|
|
.wx-admin-actions { display: flex; gap: 10px; margin-top: 16px; }
|
|
.wx-admin-link { color: var(--gold); text-decoration: none; font-weight: 500; }
|
|
.wx-admin-link:hover { text-decoration: underline; }
|
|
.field-hint { font-size: 11.5px; color: var(--text-mid); margin-top: 3px; }
|
|
|
|
/* buttons — same convention as every other app */
|
|
.btn {
|
|
display: inline-flex; align-items: center; gap: 6px;
|
|
border: 1px solid var(--card-border); background: var(--card-bg); color: var(--text-dark);
|
|
border-radius: var(--radius); padding: 7px 14px; font-size: 13px; cursor: pointer;
|
|
font-family: var(--font); transition: background .12s, border-color .12s;
|
|
}
|
|
.btn:hover { border-color: var(--text-mid); }
|
|
.btn:disabled { opacity: .5; cursor: default; }
|
|
.btn-primary { background: var(--gold); border-color: var(--gold); color: var(--navy); font-weight: 600; }
|
|
.btn-primary:hover { background: var(--gold-light); border-color: var(--gold-light); }
|
|
|
|
.card {
|
|
background: var(--card-bg); border: 1px solid var(--card-border);
|
|
border-radius: var(--radius); box-shadow: var(--shadow-sm); padding: 14px 16px; margin-bottom: 10px;
|
|
}
|
|
.error-banner {
|
|
background: #fef2f2; border: 1px solid var(--danger); color: var(--danger);
|
|
border-radius: var(--radius); padding: 10px 14px; margin-bottom: 12px; font-size: 13px;
|
|
}
|
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
.spin { animation: spin .75s linear infinite; }
|