Restructure
New Journal entries
Colour tabs update with wind
This commit is contained in:
fraxle
2026-08-31 15:12:45 +01:00
parent 47818fba33
commit 71860b9dd9
40 changed files with 3430 additions and 443 deletions
+271 -32
View File
@@ -270,6 +270,34 @@
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;
@@ -609,6 +637,10 @@
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;
@@ -632,8 +664,15 @@
bottom: auto;
max-height: calc(100vh - var(--profile-anchor-top, 0px) - 16px);
width: fit-content;
min-width: 380px;
max-width: min(880px, calc(100vw - 32px));
/* 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;
@@ -696,7 +735,11 @@
}
.utci-thead-sticky {
position: sticky;
top: 28px !important; /* Corrected to match the height of the active-profile-row + margins */
/* 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;
}
}
@@ -1257,6 +1300,48 @@
.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; }
@@ -1975,6 +2060,23 @@
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 {
@@ -2277,7 +2379,9 @@
grid-template-columns: 1fr;
justify-items: center;
row-gap: 6px;
padding: 6px 0 10px;
/* 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;
@@ -2285,6 +2389,12 @@
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;
@@ -2360,7 +2470,16 @@
.table-main--simple .forecast-simple-wrap {
display: flex;
flex-direction: column;
height: 420px;
/* 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;
}
@@ -2368,6 +2487,56 @@
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 {
@@ -2503,6 +2672,11 @@
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; }
@@ -2514,11 +2688,17 @@
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: 1;
min-height: 60px;
flex: 0 0 auto;
height: 88px;
}
.fsc-table-btn-wrap {
@@ -2566,7 +2746,7 @@
padding: 2px 4px;
}
.fsc-temp-curve {
min-height: 48px;
height: 72px;
}
}
@@ -2574,16 +2754,14 @@
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). Unlike the normal orientation — which
is two tables kept in step by useTableScroll — this is ONE table in a
single scroll pane, so both sticky edges are pure CSS.
A single table in the shared .utci-tbody-scroll horizontal scroller.
The normal orientation splits head from body so its header can stick
to the viewport; here the hour row scrolls away with the table by
design, so that machinery is not needed. The metric column pins with
plain CSS sticky, which works fine inside the scroller. The table
keeps its natural height and the page scrolls normally.
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.
@@ -2591,13 +2769,14 @@
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: 136px;
--hour-col-w: 104px;
}
.utci-table.utci-table-rotated {
/* Auto layout: with a single table there are no header widths to match,
so each hour column can simply size to its own content. */
table-layout: auto;
/* 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
@@ -2703,9 +2882,13 @@
/* 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 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. */
@@ -2719,12 +2902,31 @@
}
/* 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. */
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: var(--grp-tint, transparent);
border-bottom: 1px solid rgba(74, 50, 24, 0.18);
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 {
@@ -2732,7 +2934,10 @@
left: 0;
z-index: 3;
text-align: left;
min-width: 136px;
/* 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);
@@ -2745,12 +2950,14 @@
rule, which sets position:relative for the JS transform this view
doesn't use. */
.utci-table.utci-table-rotated thead th.rot-corner {
position: sticky;
left: 0;
/* 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;
transform: none;
box-shadow: 1px 0 0 rgba(74, 50, 24, 0.18);
}
.utci-table-rotated .rot-corner-date {
@@ -2760,12 +2967,34 @@
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. */
@@ -2828,9 +3057,19 @@
min-width: 68px;
}
.utci-table-rotated .rot-metric-label {
min-width: 112px;
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: 112px;
--hour-col-w: 96px;
}
}