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
+180 -55
View File
@@ -65,7 +65,14 @@ body {
color: #1e1208;
background: #f5edd6;
min-height: 100vh;
padding: 52px 36px 32px;
/* The side padding, as a variable: the header and the event note both
full-bleed by cancelling it, and would otherwise each need the number
restating at every breakpoint that changes it. */
--app-pad: 36px;
/* No top padding: .utci-topnav is a real header at the very top of the flow
now, and supplies its own. Was 52px, reserved for the nav when it was
absolutely positioned over this space. */
padding: 0 var(--app-pad) 32px;
position: relative;
/* overflow-x: clip (not hidden) — `clip` clips horizontal overflow
WITHOUT making this a scroll container, so `position: sticky` on
@@ -83,19 +90,27 @@ body {
}
/* ── 3. TOP NAVIGATION (Home / About links in the corner) ──────────── */
/* ── 3. SITE HEADER (wordmark left, nav links right) ───────────────── */
/* Rendered as <header id="site-nav"> — the same element the static pages
(about.html et al) use — and it now carries the wordmark and tagline that
used to be repeated inside .utci-header. It sits FIRST in the flow, at the
very top of the page, with the event banner beneath it.
Full-bleed: .utci-app pads its content in by --app-pad a side, so the header
cancels that with a matching negative margin and pads itself instead — the
bar's rule reaches both page edges while its contents still line up with
the content below. */
.utci-topnav {
position: absolute;
top: 0;
left: 0;
right: 0;
position: relative; /* keeps z-index working without leaving the flow */
z-index: 100;
margin: 0 calc(var(--app-pad) * -1) 18px;
display: flex;
align-items: center;
justify-content: flex-end;
padding: 0px 32px;
justify-content: flex-end; /* .nav-logo's margin-right:auto splits the row */
padding: 18px var(--app-pad);
gap: 28px;
background: #f5edd6;
border-bottom: 1px solid #e0d0a8;
}
.utci-topnav a {
@@ -116,9 +131,9 @@ body {
border-bottom-color: #1e1208;
}
/* Desktop: nav-links renders inline as part of nav flex row */
/* Desktop: nav-links renders inline as part of the header's flex row */
.utci-topnav .nav-links {
display: contents; /* participates in nav's flex layout transparently */
display: contents; /* participates in the header's flex layout transparently */
}
/* ── About dropdown ─────────────────────────────────────────────────── */
@@ -171,6 +186,22 @@ body {
visibility: visible;
transform: translateX(-50%) translateY(0);
}
/* The Account dropdown sits at the far right of the nav row, so a menu
centred under it (like About's) would spill past the viewport edge on
most screen widths. Right-align it to the toggle instead, growing left. */
.utci-topnav .nav-dropdown--right .nav-dropdown-menu {
left: auto;
right: 0;
transform: translateY(-4px);
}
.utci-topnav .nav-dropdown--right.nav-dropdown:hover .nav-dropdown-menu,
.utci-topnav .nav-dropdown--right.nav-dropdown:focus-within .nav-dropdown-menu {
transform: translateY(0);
}
/* Labels align to the same right edge the menu itself hangs from. */
.utci-topnav .nav-dropdown--right .nav-dropdown-menu a {
text-align: right;
}
.utci-topnav .nav-dropdown-menu a {
text-transform: none;
letter-spacing: normal;
@@ -190,15 +221,15 @@ body {
/* ── Event note ─────────────────────────────────────────────────────── */
/* Active cosmic/weather events shown as a strip across the content width,
above the nav. It is IN FLOW — it pushes the page down instead of
covering anything, which is the whole point of it not being a flyout.
One event at a time with its full message, cycling with a fade.
directly under the site header. It is IN FLOW — it pushes the page down
instead of covering anything, which is the whole point of it not being a
flyout. One event at a time with its full message, cycling with a fade.
The nav is normally position:absolute and .utci-app reserves 52px of top
padding for it. When a note is present the nav joins the flow instead so
the note can sit above it, and that padding is no longer needed. On true
mobile (≤640px) the nav is position:fixed and stays that way — there the
note simply becomes the first thing under it, so no override applies. */
It used to render ABOVE the nav, which meant repositioning the nav out of
its absolute placement whenever a note was present. The header owns the top
of the page now, so the note is simply the first thing under it — on desktop
and on mobile (where the header is fixed and .utci-app pads clear of it)
alike, with no override either way. */
.event-note {
display: flex;
align-items: baseline;
@@ -222,11 +253,20 @@ body {
same device as .almanac-entry-accent. */
.event-note-accent {
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 3px;
inset: 0;
opacity: 0.75;
pointer-events: none;
/* One element, drawn as an L: a 1px strip down the left that turns and runs
along the bottom. The note's own overflow: hidden rounds the corner off
with the rest of the box. */
clip-path: polygon(
0 0,
1px 0,
1px calc(100% - 1px),
100% calc(100% - 1px),
100% 100%,
0 100%
);
}
/* Safety-relevant weather (heat, frost, storm, wind, wet) — brass emphasis.
@@ -408,33 +448,39 @@ body {
color: #2a1a08;
}
/* Collapse on the way out — height and vertical spacing animate to zero so
the page closes the gap smoothly instead of snapping upward. */
/* Closing: the slot's height runs down to zero (measured and driven inline by
noteClose in app.js) and the note is anchored to its BOTTOM edge, so the
banner rides up behind the header's seam while everything below it rises by
the same amount at the same rate. One animated property for both movements —
a transform on the note would slide it independently of the page, and a
transform on the content below would break the fixed-position controls
living inside it. */
.event-note-slot {
/* The gap under the banner lives here rather than on the note, and is
deliberately equal to what the header gets back when .has-event-note is
dropped at the end of the close: the header's own margin-bottom (18px,
zeroed while a note is attached) plus, on a phone, the 8px .utci-app
stops padding once the note is gone. Without that, the space swapped in
at unmount showed up as a jump. */
margin-bottom: 18px;
display: grid;
grid-template-rows: 1fr; /* the row fills the slot, whatever its height */
align-items: end; /* ...and the note hangs from its bottom edge */
overflow: hidden;
}
.event-note-slot > .event-note {
min-height: 0; /* grid items floor at min-content without this */
}
/* The entry animation is fill-mode: both, so its final transform would stay
applied — cleared so it cannot offset the note against the slot. */
.event-note.is-closing {
animation: event-note-out 0.26s ease forwards;
animation: none;
pointer-events: none;
}
@keyframes event-note-out {
from { opacity: 1; transform: translateY(0); max-height: 200px; }
to { opacity: 0; transform: translateY(-4px); max-height: 0;
margin-bottom: 0; padding-top: 0; padding-bottom: 0; border-width: 0; }
}
/* Above true-mobile: the nav drops into the flow beneath the note. Its own
32px side padding is dropped so the links line up with the note and the
content rather than sitting 32px inside them. */
@media (min-width: 641px) {
.utci-app.has-event-note {
padding-top: 18px;
}
/* `relative`, not `static` — the nav keeps its z-index:100 that way, so
the About dropdown still paints over .utci-shell (which is z-index:1). */
.utci-app.has-event-note .utci-topnav {
position: relative;
padding-left: 0;
padding-right: 0;
}
}
/* The note follows the header in the flow now, so it needs no special
positioning — only the gap the header's own margin-bottom would otherwise
have given the content it sits in front of. */
@media (max-width: 640px) {
.event-note {
@@ -456,26 +502,89 @@ body {
}
}
/* Nav logo — hidden on desktop, shown on mobile */
/* ── Note attached to the header ─────────────────────────────────────
When a note is present it hangs off the header's bottom edge rather than
floating below it as a separate panel: the header drops the gap it would
otherwise leave, and the note keeps a square top with no top border, inset
to the content edge so there is a gap either side. The header's own bottom
rule becomes the seam the strip hangs from.
Placed after the ≤640px block above deliberately — these override the
note's base padding and border at every width, mobile included. */
.has-event-note .utci-topnav {
margin-bottom: 0;
}
.has-event-note .event-note {
/* Inset to the content edge rather than full-bleed, so the strip keeps a
gap either side of it. The gap below is the slot's — see .event-note-slot. */
margin: 0;
padding: 10px 15px;
/* No top border and square top corners: the header's own bottom rule is the
seam it hangs from. A tinted note's inline borderColor still tints what is
left, since that sets the colour only and this sets style and width. */
border: 1.5px solid #c9b08a;
border-top: none;
border-radius: 0 0 4px 4px;
}
@media (max-width: 640px) {
/* Matches the two-class specificity above so the phone's tighter padding
survives the override. */
.has-event-note .event-note {
padding: 10px 12px;
}
/* 18px + the 8px of .utci-app padding-top below — see .event-note-slot. */
.event-note-slot {
margin-bottom: 26px;
}
/* The bar is position:fixed and 72px tall on a phone, and .utci-app pads 80px
clear of it — the spare 8px would show as a gap above the note. Two classes
so it outranks the single-class padding-top in ui.css whatever the order. */
.utci-app.has-event-note {
padding-top: 72px;
}
}
/* The site identity, in the header on every viewport. Was display:none here
and unhidden only on mobile, back when the big .utci-title wordmark carried
the desktop identity instead. */
.nav-logo {
display: none;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
flex-shrink: 0;
/* Same device about.css uses on .nav-logo: one auto margin pushes everything
after it to the right-hand end, which works with .nav-links staying
display:contents. */
margin-right: auto;
}
.nav-logo-wordmark {
display: flex;
align-items: center;
gap: 0.05em;
/* It is the page's <h1> now, so the browser's own h1 margin and 2em size are
cleared here — the wordmark's size and weight come from .nav-logo-sun and
.nav-logo-scope, and the icon is sized in rem. */
margin: 0;
font-size: 1rem;
font-weight: inherit;
}
.nav-logo-sun,
.nav-logo-scope {
font-family: Manrope, sans-serif;
font-weight: 250;
font-size: 2.1rem;
/* 1.6rem matches .nav-logo-sun in about.css, so the wordmark is the same size on the
forecast and on every static page. The phone bar scales it back up (see the
≤640px block) where it is the centred focal point rather than a corner mark. */
font-size: 1.6rem;
color: #1e1208;
letter-spacing: -0.01em;
}
.nav-logo-icon {
width: 1.65rem;
height: 1.65rem;
width: 1.26rem; /* same 0.79 ratio to the wordmark as .nav-logo-icon */
height: 1.26rem;
color: #1e1208;
flex-shrink: 0;
}
@@ -484,7 +593,7 @@ body {
font-family: Manrope, sans-serif;
font-style: normal;
font-weight: 600;
font-size: 8px;
font-size: 10px;
letter-spacing: 0.12em;
text-transform: uppercase;
color: #7a5c2a;
@@ -548,14 +657,26 @@ body {
display: block;
line-height: 1;
}
.utci-title,
.utci-tagline {
display: none;
.nav-logo-sun,
.nav-logo-scope {
font-size: 2.1rem;
}
.nav-logo-icon {
width: 1.65rem;
height: 1.65rem;
}
/* Phone keeps the bar pinned to the viewport, with the wordmark centred in
it and the links behind the burger. The offsets are set here rather than
inherited: the desktop rule is in-flow now and carries no top/left/right,
and its full-bleed negative margin has nothing to cancel once the bar is
fixed and already edge-to-edge. */
.utci-topnav {
position: fixed;
top: 0;
left: 0;
right: 0;
margin: 0;
padding: 10px 20px;
height: 72px;
background: #f5edd6;
@@ -616,6 +737,10 @@ body {
gap: 18px;
}
.utci-topnav .nav-dropdown-menu a { padding: 0; font-size: 14px; }
/* Right-aligned text is a desktop-popup touch (see the --right rule
above) - on mobile everything is one left-indented list, so match
About's list instead of standing out. */
.utci-topnav .nav-dropdown--right .nav-dropdown-menu a { text-align: left; }
/* Burger → X animation */
.utci-topnav.nav-open .utci-burger span:nth-child(1) {
+48 -75
View File
@@ -18,18 +18,22 @@
.scope-col { grid-area: dial; }
.header-right { grid-area: hright; }
/* Left column — wordmark, tagline, search and location stacked together */
/* Left column — the location line and its search overlay. The wordmark and
tagline that used to head this stack now live in the site header, so what is
left hangs from the top of the row, level with the top of the dial. */
.header-left {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
}
/* Wraps the location line + the reveal-on-demand search overlay so the
search can position itself directly over the location to save space. */
.utci-loc-search {
position: relative;
margin-top: 18px;
/* Was 18px, clearing the tagline above it. It leads the column now. */
margin-top: 0;
width: 380px;
max-width: 100%;
}
@@ -40,45 +44,11 @@
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
justify-content: flex-start; /* level with the top of the dial */
}
/* The big "SunScope" wordmark */
.utci-title {
font-family: Manrope, sans-serif;
font-weight: 250;
font-size: clamp(48px, 6vw, 80px);
line-height: 1;
letter-spacing: -0.01em;
color: #1e1208;
display: flex;
align-items: center;
gap: 0.05em;
}
.utci-title .title-sun,
.utci-title .title-scope {
color: #1e1208;
}
.title-icon {
width: 0.82em;
height: 0.82em;
color: #1e1208;
flex-shrink: 0;
}
/* "See the world the way..." */
.utci-tagline {
font-family: Manrope, sans-serif;
font-style: normal;
font-weight: 600;
font-size: 11px;
letter-spacing: 0.12em;
text-transform: uppercase;
color: #7a5c2a;
margin-top: 12px;
}
/* The wordmark and tagline that used to head this column now live in the
site header (.nav-logo in base.css) — see the header there. */
/* "↳ Location, Somewhere" + the magnifier that reveals the search */
.utci-current-loc {
@@ -184,6 +154,7 @@
background: #fffcf2;
color: #9a7a3a;
cursor: pointer;
text-decoration: none;
transition: color 0.2s, border-color 0.2s, background 0.2s, box-shadow 0.2s;
}
@@ -213,6 +184,39 @@
.utci-loc-action.is-busy svg { animation: none; opacity: 0.55; }
}
/* Tool row under the location line — the magnifier, locate, share and journal
buttons sit on their own row now that the header has the space, each with a
small caption so the icons don't have to carry the meaning alone. */
.utci-loc-tools {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
gap: 16px;
margin-top: 10px;
}
.utci-loc-tool {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
}
.utci-loc-tool-label {
font-family: Manrope, sans-serif;
font-style: normal;
font-weight: 600;
font-size: 9.5px;
letter-spacing: 0.08em;
text-transform: uppercase;
color: #a08c62;
white-space: nowrap;
transition: color 0.2s;
}
/* Light the caption up with its button so the pair reads as one control */
.utci-loc-tool:hover .utci-loc-tool-label { color: #c8922a; }
/* Transient note under the location row: "Link copied", permission errors */
.utci-loc-note {
display: flex;
@@ -240,19 +244,6 @@
.utci-loc-note-x:hover { opacity: 1; }
/* The lat/lon below the location */
.utci-loc-coords {
font-family: JetBrains Mono, monospace;
font-style: normal;
font-size: 11px;
color: #b09870;
display: block;
flex-basis: 100%; /* drop onto its own line below the name + magnifier */
margin-top: 4px;
margin-left: 0;
letter-spacing: 0.08em;
}
/* The big UTCI dial in the middle of the header.
Change width/height to resize it. */
.scope-ring {
@@ -301,7 +292,9 @@
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
/* Top-anchored so the dial defines the row's top edge and the location and
Solar model columns beside it start on the same line. */
justify-content: flex-start;
gap: 8px;
}
@@ -594,12 +587,6 @@ body.tl-scrubbing * { transition: none !important; }
box-shadow: 0 6px 20px #64461426; /* lift it off the location underneath */
}
/* The 300px wrapper would let .utci-fetch-time's default right-align drift to
the far edge; keep it left-aligned beneath the coords as before. */
.utci-loc-search .utci-fetch-time {
text-align: left;
}
.utci-search-label {
font-family: Manrope, sans-serif;
font-size: 10px;
@@ -716,16 +703,6 @@ body.tl-scrubbing * { transition: none !important; }
align-items: center; /* centre the "Why it feels like" panel under the dial */
}
.utci-title {
font-size: clamp(40px, 12vw, 60px);
}
}
@media (max-width: 640px) {
.utci-title,
.utci-tagline {
display: none;
}
}
@media (max-width: 960px) {
@@ -753,12 +730,8 @@ body.tl-scrubbing * { transition: none !important; }
text-align: center;
}
.utci-loc-coords {
text-align: center;
}
.utci-loc-search .utci-fetch-time {
text-align: center; /* match the desktop override's specificity */
.utci-loc-tools {
justify-content: center; /* centre the tool row on mobile */
}
.utci-search-wrap {
+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;
}
}
+73 -18
View File
@@ -1110,7 +1110,9 @@
flex-direction: column;
align-items: flex-start;
gap: 3px;
margin-top: 0.5rem;
/* Was 0.5rem the recents now follow the tool row, so they need a clearer
break above them. */
margin-top: 1.25rem;
}
/* header.css sets align-items: center for the same tablet breakpoint, but
since ui.css is @imported after header.css in sunscope.css it wins at
@@ -1381,9 +1383,15 @@
}
.utci-app {
padding: 52px 10px 32px;
/* No top padding the header is in the flow at the very top of the page.
Was 52px, reserving space for the nav back when it was absolutely
positioned over it. Narrowing --app-pad re-aims the header's and the
event note's full-bleed at the same time. */
--app-pad: 10px;
padding: 0 var(--app-pad) 32px;
}
.utci-header {
grid-template-columns: 1fr; /* stack the 3 header columns */
text-align: center;
@@ -1393,9 +1401,7 @@
align-items: center;
}
.utci-current-loc,
.utci-loc-coords,
.utci-fetch-time {
.utci-current-loc {
text-align: center;
}
@@ -1446,10 +1452,6 @@
padding-top: 80px;
}
.utci-fetch-time {
text-align: center; /* override the default left-align on mobile */
}
/* Hide the export panel on phones no room for it, and the export
actions aren't useful at this size. */
.export-panel {
@@ -1524,6 +1526,15 @@
max-width: 300px;
}
/* "Last update" closes the panel out a hairline above it keeps it apart
from the contribution rows it follows. */
.insight-panel--why .utci-fetch-time {
text-align: right;
margin-top: 8px;
padding-top: 6px;
border-top: 1px solid #ede4cc;
}
/* Solar model selector inside the Why it feels like panel */
.insight-env-footer {
display: flex;
@@ -1627,10 +1638,13 @@
}
/* Week rows carry a score bar, so they are a two-line grid rather than the
single flex line the glance rows use: day and window on top, bar beneath. */
single flex line the glance rows use: day and window on top, bar beneath.
Day and window read as one sentence - "Sun 30 : all day" - so they sit
together at the left rather than being pushed to opposite edges, where the
gap between them made two short phrases look like two columns of data. */
.insight-panel--week .insight-row {
display: grid;
grid-template-columns: 20px 1fr auto;
grid-template-columns: 20px auto minmax(0, 1fr);
grid-template-areas:
"icon label value"
"icon score score";
@@ -1639,8 +1653,42 @@
align-items: center;
}
.insight-panel--week .insight-icon { grid-area: icon; }
.insight-panel--week .insight-label { grid-area: label; }
.insight-panel--week .insight-value { grid-area: value; }
.insight-panel--week .insight-label { grid-area: label; flex: none; }
.insight-panel--week .insight-value {
grid-area: value;
text-align: left;
flex-shrink: 1;
min-width: 0;
}
/* The separator is drawn, not written into the string, so the value stays a
plain window ("all day") everywhere else it is used. */
.insight-panel--week .insight-value::before {
content: ': ';
color: #8a8375;
font-weight: 400;
}
/* The washout row - no day qualified, so there is no day name and no score,
just a sentence. Forcing it through the day-and-window layout squeezed that
sentence into the narrow value column and stranded the colon on a line of
its own, so it gets the full width and drops the separator. Specificity is
raised past the rail rules below deliberately: this row stacks at every
width. */
.insight-panel--week .insight-row.insight-row--week-note {
grid-template-columns: 20px minmax(0, 1fr);
grid-template-areas:
"icon label"
"icon value";
align-items: start;
row-gap: 2px;
}
.insight-panel--week .insight-row--week-note .insight-icon { padding-top: 2px; }
.insight-panel--week .insight-row--week-note .insight-value {
font-family: Manrope, sans-serif;
font-weight: 500;
white-space: normal;
}
.insight-panel--week .insight-row--week-note .insight-value::before { content: none; }
/* How good the day is out of 100 the bar does the comparing, the number
settles close calls. */
@@ -1765,6 +1813,7 @@
margin: -6px 0 10px;
}
.insight-thermal-cat {
display: inline-block;
padding: 2px 7px;
@@ -1930,14 +1979,20 @@
row-gap: 1px;
align-items: start;
}
/* The rail is only 240px wide, so the window drops under the day name and
the score bar takes a third line rather than sharing one. */
/* The rail is only 240px wide, but "Sun 30 : all day" still fits on one
line, so the week rows keep day and window together and give the score
bar the second line - unlike the glance rows, which stack. */
.insight-panel--week .insight-row {
grid-template-columns: 20px auto minmax(0, 1fr);
grid-template-areas:
"icon label"
"icon value"
"icon score";
"icon label value"
"icon score score";
row-gap: 3px;
align-items: center;
}
.insight-panel--week .insight-label,
.insight-panel--week .insight-value {
font-size: 14px;
}
.insight-panel--glance .insight-icon,
.insight-panel--week .insight-icon {