Beta4
Fix mobile table issues
This commit is contained in:
+47
-15
@@ -82,7 +82,7 @@ body {
|
|||||||
color: #1e1208;
|
color: #1e1208;
|
||||||
background: #f5edd6;
|
background: #f5edd6;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
padding: 52px 28px 32px;
|
padding: 52px 10px 32px;
|
||||||
position: relative;
|
position: relative;
|
||||||
/* overflow-x: clip (not hidden) — `clip` clips horizontal overflow
|
/* overflow-x: clip (not hidden) — `clip` clips horizontal overflow
|
||||||
WITHOUT making this a scroll container, so `position: sticky` on
|
WITHOUT making this a scroll container, so `position: sticky` on
|
||||||
@@ -564,6 +564,12 @@ body {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
/* Fixed layout so the explicit per-cell widths set by the JS width-sync
|
||||||
|
(see useLayoutEffect in sunscope.js) are actually enforced. Without
|
||||||
|
this the browser's auto layout treats those widths as suggestions and
|
||||||
|
redistributes space, which makes the header and body columns drift
|
||||||
|
apart the further right you scroll. */
|
||||||
|
table-layout: fixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Column headings (Hour, Air, RH, Wind...).
|
/* Column headings (Hour, Air, RH, Wind...).
|
||||||
@@ -627,29 +633,40 @@ body {
|
|||||||
border-right: 1px solid #ede4cc;
|
border-right: 1px solid #ede4cc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The Dir column is centered on both desktop and mobile so the
|
||||||
|
compass vane sits with even padding either side — especially
|
||||||
|
important on mobile where the compass label is hidden and there's
|
||||||
|
no text to balance the right-aligned content. */
|
||||||
|
.utci-table th.utci-dir-cell,
|
||||||
|
.utci-table td.utci-dir-cell {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* "Tight" header — for columns whose body content is narrower than
|
||||||
|
the default tracked-out header would suggest (Air, Pcpt). Reducing
|
||||||
|
the letter-spacing shrinks the heading's natural width so it stops
|
||||||
|
driving the column wider than the body needs. Right-aligned values
|
||||||
|
then sit with balanced visual padding on either side. */
|
||||||
|
.utci-table th.utci-tight-head {
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ── 8. NOW-ROW & NIGHT-ROW DECORATIONS ─────────────────────────────── */
|
/* ── 8. NOW-ROW & NIGHT-ROW DECORATIONS ─────────────────────────────── */
|
||||||
|
|
||||||
/* "Currently this hour" — the highlighted row */
|
/* "Currently this hour" — the highlighted row.
|
||||||
|
The row gets a cream-yellow background and darker text. No left
|
||||||
|
stripe or pip — the colour shift alone signals "this is now". */
|
||||||
.utci-table tbody tr.is-now {
|
.utci-table tbody tr.is-now {
|
||||||
background: #fff8e4 !important;
|
background: #fff8e4 !important;
|
||||||
box-shadow: inset 3px 0 #c8922a; /* brass stripe down the left */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.utci-table tbody tr.is-now td {
|
.utci-table tbody tr.is-now td {
|
||||||
color: #1e1208;
|
color: #1e1208;
|
||||||
}
|
/* Brass bracket framing the current-hour row top and bottom — clean,
|
||||||
|
definitive, no left stripe needed. */
|
||||||
/* The little brass pip in the time cell of the current hour */
|
border-top: 2px solid #c8922a;
|
||||||
.now-pip {
|
border-bottom: 2px solid #c8922a;
|
||||||
display: inline-block;
|
|
||||||
width: 6px;
|
|
||||||
height: 6px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: #c8922a;
|
|
||||||
margin-right: 7px;
|
|
||||||
vertical-align: middle;
|
|
||||||
box-shadow: 0 0 8px #c8922a73;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Night-time hours get a slightly muted text colour */
|
/* Night-time hours get a slightly muted text colour */
|
||||||
@@ -843,4 +860,19 @@ body {
|
|||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Halve the hourly-table cell padding on mobile so more columns fit
|
||||||
|
in the viewport before the horizontal scrollbar kicks in. */
|
||||||
|
.utci-table th {
|
||||||
|
padding: 7px 6px;
|
||||||
|
}
|
||||||
|
.utci-table td {
|
||||||
|
padding: 5px 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Drop the compass-label text ("N", "NW"...) next to the wind vane on
|
||||||
|
mobile — the vane arrow alone is enough at small sizes. */
|
||||||
|
.wind-dir-label {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+89
-51
@@ -854,12 +854,12 @@ function ScopeReticle({ value, cat, loading, elev = 0, dt = new Date() }) {
|
|||||||
font-family="Fraunces, serif" font-weight="700">
|
font-family="Fraunces, serif" font-weight="700">
|
||||||
${value.toFixed(1)}°
|
${value.toFixed(1)}°
|
||||||
</text>
|
</text>
|
||||||
<text x=${cx} y=${cy + 7} text-anchor="middle"
|
<text x=${cx} y=${cy + 15} text-anchor="middle"
|
||||||
fill=${readoutMutedColor} font-size="6.5"
|
fill=${readoutMutedColor} font-size="6.5"
|
||||||
font-family="JetBrains Mono, monospace" letter-spacing="2">
|
font-family="JetBrains Mono, monospace" letter-spacing="2">
|
||||||
UTCI NOW
|
UTCI NOW
|
||||||
</text>
|
</text>
|
||||||
<text x=${cx} y=${cy + 19} text-anchor="middle"
|
<text x=${cx} y=${cy + 23} text-anchor="middle"
|
||||||
fill=${glowColor} font-size="7"
|
fill=${glowColor} font-size="7"
|
||||||
font-family="JetBrains Mono, monospace" letter-spacing="0.8">
|
font-family="JetBrains Mono, monospace" letter-spacing="0.8">
|
||||||
${cat.label.toUpperCase()}
|
${cat.label.toUpperCase()}
|
||||||
@@ -974,8 +974,8 @@ function UTCIForecast() {
|
|||||||
// true = visible on first load (and the only ones free users see)
|
// true = visible on first load (and the only ones free users see)
|
||||||
// false = hidden by default (Pro users can toggle these on)
|
// false = hidden by default (Pro users can toggle these on)
|
||||||
const [visibleCols, setVisibleCols] = useState({
|
const [visibleCols, setVisibleCols] = useState({
|
||||||
hour: true, air: true, rh: true, dew: true,
|
hour: true, air: true, rh: false, dew: false,
|
||||||
wind: true, dir: true,
|
wind: true, dir: false,
|
||||||
cloud: false, sun: false, direct: false, diffuse: false,
|
cloud: false, sun: false, direct: false, diffuse: false,
|
||||||
tmrt: false, delta: false, utci: false,
|
tmrt: false, delta: false, utci: false,
|
||||||
uvA: false, uvB: false, burn: false,
|
uvA: false, uvB: false, burn: false,
|
||||||
@@ -1015,47 +1015,84 @@ function UTCIForecast() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
// Synchronise the head and body table column widths.
|
// Synchronise the head and body table column widths with a
|
||||||
// Each column is sized to max(60px, header's natural width, body's
|
// "shrink-to-fit then distribute" strategy:
|
||||||
// natural width) so neither header labels nor body data ever feel
|
// • Measure each column's true natural (content-fit) width by
|
||||||
// squashed, and header text never overflows into the next column.
|
// temporarily switching both tables to table-layout: auto +
|
||||||
const MIN_COL = 60;
|
// width: max-content. White-space: nowrap on cells stops content
|
||||||
|
// from wrapping, so the measurement is the smallest width that
|
||||||
|
// won't clip the content.
|
||||||
|
// • If the body scroller has spare horizontal space (natural total
|
||||||
|
// < container width), scale every column up proportionally to
|
||||||
|
// fill it — so toggling columns off makes the remaining ones fan
|
||||||
|
// out instead of leaving an awkward gap.
|
||||||
|
// • Otherwise apply the natural widths as-is and let the body
|
||||||
|
// scroller's overflow-x: auto produce a horizontal scrollbar.
|
||||||
const sync = () => {
|
const sync = () => {
|
||||||
const headTable = headTableRef.current;
|
const headTable = headTableRef.current;
|
||||||
const bodyTable = bodyTableRef.current;
|
const bodyTable = bodyTableRef.current;
|
||||||
if (!headTable || !bodyTable) return;
|
const bodyScroll = bodyScrollRef.current;
|
||||||
|
if (!headTable || !bodyTable || !bodyScroll) return;
|
||||||
const bodyRow = bodyTable.querySelector('tbody tr');
|
const bodyRow = bodyTable.querySelector('tbody tr');
|
||||||
const headRow = headTable.querySelector('thead tr');
|
const headRow = headTable.querySelector('thead tr');
|
||||||
if (!bodyRow || !headRow) return;
|
if (!bodyRow || !headRow) return;
|
||||||
const headCells = Array.from(headRow.children);
|
const headCells = Array.from(headRow.children);
|
||||||
const bodyCells = Array.from(bodyRow.children);
|
const bodyCells = Array.from(bodyRow.children);
|
||||||
const n = Math.min(headCells.length, bodyCells.length);
|
const n = Math.min(headCells.length, bodyCells.length);
|
||||||
// Step 1: clear any previously-forced widths so we can read each
|
if (n === 0) return;
|
||||||
// cell's *natural* width (the width it'd take with just min-width
|
|
||||||
// and content driving it).
|
// Step 1: clear any previously-forced cell widths and switch the
|
||||||
headCells.forEach(c => {
|
// tables to natural sizing so the measurement reflects the true
|
||||||
c.style.width = '';
|
// content-fit width — independent of how wide the container is.
|
||||||
c.style.minWidth = '';
|
headCells.forEach(c => { c.style.width = ''; c.style.minWidth = ''; c.style.maxWidth = ''; });
|
||||||
c.style.maxWidth = '';
|
bodyCells.forEach(c => { c.style.width = ''; c.style.minWidth = ''; c.style.maxWidth = ''; });
|
||||||
});
|
headTable.style.width = 'max-content';
|
||||||
bodyCells.forEach(c => {
|
bodyTable.style.width = 'max-content';
|
||||||
c.style.width = '';
|
headTable.style.tableLayout = 'auto';
|
||||||
c.style.minWidth = '';
|
bodyTable.style.tableLayout = 'auto';
|
||||||
c.style.maxWidth = '';
|
|
||||||
});
|
// Step 2: read each cell's natural width. getBoundingClientRect
|
||||||
headTable.style.width = '';
|
// forces synchronous layout — that's what we want.
|
||||||
// Reading getBoundingClientRect forces layout — that's what we want.
|
const naturalW = new Array(n);
|
||||||
// Step 2: compute final widths as max(MIN_COL, headNatural, bodyNatural).
|
let naturalTotal = 0;
|
||||||
const finalW = new Array(n);
|
|
||||||
let totalWidth = 0;
|
|
||||||
for (let i = 0; i < n; i++) {
|
for (let i = 0; i < n; i++) {
|
||||||
const headW = headCells[i].getBoundingClientRect().width;
|
const headW = headCells[i].getBoundingClientRect().width;
|
||||||
const bodyW = bodyCells[i].getBoundingClientRect().width;
|
const bodyW = bodyCells[i].getBoundingClientRect().width;
|
||||||
const w = Math.max(MIN_COL, Math.ceil(headW), Math.ceil(bodyW));
|
const w = Math.max(Math.ceil(headW), Math.ceil(bodyW));
|
||||||
finalW[i] = w;
|
naturalW[i] = w;
|
||||||
totalWidth += w;
|
naturalTotal += w;
|
||||||
}
|
}
|
||||||
// Step 3: apply the final width to both head and body cells.
|
|
||||||
|
// Step 3: decide final widths based on available container width.
|
||||||
|
const containerW = bodyScroll.clientWidth;
|
||||||
|
const finalW = new Array(n);
|
||||||
|
let totalWidth;
|
||||||
|
if (naturalTotal > 0 && naturalTotal < containerW) {
|
||||||
|
// Spare space — distribute proportionally across columns so they
|
||||||
|
// fan out to fill the scroller (no awkward right-hand gap).
|
||||||
|
const scale = containerW / naturalTotal;
|
||||||
|
let running = 0;
|
||||||
|
for (let i = 0; i < n - 1; i++) {
|
||||||
|
finalW[i] = Math.floor(naturalW[i] * scale);
|
||||||
|
running += finalW[i];
|
||||||
|
}
|
||||||
|
// Absorb sub-pixel rounding into the last column so the total
|
||||||
|
// exactly matches the container width.
|
||||||
|
finalW[n - 1] = containerW - running;
|
||||||
|
totalWidth = containerW;
|
||||||
|
} else {
|
||||||
|
// Naturals don't fit — use them as-is and let the body scroll.
|
||||||
|
for (let i = 0; i < n; i++) finalW[i] = naturalW[i];
|
||||||
|
totalWidth = naturalTotal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 4: restore the CSS-defined table-layout: fixed so the
|
||||||
|
// explicit cell widths we apply below are honoured by the browser
|
||||||
|
// (not redistributed by the auto-layout algorithm).
|
||||||
|
headTable.style.tableLayout = '';
|
||||||
|
bodyTable.style.tableLayout = '';
|
||||||
|
|
||||||
|
// Step 5: apply the final width to both head and body cells.
|
||||||
for (let i = 0; i < n; i++) {
|
for (let i = 0; i < n; i++) {
|
||||||
const px = `${finalW[i]}px`;
|
const px = `${finalW[i]}px`;
|
||||||
headCells[i].style.width = px;
|
headCells[i].style.width = px;
|
||||||
@@ -1065,19 +1102,24 @@ function UTCIForecast() {
|
|||||||
bodyCells[i].style.minWidth = px;
|
bodyCells[i].style.minWidth = px;
|
||||||
bodyCells[i].style.maxWidth = px;
|
bodyCells[i].style.maxWidth = px;
|
||||||
}
|
}
|
||||||
// Make the header table the same total width as the body table
|
// Make both tables exactly totalWidth wide so they share the same
|
||||||
// so the inner track has somewhere to translate to.
|
// horizontal extent — column N in the header sits directly above
|
||||||
|
// column N in the body, no drift as you scroll right.
|
||||||
headTable.style.width = `${totalWidth}px`;
|
headTable.style.width = `${totalWidth}px`;
|
||||||
|
bodyTable.style.width = `${totalWidth}px`;
|
||||||
// Re-apply current horizontal offset so column alignment survives.
|
// Re-apply current horizontal offset so column alignment survives.
|
||||||
handleBodyScroll();
|
handleBodyScroll();
|
||||||
};
|
};
|
||||||
// Run once after layout
|
// Run once after layout
|
||||||
sync();
|
sync();
|
||||||
// Re-sync if the body table reflows (columns toggle, content changes)
|
// Re-sync when the scroll container's width changes (window resize,
|
||||||
|
// sidebar opens, etc). We observe the scroller — not the body table —
|
||||||
|
// because the body table's width is now driven by sync itself, which
|
||||||
|
// would otherwise create a feedback loop.
|
||||||
let ro = null;
|
let ro = null;
|
||||||
if (typeof ResizeObserver !== 'undefined' && bodyTableRef.current) {
|
if (typeof ResizeObserver !== 'undefined' && bodyScrollRef.current) {
|
||||||
ro = new ResizeObserver(sync);
|
ro = new ResizeObserver(sync);
|
||||||
ro.observe(bodyTableRef.current);
|
ro.observe(bodyScrollRef.current);
|
||||||
}
|
}
|
||||||
window.addEventListener('resize', sync);
|
window.addEventListener('resize', sync);
|
||||||
return () => {
|
return () => {
|
||||||
@@ -1500,7 +1542,7 @@ function UTCIForecast() {
|
|||||||
<div class="col-toggles">
|
<div class="col-toggles">
|
||||||
<span class="col-toggles-label">Columns:</span>
|
<span class="col-toggles-label">Columns:</span>
|
||||||
${[
|
${[
|
||||||
{ key: 'hour', label: 'Hour' },
|
// Hour and UTCI+P are always-on — no toggle button for them.
|
||||||
{ key: 'air', label: 'Air' },
|
{ key: 'air', label: 'Air' },
|
||||||
{ key: 'rh', label: 'RH' },
|
{ key: 'rh', label: 'RH' },
|
||||||
{ key: 'dew', label: 'Dew' },
|
{ key: 'dew', label: 'Dew' },
|
||||||
@@ -1520,7 +1562,6 @@ function UTCIForecast() {
|
|||||||
{ key: 'uvB', label: 'UV-B' },
|
{ key: 'uvB', label: 'UV-B' },
|
||||||
{ key: 'burn', label: 'Burn' },
|
{ key: 'burn', label: 'Burn' },
|
||||||
{ key: 'precip', label: 'Precip' },
|
{ key: 'precip', label: 'Precip' },
|
||||||
{ key: 'utciP', label: 'UTCI+P' },
|
|
||||||
].map(c => html`
|
].map(c => html`
|
||||||
<button
|
<button
|
||||||
key=${c.key}
|
key=${c.key}
|
||||||
@@ -1564,15 +1605,15 @@ function UTCIForecast() {
|
|||||||
<table class="utci-table utci-table-head" ref=${headTableRef}>
|
<table class="utci-table utci-table-head" ref=${headTableRef}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
${visibleCols.hour && html`<th>Hour</th>`}
|
<th>Hour</th>
|
||||||
${visibleCols.air && html`<th>Air <span class="col-unit">°C</span></th>`}
|
${visibleCols.air && html`<th class="utci-tight-head">Air <span class="col-unit">°C</span></th>`}
|
||||||
${visibleCols.rh && html`<th>RH <span class="col-unit">%</span></th>`}
|
${visibleCols.rh && html`<th>RH <span class="col-unit">%</span></th>`}
|
||||||
${visibleCols.dew && html`<th>Dew <span class="col-unit">°C</span></th>`}
|
${visibleCols.dew && html`<th>Dew <span class="col-unit">°C</span></th>`}
|
||||||
${visibleCols.soilT && html`<th>Soil °C <span class="col-unit">surface</span></th>`}
|
${visibleCols.soilT && html`<th>Soil °C <span class="col-unit">surface</span></th>`}
|
||||||
${visibleCols.soilT6 && html`<th>Soil 6cm <span class="col-unit">°C root</span></th>`}
|
${visibleCols.soilT6 && html`<th>Soil 6cm <span class="col-unit">°C root</span></th>`}
|
||||||
${visibleCols.soilM && html`<th>Soil moist <span class="col-unit">m³/m³</span></th>`}
|
${visibleCols.soilM && html`<th>Soil moist <span class="col-unit">m³/m³</span></th>`}
|
||||||
${visibleCols.wind && html`<th>Wind <span class="col-unit">m/s (gust)</span></th>`}
|
${visibleCols.wind && html`<th>Wind <span class="col-unit">m/s (gust)</span></th>`}
|
||||||
${visibleCols.dir && html`<th>Dir <span class="col-unit">compass</span></th>`}
|
${visibleCols.dir && html`<th class="utci-dir-cell">Dir <span class="col-unit">-</span></th>`}
|
||||||
${visibleCols.cloud && html`<th>Cloud <span class="col-unit">%</span></th>`}
|
${visibleCols.cloud && html`<th>Cloud <span class="col-unit">%</span></th>`}
|
||||||
${visibleCols.sun && html`<th>Sun <span class="col-unit">elev°</span></th>`}
|
${visibleCols.sun && html`<th>Sun <span class="col-unit">elev°</span></th>`}
|
||||||
${visibleCols.direct && html`<th>Direct <span class="col-unit">W/m²</span></th>`}
|
${visibleCols.direct && html`<th>Direct <span class="col-unit">W/m²</span></th>`}
|
||||||
@@ -1583,8 +1624,8 @@ function UTCIForecast() {
|
|||||||
${visibleCols.uvA && html`<th>UV-A <span class="col-unit">est. idx</span></th>`}
|
${visibleCols.uvA && html`<th>UV-A <span class="col-unit">est. idx</span></th>`}
|
||||||
${visibleCols.uvB && html`<th>UV-B <span class="col-unit">est. idx</span></th>`}
|
${visibleCols.uvB && html`<th>UV-B <span class="col-unit">est. idx</span></th>`}
|
||||||
${visibleCols.burn && html`<th>Burn <span class="col-unit">to MED</span></th>`}
|
${visibleCols.burn && html`<th>Burn <span class="col-unit">to MED</span></th>`}
|
||||||
${visibleCols.precip && html`<th>Precip <span class="col-unit">mm/h</span></th>`}
|
${visibleCols.precip && html`<th class="utci-tight-head">Pcpt <span class="col-unit">mm/h</span></th>`}
|
||||||
${visibleCols.utciP && html`<th>UTCI+P <span class="col-unit">°C adj.</span></th>`}
|
<th>UTCI+P <span class="col-unit">°C adj.</span></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
</table>
|
</table>
|
||||||
@@ -1607,14 +1648,12 @@ function UTCIForecast() {
|
|||||||
return html`
|
return html`
|
||||||
<tr key=${r.iso}
|
<tr key=${r.iso}
|
||||||
class=${`${isNight ? 'is-night' : ''} ${isNow ? 'is-now' : ''}`.trim()}>
|
class=${`${isNight ? 'is-night' : ''} ${isNow ? 'is-now' : ''}`.trim()}>
|
||||||
${visibleCols.hour && html`
|
|
||||||
<td class="utci-time">
|
<td class="utci-time">
|
||||||
${isNow && html`<span class="now-pip"></span>`}
|
|
||||||
<span style=${{ display: 'inline-flex', alignItems: 'center', gap: '7px', verticalAlign: 'middle' }}>
|
<span style=${{ display: 'inline-flex', alignItems: 'center', gap: '7px', verticalAlign: 'middle' }}>
|
||||||
<${SkyScope} elev=${r.elev} dt=${r.dt} size=${30} />
|
<${SkyScope} elev=${r.elev} dt=${r.dt} size=${30} />
|
||||||
<span>${r.dt.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' })}</span>
|
<span>${r.dt.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' })}</span>
|
||||||
</span>
|
</span>
|
||||||
</td>`}
|
</td>
|
||||||
${visibleCols.air && html`<td>${r.Ta.toFixed(1)}</td>`}
|
${visibleCols.air && html`<td>${r.Ta.toFixed(1)}</td>`}
|
||||||
${visibleCols.rh && html`<td>${Math.round(r.RH)}</td>`}
|
${visibleCols.rh && html`<td>${Math.round(r.RH)}</td>`}
|
||||||
${visibleCols.dew && html`<td>${r.dew != null ? r.dew.toFixed(1) : '—'}</td>`}
|
${visibleCols.dew && html`<td>${r.dew != null ? r.dew.toFixed(1) : '—'}</td>`}
|
||||||
@@ -1629,10 +1668,10 @@ function UTCIForecast() {
|
|||||||
? html`<span style=${{ opacity: 0.65, marginLeft: '4px' }}>(${r.gust.toFixed(1)})</span>`
|
? html`<span style=${{ opacity: 0.65, marginLeft: '4px' }}>(${r.gust.toFixed(1)})</span>`
|
||||||
: ''}
|
: ''}
|
||||||
</td>`}
|
</td>`}
|
||||||
${visibleCols.dir && html`<td>
|
${visibleCols.dir && html`<td class="utci-dir-cell">
|
||||||
<span style=${{ display: 'inline-flex', alignItems: 'center', gap: '6px', verticalAlign: 'middle' }}>
|
<span style=${{ display: 'inline-flex', alignItems: 'center', gap: '6px', verticalAlign: 'middle' }}>
|
||||||
<${WindVane} bearing=${r.wd} size=${28} />
|
<${WindVane} bearing=${r.wd} size=${28} />
|
||||||
<span style=${{ fontFamily: 'JetBrains Mono, monospace', fontSize: '11px' }}>${r.compass.label}</span>
|
<span class="wind-dir-label" style=${{ fontFamily: 'JetBrains Mono, monospace', fontSize: '11px' }}>${r.compass.label}</span>
|
||||||
</span>
|
</span>
|
||||||
</td>`}
|
</td>`}
|
||||||
${visibleCols.cloud && html`<td>
|
${visibleCols.cloud && html`<td>
|
||||||
@@ -1674,12 +1713,11 @@ function UTCIForecast() {
|
|||||||
<td style=${{ color: r.snow > 0 ? '#6090c8' : r.precip > 0 ? '#5090b0' : '#c0a880' }}>
|
<td style=${{ color: r.snow > 0 ? '#6090c8' : r.precip > 0 ? '#5090b0' : '#c0a880' }}>
|
||||||
${r.snow > 0 ? '❅ ' + r.snow.toFixed(1) + 'cm' : r.precip > 0 ? r.precip.toFixed(1) : '—'}
|
${r.snow > 0 ? '❅ ' + r.snow.toFixed(1) + 'cm' : r.precip > 0 ? r.precip.toFixed(1) : '—'}
|
||||||
</td>`}
|
</td>`}
|
||||||
${visibleCols.utciP && html`
|
|
||||||
<td style=${{ background: 'rgba(180,215,250,0.10)' }}>
|
<td style=${{ background: 'rgba(180,215,250,0.10)' }}>
|
||||||
<span class="utci-cell utci-cell-hero" style=${{ background: adjCat.bg, color: adjCat.fg }}>
|
<span class="utci-cell utci-cell-hero" style=${{ background: adjCat.bg, color: adjCat.fg }}>
|
||||||
${r.utciAdj.toFixed(1)}
|
${r.utciAdj.toFixed(1)}
|
||||||
</span>
|
</span>
|
||||||
</td>`}
|
</td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
})}
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
Reference in New Issue
Block a user