/* ════════════════════════════════════════════════════════════════════════ table.css — Day tabs, filter profiles, column toggles, hourly table, now-row and night-row decorations. ════════════════════════════════════════════════════════════════════════ */ /* ── 5. DAY TABS (today / tomorrow / etc.) ─────────────────────────── */ /* Note: each tab's background colour comes from inline styles in app.js (the confidence bands). This file only handles spacing, typography, and the active-tab underline. */ /* Wrapper that hosts the scrollable strip, edge fades, and chevrons. */ .utci-day-tabs-wrap { position: relative; margin-top: 12px; margin-bottom: 15px; border: 1px solid #c9b08a; } /* Edge fade gradients — only show when there's content to scroll to. */ .utci-day-tabs-wrap::before, .utci-day-tabs-wrap::after { content: ''; position: absolute; top: 0; bottom: 2px; /* leave the underline border visible */ width: 40px; pointer-events: none; z-index: 1; opacity: 0; transition: opacity 0.2s; } .utci-day-tabs-wrap::before { left: 0; background: linear-gradient(to right, #f5edd6 0%, rgba(245,237,214,0) 100%); } .utci-day-tabs-wrap::after { right: 0; background: linear-gradient(to left, #f5edd6 0%, rgba(245,237,214,0) 100%); } .utci-day-tabs-wrap.fade-left::before { opacity: 1; } .utci-day-tabs-wrap.fade-right::after { opacity: 1; } /* Chevron buttons — sit on top of the fades. */ .utci-day-scroll { position: absolute; top: 50%; transform: translateY(-50%); width: 30px; height: 30px; border-radius: 50%; border: 1px solid #d4c0a0; background: #fffcf2; color: #6b4f2a; font-size: 20px; font-family: Fraunces, serif; line-height: 1; cursor: pointer; z-index: 2; display: flex; align-items: center; justify-content: center; padding: 0 0 2px 0; box-shadow: 0 1px 4px rgba(80,55,20,0.12); transition: opacity 0.2s, background 0.15s, transform 0.15s; } .utci-day-scroll:hover { background: #fef3dc; } .utci-day-scroll.left { left: -2px; } .utci-day-scroll.right { right: -2px; } .utci-day-scroll.hidden { opacity: 0; pointer-events: none; } .utci-day-tabs { display: flex; overflow-x: auto; scroll-behavior: smooth; scrollbar-width: none; /* Firefox */ -ms-overflow-style: none; /* old Edge */ } .utci-day-tabs::-webkit-scrollbar { display: none; /* Chrome/Safari */ } .utci-day-tab { flex: 1 0 0; min-width: 90px; padding: 10px 20px; cursor: pointer; font-family: Manrope, sans-serif; font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; background: transparent; border: 1px solid #c9b08a; color: #b09870; overflow-wrap: break-word; transition: color 0.2s, border-color 0.2s, background-color 0.2s, filter 0.15s; } .utci-day-tab + .utci-day-tab { border-left: none; } .utci-day-tab:hover:not(.active):not(.locked) { filter: brightness(1.05) !important; } .utci-day-tab.active { color: #c8922a; outline: 1px solid #000; outline-offset: -1px; z-index: 1; } /* Danger-band days breathe a slow dark-red inner glow */ .utci-day-tab.danger { animation: dayTabDangerPulse 2.4s ease-in-out infinite; } @keyframes dayTabDangerPulse { 0%, 100% { box-shadow: inset 0 0 0 0 rgba(136, 0, 0, 0); } 50% { box-shadow: inset 0 0 34px 8px rgba(136, 0, 0, 0.6); } } @media (prefers-reduced-motion: reduce) { .utci-day-tab.danger { animation: none; } } /* The "Mon 12 May" date underneath each tab name */ .utci-day-date { font-family: Fraunces, serif; font-style: italic; font-size: 13px; text-transform: none; letter-spacing: 0; display: block; margin-top: 3px; color: inherit; } /* ── 5b. FILTER PROFILES (the "Profile: Basic Urban …" bar) ───────── */ .filter-profiles { display: flex; flex-direction: column; gap: 0; margin-bottom: 0; padding: 0px; background: transparent; border: none; } .filter-profiles-label { font-family: Manrope, sans-serif; font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: #7a5c2a; margin-bottom: 6px; white-space: nowrap; text-align: center; } /* Config strip - value-only pickers for the felt-model settings (vehicle type, building type, fur colour, skin type, pollen type, SunSoak env). Sits directly under the profile picker, always visible (not tucked away in the collapsible Columns accordion) so these settings stay as prominent as the profile choice that they belong to. */ .col-toggles-config { display: flex; flex-wrap: wrap; justify-content: center; gap: 12px; align-items: center; margin-top: 0; padding: 10px 14px; background: #fffcf2; border: 1.5px solid #c9b08a; border-top: none; } .col-toggles-config-item { display: inline-flex; align-items: center; gap: 8px; padding: 6px 10px; background: rgba(255,255,255,0.6); border: 1px solid #e6d7b8; border-radius: 5px; } .config-item-label { font-family: Manrope, sans-serif; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: #9a7a4a; white-space: nowrap; } /* Main thermal selection - the config item whose value also drives the day-tab hi/lo readout. Always sorted first (see DayTabs.js) and marked with a badge so it reads as distinct from the other config pickers. */ .col-toggles-config-item--main { padding: 8px 8px; border: 1.5px solid #c8922a; border-radius: 4px; background: rgba(200,146,42,0.08); } .config-item-main-badge { display: block; flex: 0 0 100%; text-align: center; font-family: Manrope, sans-serif; font-size: 13px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.04em; color: #9a6a1a; padding: 0; white-space: nowrap; cursor: help; } /* Single scrollable row of profile cards */ .profile-scroll { display: flex; flex-wrap: nowrap; gap: 6px; align-items: center; pointer-events: none; justify-content: center; /* centre when content fits */ justify-content: safe center; /* fall back to flex-start if it overflows */ overflow-x: auto; overflow-y: auto; /* forced by browser when overflow-x is non-visible */ padding-bottom: 6px; margin-bottom: 0; scrollbar-width: none; -ms-overflow-style: none; padding-left: 4px; /* breathing room at left edge */ padding-right: 4px; /* breathing room at right edge */ } .profile-scroll::-webkit-scrollbar { display: none; } .profile-scroll > * { pointer-events: auto; } /* Fade-edge wrapper — hosts the ::before/::after gradient overlays */ .profile-scroll-wrap { position: relative; width: 100%; padding-top:8px; padding-bottom:1px; background-color: #fffcf2; border-top: 1.5px solid #c9b08a; border-left: 1.5px solid #c9b08a; border-right: 1.5px solid #c9b08a; border-bottom: 1.5px solid #c9b08a; } .profile-scroll-wrap::before, .profile-scroll-wrap::after { content: ''; position: absolute; top: 0; bottom: 0; width: 52px; pointer-events: none; opacity: 0; transition: opacity 0.2s ease; z-index: 2; } .profile-scroll-wrap::before { left: 0; background: linear-gradient(to right, #f5edd6 10%, transparent 100%); } .profile-scroll-wrap::after { right: 0; background: linear-gradient(to left, #f5edd6 10%, transparent 100%); } .profile-scroll-wrap.fade-left::before { opacity: 1; } .profile-scroll-wrap.fade-right::after { opacity: 1; } .profile-scroll-chevron { position: absolute; top: 50%; transform: translateY(-50%); z-index: 3; pointer-events: none; color: #8a6a3a; font-size: 18px; line-height: 1; opacity: 0; transition: opacity 0.2s ease; user-select: none; } .profile-scroll-chevron.left { left: 4px; } .profile-scroll-chevron.right { right: 4px; } .profile-scroll-wrap.fade-left .profile-scroll-chevron.left { opacity: 1; } .profile-scroll-wrap.fade-right .profile-scroll-chevron.right { opacity: 1; } /* ── Profile & config nav dropdown ────────────────────────────────────── The profile picker + config strip used to sit in a permanent full-width band above the day tabs. They now live in a dropdown that expands downward directly under the (now permanently floating) top nav, opened by the profile trigger embedded in the middle of that nav. Keeps the original full-width horizontal layout (profile tabs, horizontally scrolling card row, config strip) - just collapsed by default. */ /* Floating trigger, fixed to the bottom of the viewport (the dropdown itself now slides up from the bottom too — see .profile-dropdown below — so the button sits right where the panel will appear from). Always visible, independent of the nav/header, which has gone back to scrolling away normally instead of floating. */ .floating-profile-btn { position: fixed; left: 50%; bottom: 8px; transform: translateX(-50%); z-index: 98; display: flex; align-items: center; gap: 8px; max-width: 92vw; padding: 10px 18px; background: #fdf8ee; border: 1.5px solid #c9b08a; border-radius: 999px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.10); cursor: pointer; color: #6a4c1a; transition: background 0.15s, border-color 0.15s, box-shadow 0.15s; } @media (min-width: 1024px) { .floating-profile-btn { display: none; } } .floating-profile-btn:hover { background: #fff; border-color: #c8922a; box-shadow: 0 3px 10px rgba(0, 0, 0, 0.14); } .floating-profile-btn.is-open { background: #fff; border-color: #c8922a; } .floating-profile-icon { font-size: 16px; line-height: 1; flex: none; } /* No fixed max-width — the pill grows to fit the label. Ellipsis only kicks in once the whole button hits its 92vw cap (min-width:0 lets these flex children actually shrink instead of forcing the button to overflow). */ .floating-profile-val { font-family: Manrope, sans-serif; font-size: 13px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.02em; color: #1e1208; min-width: 0; flex: 0 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .floating-profile-sub { font-family: Manrope, sans-serif; font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.02em; color: #6b4f2a; min-width: 0; flex: 0 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .floating-profile-caret { font-size: 10px; transition: transform 0.2s ease; } .floating-profile-btn.is-open .floating-profile-caret { transform: rotate(180deg); } /* Backdrop + dropdown panel — always mounted once opened for the first time (see ConfigPanel.js), visibility/position driven by the .is-open class so both the open AND close transitions can animate (a conditionally-mounted element only ever gets an entrance animation, never an exit one). */ .profile-dropdown-overlay { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.35); z-index: 99; opacity: 0; visibility: hidden; pointer-events: none; transition: opacity 0.2s ease, visibility 0s linear 0.2s; } .profile-dropdown-overlay.is-open { opacity: 1; visibility: visible; pointer-events: auto; transition: opacity 0.2s ease, visibility 0s linear 0s; } .profile-dropdown { position: fixed; left: 0; right: 0; bottom: 0; max-height: calc(100vh - 24px); z-index: 101; background: #f5edd6; border-top: 1.5px solid #c9b08a; box-shadow: 0 -10px 28px rgba(0, 0, 0, 0.18); opacity: 0; visibility: hidden; pointer-events: none; /* Scales up from the floating-profile-btn's position (bottom-centre) so the panel reads as growing out of that button, and shrinks back into it on close, instead of a generic slide. */ transform-origin: 50% 100%; transform: scale(0.85); transition: opacity 0.22s cubic-bezier(0.22, 1, 0.36, 1), transform 0.22s cubic-bezier(0.22, 1, 0.36, 1), visibility 0s linear 0.22s; } .profile-dropdown.is-open { opacity: 1; visibility: visible; pointer-events: auto; transform: scale(1); transition: opacity 0.22s cubic-bezier(0.22, 1, 0.36, 1), transform 0.22s cubic-bezier(0.22, 1, 0.36, 1), visibility 0s linear 0s; } .profile-dropdown-body { max-width: 900px; margin: 0 auto; max-height: calc(100vh - 24px); overflow-y: auto; padding: 12px 24px 16px; } .profile-dropdown-close { position: absolute; top: 10px; right: 14px; background: none; border: none; font-size: 24px; line-height: 1; color: #b09870; cursor: pointer; padding: 2px 8px; border-radius: 6px; transition: color 0.15s, background 0.15s; } .profile-dropdown-close:hover { color: #1e1208; background: rgba(176, 152, 112, 0.16); } /* Tablet + desktop: the floating bottom button is mobile-only, so the panel hangs off the bottom edge of the active-profile-row instead — dropping down out of that row and shrinking back into it on close. --profile-anchor-top is the row's live viewport bottom, set by ConfigPanel.js. Phones (<=640px) keep the bottom sheet untouched. */ @media (min-width: 641px) { .profile-dropdown { top: var(--profile-anchor-top, 0px); bottom: auto; max-height: calc(100vh - var(--profile-anchor-top, 0px) - 16px); border: 1.5px solid #c9b08a; border-radius: 12px; overflow: hidden; /* keeps the scrolling body inside the rounded corners */ box-shadow: 0 12px 28px rgba(0, 0, 0, 0.18); transform-origin: 50% 0; } .profile-dropdown-body { max-height: calc(100vh - var(--profile-anchor-top, 0px) - 16px); } /* Too little room under the row for the content — revert to the mobile bottom sheet (set by ConfigPanel.js) so the panel gets the full height. */ .profile-dropdown.is-bottom-sheet { top: auto; bottom: 0; max-height: calc(100vh - 24px); border: none; border-top: 1.5px solid #c9b08a; border-radius: 12px 12px 0 0; box-shadow: 0 -10px 28px rgba(0, 0, 0, 0.18); transform-origin: 50% 100%; } .profile-dropdown.is-bottom-sheet .profile-dropdown-body { max-height: calc(100vh - 24px); } } /* Read-only "what am I looking at" row above the day tabs — states the active profile and the thermal basis (SunSoak env / vehicle / building / fur colour) driving the numbers below. Clicking/Enter opens the same profile & config dropdown as the nav trigger. */ .active-profile-row { display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: 12px; padding: 8px 16px; margin-bottom: 4px; /* --row-bg is set inline per the exact selected option (see ROW_GRADIENTS in DayTabs.js) — falls back to a flat parchment tone if unset. */ background: var(--row-bg, #fdf8ee); border: 2px solid #d4c0a0; border-radius: 9px; cursor: pointer; box-shadow: inset 0 -1px 0 rgba(0,0,0,0.05), 0 1px 3px rgba(0,0,0,0.04); transition: filter 0.2s, border-color 0.2s, box-shadow 0.2s; } /* Remove alpha from the default fallback color and any potential inherited semi-transparent backgrounds while keeping the hue. */ .active-profile-row:not([style*="background"]) { background-color: #fdf8ee; } @media (min-width: 1024px) { .active-profile-row { position: sticky; top: 0 !important; /* Stick to the very top of the viewport */ z-index: 11; } .utci-thead-sticky { position: sticky; top: 28px !important; /* Corrected to match the height of the active-profile-row + margins */ z-index: 10; } } .active-profile-row:hover, .active-profile-row:focus-visible { filter: brightness(1.04); border-color: #c8922a; box-shadow: inset 0 -1px 0 rgba(0,0,0,0.05), 0 3px 8px rgba(200,146,42,0.18); outline: none; } /* Breathing room between each "PROFILE" / "VIEWING" label and its value. */ .active-profile-row-item { display: inline-flex; align-items: baseline; gap: 7px; } .active-profile-row-label { font-family: Manrope, sans-serif; font-size: 12px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: #9a7d5a; } .active-profile-row-value { font-family: Manrope, sans-serif; font-size: 15px; font-weight: 800; letter-spacing: 0.02em; text-transform: uppercase; color: #1e1208; } .active-profile-row-sep { color: #c0a880; font-size: 16px; } .active-profile-row-edit { display: inline-flex; align-items: center; gap: 5px; font-family: Manrope, sans-serif; font-size: 12.5px; font-weight: 700; letter-spacing: 0.04em; color: #9a6a1a; margin-left: 8px; opacity: 0.7; transition: opacity 0.2s, transform 0.2s, color 0.2s; } .active-profile-row:hover .active-profile-row-edit, .active-profile-row:focus-visible .active-profile-row-edit { opacity: 1; color: #c8922a; } /* Border + text accent per thermal-model group, reusing the same colour groups the CustomSelect pickers use (see .cs-btn.grp-* in ui.css) — e.g. SunSoak's "Solar model" dropdown is grp-felt. The background itself comes from --row-bg (inline, per exact selection — see ROW_GRADIENTS); these classes only set the border/text so the row still reads as belonging to its category even though the gradient varies option-to-option. */ .active-profile-row.grp-felt { border-color: rgba(7,92,58,0.35); } .active-profile-row.grp-wind { border-color: rgba(106,34,16,0.30); } .active-profile-row.grp-ambient { border-color: rgba(60,58,53,0.30); } .active-profile-row.grp-surface { border-color: rgba(11,62,114,0.30); } .active-profile-row.grp-felt .active-profile-row-value { color: #075c3a; } .active-profile-row.grp-wind .active-profile-row-value { color: #6a2210; } .active-profile-row.grp-ambient .active-profile-row-value { color: #3c3a35; } .active-profile-row.grp-surface .active-profile-row-value { color: #0b3e72; } /* Vertical rule separating general-use profiles from technical ones */ .profile-divider { display: inline-block; width: 1px; align-self: stretch; background: #c9b08a; margin: 0 2px; opacity: 0.6; flex-shrink: 0; } .profile-btn { width: 100px; height: 100px; flex: none; /* never shrink below 100px — the row scrolls instead */ padding: 0; position: relative; display: inline-flex; flex-direction: column; align-items: stretch; overflow: hidden; cursor: pointer; border: 1.5px solid #c9b08a; background: transparent; border-radius: 3px; transition: border-color 0.15s, box-shadow 0.15s; -webkit-user-select: none; user-select: none; } .profile-btn-scene { flex: 1; background-size: cover; background-position: center; display: flex; align-items: center; justify-content: center; font-size: 22px; line-height: 1; opacity: 0.7; transition: opacity 0.2s; } .profile-btn:hover .profile-btn-scene { opacity: 0.8; } .profile-btn.active .profile-btn-scene { opacity: 1; } .profile-lock-badge { position: absolute; top: 4px; right: 4px; font-size: 11px; line-height: 1; pointer-events: none; z-index: 1; } .profile-btn-label { position: absolute; bottom: 0; left: 0; right: 0; height: 24px; display: block; line-height: 24px; text-align: center; font-family: Manrope, sans-serif; font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; background: rgba(30,18,8,0.52); color: #fff; padding: 0 4px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .profile-btn:hover { border-color: #c8922a; box-shadow: 0 1px 4px rgba(200,146,42,0.25); } .profile-btn.active { border-color: #c8922a; } .profile-btn.active .profile-btn-label { background: rgba(200,146,42,0.82); color: #fff; } /* Tab strip above the profile scroll row */ .profile-tabs { display: flex; width: 360px; margin-left: auto; margin-right: auto; margin-bottom: 0px; } .profile-tab { flex: 1; padding: 7px 4px; background: transparent; border: none; border-bottom: 1.5px solid transparent; border-left: 1.5px solid #f5edd6; border-right: 1.5px solid #f5edd6; border-top: 1.5px solid #f5edd6; font-family: Manrope, sans-serif; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: #9a7a4a; cursor: pointer; transition: color 0.15s, border-color 0.15s; white-space: nowrap; border-radius:7px 7px 0px 0px; z-index:1; } .profile-tab:hover { color: #c8922a; background: #f0e4c4; } .profile-tab.active { color: #c8922a; background-color: #fffcf2; border-bottom-color: #fffcf2; border-left: 1.5px solid #c9b08a; border-right: 1.5px solid #c9b08a; border-top: 1.5px solid #c9b08a; margin-bottom: -1.5px; } /* ── 6. COLUMN TOGGLES (the "Columns: Hour Air RH ..." bar) ────────── */ .col-toggles { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin-bottom: 0; padding: 10px 14px; background: #fdf8ee; border: 1.5px solid #d4c0a0; border-bottom: none; position: relative; } /* 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; } .table-main:not(.table-main--simple) .col-toggles.col-toggles--open { display: flex; } .col-toggles-display-opts { display: flex; align-items: center; gap: 12px; margin-left: auto; padding-left: 10px; flex-shrink: 0; } .col-toggles-label { display: none; } .col-toggles-edit-btn { display: flex; align-items: center; justify-content: space-between; gap: 6px; width: 150px; padding: 6px 12px; background: #f5edd6; border: 1.5px solid #c9b08a; color: #7a5c2a; font-family: Manrope, sans-serif; font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; cursor: pointer; text-align: left; } /* Free tier: the button stays visible but opens the Extra upsell instead of the Columns bar. Muted to read as locked, still obviously clickable. */ .col-toggles-edit-btn--locked { background: #efe6d2; border-color: #c0b090; color: #9a8060; } .col-toggles-edit-btn--locked:hover { color: #7a5c2a; } .col-toggles-edit-btn-icon { width: 12px; height: 12px; flex-shrink: 0; transition: transform 0.2s ease; } .col-toggles-edit-btn--open .col-toggles-edit-btn-icon { transform: rotate(180deg); } .col-toggles-body { display: contents; } /* The little pill buttons */ .col-toggle { padding: 5px 13px; cursor: pointer; border: 1.5px solid #c9b08a; background: #f5edd6; color: #6b4228; font-family: Manrope, sans-serif; font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; transition: all 0.15s; -webkit-user-select: none; user-select: none; } .col-toggle:hover { border-color: #c8922a; color: #6b4f2a; background: #fef3dc; } /* The "pressed-in" active look */ .col-toggle.on { background: #c8922a; border-color: #c8922a; color: #fff; } /* ── COLUMN TOGGLE - group colour tints ───────────────────────────────── Idle: light group tint bg + group text + group border. Hover: darker tint matching th hover palette. On: solid saturated group colour, white text. On-hover: slightly darker solid. */ .col-toggle.grp-felt { background: rgba(223,243,236,0.50); color: #075c3a; border-color: rgba(7,92,58,0.35); } .col-toggle.grp-surface { background: rgba(221,234,248,0.50); color: #0b3e72; border-color: rgba(11,62,114,0.30); } .col-toggle.grp-ambient { background: rgba(236,234,227,0.50); color: #3c3a35; border-color: rgba(60,58,53,0.30); } .col-toggle.grp-precip { background: rgba(247,228,238,0.50); color: #6b1e3a; border-color: rgba(107,30,58,0.30); } .col-toggle.grp-sky { background: rgba(234,232,252,0.50); color: #352e7a; border-color: rgba(53,46,122,0.30); } .col-toggle.grp-wind { background: rgba(250,230,224,0.50); color: #6a2210; border-color: rgba(106,34,16,0.30); } .col-toggle.grp-airqual { background: rgba(229,240,216,0.50); color: #244408; border-color: rgba(36,68,8,0.30); } .col-toggle.grp-solar { background: rgba(250,234,204,0.50); color: #5a3004; border-color: rgba(90,48,4,0.30); } .col-toggle.grp-felt:hover { background: rgba(213,233,226,0.80); color: #075c3a; border-color: rgba(7,92,58,0.55); } .col-toggle.grp-surface:hover { background: rgba(211,224,238,0.80); color: #0b3e72; border-color: rgba(11,62,114,0.50); } .col-toggle.grp-ambient:hover { background: rgba(226,224,217,0.80); color: #3c3a35; border-color: rgba(60,58,53,0.50); } .col-toggle.grp-precip:hover { background: rgba(237,218,228,0.80); color: #6b1e3a; border-color: rgba(107,30,58,0.50); } .col-toggle.grp-sky:hover { background: rgba(224,222,242,0.80); color: #352e7a; border-color: rgba(53,46,122,0.50); } .col-toggle.grp-wind:hover { background: rgba(240,220,214,0.80); color: #6a2210; border-color: rgba(106,34,16,0.50); } .col-toggle.grp-airqual:hover { background: rgba(219,230,206,0.80); color: #244408; border-color: rgba(36,68,8,0.50); } .col-toggle.grp-solar:hover { background: rgba(240,224,194,0.80); color: #5a3004; border-color: rgba(90,48,4,0.50); } .col-toggle.grp-felt.on { background: #0f9e63; border-color: #0f9e63; color: #fff; } .col-toggle.grp-surface.on { background: #2672c8; border-color: #2672c8; color: #fff; } .col-toggle.grp-ambient.on { background: #7a7870; border-color: #7a7870; color: #fff; } .col-toggle.grp-precip.on { background: #b03060; border-color: #b03060; color: #fff; } .col-toggle.grp-sky.on { background: #6058c8; border-color: #6058c8; color: #fff; } .col-toggle.grp-wind.on { background: #b04030; border-color: #b04030; color: #fff; } .col-toggle.grp-airqual.on { background: #4a8018; border-color: #4a8018; color: #fff; } .col-toggle.grp-solar.on { background: #b07820; border-color: #b07820; color: #fff; } .col-toggle.grp-felt.on:hover { background: #0c7d4e; border-color: #0c7d4e; color: #fff; } .col-toggle.grp-surface.on:hover { background: #1e5aa0; border-color: #1e5aa0; color: #fff; } .col-toggle.grp-ambient.on:hover { background: #5e5c56; border-color: #5e5c56; color: #fff; } .col-toggle.grp-precip.on:hover { background: #8c264c; border-color: #8c264c; color: #fff; } .col-toggle.grp-sky.on:hover { background: #4c48a8; border-color: #4c48a8; color: #fff; } .col-toggle.grp-wind.on:hover { background: #8c3226; border-color: #8c3226; color: #fff; } .col-toggle.grp-airqual.on:hover { background: #3a6412; border-color: #3a6412; color: #fff; } .col-toggle.grp-solar.on:hover { background: #8c5e18; border-color: #8c5e18; color: #fff; } /* ── Vehicle group: select + windows-open checkbox fused together ── */ .col-toggle-group { display: inline-flex; align-items: stretch; gap: 0; /* Collapse any whitespace-node gap between the cs-wrap span and cs-vent */ font-size: 0; } .col-toggle-group > * { font-size: 12px; /* restore font size for children */ } /* Remove the right border + radius on the cs-wrap/cs-btn so it butts flush with the VentPill. The cs-wrap is the direct child, so we target its inner cs-btn via the grouped-left class on the button. */ .col-toggle-group .cs-wrap { display: inline-flex; align-items: stretch; } .col-toggle-group .grouped-left { border-right: none !important; border-radius: 0 !important; } .col-toggle-group .cs-wrap:only-child .grouped-left, .col-toggle-group .cs-wrap:last-child .grouped-left { border-right: 1.5px solid #c9b08a !important; border-radius: 0 !important; } /* ── 7. HOURLY TABLE ────────────────────────────────────────────────── */ .utci-table-wrap { /* No overflow on this wrapper — having any overflow here would make the wrapper a scroll container, which would break sticky-to-viewport on the .utci-thead-sticky strip inside. The body scroller (.utci-tbody-scroll) owns the horizontal scrollbar. */ border: 1.5px solid #c9b08a; background: #fffcf2; position: relative; /* needed for ::before / ::after pseudo-element indicators */ } /* ── SCROLL INDICATORS ────────────────────────────────────────────────── Fade + chevron overlays on the left/right edges of the table wrap. They appear only when there is content to scroll in that direction, driven by the scroll-fade-left / scroll-fade-right classes toggled by JS. */ .utci-table-wrap::before, .utci-table-wrap::after { content: ''; position: absolute; top: 0; bottom: 0; width: 48px; pointer-events: none; opacity: 0; transition: opacity 0.2s ease; z-index: 6; /* above sticky header (z-index 5) */ } /* Left fade — warm cream fading to transparent rightward */ .utci-table-wrap::before { left: 0; background: linear-gradient(to right, rgba(240,228,196,0.92) 0%, transparent 100%); } /* Right fade — warm cream fading to transparent leftward */ .utci-table-wrap::after { right: 0; background: linear-gradient(to left, rgba(240,228,196,0.92) 0%, transparent 100%); } .utci-table-wrap.scroll-fade-left::before { opacity: 1; } .utci-table-wrap.scroll-fade-right::after { opacity: 1; } /* Chevron arrows — sit on top of the fade, centred vertically */ .utci-table-wrap .utci-scroll-chevron { position: absolute; top: 50%; transform: translateY(-50%); z-index: 7; pointer-events: none; color: #8a6a3a; font-size: 18px; line-height: 1; opacity: 0; transition: opacity 0.2s ease; user-select: none; } .utci-table-wrap .utci-scroll-chevron.left { left: 6px; } .utci-table-wrap .utci-scroll-chevron.right { right: 6px; } .utci-table-wrap.scroll-fade-left .utci-scroll-chevron.left { opacity: 1; } .utci-table-wrap.scroll-fade-right .utci-scroll-chevron.right { opacity: 1; } /* Grab cursor on the body scroller to hint drag-to-scroll */ .utci-tbody-scroll { cursor: grab; user-select: none; /* prevent text highlighting during drag */ -webkit-user-select: none; } .utci-tbody-scroll:active { cursor: grabbing; } /* ── STICKY HEADER STRIP ──────────────────────────────────────────────── Sits above the body and locks to the top of the viewport while the page scrolls. Has overflow:hidden so the inner .utci-thead-track can be translateX'd by JS to follow the body's scrollLeft. */ .utci-thead-sticky { position: sticky; top: 0; z-index: 5; overflow: hidden; background: #f0e4c4; border-bottom: 2px solid #c9b08a; box-shadow: 0 2px 5px rgba(74, 52, 32, 0.10); } /* On mobile the nav bar is fixed at 72px - sticky header must clear it */ @media (max-width: 640px) { .utci-thead-sticky { top: 72px; } /* Profile/config dropdown - full width and scrollable on phones since it can run taller than the viewport once the config strip wraps. */ .profile-dropdown-body { max-height: calc(100vh - 16px); overflow-y: auto; } .floating-profile-val { max-width: 90px; } .floating-profile-sub { max-width: 90px; } /* Shrink the day-tab labels on phones so more days stay readable within the viewport before the tab strip needs to scroll. */ .utci-day-tab { font-size: 9px; padding: 10px 12px; } .utci-day-date { font-size: 12px; } } /* The shifted track inside the sticky header — JS sets transform:translate3d to keep its columns aligned with the body scroller's scrollLeft. */ .utci-thead-track { will-change: transform; } /* Body scroller — owns the horizontal scrollbar for the whole table. */ .utci-tbody-scroll { overflow-x: auto; overflow-y: visible; } .utci-table { width: 100%; border-collapse: collapse; font-size: 14px; /* Fixed layout so the explicit per-cell widths set by the JS width-sync (see useLayoutEffect in app.js) are actually enforced. Without this the browser's auto layout treats those widths as suggestions and redistributes space, which makes the header and body columns drift apart the further right you scroll. */ table-layout: fixed; } /* Column headings (Hour, Air, RH, Wind...). NOTE: stickiness is handled by the .utci-thead-sticky wrapper, not the