diff --git a/assets/css/table.css b/assets/css/table.css index 99ae91e..712c0d7 100644 --- a/assets/css/table.css +++ b/assets/css/table.css @@ -11,9 +11,13 @@ /* Wrapper that hosts the scrollable strip, edge fades, and chevrons. */ .utci-day-tabs-wrap { position: relative; + display: flex; margin-top: 12px; margin-bottom: 15px; border: 1px solid #c9b08a; + /* Width of the fixed metric-label column; the left fade and left chevron + both offset by this so they never sit on top of it. */ + --day-legend-w: 72px; } /* Edge fade gradients — only show when there's content to scroll to. */ @@ -30,7 +34,7 @@ transition: opacity 0.2s; } .utci-day-tabs-wrap::before { - left: 0; + left: var(--day-legend-w); background: linear-gradient(to right, #f5edd6 0%, rgba(245,237,214,0) 100%); } .utci-day-tabs-wrap::after { @@ -66,15 +70,67 @@ .utci-day-scroll:hover { background: #fef3dc; } -.utci-day-scroll.left { left: -2px; } +.utci-day-scroll.left { left: calc(var(--day-legend-w) - 2px); } .utci-day-scroll.right { right: -2px; } .utci-day-scroll.hidden { opacity: 0; pointer-events: none; } +/* Fixed first column naming the two numbers stacked in each tab — which + metrics those are changes with the active profile (SunSoak/Shade, Indoors/ + Managed, Fur/Shade, Vehicle/Windows Open), so without this the numbers are + unlabelled. Sits outside .utci-day-tabs so it never scrolls away. + + Alignment: .utci-day-tabs stretches every tab to equal height and the + numeric block is the last thing in each one, so bottom-aligning this column + with matching padding lands the labels on the same lines as the values — + no need to replicate the day-name / date / icon stack above them. */ +.utci-day-legend-col { + flex: 0 0 var(--day-legend-w); + display: flex; + flex-direction: column; + justify-content: flex-end; + align-items: flex-end; + gap: 1px; + /* Bottom 11px = the tab's 10px padding + its 1px bottom border. */ + padding: 10px 6px 11px; + background: #fdf8ee; + text-align: right; + font-family: Manrope, sans-serif; + text-transform: uppercase; + letter-spacing: 0.06em; + overflow-wrap: break-word; +} + +/* Each label matches the weight and colour family of the value beside it, so + the big/small pairing reads without a legend. Heights match the value line + boxes: 14px x 1.1 and 11px x 1.1 (see the numeric block in DayTabs.js). */ +.utci-day-legend-main { + display: flex; + align-items: center; + justify-content: flex-end; + min-height: 15px; + font-size: 9px; + font-weight: 800; + line-height: 1.1; + color: #6b4f2a; +} +.utci-day-legend-sec { + display: flex; + align-items: center; + justify-content: flex-end; + min-height: 12px; + font-size: 8px; + font-weight: 600; + line-height: 1.1; + color: #9a7d5a; +} + .utci-day-tabs { display: flex; + flex: 1 1 auto; + min-width: 0; overflow-x: auto; scroll-behavior: smooth; scrollbar-width: none; /* Firefox */ @@ -1039,6 +1095,13 @@ .utci-day-date { font-size: 12px; } + + /* Narrow the metric-label column too, so it costs as little tab width as + possible on phones. */ + .utci-day-tabs-wrap { --day-legend-w: 56px; } + .utci-day-legend-col { padding: 10px 4px 11px; letter-spacing: 0.04em; } + .utci-day-legend-main { font-size: 8px; } + .utci-day-legend-sec { font-size: 7px; } } /* The shifted track inside the sticky header — JS sets transform:translate3d diff --git a/assets/js/components/DayTabs.js b/assets/js/components/DayTabs.js index 9ba7b0d..768bb0d 100644 --- a/assets/js/components/DayTabs.js +++ b/assets/js/components/DayTabs.js @@ -133,8 +133,10 @@ export function DayTabs({ // full config strip now live in the nav dropdown (ConfigPanel); here we // only need the derived main metric to compute the day-tab numbers and to // label the "what temps am I looking at" row below. - const { isHomeOrOffice, isVehicleProfile, mainConfigKey, mainField, secondaryField, mainLabel } = - deriveProfileMain(activeProfile, outdoorsVariant); + const { + isHomeOrOffice, isVehicleProfile, mainConfigKey, mainField, secondaryField, mainLabel, + mainMetricLabel, mainMetricDesc, secondaryMetricLabel, secondaryMetricDesc, + } = deriveProfileMain(activeProfile, outdoorsVariant); // Read-only label of the current main-config value, for the row above the // day tabs — makes it obvious at a glance which temperature basis (SunSoak @@ -202,6 +204,15 @@ export function DayTabs({ onClick=${() => scrollDayTabs(1)} aria-label="Scroll days right" type="button">› + ${/* Fixed label column outside the scroller — names the two stacked + numbers in every tab, since which metrics they are changes with + the active profile. Bottom-aligned against the tabs' numeric + block (see .utci-day-legend-col in table.css). */''} +
+ ${mainMetricLabel} + ${(isVehicleProfile || secondaryField) && html` + ${secondaryMetricLabel}`} +
${days.map((d, i) => { const band = confidenceBand(i); diff --git a/assets/js/config.js b/assets/js/config.js index bf96e54..d52c5e3 100644 --- a/assets/js/config.js +++ b/assets/js/config.js @@ -192,7 +192,27 @@ export function deriveProfileMain(activeProfile, outdoorsVariant) { const mainLabel = activeProfile === 'outdoors' ? (OUTDOORS_VARIANTS[outdoorsVariant]?.name || FILTER_PROFILES.outdoors.label) : (FILTER_PROFILES[activeProfile]?.label || 'SunSoak'); - return { isHomeOrOffice, isVehicleProfile, isPetsProfile, mainConfigKey, mainField, secondaryField, mainLabel }; + + // Human names for the two numbers stacked in each day tab, so the label + // column beside the strip can say what they are. Pulled from + // COL_DESCRIPTIONS so they never drift from the table column tooltips — + // every field matches its column key except utciAdj, whose column is utciP. + const colKey = mainField === 'utciAdj' ? 'utciP' : mainField; + const mainMetric = COL_DESCRIPTIONS[colKey] || {}; + // Vehicle/Driver has no secondary column — the small number is a live + // re-run of the cabin model with ventilation forced on (see DayTabs). + const secondaryMetric = isVehicleProfile + ? { title: 'Windows Open', desc: 'The same vehicle interior estimate, recalculated with the windows open — how much cooler the cabin would run if it were ventilated.' } + : (secondaryField ? (COL_DESCRIPTIONS[secondaryField] || {}) : null); + + return { + isHomeOrOffice, isVehicleProfile, isPetsProfile, + mainConfigKey, mainField, secondaryField, mainLabel, + mainMetricLabel: mainMetric.title || '', + mainMetricDesc: mainMetric.desc || '', + secondaryMetricLabel: secondaryMetric?.title || '', + secondaryMetricDesc: secondaryMetric?.desc || '', + }; } // Emoji glyph per place/activity variant.