3076 lines
99 KiB
CSS
3076 lines
99 KiB
CSS
/* ════════════════════════════════════════════════════════════════════════
|
||
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;
|
||
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. */
|
||
.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: var(--day-legend-w);
|
||
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: 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 every stacked row in each tab — day name, date,
|
||
the weather/status icon, and the two numbers (which metrics those are
|
||
changes with the active profile: SunSoak/Shade, Indoors/Managed, Fur/Shade,
|
||
Vehicle/Windows Open) — so without this column the tabs read as unlabelled
|
||
numbers. Sits outside .utci-day-tabs so it never scrolls away.
|
||
|
||
Alignment: every label is absolutely positioned at a fixed offset from the
|
||
top of the column, matching the corresponding row inside .utci-day-tab.
|
||
Taking the labels out of normal flow is what makes this robust across
|
||
profiles: they no longer stack against each other, so a label that wraps
|
||
to two lines ("Managed Indoors") can't push the rows below it, and a
|
||
shorter one ("Shade Air") can't pull them up. Previously every label's
|
||
position depended on the height of the ones above it, so alignment tuned
|
||
against one profile drifted on the others.
|
||
|
||
The offsets below are measured from the tab's own stack:
|
||
1px border + 10px padding → day 16px → +3px date 16px → +6px icon 30px
|
||
→ +2px values
|
||
The legend column has no top border of its own, hence the leading 11px. */
|
||
.utci-day-legend-col {
|
||
position: relative;
|
||
flex: 0 0 var(--day-legend-w);
|
||
padding: 0 6px 0 0;
|
||
background: #fdf8ee;
|
||
text-align: right;
|
||
font-family: Manrope, sans-serif;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.06em;
|
||
overflow-wrap: break-word;
|
||
}
|
||
|
||
/* Shared anchoring for every absolutely-positioned label row. `left: 0` +
|
||
`right` (rather than a width) keeps them right-aligned against the column's
|
||
padding while still allowing long labels to wrap leftwards. */
|
||
.utci-day-legend-day,
|
||
.utci-day-legend-date,
|
||
.utci-day-legend-icon,
|
||
.utci-day-legend-values {
|
||
position: absolute;
|
||
left: 0;
|
||
right: 6px;
|
||
}
|
||
|
||
.utci-day-legend-day {
|
||
top: 11px;
|
||
height: 16px;
|
||
/* Explicit line-height (not the browser default "normal") so the row's
|
||
height is identical in Chrome and Firefox — those engines compute
|
||
"normal" differently for the same font and size. */
|
||
line-height: 16px;
|
||
font-size: 8px;
|
||
font-weight: 700;
|
||
color: #9a7d5a;
|
||
}
|
||
.utci-day-legend-date {
|
||
top: 30px;
|
||
height: 16px;
|
||
line-height: 16px;
|
||
font-size: 8px;
|
||
font-weight: 700;
|
||
color: #9a7d5a;
|
||
}
|
||
.utci-day-legend-icon {
|
||
top: 52px;
|
||
height: 30px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
font-size: 8px;
|
||
font-weight: 700;
|
||
color: #9a7d5a;
|
||
}
|
||
|
||
/* Positioning context for the two value labels. The separator and the
|
||
secondary label are pinned to fixed offsets inside it (matching the tab's
|
||
own hi/lo block) rather than stacking after the main label, so a main
|
||
label that wraps to two lines can't shove them down. */
|
||
.utci-day-legend-values {
|
||
top: 84px;
|
||
height: 0;
|
||
}
|
||
|
||
/* Divider between the top (warmer) and bottom (cooler) values — mirrors the
|
||
thin line added between the hi/lo numbers inside each tab. */
|
||
.utci-day-legend-sep {
|
||
position: absolute;
|
||
top: 17px;
|
||
left: 0;
|
||
right: 0;
|
||
height: 1px;
|
||
background: rgba(0, 0, 0, 0.15);
|
||
}
|
||
|
||
/* 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 {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
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 {
|
||
/* 20px matches the tab's secondary number, measured from the top of its
|
||
hi/lo block (15px main line + 5px separator gap). Absolute, so it holds
|
||
that offset even if the main label above it wraps. */
|
||
position: absolute;
|
||
top: 20px;
|
||
left: 0;
|
||
right: 0;
|
||
display: flex;
|
||
/* Top-aligned (not centred) so the label stays level with the TOP of the
|
||
cooler/secondary value beside it even when the label text wraps. */
|
||
align-items: flex-start;
|
||
justify-content: flex-end;
|
||
min-height: 12px;
|
||
font-size: 8px;
|
||
font-weight: 600;
|
||
/* Fixed 12.1px line box = the secondary number's own line box (11px x 1.1),
|
||
so the smaller label text centres on the number rather than riding above
|
||
it. Stated in px, not a ratio, so it holds when the mobile media query
|
||
drops the label's font-size. */
|
||
line-height: 12.1px;
|
||
color: #9a7d5a;
|
||
}
|
||
|
||
.utci-day-tabs {
|
||
display: flex;
|
||
flex: 1 1 auto;
|
||
min-width: 0;
|
||
overflow-x: auto;
|
||
scroll-behavior: smooth;
|
||
scrollbar-width: none; /* Firefox */
|
||
-ms-overflow-style: none; /* old Edge */
|
||
/* Grab cursor hints drag-to-scroll, matching .utci-tbody-scroll */
|
||
cursor: grab;
|
||
}
|
||
.utci-day-tabs:active {
|
||
cursor: grabbing;
|
||
}
|
||
.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;
|
||
}
|
||
|
||
/* Explicit line-height (rather than the browser-default "normal") so this
|
||
row's rendered height is identical across browsers — Firefox and Chrome
|
||
compute "normal" differently for the same font/size, which threw off the
|
||
legend column's row-matching in .utci-day-legend-day (table.css). */
|
||
.utci-day-name {
|
||
display: block;
|
||
line-height: 16px;
|
||
}
|
||
|
||
.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;
|
||
}
|
||
|
||
/* The sky glyph and anything composited onto it. Anchors .utci-day-wind and
|
||
is inline-block so it hugs the 30px icon rather than the full tab width. */
|
||
.utci-day-icon {
|
||
position: relative;
|
||
display: inline-block;
|
||
line-height: 0;
|
||
}
|
||
|
||
/* Gust lines overlaid on the sky glyph (Near Gale and above, where the wind
|
||
isn't already the main icon). Absolutely positioned, so it costs the tab no
|
||
layout width — at the 90px min-width there is no room for a second glyph
|
||
beside the sky icon, and the tab's rows stay aligned with the legend column.
|
||
|
||
Concentric with the sky glyph rather than tucked into a corner: hanging it
|
||
off one side made the pair read as two separate stickers, one of them
|
||
falling off the edge. Centred, the gusts read as blowing THROUGH the cloud —
|
||
one combined symbol. The -44% (rather than -50%) vertical offset drops it a
|
||
couple of pixels below true centre, so the lines cross the base of the cloud
|
||
instead of cutting through its middle. */
|
||
.utci-day-wind {
|
||
position: absolute;
|
||
left: 50%;
|
||
top: 50%;
|
||
transform: translate(-50%, -44%);
|
||
line-height: 0;
|
||
pointer-events: none;
|
||
}
|
||
|
||
/* 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;
|
||
/* Explicit, not "normal" — pins the row height so it renders identically
|
||
across browsers (see .utci-day-name for why this matters). */
|
||
line-height: 16px;
|
||
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;
|
||
}
|
||
|
||
/* Hidden reference copy of the Activities tab (see ConfigPanel.js) — stays
|
||
in normal flow so its natural width sets the dropdown's fixed size, but
|
||
is never seen or reachable. */
|
||
.profile-scroll-sizer {
|
||
visibility: hidden;
|
||
height: 0;
|
||
overflow: hidden;
|
||
pointer-events: none;
|
||
}
|
||
.profile-scroll-sizer .profile-scroll { pointer-events: none; }
|
||
|
||
/* 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-height: calc(100vh - 24px);
|
||
overflow-y: auto;
|
||
padding: 16px;
|
||
}
|
||
.profile-dropdown-close {
|
||
position: absolute;
|
||
top: 10px;
|
||
right: 14px;
|
||
/* .profile-tab carries z-index:1, and a flex item honours z-index without
|
||
needing position — so without this the tabs paint straight over the
|
||
close button wherever the tab strip reaches the panel's right edge. */
|
||
z-index: 6;
|
||
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);
|
||
width: fit-content;
|
||
/* The panel's width is pinned by the hidden Activities sizer (see
|
||
.profile-scroll-sizer / ConfigPanel.js): 9 x 100px cards + gaps +
|
||
padding, ~1000px all-in. The old 880px cap clipped the last card or
|
||
two, so give it room — and a min-width past that natural size so the
|
||
config strip underneath has enough room to pack into fewer rows.
|
||
Both are capped to the viewport, so narrow desktops and tablets
|
||
still shrink normally. */
|
||
min-width: min(1080px, calc(100vw - 32px));
|
||
max-width: min(1120px, calc(100vw - 32px));
|
||
margin: 0 auto;
|
||
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 2px 5px rgba(0,0,0,0.08);
|
||
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;
|
||
/* Clears the profile row pinned above it. DayTabs measures that row and
|
||
publishes its height (+ its 4px margin) as --profile-row-h — it is one
|
||
line or two depending on the labels, and the old fixed 28px left the
|
||
table header tucked under it. */
|
||
top: var(--profile-row-h, 52px) !important;
|
||
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;
|
||
padding: 4px 10px;
|
||
background: rgba(200,146,42,0.12);
|
||
border: 1px solid rgba(200,146,42,0.35);
|
||
border-radius: 999px;
|
||
transition: background 0.2s, border-color 0.2s, color 0.2s, transform 0.2s;
|
||
}
|
||
.active-profile-row:hover .active-profile-row-edit,
|
||
.active-profile-row:focus-visible .active-profile-row-edit {
|
||
color: #c8922a;
|
||
background: rgba(200,146,42,0.2);
|
||
border-color: #c8922a;
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
/* 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-pets { border-color: rgba(10,81,88,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; }
|
||
.active-profile-row.grp-pets .active-profile-row-value { color: #0a5158; }
|
||
|
||
/* 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.
|
||
|
||
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-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;
|
||
}
|
||
}
|
||
|
||
|
||
.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-pets { background: rgba(214,238,240,0.50); color: #0a5158; border-color: rgba(10,81,88,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-pets:hover { background: rgba(204,228,230,0.80); color: #0a5158; border-color: rgba(10,81,88,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-pets.on { background: #17868f; border-color: #17868f; 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-pets.on:hover { background: #116b73; border-color: #116b73; 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.
|
||
Offset by the pinned Hour column's width (--hour-col-w, set by JS in
|
||
useTableScroll.js) so the fade sits over the first column that
|
||
actually scrolls, not over the Hour column itself. */
|
||
.utci-table-wrap::before {
|
||
left: var(--hour-col-w, 0px);
|
||
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: calc(var(--hour-col-w, 0px) + 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;
|
||
/* Room for the close bar below — the tab strip goes full width on
|
||
phones, so the close button can no longer share that top line. */
|
||
padding-top: 42px;
|
||
}
|
||
|
||
/* Give the sheet its own header strip with a proper thumb-sized close
|
||
target. Previously the × sat on top of the full-width tab row, where
|
||
it was both invisible and unhittable — the sheet covers most of the
|
||
viewport on a phone, so there was almost no backdrop left to tap
|
||
either, and no way back to the table. The button is a child of the
|
||
fixed panel (not the scrolling body), so it stays put while the
|
||
config list scrolls under it. */
|
||
.profile-dropdown-close {
|
||
top: 6px;
|
||
right: 10px;
|
||
width: 34px;
|
||
height: 34px;
|
||
padding: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 22px;
|
||
color: #8a6a3a;
|
||
background: #fffcf2;
|
||
border: 1.5px solid #c9b08a;
|
||
border-radius: 50%;
|
||
}
|
||
|
||
/* Bottom-sheet grab handle — the usual "drag/tap me away" cue, and a
|
||
second visual hint that the top strip is chrome, not content. */
|
||
.profile-dropdown::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: 15px;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
width: 42px;
|
||
height: 4px;
|
||
border-radius: 999px;
|
||
background: #d8c7a4;
|
||
z-index: 6;
|
||
pointer-events: none;
|
||
}
|
||
.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;
|
||
}
|
||
|
||
/* 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-day, .utci-day-legend-date, .utci-day-legend-icon { font-size: 7px; }
|
||
.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
|
||
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 <th> itself — see the new wrapper rules higher up. */
|
||
.utci-table th {
|
||
background: #f0e4c4;
|
||
color: #6b4f2a;
|
||
font-family: Manrope, sans-serif;
|
||
font-size: 12px;
|
||
text-align: center;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.07em;
|
||
font-weight: 700;
|
||
padding: 14px 12px;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
@media (max-width: 800px) {
|
||
.utci-table th {
|
||
font-size: 10px;
|
||
letter-spacing: 0.08em;
|
||
}
|
||
}
|
||
|
||
/* The Hour column aligns left, the rest align right */
|
||
.utci-table th:first-child,
|
||
.utci-table td:first-child {
|
||
text-align: left;
|
||
}
|
||
|
||
/* ── PINNED HOUR COLUMN ──────────────────────────────────────────────────
|
||
The Hour/time column stays put while the rest of the table scrolls
|
||
horizontally, mirroring the fixed label column on the day tabs strip.
|
||
The body scroller genuinely scrolls (overflow-x: auto) so a plain
|
||
sticky left:0 works there. The header track instead gets shifted by
|
||
JS via translate3d to mirror the body's scrollLeft - so its first
|
||
cells get an equal-and-opposite translate3d applied by JS (see
|
||
handleBodyScroll in useTableScroll.js) to cancel that shift and stay
|
||
visually pinned at the left edge. */
|
||
.utci-table tbody td:first-child {
|
||
position: sticky;
|
||
left: 0;
|
||
z-index: 2;
|
||
background: #fffcf2;
|
||
}
|
||
.utci-table tbody tr:hover td:first-child {
|
||
background: #fef8ea;
|
||
}
|
||
.utci-table tbody tr.is-now td:first-child {
|
||
background: #fff8e4;
|
||
}
|
||
|
||
.utci-table thead th:first-child {
|
||
position: relative;
|
||
z-index: 3;
|
||
will-change: transform;
|
||
}
|
||
|
||
/* The "°C" / "m/s" etc. unit underneath each heading */
|
||
.utci-table th .col-unit {
|
||
display: block;
|
||
color: #c8922a;
|
||
font-size: 10px;
|
||
margin-top: 2px;
|
||
}
|
||
|
||
/* Stands in for the unit when the column has no data for the selected day —
|
||
currently only AQI and Pollen, which CAMS stops forecasting before the
|
||
14-day table runs out. Muted and italic so it reads as an absence, not a
|
||
value. The cells carry the full explanation as a title. */
|
||
.utci-table th .col-unit--none {
|
||
color: #a89880;
|
||
font-style: italic;
|
||
text-transform: none;
|
||
letter-spacing: 0.02em;
|
||
}
|
||
|
||
/* Environment modifier badge — shown in SunSoak th when non-default env active */
|
||
.col-env-badge {
|
||
display: block;
|
||
font-size: 9px;
|
||
font-weight: 700;
|
||
font-family: Manrope, sans-serif;
|
||
letter-spacing: 0.04em;
|
||
color: #a06828;
|
||
opacity: 0.85;
|
||
margin-top: 2px;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
/* col-unit header spans always visible - units toggle only affects body cells */
|
||
|
||
@media (max-width: 800px) {
|
||
.utci-table th .col-unit {
|
||
font-size: 9px;
|
||
}
|
||
}
|
||
|
||
.utci-table tbody tr {
|
||
border-bottom: 1px solid #ffffff;
|
||
transition: background 0.1s;
|
||
}
|
||
|
||
.utci-table tbody tr:hover {
|
||
background: #fef8ea;
|
||
}
|
||
|
||
.utci-table td {
|
||
padding: 10px 12px;
|
||
text-align: center;
|
||
font-family: JetBrains Mono, monospace;
|
||
font-variant-numeric: tabular-nums; /* stops digits jiggling between rows */
|
||
white-space: nowrap;
|
||
color: #4a3420;
|
||
/* Baseline minimum column width — the JS width-sync uses max(60, head,
|
||
body) so very-narrow data columns ("Air 5.0") still feel comfortable
|
||
to read instead of being squashed. */
|
||
min-width: 60px;
|
||
}
|
||
|
||
/* Subtle vertical column dividers — same pale cream as the row
|
||
dividers so the table reads as a soft grid, not a hard one. The
|
||
last column has no divider so it doesn't double-up with the
|
||
container border. */
|
||
.utci-table th:not(:last-child),
|
||
.utci-table td:not(:last-child) {
|
||
border-right: 1px solid #ffffff;
|
||
}
|
||
|
||
/* The Dir column is centered on both desktop and mobile so the
|
||
compass vane sits with even padding either side — especially
|
||
important on mobile where the compass label is hidden and there's
|
||
no text to balance the right-aligned content. */
|
||
.utci-table th.utci-dir-cell,
|
||
.utci-table td.utci-dir-cell {
|
||
text-align: center;
|
||
}
|
||
|
||
/* "Tight" header — for columns whose body content is narrower than
|
||
the default tracked-out header would suggest (Air, Pcpt). Reducing
|
||
the letter-spacing shrinks the heading's natural width so it stops
|
||
driving the column wider than the body needs. Right-aligned values */
|
||
.utci-table th.utci-tight-head {
|
||
letter-spacing: 0.04em;
|
||
}
|
||
|
||
/* ── CLICKABLE COLUMN HEADERS ───────────────────────────────────────────
|
||
.col-info-th makes every <th> show a pointer cursor and a subtle hover
|
||
tint to signal it can be clicked for a description popup. */
|
||
.utci-table th.col-info-th {
|
||
cursor: pointer;
|
||
transition: background 0.15s;
|
||
}
|
||
.utci-table th.col-info-th:hover {
|
||
background: #e8d8a8;
|
||
}
|
||
|
||
/* ── 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;
|
||
margin-top: 4px;
|
||
}
|
||
.row-stepper {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 3px;
|
||
}
|
||
.row-stepper-label {
|
||
font-family: Manrope, sans-serif;
|
||
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;
|
||
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;
|
||
}
|
||
.row-stepper-btn:hover:not(:disabled) {
|
||
border-color: #c8922a;
|
||
background: #fef3dc;
|
||
}
|
||
.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.
|
||
JS sets left/top via inline style (centred on the clicked th). */
|
||
.col-info-popup {
|
||
position: fixed; /* set by JS — stays in viewport regardless of scroll */
|
||
z-index: 200;
|
||
width: 260px;
|
||
max-height: 260px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
background: #fffcf2;
|
||
border: 1.5px solid #c9b08a;
|
||
border-radius: 6px;
|
||
box-shadow: 0 4px 16px rgba(74,52,32,0.18);
|
||
padding: 14px 16px 16px;
|
||
pointer-events: all;
|
||
}
|
||
/* Arrow points DOWN toward the column header (default — popup is above the th) */
|
||
.col-info-popup::after {
|
||
content: '';
|
||
position: absolute;
|
||
bottom: -7px;
|
||
left: var(--arrow-left, 50%);
|
||
width: 12px;
|
||
height: 12px;
|
||
background: #fffcf2;
|
||
border-right: 1.5px solid #c9b08a;
|
||
border-bottom: 1.5px solid #c9b08a;
|
||
transform: translateX(-50%) rotate(45deg);
|
||
}
|
||
/* Arrow points UP toward the column header (popup is below the th) */
|
||
.col-info-popup.col-info-popup--below::after {
|
||
bottom: auto;
|
||
top: -7px;
|
||
border-right: none;
|
||
border-bottom: none;
|
||
border-left: 1.5px solid #c9b08a;
|
||
border-top: 1.5px solid #c9b08a;
|
||
transform: translateX(-50%) rotate(45deg);
|
||
}
|
||
.col-info-title {
|
||
display: block;
|
||
font-family: Manrope, sans-serif;
|
||
font-size: 12px;
|
||
font-weight: 700;
|
||
color: #4a3420;
|
||
margin-bottom: 6px;
|
||
text-transform: none;
|
||
letter-spacing: 0;
|
||
}
|
||
.col-info-desc {
|
||
font-family: Manrope, sans-serif;
|
||
font-size: 12px;
|
||
color: #6b4f2a;
|
||
line-height: 1.5;
|
||
margin: 0;
|
||
overflow-y: auto; /* scrolls when text exceeds max-height cap */
|
||
flex: 1 1 auto;
|
||
}
|
||
/* "More about this column" deep-link to the reference page. Sits below the
|
||
summary and never shrinks, so it stays visible if the summary scrolls. */
|
||
.col-info-more {
|
||
display: block;
|
||
flex: 0 0 auto;
|
||
margin-top: 8px;
|
||
font-family: Manrope, sans-serif;
|
||
font-size: 11px;
|
||
font-weight: 700;
|
||
color: #b07d1e;
|
||
text-decoration: none;
|
||
}
|
||
.col-info-more:hover,
|
||
.col-info-more:focus-visible {
|
||
color: #8a5f12;
|
||
text-decoration: underline;
|
||
}
|
||
.col-info-close {
|
||
position: absolute;
|
||
top: 6px;
|
||
right: 8px;
|
||
background: none;
|
||
border: none;
|
||
font-size: 16px;
|
||
color: #a08060;
|
||
cursor: pointer;
|
||
line-height: 1;
|
||
padding: 0 2px;
|
||
}
|
||
.col-info-close:hover {
|
||
color: #4a3420;
|
||
}
|
||
|
||
/* ── POPUP OPEN/CLOSE ANIMATION ─────────────────────────────────────────
|
||
The popup mounts/unmounts instantly in JS, so we animate it via a
|
||
scale + fade keyframe anchored to the arrow tip (transform-origin).
|
||
Above-the-target: origin is bottom-center. Below: top-center. */
|
||
@keyframes popup-open {
|
||
from { opacity: 0; transform: translateX(-50%) translateY(-100%) scaleY(0.6); }
|
||
to { opacity: 1; transform: translateX(-50%) translateY(-100%) scaleY(1); }
|
||
}
|
||
@keyframes popup-open-below {
|
||
from { opacity: 0; transform: translateX(-50%) scaleY(0.6); }
|
||
to { opacity: 1; transform: translateX(-50%) scaleY(1); }
|
||
}
|
||
|
||
.col-info-popup:not(.col-info-popup--below) {
|
||
transform-origin: bottom center;
|
||
animation: popup-open 0.22s cubic-bezier(0.34, 1.4, 0.64, 1) both;
|
||
}
|
||
.col-info-popup.col-info-popup--below {
|
||
transform-origin: top center;
|
||
animation: popup-open-below 0.22s cubic-bezier(0.34, 1.4, 0.64, 1) both;
|
||
}
|
||
|
||
/* ── EVENT TAG POPUP — SLIDESHOW ────────────────────────────────────────
|
||
The stage holds the current slide. On transition, evTransition is set
|
||
to 'exiting' (fade out) then 'entering' (fade in) via the hook. */
|
||
@keyframes ev-fade-in { from { opacity: 0; } to { opacity: 1; } }
|
||
@keyframes ev-fade-out { from { opacity: 1; } to { opacity: 0; } }
|
||
|
||
.ev-popup-stage {
|
||
transition: opacity 0.38s ease;
|
||
}
|
||
.ev-popup-entering {
|
||
animation: ev-fade-in 0.38s ease both;
|
||
}
|
||
.ev-popup-exiting {
|
||
animation: ev-fade-out 0.38s ease both;
|
||
}
|
||
|
||
/* Slideshow navigation dots */
|
||
.ev-popup-dots {
|
||
display: flex;
|
||
gap: 5px;
|
||
margin-top: 8px;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
.ev-popup-dot {
|
||
width: 6px;
|
||
height: 6px;
|
||
border-radius: 50%;
|
||
background: #c9b08a;
|
||
opacity: 0.4;
|
||
cursor: pointer;
|
||
transition: opacity 0.2s, transform 0.2s;
|
||
flex-shrink: 0;
|
||
}
|
||
.ev-popup-dot.active {
|
||
opacity: 1;
|
||
transform: scale(1.35);
|
||
background: #7a5530;
|
||
}
|
||
.ev-popup-dot:hover {
|
||
opacity: 0.75;
|
||
}
|
||
|
||
|
||
/* ── 8. NOW-ROW & NIGHT-ROW DECORATIONS ─────────────────────────────── */
|
||
|
||
/* "Currently this hour" — the highlighted row.
|
||
The row gets a cream-yellow background and darker text. No left
|
||
stripe or pip — the colour shift alone signals "this is now". */
|
||
.utci-table tbody tr.is-now {
|
||
background: #fff8e4 !important;
|
||
}
|
||
|
||
.utci-table tbody tr.is-now td {
|
||
color: #1e1208;
|
||
/* Brass bracket framing the current-hour row top and bottom — clean,
|
||
definitive, no left stripe needed. */
|
||
border-top: 2px solid #c8922a;
|
||
border-bottom: 2px solid #c8922a;
|
||
}
|
||
|
||
/* Night-time hours get a slightly muted text colour */
|
||
.utci-table tbody tr.is-night td {
|
||
color: #7a5c30;
|
||
}
|
||
|
||
/* The time cell — uses the display serif instead of monospace */
|
||
.utci-time {
|
||
font-family: Fraunces, serif !important;
|
||
font-weight: 500;
|
||
font-size: 16px;
|
||
color: #1e1208 !important;
|
||
}
|
||
.utci-time svg {
|
||
margin: -6px 0;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
/* Lays out the sky scope + "3pm" + any event tags on one line. Shared by
|
||
both orientations — it leads the row normally and heads the column when
|
||
the table is rotated. */
|
||
.utci-time-inner {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 7px;
|
||
vertical-align: middle;
|
||
}
|
||
.utci-time-inner svg {
|
||
margin: -6px 0;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
/* Event tags are grouped rather than left as loose siblings so that when
|
||
the rotated view stacks the time cell into a column, they stay on one
|
||
horizontal line instead of forming a vertical stack of their own. The
|
||
gap matches .utci-time-inner so the inline layout is unchanged. */
|
||
.utci-time-tags {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 7px;
|
||
}
|
||
|
||
@media (max-width: 800px) {
|
||
.utci-time {
|
||
font-size: 15px;
|
||
}
|
||
.utci-cell-hero {
|
||
font-size: 15px !important;
|
||
padding: 5px 12px;
|
||
min-width: 60px;
|
||
}
|
||
}
|
||
|
||
/* The coloured pill inside UTCI / UTCI+P cells.
|
||
The pill's BACKGROUND comes from inline styles in app.js
|
||
(the utciCategory() function decides which thermal-stress colour
|
||
to apply). This rule just handles the pill shape. */
|
||
.utci-cell {
|
||
display: inline-block;
|
||
padding: 4px 12px;
|
||
border-radius: 3px;
|
||
font-weight: 600;
|
||
min-width: 60px;
|
||
text-align: center;
|
||
font-size: 14px;
|
||
}
|
||
|
||
/* Hero variant used for the UTCI+P column — the headline number.
|
||
Bigger, bolder, more breathing room so the eye lands here first. */
|
||
.utci-cell-hero {
|
||
padding: 6px 4px;
|
||
font-size: 17px;
|
||
font-weight: 700;
|
||
min-width: 63px;
|
||
letter-spacing: 0.01em;
|
||
box-shadow: 0 1px 0 rgba(0,0,0,0.15) inset, 0 0 0 1px rgba(255,255,255,0.08) inset;
|
||
}
|
||
|
||
.utci-band-label {
|
||
font-family: Manrope, sans-serif;
|
||
font-size: 10px;
|
||
font-weight: 700;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.06em;
|
||
}
|
||
|
||
/* Faint vertical divider before each new column group in the forecast table. */
|
||
.utci-table .col-group-start {
|
||
border-left: 2px solid rgba(74, 50, 24, 0.18);
|
||
}
|
||
|
||
/* ── COLUMN GROUP COLOURS — header cell tints per group ─────────────────
|
||
Each grp-* class gives the th a distinct background + text colour.
|
||
The hover state darkens slightly. The col-unit underneath inherits
|
||
a matching accent colour. */
|
||
|
||
.utci-table th.grp-felt { background: rgba(223,243,236,0.50); color: #075c3a; }
|
||
.utci-table th.grp-surface { background: rgba(221,234,248,0.50); color: #0b3e72; }
|
||
.utci-table th.grp-pets { background: rgba(214,238,240,0.50); color: #0a5158; }
|
||
.utci-table th.grp-ambient { background: rgba(236,234,227,0.50); color: #3c3a35; }
|
||
.utci-table th.grp-precip { background: rgba(247,228,238,0.50); color: #6b1e3a; }
|
||
.utci-table th.grp-sky { background: rgba(234,232,252,0.50); color: #352e7a; }
|
||
.utci-table th.grp-wind { background: rgba(250,230,224,0.50); color: #6a2210; }
|
||
.utci-table th.grp-airqual { background: rgba(229,240,216,0.50); color: #244408; }
|
||
.utci-table th.grp-solar { background: rgba(250,234,204,0.50); color: #5a3004; }
|
||
|
||
.utci-table th.grp-felt.col-info-th:hover { background: rgba(213,233,226,0.80); }
|
||
.utci-table th.grp-surface.col-info-th:hover { background: rgba(211,224,238,0.80); }
|
||
.utci-table th.grp-pets.col-info-th:hover { background: rgba(204,228,230,0.80); }
|
||
.utci-table th.grp-ambient.col-info-th:hover { background: rgba(226,224,217,0.80); }
|
||
.utci-table th.grp-precip.col-info-th:hover { background: rgba(237,218,228,0.80); }
|
||
.utci-table th.grp-sky.col-info-th:hover { background: rgba(224,222,242,0.80); }
|
||
.utci-table th.grp-wind.col-info-th:hover { background: rgba(240,220,214,0.80); }
|
||
.utci-table th.grp-airqual.col-info-th:hover { background: rgba(219,230,206,0.80); }
|
||
.utci-table th.grp-solar.col-info-th:hover { background: rgba(240,224,194,0.80); }
|
||
|
||
.utci-table th.grp-felt .col-unit { color: #0f9e63; }
|
||
.utci-table th.grp-surface .col-unit { color: #2672c8; }
|
||
.utci-table th.grp-pets .col-unit { color: #17868f; }
|
||
.utci-table th.grp-ambient .col-unit { color: #7a7870; }
|
||
.utci-table th.grp-precip .col-unit { color: #b03060; }
|
||
.utci-table th.grp-sky .col-unit { color: #6058c8; }
|
||
.utci-table th.grp-wind .col-unit { color: #b04030; }
|
||
.utci-table th.grp-airqual .col-unit { color: #4a8018; }
|
||
.utci-table th.grp-solar .col-unit { color: #b07820; }
|
||
|
||
/* ── GROUP LABEL ROW — spans above the column header row ────────────────
|
||
A second tr in the thead. Each cell spans all visible columns in its
|
||
group. The Hour column gets a blank matching-background cell. */
|
||
|
||
.grp-label-row th {
|
||
font-family: Manrope, sans-serif;
|
||
font-size: 9px;
|
||
font-weight: 700;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.10em;
|
||
padding: 4px 8px 2px;
|
||
text-align: center;
|
||
white-space: nowrap;
|
||
border-bottom: 1px solid rgba(0,0,0,0.06);
|
||
}
|
||
|
||
.utci-table .grp-label-row th.grp-felt { background: rgba(223,243,236,0.50); border-left: 2px solid rgba(74,50,24,0.18); border-bottom: 1px solid rgba(74,50,24,0.18);}
|
||
.utci-table .grp-label-row th.grp-surface { background: rgba(221,234,248,0.50); border-left: 2px solid rgba(74,50,24,0.18); border-bottom: 1px solid rgba(74,50,24,0.18);}
|
||
.utci-table .grp-label-row th.grp-pets { background: rgba(214,238,240,0.50); border-left: 2px solid rgba(74,50,24,0.18); border-bottom: 1px solid rgba(74,50,24,0.18);}
|
||
.utci-table .grp-label-row th.grp-ambient { background: rgba(236,234,227,0.50); border-left: 2px solid rgba(74,50,24,0.18); border-bottom: 1px solid rgba(74,50,24,0.18);}
|
||
.utci-table .grp-label-row th.grp-precip { background: rgba(247,228,238,0.50); border-left: 2px solid rgba(74,50,24,0.18); border-bottom: 1px solid rgba(74,50,24,0.18);}
|
||
.utci-table .grp-label-row th.grp-sky { background: rgba(234,232,252,0.50); border-left: 2px solid rgba(74,50,24,0.18); border-bottom: 1px solid rgba(74,50,24,0.18);}
|
||
.utci-table .grp-label-row th.grp-wind { background: rgba(250,230,224,0.50); border-left: 2px solid rgba(74,50,24,0.18); border-bottom: 1px solid rgba(74,50,24,0.18);}
|
||
.utci-table .grp-label-row th.grp-airqual { background: rgba(229,240,216,0.50); border-left: 2px solid rgba(74,50,24,0.18); border-bottom: 1px solid rgba(74,50,24,0.18);}
|
||
.utci-table .grp-label-row th.grp-solar { background: rgba(250,234,204,0.50); border-left: 2px solid rgba(74,50,24,0.18); border-bottom: 1px solid rgba(74,50,24,0.18);}
|
||
|
||
/* ── POPUP — mobile centre-alignment ───────────────────────────────── */
|
||
@media (max-width: 640px) {
|
||
.col-info-title,
|
||
.col-info-desc,
|
||
.col-info-more {
|
||
text-align: center;
|
||
}
|
||
}
|
||
|
||
/* ── FILTER PROFILES + COLUMN TOGGLES — mobile fixes ───────────────── */
|
||
@media (max-width: 640px) {
|
||
|
||
/* ── Profile bar — single scroll row on all screen sizes ───────────
|
||
No wrapping needed; scroll strip handles layout. Dividers stay. */
|
||
.filter-profiles {
|
||
padding: 4px 0 0;
|
||
}
|
||
|
||
.profile-tabs {
|
||
width: 100%;
|
||
margin-left: 0;
|
||
margin-right: 0;
|
||
}
|
||
|
||
/* Slightly smaller cards on mobile */
|
||
.profile-btn {
|
||
width: 80px;
|
||
height: 80px;
|
||
flex: none;
|
||
}
|
||
|
||
.profile-btn-scene {
|
||
font-size: 18px;
|
||
}
|
||
|
||
.profile-btn-label {
|
||
height: 20px;
|
||
font-size: 9px;
|
||
}
|
||
|
||
/* ── Column toggles bar ─────────────────────────────────────────────
|
||
Same approach — label full row, toggles at ~31% each. */
|
||
.col-toggles,
|
||
.col-toggles-config {
|
||
flex-wrap: wrap;
|
||
gap: 6px;
|
||
padding: 10px 10px;
|
||
align-items: stretch;
|
||
}
|
||
|
||
|
||
.col-toggle {
|
||
flex: 0 0 calc((100% - 12px) / 3);
|
||
min-width: 0;
|
||
box-sizing: border-box;
|
||
text-align: center;
|
||
padding: 7px 2px;
|
||
font-size: 11px;
|
||
letter-spacing: 0.03em;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
/* Pollen CustomSelect in col-toggles — match toggle width */
|
||
.col-toggles .cs-wrap {
|
||
flex: 0 0 calc((100% - 12px) / 3);
|
||
min-width: 0;
|
||
}
|
||
.col-toggles .cs-btn {
|
||
width: 100%;
|
||
min-width: 0;
|
||
padding: 7px 6px;
|
||
font-size: 11px;
|
||
letter-spacing: 0.03em;
|
||
justify-content: center;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* Config strip - each labelled item becomes its own stacked card on
|
||
mobile (label above control) rather than a label crammed next to a
|
||
squeezed pill, so multiple pickers read as separate rows, not a
|
||
jumbled wall of controls. */
|
||
.col-toggles-config {
|
||
gap: 8px;
|
||
}
|
||
|
||
.col-toggles-config-item {
|
||
flex: 0 0 100%;
|
||
min-width: 0;
|
||
flex-direction: column;
|
||
align-items: stretch;
|
||
gap: 5px;
|
||
padding: 8px 10px;
|
||
background: rgba(255,255,255,0.55);
|
||
border: 1px solid #e6d7b8;
|
||
border-radius: 5px;
|
||
}
|
||
|
||
/* ...except the plain single-picker cards (SunSoak, Fur, Burn, Pollen),
|
||
which pair up two per row. Six full-width cards turned the sheet into
|
||
a scrolling wall that buried the table; pairing halves that. Cards
|
||
holding a fused control group (Vehicle, Indoors) keep a full row —
|
||
they already wrap internally and have no width to spare — and so does
|
||
the main item, which carries the badge and the gold emphasis.
|
||
:has() gates the whole thing: a browser without it just drops these
|
||
rules and keeps the old one-per-row stack. */
|
||
.col-toggles-config-item:not(.col-toggles-config-item--main):not(:has(.col-toggle-group)) {
|
||
flex: 1 1 calc((100% - 8px) / 2);
|
||
}
|
||
.col-toggles-config-item:not(.col-toggles-config-item--main):not(:has(.col-toggle-group)) .cs-btn {
|
||
font-size: 11px;
|
||
padding: 8px 4px;
|
||
letter-spacing: 0.02em;
|
||
}
|
||
|
||
/* Re-affirm the gold emphasis for the "main" item so it still stands
|
||
out after the generic card border above. */
|
||
.col-toggles-config-item--main {
|
||
border-color: #c8922a;
|
||
background: rgba(200,146,42,0.10);
|
||
}
|
||
|
||
.config-item-label {
|
||
text-align: center;
|
||
}
|
||
|
||
/* The "SunSoak Config:" badge sits above the main item as its own
|
||
centred header row, instead of squeezed inline before the pill. */
|
||
.config-item-main-badge {
|
||
display: block;
|
||
flex: 0 0 100%;
|
||
width: 100%;
|
||
text-align: center;
|
||
padding: 0 0 2px;
|
||
}
|
||
|
||
.col-toggles-config-item .cs-wrap,
|
||
.col-toggles-config-item .col-toggle-group {
|
||
flex: 1 1 auto;
|
||
min-width: 0;
|
||
width: 100%;
|
||
}
|
||
.col-toggles-config-item .cs-btn {
|
||
width: 100%;
|
||
min-width: 0;
|
||
padding: 8px 6px;
|
||
font-size: 12px;
|
||
letter-spacing: 0.03em;
|
||
justify-content: center;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* Fused group: 1 slot by default, 2 slots when VentPill is active */
|
||
.col-toggle-group {
|
||
flex: 0 0 calc((100% - 12px) / 3);
|
||
min-width: 0;
|
||
display: inline-flex;
|
||
align-items: stretch;
|
||
}
|
||
.col-toggle-group--expanded {
|
||
flex: 0 0 calc((100% - 12px) / 3 * 2 + 6px);
|
||
}
|
||
/* Vehicle group fuses three children (select + speed + vent) — full row. */
|
||
.col-toggle-group--expanded.col-toggle-group--triple {
|
||
flex: 0 0 100%;
|
||
}
|
||
.col-toggle-group .cs-wrap {
|
||
flex: 1 1 0;
|
||
min-width: 0;
|
||
width: 100%;
|
||
}
|
||
.col-toggle-group .cs-btn {
|
||
width: 100%;
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
font-size: 11px;
|
||
padding: 7px 6px;
|
||
justify-content: center;
|
||
}
|
||
|
||
/* Vehicle's triple group (type + speed + vent) doesn't fit fused on one
|
||
line at phone widths — the vent pill won't shrink below its text, so
|
||
it either overflows or forces the row taller. Give the type select its
|
||
own full-width row and let speed+vent share the row below, still
|
||
fused to each other, so nothing has to fight for space. */
|
||
.col-toggle-group--triple {
|
||
flex-wrap: wrap;
|
||
align-content: flex-start;
|
||
row-gap: 6px;
|
||
}
|
||
.col-toggle-group--triple .cs-wrap:first-child {
|
||
flex: 0 0 100%;
|
||
}
|
||
.col-toggle-group--triple .cs-wrap:first-child .grouped-left {
|
||
border-right: 1.5px solid #c9b08a !important;
|
||
border-radius: 3px !important;
|
||
}
|
||
/* The base .col-toggle-group .cs-wrap rule sets min-width:0, which lets
|
||
this select shrink to nothing and get squeezed onto row 1 next to the
|
||
now-full-width type select instead of wrapping. Restore a real min
|
||
width so it's forced onto row 2 with the vent pill instead. */
|
||
.col-toggle-group--triple .cs-wrap:nth-child(2) {
|
||
flex: 1 1 0;
|
||
min-width: 80px;
|
||
width: auto;
|
||
}
|
||
.col-toggle-group--triple .cs-vent {
|
||
flex: 1 1 0;
|
||
min-width: 0;
|
||
}
|
||
|
||
}
|
||
|
||
/* ── MOBILE TABLE DENSITY — squeeze all basic profile columns in ────────
|
||
Target: 7 cols (hour air wind cloud utciP precip vis) on a 390px screen.
|
||
Worst-case minimum width at these values: 366px - fits iPhone SE (375px).
|
||
Levers: tighter cell padding, smaller min-width, reduced font sizes.
|
||
The existing 800px and 640px blocks handle tablets and phablets above.
|
||
DENSITY-BREAKPOINT - change 480px here to adjust where dense layout kicks in.
|
||
-------------------------------------------------------------------- */
|
||
|
||
/* -- MOBILE TABLE DENSITY - squeeze all basic profile columns in --------
|
||
Target: 7 cols (hour air wind cloud utciP precip vis) on a 390px screen.
|
||
Worst-case minimum width: 366px - fits iPhone SE (375px) with room to spare.
|
||
DENSITY-BREAKPOINT - change 480px to adjust where dense layout kicks in.
|
||
-------------------------------------------------------------------- */
|
||
@media (max-width: 480px) {
|
||
|
||
/* -- Table header cells -----------------------------------------------
|
||
Reduce padding so headers do not drive columns wider than data needs. */
|
||
.utci-table th {
|
||
font-size: 9px;
|
||
padding: 10px 4px;
|
||
letter-spacing: 0.04em;
|
||
}
|
||
|
||
.utci-table th .col-unit {
|
||
font-size: 8px;
|
||
}
|
||
|
||
/* -- Table body cells -------------------------------------------------
|
||
Drop side padding 12px to 4px, shrink min-width 60px to 44px.
|
||
JetBrains Mono at 11px reads cleanly on retina screens. */
|
||
.utci-table td {
|
||
padding: 10px 4px;
|
||
font-size: 11px;
|
||
min-width: 44px;
|
||
}
|
||
|
||
/* -- Time (Hour) column -----------------------------------------------
|
||
Drop Fraunces from 16px to 13px and tighten the hour cell min-width. */
|
||
.utci-time {
|
||
font-size: 13px;
|
||
}
|
||
|
||
.utci-table td:first-child {
|
||
min-width: 42px;
|
||
}
|
||
|
||
/* -- SkyScope moon-sun icon in the hour cell ---------------------------
|
||
JS renders at size=38. Clamp to 25px via CSS to stop it ballooning
|
||
the hour column width and driving up row height. */
|
||
.utci-time svg {
|
||
width: 34px !important;
|
||
height: 34px !important;
|
||
margin: -5px 0;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
/* -- Cloud, wind-vane and precip icons in body cells -------------------
|
||
Clamp the 28px SVGs down to 20px on mobile to recover column width
|
||
without touching the JS size prop. */
|
||
.utci-table td svg {
|
||
width: 20px !important;
|
||
height: 20px !important;
|
||
flex-shrink: 0;
|
||
}
|
||
/* Override for SkyScope - keep it larger than other cell icons */
|
||
.utci-table td.utci-time svg {
|
||
width: 26px !important;
|
||
height: 26px !important;
|
||
}
|
||
|
||
/* -- Hero UTCI+P pill -------------------------------------------------
|
||
Shrink min-width and font - still the visual anchor but no longer
|
||
forcing the column to be the widest. */
|
||
.utci-cell-hero {
|
||
font-size: 14px;
|
||
min-width: 50px;
|
||
padding: 5px 2px;
|
||
}
|
||
|
||
/* -- Vis column -----------------------------------------------------------
|
||
Short values (8-99km) need less room than the default 44px min-width. */
|
||
.utci-vis-cell {
|
||
min-width: 30px;
|
||
}
|
||
|
||
/* -- Standard UTCI pill (non-hero) ------------------------------------*/
|
||
.utci-cell {
|
||
font-size: 12px;
|
||
min-width: 44px;
|
||
padding: 3px 5px;
|
||
}
|
||
|
||
/* -- Band label inside pills ------------------------------------------
|
||
Tighten letter-spacing to save a little width. */
|
||
.utci-band-label {
|
||
font-size: 9px;
|
||
letter-spacing: 0.03em;
|
||
}
|
||
}
|
||
|
||
/* ── SIMPLE / TABLE VIEW TOGGLE ─────────────────────────────────────────── */
|
||
|
||
.forecast-view-toggle-label {
|
||
font-family: Manrope, sans-serif;
|
||
font-size: 11px;
|
||
font-weight: 700;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.07em;
|
||
color: #7a5c2a;
|
||
flex-shrink: 0;
|
||
margin-left: 0;
|
||
}
|
||
|
||
.forecast-view-toggle {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
flex-shrink: 0;
|
||
}
|
||
.fvt-btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 3px;
|
||
font-family: Manrope, sans-serif;
|
||
font-size: 11px;
|
||
font-weight: 700;
|
||
line-height: 1;
|
||
padding: 6px 8px;
|
||
cursor: pointer;
|
||
border: 1.5px solid #c9b08a;
|
||
background: #f5edd6;
|
||
color: #6b4228;
|
||
transition: background 0.12s, border-color 0.12s, color 0.12s;
|
||
}
|
||
.fvt-btn:first-child {
|
||
border-radius: 3px 0 0 3px;
|
||
}
|
||
/* Only the outer corners are rounded; every segment after the first drops
|
||
its left border so neighbours share one 1.5px rule rather than stacking
|
||
two. Keyed off the sibling rather than :last-child so it still holds with
|
||
three segments (Quick / Detailed / Table). */
|
||
.fvt-btn + .fvt-btn {
|
||
border-left: none;
|
||
}
|
||
.fvt-btn:last-child {
|
||
border-radius: 0 3px 3px 0;
|
||
}
|
||
.fvt-btn:hover {
|
||
border-color: #c8922a;
|
||
background: #fef3dc;
|
||
z-index: 1;
|
||
}
|
||
.fvt-btn.on {
|
||
background: #c8922a;
|
||
border-color: #c8922a;
|
||
color: #fff;
|
||
}
|
||
|
||
.fvt-btn-icon {
|
||
display: inline-block;
|
||
vertical-align: middle;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
/* Toolbar row sitting directly above col-toggles (zero gap, normal flow):
|
||
View toggle on the left, and — in Quick view — the thermal-model tabs
|
||
centered, touching the col-toggles top border so they read as folder
|
||
tabs attached to it. Kept as a 3-column grid (with an empty right
|
||
track) so the center tabs stay genuinely centered on the row rather
|
||
than just sitting flush after the View toggle. */
|
||
.table-toolbar-row {
|
||
display: grid;
|
||
grid-template-columns: 1fr auto 1fr;
|
||
align-items: end;
|
||
padding: 0;
|
||
}
|
||
.fvt-toolbar-left {
|
||
grid-column: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
justify-self: start;
|
||
padding-bottom: 8px;
|
||
}
|
||
.fvt-thermal-tabs {
|
||
grid-column: 2;
|
||
justify-self: center;
|
||
display: flex;
|
||
width: 360px;
|
||
align-items: center;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
/* Mobile: stack instead of two columns — View on top, thermal tabs
|
||
below it so they still sit flush against the col-toggles border
|
||
underneath. Placed after the base rules above so it wins the cascade
|
||
(equal specificity, later source order) regardless of where in the
|
||
file the @media block lives. */
|
||
@media (max-width: 640px) {
|
||
.table-toolbar-row {
|
||
grid-template-columns: 1fr;
|
||
justify-items: center;
|
||
row-gap: 6px;
|
||
/* No bottom padding: the tabs have to touch the col-toggles border
|
||
below them, or they read as floating above the box. */
|
||
padding: 6px 0 0;
|
||
}
|
||
.fvt-toolbar-left {
|
||
grid-column: 1;
|
||
grid-row: 1;
|
||
justify-self: center;
|
||
padding-bottom: 0;
|
||
}
|
||
/* Quick view needs the row flush so its thermal tabs attach to the box
|
||
below. Detailed and Table have no tabs, so the View buttons would
|
||
otherwise sit right on the table — give those a gap. */
|
||
.table-main:not(.table-main--simple) .table-toolbar-row {
|
||
padding-bottom: 10px;
|
||
}
|
||
.fvt-thermal-tabs {
|
||
grid-column: 1;
|
||
grid-row: 2;
|
||
justify-self: stretch;
|
||
width: 100%;
|
||
}
|
||
}
|
||
|
||
.fvt-thermal-tab {
|
||
flex: 1;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-family: Manrope, sans-serif;
|
||
font-size: 11px;
|
||
font-weight: 700;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.06em;
|
||
padding: 9px 2px;
|
||
cursor: pointer;
|
||
background: transparent;
|
||
border: 1.5px solid #f5edd6;
|
||
border-left: none;
|
||
border-bottom: 1.5px solid transparent;
|
||
color: #9a7a4a;
|
||
transition: color 0.15s, border-color 0.15s, background 0.15s;
|
||
border-radius: 7px 7px 0 0;
|
||
z-index: 1;
|
||
}
|
||
.fvt-thermal-tab:first-child {
|
||
border-left: 1.5px solid #f5edd6;
|
||
}
|
||
.fvt-thermal-tab:hover {
|
||
color: #c8922a;
|
||
background: #f0e4c4;
|
||
}
|
||
.fvt-thermal-tab.on {
|
||
color: #c8922a;
|
||
background-color: #fdf8ee;
|
||
border-bottom-color: #fdf8ee;
|
||
border-left: 1.5px solid #d4c0a0;
|
||
border-right: 1.5px solid #d4c0a0;
|
||
border-top: 1.5px solid #d4c0a0;
|
||
margin-bottom: -1.5px;
|
||
}
|
||
|
||
/* 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;
|
||
margin-left: auto;
|
||
flex-shrink: 0;
|
||
}
|
||
.table-main--simple .fvt-interval {
|
||
display: inline-flex;
|
||
}
|
||
|
||
/* Hide table-mode column controls when in simple mode */
|
||
.table-main--simple .col-toggles-label,
|
||
.table-main--simple .col-toggles-edit-btn,
|
||
.table-main--simple .col-toggles-body {
|
||
display: none !important;
|
||
}
|
||
|
||
/* Show simple view, hide table; swap in table mode */
|
||
.forecast-simple-wrap {
|
||
display: none;
|
||
}
|
||
.table-main--simple {
|
||
display: block;
|
||
}
|
||
.table-main--simple .forecast-simple-wrap {
|
||
display: flex;
|
||
flex-direction: column;
|
||
/* Sized by its contents — cards, then the curve at its own fixed height,
|
||
then the button. It used to be a fixed 420px with the curve set to flex: 1
|
||
to absorb whatever was left over, which made the curve's height a
|
||
subtraction: panel minus cards minus button. That left it one card-height
|
||
change away from falling under .fsc-temp-curve's min-height, at which point
|
||
the column overflowed .forecast-simple-scroll (overflow-y: hidden) and the
|
||
BOTTOM of the curve — where the cold end of the scale lives — was clipped
|
||
off. Content-sized panel + fixed-height curve: no leftover space to gap,
|
||
none to run out of. */
|
||
height: auto;
|
||
border: 1.5px solid #d4c0a0;
|
||
background: #fdf8ee;
|
||
}
|
||
.table-main--simple .utci-table-wrap {
|
||
display: none;
|
||
}
|
||
|
||
/* ── SCROLL INDICATORS (quick view) ──────────────────────────────────────
|
||
Same fade + chevron pair the detailed table carries, driven by the
|
||
scroll-fade-left / scroll-fade-right classes app.js toggles from the
|
||
card strip's scroll position. The gradients fade from the panel's own
|
||
cream so they read as the content running under the edge. */
|
||
.table-main--simple .forecast-simple-wrap {
|
||
position: relative;
|
||
}
|
||
.forecast-simple-wrap::before,
|
||
.forecast-simple-wrap::after {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0;
|
||
bottom: 0;
|
||
width: 48px;
|
||
pointer-events: none;
|
||
opacity: 0;
|
||
transition: opacity 0.2s ease;
|
||
z-index: 6;
|
||
}
|
||
.forecast-simple-wrap::before {
|
||
left: 0;
|
||
background: linear-gradient(to right, rgba(253,248,238,0.95) 0%, transparent 100%);
|
||
}
|
||
.forecast-simple-wrap::after {
|
||
right: 0;
|
||
background: linear-gradient(to left, rgba(253,248,238,0.95) 0%, transparent 100%);
|
||
}
|
||
.forecast-simple-wrap.scroll-fade-left::before { opacity: 1; }
|
||
.forecast-simple-wrap.scroll-fade-right::after { opacity: 1; }
|
||
|
||
.fsc-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;
|
||
}
|
||
.fsc-scroll-chevron.left { left: 6px; }
|
||
.fsc-scroll-chevron.right { right: 6px; }
|
||
|
||
.forecast-simple-wrap.scroll-fade-left .fsc-scroll-chevron.left { opacity: 1; }
|
||
.forecast-simple-wrap.scroll-fade-right .fsc-scroll-chevron.right { opacity: 1; }
|
||
|
||
/* ── SIMPLE VIEW CARDS ───────────────────────────────────────────────────── */
|
||
|
||
.forecast-simple-scroll {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow-x: auto;
|
||
overflow-y: hidden;
|
||
scrollbar-width: thin;
|
||
scrollbar-color: #c9b08a transparent;
|
||
cursor: grab;
|
||
}
|
||
.forecast-simple-scroll:active {
|
||
cursor: grabbing;
|
||
}
|
||
.forecast-simple-scroll::-webkit-scrollbar {
|
||
height: 4px;
|
||
}
|
||
.forecast-simple-scroll::-webkit-scrollbar-track {
|
||
background: transparent;
|
||
}
|
||
.forecast-simple-scroll::-webkit-scrollbar-thumb {
|
||
background: #c9b08a;
|
||
border-radius: 2px;
|
||
}
|
||
.forecast-simple-inner {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
width: max-content;
|
||
min-width: 100%;
|
||
}
|
||
/* Half-hour tracks across the whole day, plus a blank inset spacer at each
|
||
end (see fscPlot in app.js). Each card spans 2*interval tracks positioned
|
||
so it is centred on the hour it is labelled with; the outer cards overhang
|
||
into the spacers instead of off the edge. Columns are set inline. */
|
||
.forecast-simple-cards {
|
||
display: grid;
|
||
padding: 12px 0 0;
|
||
}
|
||
|
||
.fsc-card {
|
||
/* One fixed width at every hour interval — a 4h card is not a wider card,
|
||
just a card with more space either side of it. It spans 2*bucket grid
|
||
tracks (see fscPlot in app.js) and is centred within that span, so it
|
||
stays over its landing hour and keeps lining up with the curve below
|
||
however wide the span gets. Must match FSC_CARD_W in app.js. */
|
||
width: 92px;
|
||
/* Kept alongside the fixed width: .fsc-feel is nowrap, so a long band label
|
||
("Very Strong Heat Stress") gives the card a ~146px min-content. Left to
|
||
the automatic minimum that can push the tracks it spans wider than their
|
||
neighbours', and the grid stops being uniform — which is the one thing
|
||
the connector-line maths (hourAt in app.js) assumes. */
|
||
min-width: 0;
|
||
justify-self: center;
|
||
text-align: center;
|
||
border: none;
|
||
background: transparent;
|
||
padding: 10px 8px 12px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 6px;
|
||
border-radius: 1px;
|
||
}
|
||
.fsc-card--now {
|
||
border: 1.5px solid #c8922a;
|
||
background: #fff9ee;
|
||
box-shadow: 0 2px 10px rgba(200,146,42,0.18);
|
||
position: relative;
|
||
z-index: 1;
|
||
}
|
||
|
||
/* Same display serif as the hour cell in the Detailed and Table views
|
||
(.utci-time) — the time reads as the same thing in all three. Now the
|
||
cards are a fixed 92px this can carry the full 16px too. */
|
||
.fsc-time {
|
||
font-family: Fraunces, serif;
|
||
font-size: 16px;
|
||
font-weight: 500;
|
||
color: #1e1208;
|
||
line-height: 1;
|
||
}
|
||
.fsc-card--now .fsc-time {
|
||
color: #c8922a;
|
||
}
|
||
|
||
.fsc-feel {
|
||
font-family: Manrope, sans-serif;
|
||
font-size: 10px;
|
||
font-weight: 700;
|
||
padding: 2px 6px;
|
||
border-radius: 2px;
|
||
line-height: 1.3;
|
||
max-width: 100%;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.fsc-scope-wrap {
|
||
position: relative;
|
||
width: 42px;
|
||
height: 42px;
|
||
flex-shrink: 0;
|
||
}
|
||
.fsc-wx-overlay {
|
||
position: absolute;
|
||
inset: 0;
|
||
top: 3px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.fsc-meta {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 2px;
|
||
width: 100%;
|
||
}
|
||
.fsc-meta-row {
|
||
font-family: Manrope, sans-serif;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
line-height: 1.25;
|
||
color: #6b5230;
|
||
/* Was 0.8 at 10px, which read as grey-on-cream mush. The wider cards have
|
||
the room, and these three lines are the only numbers on the card besides
|
||
the temperature — they should be legible, not decorative. */
|
||
opacity: 0.9;
|
||
}
|
||
.fsc-card--now .fsc-meta-row { opacity: 1; color: #4a3218; }
|
||
/* Speed and direction read as one fact ("21mph E"), so they share a line and
|
||
must never break across two. The direction sits a shade back from the
|
||
number — it qualifies the speed rather than competing with it. */
|
||
.fsc-meta-wind { white-space: nowrap; }
|
||
.fsc-meta-dir { opacity: 0.75; letter-spacing: 0.02em; }
|
||
.fsc-meta-rain { color: #2a6a90; }
|
||
.fsc-card--now .fsc-meta-rain { color: #1a4a70; }
|
||
|
||
.fsc-temp {
|
||
font-family: Fraunces, serif;
|
||
font-size: 22px;
|
||
font-weight: 700;
|
||
color: #1e1208;
|
||
line-height: 1;
|
||
}
|
||
|
||
/* The curve plots a FIXED −15…45°C scale across its full height (see fscMin /
|
||
fscMax in app.js) with preserveAspectRatio="none", so this height is the
|
||
whole scale — every degree from freezing to danger is somewhere in these
|
||
88px. It must not be flexible: the connector lines are drawn from the SVG's
|
||
top edge and have to stay flush under the cards, so any height the box does
|
||
not get is height the cold end loses off the bottom. */
|
||
.fsc-temp-curve {
|
||
display: block;
|
||
width: 100%;
|
||
flex: 0 0 auto;
|
||
height: 88px;
|
||
}
|
||
|
||
.fsc-table-btn-wrap {
|
||
padding: 10px 16px 12px;
|
||
border-top: 1px solid #d4c0a0;
|
||
background: #fdf8ee;
|
||
flex-shrink: 0;
|
||
}
|
||
.fsc-table-btn {
|
||
display: block;
|
||
width: 100%;
|
||
padding: 10px 16px;
|
||
border: 1.5px solid #a87020;
|
||
border-radius: 6px;
|
||
background: linear-gradient(to bottom, #d4a030, #b8781a);
|
||
font-family: Manrope, sans-serif;
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
color: #fff;
|
||
letter-spacing: 0.03em;
|
||
cursor: pointer;
|
||
text-align: center;
|
||
box-shadow: 0 1px 4px rgba(160,100,20,0.22);
|
||
text-shadow: 0 1px 2px rgba(0,0,0,0.18);
|
||
}
|
||
.fsc-table-btn:hover {
|
||
background: linear-gradient(to bottom, #e8b848, #cc9030);
|
||
border-color: #c8922a;
|
||
box-shadow: 0 2px 8px rgba(200,146,42,0.28);
|
||
}
|
||
|
||
@media (max-width: 600px) {
|
||
.fsc-card {
|
||
/* Width holds at 92px on mobile too — the row scrolls horizontally, so
|
||
narrowing the cards buys nothing and costs legibility. Only the inner
|
||
padding tightens. */
|
||
padding: 8px 4px 10px;
|
||
gap: 5px;
|
||
}
|
||
.fsc-temp {
|
||
font-size: 19px;
|
||
}
|
||
.fsc-feel {
|
||
font-size: 9px;
|
||
padding: 2px 4px;
|
||
}
|
||
.fsc-temp-curve {
|
||
height: 72px;
|
||
}
|
||
}
|
||
|
||
/* ══════════════════════════════════════════════════════════════════════
|
||
ROTATED TABLE — hours along the top, metrics down the side
|
||
─────────────────────────────────────────────────────────────────────
|
||
Toggled by the rotate button in the table's top-left cell (state lives
|
||
in useAppState as tableRotated). Built the same way as the normal
|
||
orientation: head and body are separate tables sharing useTableScroll's
|
||
width sync, so the hour row can stick to the viewport as the page
|
||
scrolls. (A <thead> inside .utci-tbody-scroll can only ever stick to
|
||
that scroller, which never scrolls vertically — so it would just
|
||
travel away with the table.) The metric column pins with plain CSS
|
||
sticky inside the body scroller, and the corner is held to the left
|
||
edge by the head track's counter-translate.
|
||
══════════════════════════════════════════════════════════════════════ */
|
||
|
||
/* Fallback only, for the first paint before the measurement lands.
|
||
useTableScroll measures the pinned column and writes --hour-col-w
|
||
inline — it can't be fixed here, because auto layout widens that
|
||
column to fit the longest metric name on show. */
|
||
.utci-table-wrap.is-rotated {
|
||
--hour-col-w: 104px;
|
||
}
|
||
|
||
.utci-table.utci-table-rotated {
|
||
/* Fixed layout, like the normal orientation: head and body are two tables
|
||
kept in step by useTableScroll's width sync, and only fixed layout
|
||
honours the widths it writes onto the first row of each. */
|
||
table-layout: fixed;
|
||
width: max-content;
|
||
min-width: 100%;
|
||
/* The separate border model is required, not cosmetic. Under
|
||
border-collapse:collapse the borders belong to the TABLE rather than
|
||
to the cells, so they do not travel with a sticky cell — the brass
|
||
now-column brackets and the row separators paint straight over the
|
||
pinned metric column and corner as you scroll. Separate borders
|
||
belong to their cell and move with it. */
|
||
border-collapse: separate;
|
||
border-spacing: 0;
|
||
}
|
||
|
||
/* Row separators have to move to the cells: a <tr> border never paints
|
||
in the separate model. */
|
||
.utci-table-rotated tbody td,
|
||
.utci-table-rotated tbody th {
|
||
border-bottom: 1px solid #ffffff;
|
||
}
|
||
|
||
/* ── SPACING ──────────────────────────────────────────────────────────
|
||
Cells carry icons alongside their numbers (sky scope, cloud, precip,
|
||
wind vane), so they need noticeably more room than a bare figure. */
|
||
.utci-table-rotated td,
|
||
.utci-table-rotated th {
|
||
padding: 11px 16px;
|
||
min-width: 92px;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
/* The hour row is deliberately NOT sticky: pinning it would mean the
|
||
metric rows sliding underneath, and with only a dozen of them the
|
||
table reads as shrinking away to nothing. It scrolls with the page. */
|
||
.utci-table-rotated thead th {
|
||
vertical-align: middle;
|
||
}
|
||
|
||
/* Hour headings read as times, not as column labels — same display serif
|
||
as the time cells in the normal orientation. */
|
||
.utci-table-rotated .rot-hour-th {
|
||
font-family: Fraunces, serif;
|
||
font-weight: 500;
|
||
font-size: 16px;
|
||
text-transform: none;
|
||
letter-spacing: 0;
|
||
color: #1e1208;
|
||
min-width: 76px;
|
||
}
|
||
|
||
/* Top-aligned so every scope sits on the same line. The header row is as
|
||
tall as its tallest cell — an hour carrying event tags — and centring
|
||
would push the emptier hours down out of step with it. */
|
||
.utci-table-rotated thead th.rot-hour-th {
|
||
vertical-align: top;
|
||
}
|
||
|
||
/* Stacked, not inline: rotated, every hour is its own COLUMN, so sitting
|
||
the scope beside the time would set the width of all of them. Stacking
|
||
puts the scope over the label and lets the columns stay narrow. The
|
||
negative svg margin that tightens the inline layout has to go — here it
|
||
would eat into the row above. */
|
||
.utci-table-rotated .rot-hour-th .utci-time-inner {
|
||
flex-direction: column;
|
||
gap: 3px;
|
||
}
|
||
.utci-table-rotated .rot-hour-th .utci-time-inner svg {
|
||
margin: 0;
|
||
}
|
||
.utci-table-rotated .rot-hour-th.is-night {
|
||
color: #7a5c30;
|
||
}
|
||
|
||
/* ── STICKY METRIC COLUMN (left) ──────────────────────────────────────
|
||
The column-group tints (.utci-table th.grp-*) are 50% transparent,
|
||
which is fine for a heading that has nothing behind it — but data
|
||
cells scroll UNDER this column, so any transparency lets them show
|
||
through. The tint is therefore composited over an opaque cream base:
|
||
--grp-tint carries the group colour, painted as a background-image
|
||
layer on top of an opaque background-color. */
|
||
/* Set on the group divider rows as well as the metric labels, so a group
|
||
heading and the metrics beneath it resolve to the SAME colour. Both
|
||
composite over the identical opaque base below — a shared tint over
|
||
two different backgrounds is what makes them drift apart. */
|
||
.utci-table-rotated .grp-felt { --grp-tint: rgba(223,243,236,0.50); }
|
||
.utci-table-rotated .grp-surface { --grp-tint: rgba(221,234,248,0.50); }
|
||
.utci-table-rotated .grp-pets { --grp-tint: rgba(214,238,240,0.50); }
|
||
.utci-table-rotated .grp-ambient { --grp-tint: rgba(236,234,227,0.50); }
|
||
.utci-table-rotated .grp-precip { --grp-tint: rgba(247,228,238,0.50); }
|
||
.utci-table-rotated .grp-sky { --grp-tint: rgba(234,232,252,0.50); }
|
||
.utci-table-rotated .grp-wind { --grp-tint: rgba(250,230,224,0.50); }
|
||
.utci-table-rotated .grp-airqual { --grp-tint: rgba(229,240,216,0.50); }
|
||
.utci-table-rotated .grp-solar { --grp-tint: rgba(250,234,204,0.50); }
|
||
|
||
/* Over-qualified on purpose: the normal orientation's group rules
|
||
(.utci-table th.grp-felt, .utci-table .grp-label-row th.grp-felt) are
|
||
specific enough to win otherwise and would restore the transparency. */
|
||
.utci-table.utci-table-rotated tbody th.rot-metric-label,
|
||
.utci-table.utci-table-rotated tbody th.rot-metric-label.col-info-th:hover,
|
||
.utci-table.utci-table-rotated tbody .grp-label-row--rotated th {
|
||
background-color: #f0e4c4;
|
||
background-image: linear-gradient(var(--grp-tint, transparent), var(--grp-tint, transparent));
|
||
}
|
||
|
||
/* The group rules carry a 2px left border that marks where a group STARTS
|
||
along the columns. Rotated, groups are stacked rows and have no left
|
||
edge to mark, so it shows up as a stray line — and its width offsets
|
||
the label 2px against the metric names below it.
|
||
The top rule matches .grp-label-fill's so the group boundary runs unbroken
|
||
from the pinned label across every hour column. */
|
||
.utci-table.utci-table-rotated tbody .grp-label-row--rotated th {
|
||
border-left: 0;
|
||
border-top: 2px solid rgba(74, 50, 24, 0.18);
|
||
border-bottom: 0;
|
||
}
|
||
|
||
/* The group heading pins with the metric names it labels. */
|
||
.utci-table.utci-table-rotated tbody th.rot-group-label {
|
||
position: sticky;
|
||
left: 0;
|
||
z-index: 3;
|
||
text-align: left;
|
||
padding: 5px 16px;
|
||
box-shadow: 1px 0 0 rgba(74, 50, 24, 0.18);
|
||
}
|
||
|
||
/* The per-hour cells filling out a divider row. They carry the group tint
|
||
and, in the current hour's column, its share of the brass bracket.
|
||
|
||
Composited over the SAME opaque cream base as the sticky label at the row's
|
||
left end, for the same reason that label needs one: --grp-tint is only 50%
|
||
opaque, so painting it straight onto the cell resolved it against the table
|
||
background while the label resolved it against #f0e4c4. The divider then read
|
||
as a dark cap at the left fading to a much paler band across the hours — one
|
||
tint, two backgrounds, two colours. One base, and the row is a single shade
|
||
end to end. The brass now-column bracket is unaffected: it is a border on
|
||
these cells, not a background, so it still paints over the top. */
|
||
.utci-table.utci-table-rotated tbody td.grp-label-fill {
|
||
padding: 5px 0;
|
||
min-width: 0;
|
||
background-color: #f0e4c4;
|
||
background-image: linear-gradient(var(--grp-tint, transparent), var(--grp-tint, transparent));
|
||
/* One rule, along the very top of the category block — the same 2px
|
||
rgba(74,50,24,0.18) the unrotated table draws down the left of each new
|
||
column group (.col-group-start), so a group boundary looks the same
|
||
whichever way the table is turned. Nothing along the bottom: the divider
|
||
and the metrics under it are one block, and a second line there just read
|
||
as a double rule around the label. Every cell in the row carries the edge
|
||
— under border-collapse:separate a <tr> border never paints, so the row's
|
||
rule has to be drawn cell by cell (see the row-separator rule above). */
|
||
border-top: 2px solid rgba(74, 50, 24, 0.18);
|
||
border-bottom: 0;
|
||
}
|
||
|
||
.utci-table.utci-table-rotated tbody th.rot-metric-label {
|
||
position: sticky;
|
||
left: 0;
|
||
z-index: 3;
|
||
text-align: left;
|
||
/* Was 136px, sized around the old long date in the corner. The names and
|
||
their unit lines need far less than that, and every pixel here is taken
|
||
straight off the hours. */
|
||
min-width: 104px;
|
||
/* box-shadow rather than border-right so the divider sits outside the
|
||
cell box and cannot add to the column's measured width. */
|
||
box-shadow: 1px 0 0 rgba(74, 50, 24, 0.18);
|
||
}
|
||
|
||
/* The corner cell sits in both sticky tracks, so it needs to win over
|
||
each of them. */
|
||
/* The corner pins alongside the metric names below it. Over-qualified so
|
||
it beats the normal orientation's `.utci-table thead th:first-child`
|
||
rule, which sets position:relative for the JS transform this view
|
||
doesn't use. */
|
||
.utci-table.utci-table-rotated thead th.rot-corner {
|
||
/* Left-pinned by the head-track counter-translate useTableScroll applies to
|
||
thead th:first-child — the same device the normal orientation's Hour
|
||
header uses. CSS sticky would do nothing here: the head table sits in a
|
||
translated track, not in the scroller. */
|
||
position: relative;
|
||
z-index: 4;
|
||
background: #f0e4c4;
|
||
text-align: left;
|
||
box-shadow: 1px 0 0 rgba(74, 50, 24, 0.18);
|
||
}
|
||
.utci-table-rotated .rot-corner-date {
|
||
display: block;
|
||
font-size: 10px;
|
||
letter-spacing: 0.08em;
|
||
color: #a08040;
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
/* This cell is what widens the pinned metric-name column, so the date is
|
||
allowed to wrap rather than forcing the column past the names' width. */
|
||
.utci-table.utci-table-rotated thead th.rot-corner {
|
||
white-space: normal;
|
||
}
|
||
.utci-table-rotated .rot-corner-controls {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
/* The pinned column carries text, not figures with icons, so it does not
|
||
need the 16px side padding the data cells use — and it is the one column
|
||
that costs the hours their width. */
|
||
.utci-table.utci-table-rotated thead th.rot-corner,
|
||
.utci-table.utci-table-rotated tbody th.rot-metric-label,
|
||
.utci-table.utci-table-rotated tbody th.rot-group-label {
|
||
padding-left: 10px;
|
||
padding-right: 10px;
|
||
}
|
||
|
||
/* Keep the interval stepper inside that width; it would otherwise set the
|
||
column on its own. */
|
||
.utci-table-rotated .rot-corner .row-stepper { gap: 2px; }
|
||
.utci-table-rotated .rot-corner .row-stepper-btn { min-width: 26px; }
|
||
.utci-table-rotated .rot-corner .row-stepper-value { min-width: 20px; }
|
||
|
||
@media (max-width: 640px) {
|
||
/* Tighter segments on narrow screens — the toolbar is already tight,
|
||
and there are three of them now. */
|
||
.fvt-btn {
|
||
padding: 6px 6px;
|
||
gap: 2px;
|
||
}
|
||
}
|
||
|
||
/* ── GROUP DIVIDER ROWS ───────────────────────────────────────────────
|
||
Stand in for the spanning group labels that sit above the columns in
|
||
the normal orientation. The cell spans the full width, so the LABEL is
|
||
what gets pinned rather than the cell. */
|
||
.utci-table-rotated .grp-label-row--rotated th {
|
||
text-align: left;
|
||
padding: 5px 16px;
|
||
border-right: 0;
|
||
}
|
||
|
||
/* ── NOW / NIGHT ──────────────────────────────────────────────────────
|
||
Rotated, "now" is a column rather than a row, so the brass bracket
|
||
that frames it runs down each side instead of across top and bottom. */
|
||
.utci-table-rotated td.is-now,
|
||
.utci-table-rotated th.rot-hour-th.is-now {
|
||
border-left: 2px solid #c8922a;
|
||
border-right: 2px solid #c8922a;
|
||
}
|
||
.utci-table-rotated th.rot-hour-th.is-now {
|
||
background: #fff8e4;
|
||
border-top: 2px solid #c8922a;
|
||
}
|
||
.utci-table-rotated td.is-night {
|
||
color: #7a5c30;
|
||
}
|
||
|
||
/* ── TOOLBAR BUTTON HEIGHTS ───────────────────────────────────────────
|
||
The view segments and Edit columns sit on one row and have to agree on
|
||
height. They didn't: the segments set line-height:1 while Edit columns
|
||
leaves it at normal, so the same 6px padding produced a box ~3px
|
||
taller. Pinning one line-height and one min-height across both settles
|
||
it regardless of font-size differences. */
|
||
.fvt-btn,
|
||
.col-toggles-edit-btn {
|
||
min-height: 29px;
|
||
line-height: 1;
|
||
}
|
||
|
||
/* ── MOBILE ───────────────────────────────────────────────────────── */
|
||
@media (max-width: 640px) {
|
||
.utci-rotated-scroll {
|
||
max-height: min(70vh, 620px);
|
||
}
|
||
.utci-table-rotated td,
|
||
.utci-table-rotated th {
|
||
padding: 9px 11px;
|
||
min-width: 80px;
|
||
}
|
||
.utci-table-rotated .rot-hour-th {
|
||
font-size: 14px;
|
||
min-width: 68px;
|
||
}
|
||
.utci-table-rotated .rot-metric-label {
|
||
min-width: 96px;
|
||
}
|
||
.utci-table.utci-table-rotated thead th.rot-corner,
|
||
.utci-table.utci-table-rotated tbody th.rot-metric-label,
|
||
.utci-table.utci-table-rotated tbody th.rot-group-label {
|
||
padding-left: 8px;
|
||
padding-right: 8px;
|
||
}
|
||
.utci-table-rotated .rot-corner-date {
|
||
font-size: 9px;
|
||
letter-spacing: 0.04em;
|
||
}
|
||
.utci-table-wrap.is-rotated {
|
||
--hour-col-w: 96px;
|
||
}
|
||
}
|