diff --git a/assets/css/table.css b/assets/css/table.css
index 0abd179..b66ace0 100644
--- a/assets/css/table.css
+++ b/assets/css/table.css
@@ -104,16 +104,30 @@
}
.utci-day-tab:hover:not(.active):not(.locked) {
- filter: brightness(1.14) !important;
+ filter: brightness(1.05) !important;
}
.utci-day-tab.active {
color: #c8922a;
- outline: 3px solid #c8922a;
- outline-offset: -2px;
+ outline: 1px solid #000;
+ outline-offset: -1px;
z-index: 1;
}
+/* 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;
diff --git a/assets/css/ui.css b/assets/css/ui.css
index 02f74f8..abe16d2 100644
--- a/assets/css/ui.css
+++ b/assets/css/ui.css
@@ -1698,6 +1698,13 @@
beneath its label instead of letting long values overflow sideways.
Mobile keeps the inline label/value layout (plenty of width there). */
@media (min-width: 901px) {
+ /* Glance rail on the left, hourly table on the right */
+ .table-with-rail {
+ grid-template-columns: auto minmax(0, 1fr);
+ }
+ .glance-rail { grid-column: 1; grid-row: 1; }
+ .table-main { grid-column: 2; grid-row: 1; }
+
.insight-panel--glance .insight-row {
display: grid;
grid-template-columns: 20px 1fr;
diff --git a/assets/js/app.js b/assets/js/app.js
index ddb14b1..19c0f43 100644
--- a/assets/js/app.js
+++ b/assets/js/app.js
@@ -1523,10 +1523,18 @@ export function UTCIForecast() {
${label}
${value}
`;
+ const comfortItem = glanceSummary.find(i => i.label === 'Comfortable window');
+ const restSummary = glanceSummary.filter(i => i.label !== 'Comfortable window');
return [
- ...glanceSummary.flatMap(item => [
+ ...restSummary.flatMap(item => [
renderRow(item),
- ...(item.label === 'Peak felt temp' ? heatEvents.map(e => renderRow(e, 'ev-')) : []),
+ // After the peak/heat block, drop in the comfortable window
+ ...(item.label === 'Peak felt temp'
+ ? [
+ ...heatEvents.map(e => renderRow(e, 'ev-')),
+ ...(comfortItem ? [renderRow(comfortItem)] : []),
+ ]
+ : []),
]),
...otherEvents.map(e => renderRow(e, 'ev-')),
];
diff --git a/assets/js/components/DayTabs.js b/assets/js/components/DayTabs.js
index e056171..61b2e0f 100644
--- a/assets/js/components/DayTabs.js
+++ b/assets/js/components/DayTabs.js
@@ -69,21 +69,31 @@ function weatherGradient(r, g, b) {
// 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 weatherGradientNeutral(r, g, b) {
- const blend = (c) => Math.round(c + (255 - c) * 0.58);
- const [mr, mg, mb] = [blend(r), blend(g), blend(b)];
+// edgeBlend pastelises the outer edge (higher = lighter). centerBlend, when
+// supplied, blends the centre straight from the raw colour for a lighter core
+// (used by the Danger tier to keep a dark edge but a light centre); otherwise
+// the centre is just a lightened version of the edge (original behaviour).
+function weatherGradientNeutral(r, g, b, edgeBlend = 0.50, centerBlend = null) {
+ const blend = (c, amt) => Math.round(c + (255 - c) * amt);
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})`;
+ const [er, eg, eb] = [blend(r, edgeBlend), blend(g, edgeBlend), blend(b, edgeBlend)];
+ const [cr, cg, cb] = centerBlend != null
+ ? [blend(r, centerBlend), blend(g, centerBlend), blend(b, centerBlend)]
+ : [lighten(er), lighten(eg), lighten(eb)];
+ const center = `rgb(${cr},${cg},${cb})`;
+ const edge = `rgb(${er},${eg},${eb})`;
return `radial-gradient(circle at 50% 50%, ${center} 0%, ${center} 28%, ${edge} 100%)`;
}
-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)];
+function weatherGradientActive(r, g, b, edgeBlend = 0.42, centerBlend = null) {
+ const blend = (c, amt) => Math.round(c + (255 - c) * amt);
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})`;
+ const [er, eg, eb] = [blend(r, edgeBlend), blend(g, edgeBlend), blend(b, edgeBlend)];
+ const [cr, cg, cb] = centerBlend != null
+ ? [blend(r, centerBlend), blend(g, centerBlend), blend(b, centerBlend)]
+ : [lighten(er), lighten(eg), lighten(eb)];
+ const center = `rgb(${cr},${cg},${cb})`;
+ const edge = `rgb(${er},${eg},${eb})`;
return `radial-gradient(circle at 50% 50%, ${center} 0%, ${center} 28%, ${edge} 100%)`;
}
@@ -322,6 +332,7 @@ export function DayTabs({
// Base RGB for each weather condition — pastelised by weatherGradient()
const [wR, wG, wB] = (daySnow >= 0.1) ? [100, 160, 230]
+ : (dayHi > 44) ? [136, 0, 0]
: (dayHi > 38) ? [210, 60, 30]
: showPrecip ? [ 50, 120, 200]
: (dayHi > 32) ? [220, 110, 30]
@@ -331,15 +342,23 @@ export function DayTabs({
: modalCloudCat === 'scattered' ? [160, 150, 130]
: modalCloudCat === 'wispy' ? [200, 180, 130]
: [220, 190, 50];
+ // Danger days keep a dark, saturated edge with a lighter centre.
+ const isDanger = dayHi !== null && dayHi > 44;
const wBg = weatherGradient(wR, wG, wB);
- const wBgNeutral = weatherGradientNeutral(wR, wG, wB);
- const wBgActive = weatherGradientActive(wR, wG, wB);
+ const wBgNeutral = isDanger
+ ? weatherGradientNeutral(wR, wG, wB, 0.15, 0.72)
+ : weatherGradientNeutral(wR, wG, wB);
+ const wBgActive = isDanger
+ ? weatherGradientActive(wR, wG, wB, 0.12, 0.72)
+ : weatherGradientActive(wR, wG, wB);
const wText = '#2a1d10';
+ // Active-tab outline: the day's weather colour, 20% darker.
+ const wOutline = `rgb(${Math.round(wR * 0.8)},${Math.round(wG * 0.8)},${Math.round(wB * 0.8)})`;
return html`