UI Overhaul
Profiles now in popup
Current profile always on screen
Hidden columns only when edit neeeded
Add pulldowns in to profile config
Customised the day tabs for vehicle, indoor and pet
This commit is contained in:
fraxle
2026-07-18 15:45:20 +01:00
parent f92e191a44
commit 916efca55e
13 changed files with 1594 additions and 610 deletions
+520 -61
View File
@@ -85,7 +85,8 @@
}
.utci-day-tab {
flex: 1 0 auto;
flex: 1 0 0;
min-width: 90px;
padding: 10px 20px;
cursor: pointer;
font-family: Manrope, sans-serif;
@@ -96,6 +97,7 @@
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;
}
@@ -165,6 +167,69 @@
text-align: center;
}
/* Config strip - value-only pickers for the felt-model settings (vehicle
type, building type, fur colour, skin type, pollen type, SunSoak env).
Sits directly under the profile picker, always visible (not tucked away
in the collapsible Columns accordion) so these settings stay as
prominent as the profile choice that they belong to. */
.col-toggles-config {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 12px;
align-items: center;
margin-top: 0;
padding: 10px 14px;
background: #fffcf2;
border: 1.5px solid #c9b08a;
border-top: none;
}
.col-toggles-config-item {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 6px 10px;
background: rgba(255,255,255,0.6);
border: 1px solid #e6d7b8;
border-radius: 5px;
}
.config-item-label {
font-family: Manrope, sans-serif;
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
color: #9a7a4a;
white-space: nowrap;
}
/* Main thermal selection - the config item whose value also drives the
day-tab hi/lo readout. Always sorted first (see DayTabs.js) and marked
with a badge so it reads as distinct from the other config pickers. */
.col-toggles-config-item--main {
padding: 8px 8px;
border: 1.5px solid #c8922a;
border-radius: 4px;
background: rgba(200,146,42,0.08);
}
.config-item-main-badge {
display: block;
flex: 0 0 100%;
text-align: center;
font-family: Manrope, sans-serif;
font-size: 13px;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.04em;
color: #9a6a1a;
padding: 0;
white-space: nowrap;
cursor: help;
}
/* Single scrollable row of profile cards */
.profile-scroll {
display: flex;
@@ -241,6 +306,223 @@
.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;
}
.floating-profile-btn:hover { background: #fff; border-color: #c8922a; box-shadow: 0 3px 10px rgba(0, 0, 0, 0.14); }
.floating-profile-btn.is-open { background: #fff; border-color: #c8922a; }
.floating-profile-icon { font-size: 16px; line-height: 1; flex: none; }
/* No fixed max-width — the pill grows to fit the label. Ellipsis only
kicks in once the whole button hits its 92vw cap (min-width:0 lets these
flex children actually shrink instead of forcing the button to overflow). */
.floating-profile-val {
font-family: Manrope, sans-serif;
font-size: 13px;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.02em;
color: #1e1208;
min-width: 0;
flex: 0 1 auto;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.floating-profile-sub {
font-family: Manrope, sans-serif;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.02em;
color: #6b4f2a;
min-width: 0;
flex: 0 1 auto;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.floating-profile-caret {
font-size: 10px;
transition: transform 0.2s ease;
}
.floating-profile-btn.is-open .floating-profile-caret { transform: rotate(180deg); }
/* Backdrop + dropdown panel — always mounted once opened for the first time
(see ConfigPanel.js), visibility/position driven by the .is-open class so
both the open AND close transitions can animate (a conditionally-mounted
element only ever gets an entrance animation, never an exit one). */
.profile-dropdown-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.35);
z-index: 99;
opacity: 0;
visibility: hidden;
pointer-events: none;
transition: opacity 0.2s ease, visibility 0s linear 0.2s;
}
.profile-dropdown-overlay.is-open {
opacity: 1;
visibility: visible;
pointer-events: auto;
transition: opacity 0.2s ease, visibility 0s linear 0s;
}
.profile-dropdown {
position: fixed;
left: 0;
right: 0;
bottom: 0;
max-height: calc(100vh - 24px);
z-index: 101;
background: #f5edd6;
border-top: 1.5px solid #c9b08a;
box-shadow: 0 -10px 28px rgba(0, 0, 0, 0.18);
opacity: 0;
visibility: hidden;
pointer-events: none;
/* Scales up from the floating-profile-btn's position (bottom-centre) so
the panel reads as growing out of that button, and shrinks back into
it on close, instead of a generic slide. */
transform-origin: 50% 100%;
transform: scale(0.85);
transition: opacity 0.22s cubic-bezier(0.22, 1, 0.36, 1),
transform 0.22s cubic-bezier(0.22, 1, 0.36, 1),
visibility 0s linear 0.22s;
}
.profile-dropdown.is-open {
opacity: 1;
visibility: visible;
pointer-events: auto;
transform: scale(1);
transition: opacity 0.22s cubic-bezier(0.22, 1, 0.36, 1),
transform 0.22s cubic-bezier(0.22, 1, 0.36, 1),
visibility 0s linear 0s;
}
.profile-dropdown-body {
max-width: 900px;
margin: 0 auto;
max-height: calc(100vh - 24px);
overflow-y: auto;
padding: 12px 24px 16px;
}
.profile-dropdown-close {
position: absolute;
top: 10px;
right: 14px;
background: none;
border: none;
font-size: 24px;
line-height: 1;
color: #b09870;
cursor: pointer;
padding: 2px 8px;
border-radius: 6px;
transition: color 0.15s, background 0.15s;
}
.profile-dropdown-close:hover { color: #1e1208; background: rgba(176, 152, 112, 0.16); }
/* 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: 2px 16px;
margin-bottom: 6px;
/* --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: 1.5px solid #d4c0a0;
border-radius: 6px;
cursor: pointer;
transition: filter 0.15s, border-color 0.15s;
}
.active-profile-row:hover,
.active-profile-row:focus-visible {
filter: brightness(1.06);
border-color: #c8922a;
outline: none;
}
.active-profile-row-item {
display: inline-flex;
align-items: center;
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 {
font-family: Manrope, sans-serif;
font-size: 13px;
font-weight: 700;
letter-spacing: 0.04em;
color: #9a6a1a;
margin-left: 4px;
}
/* Border + text accent per thermal-model group, reusing the same colour
groups the CustomSelect pickers use (see .cs-btn.grp-* in ui.css) — e.g.
SunSoak's "Solar model" dropdown is grp-felt. The background itself comes
from --row-bg (inline, per exact selection — see ROW_GRADIENTS); these
classes only set the border/text so the row still reads as belonging to
its category even though the gradient varies option-to-option. */
.active-profile-row.grp-felt { border-color: rgba(7,92,58,0.35); }
.active-profile-row.grp-wind { border-color: rgba(106,34,16,0.30); }
.active-profile-row.grp-ambient { border-color: rgba(60,58,53,0.30); }
.active-profile-row.grp-surface { border-color: rgba(11,62,114,0.30); }
.active-profile-row.grp-felt .active-profile-row-value { color: #075c3a; }
.active-profile-row.grp-wind .active-profile-row-value { color: #6a2210; }
.active-profile-row.grp-ambient .active-profile-row-value { color: #3c3a35; }
.active-profile-row.grp-surface .active-profile-row-value { color: #0b3e72; }
/* Vertical rule separating general-use profiles from technical ones */
.profile-divider {
display: inline-block;
@@ -394,8 +676,22 @@
background: #fdf8ee;
border: 1.5px solid #d4c0a0;
border-bottom: none;
position: relative;
}
/* Whole Columns bar (Hours + column pills) stays collapsed behind the
Edit columns / Hide Columns toggle in the toolbar row until opened.
Scoped to detailed (table) view — quick view keeps its own reduced
col-toggles bar (Hours only) visible regardless of this state. */
.table-main:not(.table-main--simple) .col-toggles {
display: none;
}
.table-main:not(.table-main--simple) .col-toggles.col-toggles--open {
display: flex;
}
.col-toggles-display-opts {
display: flex;
align-items: center;
@@ -406,18 +702,37 @@
}
.col-toggles-label {
font-family: Manrope, sans-serif;
font-size: 12px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.08em;
color: #7a5c2a;
margin-right: 4px;
white-space: nowrap;
display: none;
}
.col-toggles-edit-btn {
display: none;
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;
}
.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 {
@@ -620,6 +935,15 @@
top: 72px;
}
/* Profile/config dropdown - full width and scrollable on phones since it
can run taller than the viewport once the config strip wraps. */
.profile-dropdown-body {
max-height: calc(100vh - 16px);
overflow-y: auto;
}
.floating-profile-val { max-width: 90px; }
.floating-profile-sub { max-width: 90px; }
/* Shrink the day-tab labels on phones so more days stay readable
within the viewport before the tab strip needs to scroll. */
.utci-day-tab {
@@ -1128,53 +1452,14 @@
/* ── Column toggles bar ─────────────────────────────────────────────
Same approach — label full row, toggles at ~31% each. */
.col-toggles {
.col-toggles,
.col-toggles-config {
flex-wrap: wrap;
gap: 6px;
padding: 10px 10px;
align-items: stretch;
}
.col-toggles-label {
display: none;
}
.col-toggles-edit-btn {
display: flex;
align-items: center;
justify-content: space-between;
flex: 0 0 100%;
padding: 8px 12px;
background: #f5edd6;
border: 1.5px solid #c9b08a;
color: #7a5c2a;
font-family: Manrope, sans-serif;
font-size: 12px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.08em;
cursor: pointer;
text-align: left;
}
.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: none;
}
.col-toggles-body--open {
display: contents;
}
.col-toggle {
flex: 0 0 calc((100% - 12px) / 3);
@@ -1204,6 +1489,63 @@
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;
}
/* 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);
@@ -1234,6 +1576,37 @@
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 ────────
@@ -1394,12 +1767,106 @@
flex-shrink: 0;
}
/* Interval buttons shown in the col-toggles bar in simple mode */
/* 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 14px;
}
.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;
padding: 6px 10px 0;
}
.fvt-toolbar-left {
grid-column: 1;
grid-row: 1;
justify-self: center;
padding-bottom: 0;
}
.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;
}
/* Interval buttons shown in the col-toggles bar in simple mode, pushed
to the far right since Columns/Edit controls are hidden there. */
.fvt-interval {
display: none;
align-items: center;
gap: 2px;
margin-right: 6px;
margin-left: auto;
flex-shrink: 0;
}
.table-main--simple .fvt-interval {
@@ -1571,14 +2038,6 @@
min-height: 60px;
}
.fvt-model-sep {
width: 1px;
height: 18px;
background: #d4c0a0;
margin: 0 4px;
flex-shrink: 0;
}
.fsc-table-btn-wrap {
padding: 10px 16px 12px;
border-top: 1px solid #d4c0a0;
+51 -8
View File
@@ -634,12 +634,14 @@
border-radius: 0;
}
/* The floating dropdown panel */
/* The floating dropdown panel. Centred under/over its button and clamped to
the nearest scrollable ancestor's edges via an inline `left` (px) set in
JS (see the useLayoutEffect in CustomSelect) — `left: 0` here is only the
pre-measurement fallback for the very first paint. */
.cs-panel {
position: absolute;
top: calc(100% + 4px);
left: 0; /* left edge flushes with button's left edge by default */
right: auto;
left: 0;
z-index: 1000;
background: #fffcf2;
border: 1.5px solid #c9b08a;
@@ -647,13 +649,47 @@
box-shadow: 0 4px 16px rgba(30,18,8,0.13);
min-width: 100%;
white-space: nowrap;
overflow: hidden;
/* Safety net only — long lists (SunSoak, Indoors, ...) lay out as two
columns instead (see .cs-panel--cols) so every option is visible at a
glance rather than relying on people to notice a cropped list needs
scrolling. This cap only bites in the rare case even that doesn't fit
the space available (see the JS-computed inline max-height). */
max-height: min(300px, 60vh);
overflow-y: auto;
overflow-x: hidden;
transform-origin: top center;
animation: cs-panel-in 0.15s ease-out;
}
/* Flip panel to right-align when left-align would overflow the viewport */
.cs-panel.cs-panel--right {
left: auto;
right: 0;
/* Two-column layout for longer option lists (>5 items) — halves the height
needed so lists fit in the available space without scrolling. */
.cs-panel.cs-panel--cols {
column-count: 2;
column-gap: 0;
width: max-content;
max-width: 90vw;
}
.cs-panel.cs-panel--cols .cs-option {
break-inside: avoid;
}
@keyframes cs-panel-in {
from { opacity: 0; transform: translateY(-6px) scaleY(0.92); }
to { opacity: 1; transform: translateY(0) scaleY(1); }
}
/* Flip panel to open upward when it would overflow the bottom of the
viewport — e.g. these selectors sitting inside the profile/config bottom
sheet, which is anchored to the bottom of the screen. Animates in from
the opposite direction so it still reads as growing toward the button. */
.cs-panel.cs-panel--up {
top: auto;
bottom: calc(100% + 4px);
transform-origin: bottom center;
animation: cs-panel-in-up 0.15s ease-out;
}
@keyframes cs-panel-in-up {
from { opacity: 0; transform: translateY(6px) scaleY(0.92); }
to { opacity: 1; transform: translateY(0) scaleY(1); }
}
/* Card-grid panel — wrapping flex grid of scene cards */
@@ -665,6 +701,7 @@
min-width: 0;
width: max-content;
max-width: min(320px, 90vw);
max-height: none;
white-space: normal;
overflow: visible;
}
@@ -1233,6 +1270,12 @@
font-weight: 600;
}
.utci-legend-label--secondary {
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid #d4c0a0;
}
.utci-legend-row {
display: flex;
flex-wrap: wrap;