Fix UI issues
This commit is contained in:
fraxle
2026-06-02 22:40:15 +01:00
parent 137e1a3058
commit e00d38cbfb
7 changed files with 61 additions and 28 deletions
+9 -3
View File
@@ -395,8 +395,14 @@
text-transform: uppercase; text-transform: uppercase;
} }
/* ── Mobile header layout ────────────────────────────────────────────── */ /* ── Stacked header layout (mobile + tablet) ─────────────────────────────
@media (max-width: 640px) { The desktop 3-column grid (left | dial | why-panel) needs ~960px of
viewport to fit: the location/search box is 300px, the "Why it feels like"
panel ~300px, plus the 220px dial and gaps. Below that the rigid columns
can't shrink and the why-panel overflows off the right edge, so we stack
everything into one centred column — matching where the glance rail also
drops below the table. */
@media (max-width: 960px) {
.utci-header { .utci-header {
grid-template-columns: 1fr; /* single column — stack title, dial, search */ grid-template-columns: 1fr; /* single column — stack title, dial, search */
grid-template-areas: none; /* flow in DOM order: left, dial, selector, why */ grid-template-areas: none; /* flow in DOM order: left, dial, selector, why */
@@ -414,7 +420,7 @@
} }
.header-right { .header-right {
align-items: flex-start; /* left-align location on mobile */ align-items: center; /* centre the "Why it feels like" panel under the dial */
} }
.utci-title { .utci-title {
+5 -5
View File
@@ -210,9 +210,9 @@
} }
.profile-btn { .profile-btn {
width: 80px; width: 100px;
height: 80px; height: 100px;
flex: none; /* never shrink below 80px — the row scrolls instead */ flex: none; /* never shrink below 100px — the row scrolls instead */
padding: 0; padding: 0;
position: relative; position: relative;
display: inline-flex; display: inline-flex;
@@ -958,8 +958,8 @@
/* Slightly smaller cards on mobile */ /* Slightly smaller cards on mobile */
.profile-btn { .profile-btn {
width: 68px; width: 80px;
height: 68px; height: 80px;
flex: none; flex: none;
} }
+22 -10
View File
@@ -447,12 +447,12 @@
Used for the Places / Activities / Work triggers in the profile bar. */ Used for the Places / Activities / Work triggers in the profile bar. */
.profile-scroll .cs-wrap { .profile-scroll .cs-wrap {
height: 80px; height: 100px;
} }
.cs-btn.profile-card { .cs-btn.profile-card {
width: 80px; width: 100px;
height: 80px; height: 100px;
padding: 0; padding: 0;
position: relative; position: relative;
flex-direction: column; flex-direction: column;
@@ -490,21 +490,32 @@
left: 0; left: 0;
right: 0; right: 0;
height: 24px; height: 24px;
display: flex; /* Block (not flex) so text-overflow:ellipsis cuts cleanly on the trailing
align-items: center; edge — e.g. "Forest / Wo…" — instead of a flex item clipping both sides.
justify-content: center; line-height vertically centres the single line; text-align centres short
labels while long ones ellipsise against the right pad that clears the
arrow. */
display: block;
line-height: 24px;
text-align: center;
font-size: 10px; font-size: 10px;
font-weight: 700; font-weight: 700;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.05em; letter-spacing: 0.05em;
background: rgba(30,18,8,0.52); background: rgba(30,18,8,0.52);
color: #fff; color: #fff;
padding: 0 14px 0 4px; padding: 0 17px 0 6px;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
/* When the label overflows it must read left-to-right so the ellipsis lands on
the end ("Forest / Wo…"); centred overflow would clip the start too. */
.cs-btn.profile-card.on .cs-btn-label {
text-align: left;
}
.cs-btn.profile-card .cs-arrow { .cs-btn.profile-card .cs-arrow {
position: absolute; position: absolute;
right: 6px; right: 6px;
@@ -540,11 +551,11 @@
@media (max-width: 640px) { @media (max-width: 640px) {
.profile-scroll .cs-wrap { .profile-scroll .cs-wrap {
height: 68px; height: 80px;
} }
.cs-btn.profile-card { .cs-btn.profile-card {
width: 68px; width: 80px;
height: 68px; height: 80px;
} }
.cs-btn.profile-card .cs-arrow { .cs-btn.profile-card .cs-arrow {
bottom: 7px; bottom: 7px;
@@ -555,6 +566,7 @@
} }
.cs-btn.profile-card .cs-btn-label { .cs-btn.profile-card .cs-btn-label {
height: 20px; height: 20px;
line-height: 20px;
font-size: 9px; font-size: 9px;
} }
} }
+17 -2
View File
@@ -63,6 +63,19 @@ function weatherGradient(r, g, b) {
return `linear-gradient(135deg, ${light} 0%, ${mid} 60%, ${dark} 100%)`; return `linear-gradient(135deg, ${light} 0%, ${mid} 60%, ${dark} 100%)`;
} }
// Active-tab variant: a more solid (less pastelised) version of the weather
// colour, drawn as a radial gradient whose strong colour sits at the outer
// edge and softens toward a lighter centre — so the hue reads as radiating
// inward from the outside of the tab.
function weatherGradientActive(r, g, b) {
const blend = (c) => Math.round(c + (255 - c) * 0.42);
const [mr, mg, mb] = [blend(r), blend(g), blend(b)];
const lighten = (c) => Math.min(255, Math.round(c + (255 - c) * 0.38));
const center = `rgb(${lighten(mr)},${lighten(mg)},${lighten(mb)})`;
const edge = `rgb(${mr},${mg},${mb})`;
return `radial-gradient(circle at 50% 50%, ${center} 0%, ${center} 28%, ${edge} 100%)`;
}
export function DayTabs({ export function DayTabs({
days, days,
selectedDay, setSelectedDay, selectedDay, setSelectedDay,
@@ -328,6 +341,7 @@ export function DayTabs({
: modalCloudCat === 'wispy' ? [200, 180, 130] : modalCloudCat === 'wispy' ? [200, 180, 130]
: [220, 190, 50]; : [220, 190, 50];
const wBg = weatherGradient(wR, wG, wB); const wBg = weatherGradient(wR, wG, wB);
const wBgActive = weatherGradientActive(wR, wG, wB);
const wText = '#2a1d10'; const wText = '#2a1d10';
return html` return html`
@@ -347,7 +361,7 @@ export function DayTabs({
? `${band.label} · SunScope Extra unlocks day ${i + 1}` ? `${band.label} · SunScope Extra unlocks day ${i + 1}`
: `${band.label} · day ${i + 1} of 14`} : `${band.label} · day ${i + 1} of 14`}
style=${{ style=${{
background: wBg, background: isActive ? wBgActive : wBg,
color: wText, color: wText,
border: '1px solid #c9b08a', border: '1px solid #c9b08a',
opacity: locked ? 0.5 : 1, opacity: locked ? 0.5 : 1,
@@ -361,7 +375,7 @@ export function DayTabs({
bottom: 0, bottom: 0,
left: 0, left: 0,
right: 0, right: 0,
height: isActive ? '5px' : '4px', height: isActive ? '8px' : '7px',
background: band.bg, background: band.bg,
borderTop: '1px solid #c9b08a', borderTop: '1px solid #c9b08a',
borderRadius: '0 0 2px 2px', borderRadius: '0 0 2px 2px',
@@ -387,6 +401,7 @@ export function DayTabs({
marginTop: '2px', marginTop: '2px',
letterSpacing: 0, letterSpacing: 0,
textTransform: 'none', textTransform: 'none',
textShadow: '0 0 14px rgba(255,255,255,0.85), 0 0 11px rgba(255,255,255,0.7), 0 0 8px rgba(255,255,255,0.6), 0 0 5px rgba(255,255,255,0.5)',
}}> }}>
<span style=${{ color: '#c0622a', fontWeight: 600 }}>${dayHi}°</span> <span style=${{ color: '#c0622a', fontWeight: 600 }}>${dayHi}°</span>
<span style=${{ opacity: 0.5 }}> / </span> <span style=${{ opacity: 0.5 }}> / </span>
+3 -3
View File
@@ -339,17 +339,17 @@ export function useAppState() {
const activityValue = activeProfile === 'outdoors' && activityVariantKeys.includes(outdoorsVariant) const activityValue = activeProfile === 'outdoors' && activityVariantKeys.includes(outdoorsVariant)
? outdoorsVariant ? outdoorsVariant
: 'off'; : 'off';
const activityLabel = activityOptions.find((o) => o.value === activityValue)?.label; const activityLabel = activityOptions.find((o) => o.value === activityValue)?.name;
const placeValue = activeProfile === 'outdoors' && placeVariantKeys.includes(outdoorsVariant) const placeValue = activeProfile === 'outdoors' && placeVariantKeys.includes(outdoorsVariant)
? outdoorsVariant ? outdoorsVariant
: 'off'; : 'off';
const placeLabel = placeOptions.find((o) => o.value === placeValue)?.label; const placeLabel = placeOptions.find((o) => o.value === placeValue)?.name;
const workValue = activeProfile === 'farming' const workValue = activeProfile === 'farming'
? 'farming' ? 'farming'
: activeProfile === 'outdoors' && workVariantKeys.includes(outdoorsVariant) : activeProfile === 'outdoors' && workVariantKeys.includes(outdoorsVariant)
? outdoorsVariant ? outdoorsVariant
: 'off'; : 'off';
const workLabel = workOptions.find((o) => o.value === workValue)?.label; const workLabel = workOptions.find((o) => o.value === workValue)?.name;
// ── 6. TABLE REFS ──────────────────────────────────────────────────── // ── 6. TABLE REFS ────────────────────────────────────────────────────
const headStickyRef = useRef(null); const headStickyRef = useRef(null);
+4 -4
View File
@@ -27,7 +27,7 @@
css/ui.css Status banners, legend, about, footer, responsive css/ui.css Status banners, legend, about, footer, responsive
*/ */
@import "./css/base.css"; @import "./css/base.css?v=15g";
@import "./css/header.css"; @import "./css/header.css?v=15g";
@import "./css/table.css"; @import "./css/table.css?v=15g";
@import "./css/ui.css"; @import "./css/ui.css?v=15g";
+1 -1
View File
@@ -70,7 +70,7 @@
<link rel="preconnect" href="https://api.open-meteo.com" crossorigin /> <link rel="preconnect" href="https://api.open-meteo.com" crossorigin />
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>☀️</text></svg>" /> <link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>☀️</text></svg>" />
<link rel="stylesheet" href="./assets/sunscope.css?v=15b"> <link rel="stylesheet" href="./assets/sunscope.css?v=15g">
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>