diff --git a/assets/css/table.css b/assets/css/table.css index f74f2e1..e744e4d 100644 --- a/assets/css/table.css +++ b/assets/css/table.css @@ -926,13 +926,61 @@ /* Whole Columns bar (Hours + column pills) stays collapsed behind the Edit columns / Hide Columns toggle in the toolbar row until opened. Scoped to detailed (table) view — quick view keeps its own reduced - col-toggles bar (Hours only) visible regardless of this state. */ -.table-main:not(.table-main--simple) .col-toggles { - display: none; + col-toggles bar (Hours only) visible regardless of this state. + + It slides rather than snapping: the wrapper is a 1-row grid animated + 0fr → 1fr, which is the only way to transition to an unknown content + height in CSS. The bar's own padding/top border have to collapse with + it, otherwise a 25px sliver of empty panel stays behind at 0fr. + + Note the wrapper carries key=${forecastView} in app.js. Quick view keeps + this bar expanded and detailed view collapses it, so on a view switch the + browser would otherwise animate that difference — the panel would flash + open and slide shut. Remounting gives it no previous value to transition + from, so the collapsed state just applies. */ +.col-toggles-wrap { + display: grid; + grid-template-rows: 1fr; } -.table-main:not(.table-main--simple) .col-toggles.col-toggles--open { - display: flex; +.table-main:not(.table-main--simple) .col-toggles-wrap { + grid-template-rows: 0fr; + transition: grid-template-rows 0.28s cubic-bezier(0.4, 0, 0.2, 1); +} + +.table-main:not(.table-main--simple) .col-toggles-wrap > .col-toggles { + overflow: hidden; + min-height: 0; + padding-top: 0; + padding-bottom: 0; + border-top-width: 0; + opacity: 0; + transition: + padding 0.28s cubic-bezier(0.4, 0, 0.2, 1), + border-top-width 0.28s cubic-bezier(0.4, 0, 0.2, 1), + opacity 0.18s ease; +} + +.table-main:not(.table-main--simple) .col-toggles-wrap--open { + grid-template-rows: 1fr; +} + +.table-main:not(.table-main--simple) .col-toggles-wrap--open > .col-toggles { + padding-top: 10px; + padding-bottom: 10px; + border-top-width: 1.5px; + opacity: 1; + transition: + padding 0.28s cubic-bezier(0.4, 0, 0.2, 1), + border-top-width 0.28s cubic-bezier(0.4, 0, 0.2, 1), + opacity 0.22s ease 0.06s; +} + +@media (prefers-reduced-motion: reduce) { + .table-main:not(.table-main--simple) .col-toggles-wrap, + .table-main:not(.table-main--simple) .col-toggles-wrap > .col-toggles { + transition: none; + } } @@ -1392,38 +1440,94 @@ background: #e8d8a8; } -/* ── HOUR-INTERVAL SELECTOR ────────────────────────────────────────────── - Compact 1h/2h/3h/4h segmented control inside the "Hour" header. Buckets - the table into multi-hour rows (clock-aligned from midnight). Lives in the - header so it stays put while the body scrolls. */ +/* ── ROW-INTERVAL STEPPER ──────────────────────────────────────────────── + "Rows [−] 3h [+]" — sets how many hours each table row covers + (clock-aligned buckets from midnight). Used twice: bare inside the "Hour" + header, where it stays put while the body scrolls, and with its label in + the simple-view toggle bar (see .fvt-interval below). */ .hour-interval { display: flex; justify-content: flex-start; - gap: 2px; margin-top: 4px; } -.hour-interval-btn { +.row-stepper { + display: inline-flex; + align-items: center; + gap: 3px; +} +.row-stepper-label { font-family: Manrope, sans-serif; - font-size: 10px; + font-size: 11px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.07em; + color: #7a5c2a; + margin-right: 2px; +} +.row-stepper-btn { + display: flex; + align-items: center; + justify-content: center; + min-width: 26px; + min-height: 26px; + padding: 0; + font-family: Manrope, sans-serif; + font-size: 14px; font-weight: 700; line-height: 1; - padding: 3px 4px; cursor: pointer; border: 1px solid #c9b08a; background: #f5edd6; color: #6b4228; border-radius: 3px; + touch-action: manipulation; + user-select: none; transition: background 0.12s, border-color 0.12s, color 0.12s; } -.hour-interval-btn:hover { +.row-stepper-btn:hover:not(:disabled) { border-color: #c8922a; background: #fef3dc; } -.hour-interval-btn.on { +.row-stepper-btn:active:not(:disabled) { background: #c8922a; border-color: #c8922a; color: #fff; } +.row-stepper-btn:disabled { + opacity: 0.35; + cursor: default; +} +/* Fixed width so the row doesn't jitter as the value changes. */ +.row-stepper-value { + min-width: 26px; + text-align: center; + font-family: Manrope, sans-serif; + font-size: 11px; + font-weight: 700; + line-height: 1; + color: #6b4228; + user-select: none; +} + +/* Roomier tap targets on touch-sized screens. The simple-view stepper gets + the full bump; the one in the Hour header stays tighter because that + column is pinned and its width is taken straight off the viewport. */ +@media (max-width: 640px) { + .row-stepper-btn { + min-width: 28px; + min-height: 28px; + font-size: 15px; + } + .fvt-interval .row-stepper-btn { + min-width: 34px; + min-height: 34px; + font-size: 16px; + } + .fvt-interval .row-stepper-value { + min-width: 30px; + font-size: 12px; + } +} /* ── COLUMN DESCRIPTION POPUP ─────────────────────────────────────────── Fixed-position card that appears below a clicked column header. @@ -2154,27 +2258,17 @@ margin-bottom: -1.5px; } -/* Interval buttons shown in the col-toggles bar in simple mode, pushed - to the far right since Columns/Edit controls are hidden there. */ +/* Row stepper shown in the col-toggles bar in simple mode, pushed to the + far right since Columns/Edit controls are hidden there. */ .fvt-interval { display: none; align-items: center; - gap: 2px; margin-left: auto; flex-shrink: 0; } .table-main--simple .fvt-interval { display: inline-flex; } -.fvt-interval-label { - font-family: Manrope, sans-serif; - font-size: 11px; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.07em; - color: #7a5c2a; - margin-right: 3px; -} /* Hide table-mode column controls when in simple mode */ .table-main--simple .col-toggles-label, diff --git a/assets/js/app.js b/assets/js/app.js index 57340f3..9f0ccc3 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -31,7 +31,7 @@ import { FUR_COLORS, confidenceBand, moonGlyph, skyFillForElev, } from './utils.js'; -import { SkyScope, WindVane, CloudIcon, ScopeReticle, PrecipIcon, CustomSelect, VentPill } from './components.js'; +import { SkyScope, WindVane, CloudIcon, ScopeReticle, PrecipIcon, CustomSelect, VentPill, RowStepper } from './components.js'; import { getCellTagEvents, getUpcomingEvents } from './events.js'; import { POLLEN_TYPES, COL_DESCRIPTIONS, UTCI_ENVIRONMENTS, FILTER_PROFILES, variantIcons, deriveProfileMain } from './config.js'; import { useAppState } from './hooks/useAppState.js'; @@ -1103,10 +1103,10 @@ export function UTCIForecast() { `; })()} +