diff --git a/frontend/src/index.css b/frontend/src/index.css index c07c3ee..50091d3 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -34,47 +34,56 @@ html, body, #root { height: 100%; margin: 0; font-size: 14px; } ::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { background: var(--card-border); border-radius: 2px; } -/* ── Reception display (full-screen, public, no chrome) ─────────────────── */ +/* ── 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: flex; - flex-direction: column; - padding: clamp(16px, 3vh, 40px) clamp(20px, 4vw, 56px); - font-size: clamp(14px, 1.1vw, 18px); + 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; - margin-bottom: clamp(12px, 2vh, 28px); flex-wrap: wrap; gap: 12px; } -.wx-clock { font-size: clamp(2.2rem, 5vw, 4rem); font-weight: 700; line-height: 1; letter-spacing: -0.02em; } -.wx-date { color: var(--text-muted); font-size: clamp(1rem, 1.6vw, 1.4rem); margin-top: 4px; } -.wx-hotel-name { color: var(--gold); font-size: clamp(1.1rem, 1.8vw, 1.6rem); font-weight: 600; letter-spacing: .03em; text-transform: uppercase; } +.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; - gap: clamp(12px, 2vw, 28px); - margin-bottom: clamp(16px, 3vh, 32px); + gap: clamp(10px, 1.8vw, 24px); } -.wx-now-temp { font-size: clamp(3.2rem, 7vw, 6rem); font-weight: 700; line-height: 1; } -.wx-now-meta { display: flex; flex-direction: column; gap: 4px; color: var(--text-muted); font-size: clamp(0.9rem, 1.2vw, 1.15rem); } +.wx-now-temp { font-size: clamp(2.4rem, 5.5vw, 4.6rem); font-weight: 700; line-height: 1; } +.wx-now-meta { display: flex; flex-direction: column; gap: 4px; color: var(--text-muted); font-size: clamp(0.8rem, 1.05vw, 1.05rem); } .wx-section-label { - font-size: clamp(0.75rem, 1vw, 0.95rem); + font-size: clamp(0.7rem, 0.95vw, 0.9rem); font-weight: 700; text-transform: uppercase; letter-spacing: .08em; color: var(--text-muted); - margin-bottom: clamp(8px, 1.2vh, 14px); } .wx-section-label-row { display: flex; @@ -82,73 +91,110 @@ html, body, #root { height: 100%; margin: 0; font-size: 14px; } justify-content: space-between; flex-wrap: wrap; gap: 8px; - margin-bottom: clamp(8px, 1.2vh, 14px); } -.wx-section-label-row .wx-section-label { margin-bottom: 0; } /* 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.75rem, 0.9vw, 0.95rem); } +.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%/wind, shown on hourly and 3-hourly point cards */ -.wx-meta-precip { font-size: clamp(0.62rem, 0.75vw, 0.75rem); color: #7dd3fc; } -.wx-meta-wind { font-size: clamp(0.62rem, 0.75vw, 0.75rem); color: var(--text-muted); white-space: nowrap; } +/* Rain% + wind, one line, shown on hourly and 3-hourly point cards */ +.wx-meta { display: flex; align-items: center; gap: 4px; font-size: clamp(0.6rem, 0.72vw, 0.72rem); white-space: nowrap; } +.wx-meta-precip { color: #7dd3fc; } +.wx-meta-wind { color: var(--text-muted); } +.wx-meta-sep { color: var(--text-muted); opacity: .5; } /* 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. */ + 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); border: 1px solid var(--surface-2); border-radius: var(--radius); - padding: clamp(10px, 1.6vh, 18px); - margin-bottom: clamp(16px, 2.5vh, 28px); + 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; - gap: clamp(6px, 1vw, 14px); + align-items: stretch; + gap: clamp(5px, 0.8vw, 12px); overflow-x: auto; - padding-bottom: 4px; - flex: 0 0 auto; + overflow-y: hidden; + flex: 1; + min-height: 0; } .wx-hour-card { - flex: 1 0 68px; + flex: 1 0 clamp(52px, 5.2vw, 76px); display: flex; flex-direction: column; align-items: center; - gap: 5px; - padding: clamp(8px, 1vh, 14px) 4px; + justify-content: space-between; + gap: 4px; + 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.75rem, 0.9vw, 0.95rem); color: var(--text-muted); } -.wx-hour-temp { font-size: clamp(0.95rem, 1.1vw, 1.2rem); font-weight: 600; } +.wx-hour-time { font-size: clamp(0.68rem, 0.82vw, 0.88rem); color: var(--text-muted); } +.wx-hour-temp { font-size: clamp(0.85rem, 1vw, 1.1rem); font-weight: 600; } -/* Next 2 days — 3-hourly, both under the "Coming up" label */ +/* "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(12px, 1.6vw, 24px); - margin-bottom: clamp(16px, 2.5vh, 28px); + gap: clamp(10px, 1.4vw, 20px); + flex: 1; + min-height: 0; } .wx-next-day-card { background: var(--surface); border-radius: var(--radius); - padding: clamp(10px, 1.4vh, 18px); + 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; margin-bottom: 8px; } -.wx-next-day-title { font-weight: 700; font-size: clamp(0.95rem, 1.2vw, 1.2rem); } -.wx-next-day-points { display: flex; gap: clamp(4px, 0.8vw, 10px); overflow-x: auto; } -.wx-point { display: flex; flex-direction: column; align-items: center; gap: 4px; flex: 1 0 56px; } -.wx-point-time { font-size: clamp(0.65rem, 0.8vw, 0.8rem); color: var(--text-muted); } -.wx-point-temp { font-size: clamp(0.85rem, 1vw, 1.05rem); font-weight: 600; } +.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, still under the "Coming up" label above */ +/* 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(10px, 1.4vw, 20px); - margin-top: auto; + gap: clamp(8px, 1.2vw, 16px); + flex: 0 0 auto; } .wx-day-card { @@ -156,19 +202,20 @@ html, body, #root { height: 100%; margin: 0; font-size: 14px; } flex-direction: column; align-items: center; gap: 6px; - padding: clamp(10px, 1.4vh, 16px) 6px; + padding: clamp(8px, 1.2vh, 14px) 6px; border-radius: var(--radius); background: var(--surface); } -.wx-day-name { font-weight: 700; font-size: clamp(0.85rem, 1vw, 1.05rem); } -.wx-day-temps { display: flex; gap: 8px; font-size: clamp(0.8rem, 0.95vw, 1rem); } +.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.7rem, 0.8vw, 0.85rem); margin-top: 10px; } +.wx-updated { text-align: right; color: var(--text-muted); font-size: clamp(0.65rem, 0.75vw, 0.8rem); } .wx-fallback-screen { - height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; + 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); } diff --git a/frontend/src/pages/Display.tsx b/frontend/src/pages/Display.tsx index 35c2a40..91a95e6 100644 --- a/frontend/src/pages/Display.tsx +++ b/frontend/src/pages/Display.tsx @@ -51,17 +51,21 @@ function AstroRow({ astro }: { astro?: Astro }) { ) } +// Rain% + wind on one line ("12% · SW 8mph") — was two separate lines, which was +// part of what pushed the hourly strip past a single viewport's worth of height. function PointMeta({ point }: { point: ForecastPoint }) { + if (point.precip_prob == null && point.wind_speed_mph == null) return null return ( - <> + {point.precip_prob != null && {Math.round(point.precip_prob)}%} + {point.precip_prob != null && point.wind_speed_mph != null && ·} {point.wind_speed_mph != null && ( {point.wind_dir_deg != null ? `${compassFromDeg(point.wind_dir_deg)} ` : ''} {Math.round(point.wind_speed_mph)}mph )} - > + ) } @@ -173,40 +177,44 @@ export default function Display() {