RC2
Tweaks and new pulldowns
This commit is contained in:
+60
-24
@@ -138,7 +138,7 @@
|
|||||||
|
|
||||||
.filter-profiles-label {
|
.filter-profiles-label {
|
||||||
font-family: JetBrains Mono, monospace;
|
font-family: JetBrains Mono, monospace;
|
||||||
font-size: 9px;
|
font-size: 12px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.18em;
|
letter-spacing: 0.18em;
|
||||||
color: #b09870;
|
color: #b09870;
|
||||||
@@ -147,13 +147,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.profile-btn {
|
.profile-btn {
|
||||||
padding: 4px 11px;
|
padding: 5px 13px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: 1.5px solid #c9b08a;
|
border: 1.5px solid #c9b08a;
|
||||||
background: #f5edd6;
|
background: #f5edd6;
|
||||||
color: #9a7d5a;
|
color: #9a7d5a;
|
||||||
font-family: JetBrains Mono, monospace;
|
font-family: JetBrains Mono, monospace;
|
||||||
font-size: 11px;
|
font-size: 13px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.1em;
|
letter-spacing: 0.1em;
|
||||||
transition: all 0.15s;
|
transition: all 0.15s;
|
||||||
@@ -189,7 +189,7 @@
|
|||||||
|
|
||||||
.col-toggles-label {
|
.col-toggles-label {
|
||||||
font-family: JetBrains Mono, monospace;
|
font-family: JetBrains Mono, monospace;
|
||||||
font-size: 9px;
|
font-size: 12px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.18em;
|
letter-spacing: 0.18em;
|
||||||
color: #b09870;
|
color: #b09870;
|
||||||
@@ -199,13 +199,13 @@
|
|||||||
|
|
||||||
/* The little pill buttons */
|
/* The little pill buttons */
|
||||||
.col-toggle {
|
.col-toggle {
|
||||||
padding: 4px 11px;
|
padding: 5px 13px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: 1.5px solid #c9b08a;
|
border: 1.5px solid #c9b08a;
|
||||||
background: #f5edd6;
|
background: #f5edd6;
|
||||||
color: #9a7d5a;
|
color: #9a7d5a;
|
||||||
font-family: JetBrains Mono, monospace;
|
font-family: JetBrains Mono, monospace;
|
||||||
font-size: 9px;
|
font-size: 12px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.1em;
|
letter-spacing: 0.1em;
|
||||||
transition: all 0.15s;
|
transition: all 0.15s;
|
||||||
@@ -226,16 +226,29 @@
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Burn select — looks like a col-toggle pill but is a <select>.
|
/* Burn select — unlike regular col-toggle buttons, the select never
|
||||||
The native dropdown arrow is kept (no appearance:none) so users
|
goes full-brass background so the native dropdown list stays readable.
|
||||||
know it's a dropdown. Active (burn on) state uses the same brass
|
Active state = brass border + brass text on parchment.
|
||||||
as .col-toggle.on and the label updates to show the chosen skin. */
|
Inactive state = same as a regular col-toggle pill. */
|
||||||
.burn-select {
|
.burn-select {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
/* Override the inherited .col-toggle.on brass background */
|
||||||
|
.col-toggle.burn-select.on {
|
||||||
|
background: #f5edd6;
|
||||||
|
color: #c8922a;
|
||||||
|
border-color: #c8922a;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.col-toggle.burn-select.on:hover {
|
||||||
|
background: #fef3dc;
|
||||||
|
color: #c8922a;
|
||||||
|
border-color: #c8922a;
|
||||||
|
}
|
||||||
.burn-select:focus {
|
.burn-select:focus {
|
||||||
border-color: #c8922a;
|
border-color: #c8922a;
|
||||||
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Vehicle group: select + windows-open checkbox fused together ── */
|
/* ── Vehicle group: select + windows-open checkbox fused together ── */
|
||||||
@@ -243,18 +256,28 @@
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
gap: 0;
|
gap: 0;
|
||||||
|
/* Collapse any whitespace-node gap between the cs-wrap span and cs-vent */
|
||||||
|
font-size: 0;
|
||||||
|
}
|
||||||
|
.col-toggle-group > * {
|
||||||
|
font-size: 12px; /* restore font size for children */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Remove the right border + radius on the select so it butts up flush
|
/* Remove the right border + radius on the cs-wrap/cs-btn so it butts
|
||||||
with the checkbox label. When the label is absent (hide state), the
|
flush with the VentPill. The cs-wrap is the direct child, so we
|
||||||
select is the only child and :last-child restores full pill rounding. */
|
target its inner cs-btn via the grouped-left class on the button. */
|
||||||
.col-toggle-group .grouped-left {
|
.col-toggle-group .cs-wrap {
|
||||||
border-right: none;
|
display: inline-flex;
|
||||||
border-radius: 20px 0 0 20px;
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
.col-toggle-group .grouped-left:last-child {
|
.col-toggle-group .grouped-left {
|
||||||
border-right: 1.5px solid #c9b08a;
|
border-right: none !important;
|
||||||
border-radius: 20px;
|
border-radius: 20px 0 0 20px !important;
|
||||||
|
}
|
||||||
|
.col-toggle-group .cs-wrap:only-child .grouped-left,
|
||||||
|
.col-toggle-group .cs-wrap:last-child .grouped-left {
|
||||||
|
border-right: 1.5px solid #c9b08a !important;
|
||||||
|
border-radius: 20px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The checkbox pill — same height/padding as a col-toggle but styled as a label */
|
/* The checkbox pill — same height/padding as a col-toggle but styled as a label */
|
||||||
@@ -262,8 +285,8 @@
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
padding: 4px 10px 4px 9px;
|
padding: 5px 12px 5px 10px;
|
||||||
font-size: 9px;
|
font-size: 12px;
|
||||||
font-family: JetBrains Mono, monospace;
|
font-family: JetBrains Mono, monospace;
|
||||||
letter-spacing: 0.08em;
|
letter-spacing: 0.08em;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
@@ -433,15 +456,22 @@
|
|||||||
background: #f0e4c4;
|
background: #f0e4c4;
|
||||||
color: #6b4f2a;
|
color: #6b4f2a;
|
||||||
font-family: JetBrains Mono, monospace;
|
font-family: JetBrains Mono, monospace;
|
||||||
font-size: 10px;
|
font-size: 12px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.14em;
|
letter-spacing: 0.12em;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
padding: 14px 12px;
|
padding: 14px 12px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 800px) {
|
||||||
|
.utci-table th {
|
||||||
|
font-size: 10px;
|
||||||
|
letter-spacing: 0.14em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* The Hour column aligns left, the rest align right */
|
/* The Hour column aligns left, the rest align right */
|
||||||
.utci-table th:first-child,
|
.utci-table th:first-child,
|
||||||
.utci-table td:first-child {
|
.utci-table td:first-child {
|
||||||
@@ -452,10 +482,16 @@
|
|||||||
.utci-table th .col-unit {
|
.utci-table th .col-unit {
|
||||||
display: block;
|
display: block;
|
||||||
color: #c8922a;
|
color: #c8922a;
|
||||||
font-size: 9px;
|
font-size: 10px;
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 800px) {
|
||||||
|
.utci-table th .col-unit {
|
||||||
|
font-size: 9px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.utci-table tbody tr {
|
.utci-table tbody tr {
|
||||||
border-bottom: 1px solid #ede4cc;
|
border-bottom: 1px solid #ede4cc;
|
||||||
transition: background 0.1s;
|
transition: background 0.1s;
|
||||||
|
|||||||
@@ -3,6 +3,188 @@
|
|||||||
════════════════════════════════════════════════════════════════════════ */
|
════════════════════════════════════════════════════════════════════════ */
|
||||||
|
|
||||||
|
|
||||||
|
/* ── CUSTOM SELECT DROPDOWN ─────────────────────────────────────────── */
|
||||||
|
/* Cross-platform replacement for native <select> on the col-toggles bar */
|
||||||
|
|
||||||
|
.cs-wrap {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The visible pill button */
|
||||||
|
.cs-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
padding: 5px 10px 5px 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1.5px solid #c9b08a;
|
||||||
|
background: #f5edd6;
|
||||||
|
color: #9a7d5a;
|
||||||
|
font-family: JetBrains Mono, monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
transition: all 0.15s;
|
||||||
|
user-select: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.cs-btn:hover {
|
||||||
|
border-color: #c8922a;
|
||||||
|
color: #6b4f2a;
|
||||||
|
background: #fef3dc;
|
||||||
|
}
|
||||||
|
/* Active state — brass border + brass text, parchment background */
|
||||||
|
.cs-btn.on {
|
||||||
|
border-color: #c8922a;
|
||||||
|
color: #c8922a;
|
||||||
|
background: #f5edd6;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.cs-btn.on:hover {
|
||||||
|
background: #fef3dc;
|
||||||
|
color: #a06e18;
|
||||||
|
border-color: #a06e18;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The chevron arrow inside the button */
|
||||||
|
.cs-arrow {
|
||||||
|
font-size: 9px;
|
||||||
|
opacity: 0.6;
|
||||||
|
margin-left: 2px;
|
||||||
|
transition: transform 0.15s;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.cs-wrap.open .cs-arrow {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Grouped left (fused with checkbox pill) */
|
||||||
|
.cs-btn.grouped-left {
|
||||||
|
border-right: none;
|
||||||
|
border-radius: 20px 0 0 20px;
|
||||||
|
}
|
||||||
|
.cs-btn.grouped-left:only-child,
|
||||||
|
.cs-btn.grouped-left.last-child {
|
||||||
|
border-right: 1.5px solid #c9b08a;
|
||||||
|
border-radius: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The floating dropdown panel */
|
||||||
|
.cs-panel {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(100% + 4px);
|
||||||
|
left: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
background: #fffcf2;
|
||||||
|
border: 1.5px solid #c9b08a;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 4px 16px rgba(30,18,8,0.13);
|
||||||
|
min-width: 100%;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Each option row */
|
||||||
|
.cs-option {
|
||||||
|
display: block;
|
||||||
|
padding: 8px 14px;
|
||||||
|
font-family: JetBrains Mono, monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
color: #4a3420;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.1s, color 0.1s;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.cs-option:hover {
|
||||||
|
background: #fef3dc;
|
||||||
|
color: #1e1208;
|
||||||
|
}
|
||||||
|
.cs-option.selected {
|
||||||
|
color: #c8922a;
|
||||||
|
font-weight: 600;
|
||||||
|
background: #fdf8ee;
|
||||||
|
}
|
||||||
|
.cs-option.disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
.cs-option.disabled:hover {
|
||||||
|
background: transparent;
|
||||||
|
color: #4a3420;
|
||||||
|
}
|
||||||
|
/* Hide option divider between show/hide */
|
||||||
|
.cs-divider {
|
||||||
|
height: 1px;
|
||||||
|
background: #e8d8c0;
|
||||||
|
margin: 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── CUSTOM CHECKBOX PILL (ventilation / managed) ───────────────────── */
|
||||||
|
|
||||||
|
.cs-vent {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 5px 12px 5px 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-family: JetBrains Mono, monospace;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1.5px solid #c9b08a;
|
||||||
|
border-left: none;
|
||||||
|
margin-left: -1px;
|
||||||
|
padding-left: 11px;
|
||||||
|
border-radius: 0 20px 20px 0;
|
||||||
|
background: #f5edd6;
|
||||||
|
color: #9a7d5a;
|
||||||
|
user-select: none;
|
||||||
|
transition: background 0.15s, color 0.15s, border-color 0.15s;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.cs-vent:hover {
|
||||||
|
border-color: #c8922a;
|
||||||
|
color: #6b4f2a;
|
||||||
|
background: #fef3dc;
|
||||||
|
}
|
||||||
|
.cs-vent.on {
|
||||||
|
border-color: #c8922a;
|
||||||
|
color: #c8922a;
|
||||||
|
background: #f5edd6;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.cs-vent.on:hover {
|
||||||
|
background: #fef3dc;
|
||||||
|
color: #a06e18;
|
||||||
|
border-color: #a06e18;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The little square tick box inside the vent pill */
|
||||||
|
.cs-checkbox {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 13px;
|
||||||
|
height: 13px;
|
||||||
|
border: 1.5px solid currentColor;
|
||||||
|
border-radius: 2px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition: background 0.12s;
|
||||||
|
}
|
||||||
|
.cs-checkbox.checked::after {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
width: 7px;
|
||||||
|
height: 5px;
|
||||||
|
border-left: 2px solid currentColor;
|
||||||
|
border-bottom: 2px solid currentColor;
|
||||||
|
transform: rotate(-45deg) translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ── 9. STATUS & ERROR BANNERS ("Acquiring forecast data…") ────────── */
|
/* ── 9. STATUS & ERROR BANNERS ("Acquiring forecast data…") ────────── */
|
||||||
|
|
||||||
.utci-status {
|
.utci-status {
|
||||||
|
|||||||
+71
-65
@@ -30,7 +30,7 @@ import {
|
|||||||
VEHICLE_TYPES, BUILDING_TYPES,
|
VEHICLE_TYPES, BUILDING_TYPES,
|
||||||
cloudCategory, confidenceBand, moonGlyph,
|
cloudCategory, confidenceBand, moonGlyph,
|
||||||
} from './utils.js';
|
} from './utils.js';
|
||||||
import { SkyScope, WindVane, CloudIcon, ScopeReticle, PrecipIcon } from './components.js';
|
import { SkyScope, WindVane, CloudIcon, ScopeReticle, PrecipIcon, CustomSelect, VentPill } from './components.js';
|
||||||
|
|
||||||
const html = htm.bind(h);
|
const html = htm.bind(h);
|
||||||
|
|
||||||
@@ -1068,11 +1068,17 @@ export function UTCIForecast() {
|
|||||||
<span class="col-toggles-label">Columns:</span>
|
<span class="col-toggles-label">Columns:</span>
|
||||||
|
|
||||||
${activeProfile === 'outdoors' && html`
|
${activeProfile === 'outdoors' && html`
|
||||||
<select
|
<${CustomSelect}
|
||||||
class=${`col-toggle burn-select on`}
|
|
||||||
value=${outdoorsVariant}
|
value=${outdoorsVariant}
|
||||||
onChange=${(e) => {
|
isOn=${true}
|
||||||
const v = e.target.value;
|
hideLabel="Outdoors"
|
||||||
|
hidingLabel="Hide Outdoors"
|
||||||
|
options=${Object.entries(OUTDOORS_VARIANTS).map(([k, v]) => ({
|
||||||
|
value: k,
|
||||||
|
label: v.name + (v.proOnly && !isPro ? ' 🔒' : ''),
|
||||||
|
disabled: false,
|
||||||
|
}))}
|
||||||
|
onChange=${(v) => {
|
||||||
if (OUTDOORS_VARIANTS[v]?.proOnly && !isPro) {
|
if (OUTDOORS_VARIANTS[v]?.proOnly && !isPro) {
|
||||||
setProPromptSource(`variant:${v}`);
|
setProPromptSource(`variant:${v}`);
|
||||||
setProPromptDay(0);
|
setProPromptDay(0);
|
||||||
@@ -1080,10 +1086,8 @@ export function UTCIForecast() {
|
|||||||
}
|
}
|
||||||
setOutdoorsVariant(v);
|
setOutdoorsVariant(v);
|
||||||
setVisibleCols({ ...OUTDOORS_VARIANTS[v].cols });
|
setVisibleCols({ ...OUTDOORS_VARIANTS[v].cols });
|
||||||
}}>
|
}}
|
||||||
${Object.entries(OUTDOORS_VARIANTS).map(([k, v]) => html`
|
/>`}
|
||||||
<option key=${k} value=${k}>${v.name}${v.proOnly && !isPro ? ' 🔒' : ''}</option>`)}
|
|
||||||
</select>`}
|
|
||||||
|
|
||||||
${isPro && (activeProfile === 'custom' || FILTER_PROFILES[activeProfile].cols['air']) && html`<button class=${`col-toggle${visibleCols.air ? ' on' : ''}`} onClick=${() => toggleCol('air')}>Air</button>`}
|
${isPro && (activeProfile === 'custom' || FILTER_PROFILES[activeProfile].cols['air']) && html`<button class=${`col-toggle${visibleCols.air ? ' on' : ''}`} onClick=${() => toggleCol('air')}>Air</button>`}
|
||||||
${isPro && (activeProfile === 'custom' || FILTER_PROFILES[activeProfile].cols['rh']) && html`<button class=${`col-toggle${visibleCols.rh ? ' on' : ''}`} onClick=${() => toggleCol('rh')}>RH</button>`}
|
${isPro && (activeProfile === 'custom' || FILTER_PROFILES[activeProfile].cols['rh']) && html`<button class=${`col-toggle${visibleCols.rh ? ' on' : ''}`} onClick=${() => toggleCol('rh')}>RH</button>`}
|
||||||
@@ -1105,84 +1109,87 @@ export function UTCIForecast() {
|
|||||||
${isPro && (activeProfile === 'custom' || FILTER_PROFILES[activeProfile].cols['uvB']) && html`<button class=${`col-toggle${visibleCols.uvB ? ' on' : ''}`} onClick=${() => toggleCol('uvB')}>UV-B</button>`}
|
${isPro && (activeProfile === 'custom' || FILTER_PROFILES[activeProfile].cols['uvB']) && html`<button class=${`col-toggle${visibleCols.uvB ? ' on' : ''}`} onClick=${() => toggleCol('uvB')}>UV-B</button>`}
|
||||||
|
|
||||||
${(activeProfile === 'custom' || FILTER_PROFILES[activeProfile]?.cols['burn']) && html`
|
${(activeProfile === 'custom' || FILTER_PROFILES[activeProfile]?.cols['burn']) && html`
|
||||||
<select
|
<${CustomSelect}
|
||||||
class=${`col-toggle burn-select${visibleCols.burn ? ' on' : ''}`}
|
value=${skinType}
|
||||||
value=${visibleCols.burn ? skinType : 'off'}
|
isOn=${visibleCols.burn}
|
||||||
onChange=${(e) => {
|
hideLabel="Burn"
|
||||||
if (e.target.value === 'off') {
|
hidingLabel="Hide Burn"
|
||||||
|
options=${Object.entries(SKIN_TYPES).map(([k, v]) => ({
|
||||||
|
value: k,
|
||||||
|
label: v.name.split(' · ')[1] + ' skin',
|
||||||
|
}))}
|
||||||
|
onChange=${(v) => {
|
||||||
|
if (v === 'off') {
|
||||||
setVisibleCols(prev => ({ ...prev, burn: false }));
|
setVisibleCols(prev => ({ ...prev, burn: false }));
|
||||||
} else {
|
} else {
|
||||||
setSkinType(e.target.value);
|
setSkinType(v);
|
||||||
setVisibleCols(prev => ({ ...prev, burn: true }));
|
setVisibleCols(prev => ({ ...prev, burn: true }));
|
||||||
}
|
}
|
||||||
}}>
|
}}
|
||||||
<option value="off">${visibleCols.burn ? 'Hide Burn' : 'Burn'}</option>
|
/>`}
|
||||||
${Object.entries(SKIN_TYPES).map(([k, v]) => html`
|
|
||||||
<option key=${k} value=${k}>${v.name.split(' · ')[1]} skin</option>`)}
|
|
||||||
</select>`}
|
|
||||||
|
|
||||||
${(activeProfile === 'custom' || FILTER_PROFILES[activeProfile]?.cols['vehicleT']) && html`
|
${(activeProfile === 'custom' || FILTER_PROFILES[activeProfile]?.cols['vehicleT']) && html`
|
||||||
<span class="col-toggle-group">
|
<span class="col-toggle-group">
|
||||||
<select
|
<${CustomSelect}
|
||||||
class=${`col-toggle burn-select grouped-left${visibleCols.vehicleT ? ' on' : ''}`}
|
value=${vehicleType}
|
||||||
value=${visibleCols.vehicleT ? vehicleType : 'off'}
|
isOn=${visibleCols.vehicleT}
|
||||||
onChange=${(e) => {
|
hideLabel="Vehicle"
|
||||||
if (e.target.value === 'off') {
|
hidingLabel="Hide Vehicle"
|
||||||
|
groupedLeft=${true}
|
||||||
|
isLastChild=${!visibleCols.vehicleT}
|
||||||
|
options=${Object.entries(VEHICLE_TYPES).map(([k, v]) => ({
|
||||||
|
value: k,
|
||||||
|
label: v.name,
|
||||||
|
}))}
|
||||||
|
onChange=${(v) => {
|
||||||
|
if (v === 'off') {
|
||||||
setVisibleCols(prev => ({ ...prev, vehicleT: false }));
|
setVisibleCols(prev => ({ ...prev, vehicleT: false }));
|
||||||
setVehicleVent(false);
|
setVehicleVent(false);
|
||||||
} else {
|
} else {
|
||||||
setVehicleType(e.target.value);
|
setVehicleType(v);
|
||||||
setVisibleCols(prev => ({ ...prev, vehicleT: true }));
|
setVisibleCols(prev => ({ ...prev, vehicleT: true }));
|
||||||
}
|
}
|
||||||
}}>
|
}}
|
||||||
<option value="off">${visibleCols.vehicleT ? 'Hide Vehicle' : 'Vehicle'}</option>
|
/>
|
||||||
${Object.entries(VEHICLE_TYPES).map(([k, v]) => html`
|
|
||||||
<option key=${k} value=${k}>${v.name}</option>`)}
|
|
||||||
</select>
|
|
||||||
${visibleCols.vehicleT && html`
|
${visibleCols.vehicleT && html`
|
||||||
<label
|
<${VentPill}
|
||||||
class=${`col-toggle-vent grouped-right${vehicleVent ? ' on' : ''}`}
|
checked=${vehicleVent}
|
||||||
title="Ventilation — open windows significantly reduce cabin heat build-up">
|
onChange=${() => setVehicleVent(v => !v)}
|
||||||
<input
|
label="Ventilation"
|
||||||
type="checkbox"
|
title="Ventilation — open windows significantly reduce cabin heat build-up"
|
||||||
checked=${vehicleVent}
|
/>`}
|
||||||
onChange=${() => setVehicleVent(v => !v)}
|
|
||||||
style=${{ position: 'absolute', opacity: 0, width: 0, height: 0 }} />
|
|
||||||
<span class="vent-checkbox${vehicleVent ? ' checked' : ''}"></span>
|
|
||||||
Ventilation
|
|
||||||
</label>`}
|
|
||||||
</span>`}
|
</span>`}
|
||||||
|
|
||||||
${(activeProfile === 'custom' || FILTER_PROFILES[activeProfile].cols['indoorT'] || FILTER_PROFILES[activeProfile].cols['managedT']) && html`
|
${(activeProfile === 'custom' || FILTER_PROFILES[activeProfile].cols['indoorT'] || FILTER_PROFILES[activeProfile].cols['managedT']) && html`
|
||||||
<span class="col-toggle-group">
|
<span class="col-toggle-group">
|
||||||
<select
|
<${CustomSelect}
|
||||||
class=${`col-toggle burn-select grouped-left${indoorMode === 'on' ? ' on' : ''}`}
|
value=${buildingType}
|
||||||
value=${indoorMode === 'on' ? buildingType : 'off'}
|
isOn=${indoorMode === 'on'}
|
||||||
onChange=${(e) => {
|
hideLabel="Indoors"
|
||||||
if (e.target.value === 'off') {
|
hidingLabel="Hide Indoors"
|
||||||
|
groupedLeft=${true}
|
||||||
|
isLastChild=${indoorMode !== 'on'}
|
||||||
|
options=${Object.entries(BUILDING_TYPES).map(([k, v]) => ({
|
||||||
|
value: k,
|
||||||
|
label: v.name,
|
||||||
|
}))}
|
||||||
|
onChange=${(v) => {
|
||||||
|
if (v === 'off') {
|
||||||
setIndoorMode('off');
|
setIndoorMode('off');
|
||||||
setIndoorManaged(false);
|
setIndoorManaged(false);
|
||||||
} else {
|
} else {
|
||||||
setBuildingType(e.target.value);
|
setBuildingType(v);
|
||||||
setIndoorMode('on');
|
setIndoorMode('on');
|
||||||
}
|
}
|
||||||
}}>
|
}}
|
||||||
<option value="off">${indoorMode === 'on' ? 'Hide Indoors' : 'Indoors'}</option>
|
/>
|
||||||
${Object.entries(BUILDING_TYPES).map(([k, v]) => html`
|
|
||||||
<option key=${k} value=${k}>${v.name}</option>`)}
|
|
||||||
</select>
|
|
||||||
${indoorMode === 'on' && html`
|
${indoorMode === 'on' && html`
|
||||||
<label
|
<${VentPill}
|
||||||
class=${`col-toggle-vent grouped-right${indoorManaged ? ' on' : ''}`}
|
checked=${indoorManaged}
|
||||||
title="Managed: curtains closed by day, windows open when cooler outside">
|
onChange=${() => setIndoorManaged(v => !v)}
|
||||||
<input
|
label="Managed"
|
||||||
type="checkbox"
|
title="Managed: curtains closed by day, windows open when cooler outside"
|
||||||
checked=${indoorManaged}
|
/>`}
|
||||||
onChange=${() => setIndoorManaged(v => !v)}
|
|
||||||
style=${{ position: 'absolute', opacity: 0, width: 0, height: 0 }} />
|
|
||||||
<span class="vent-checkbox${indoorManaged ? ' checked' : ''}"></span>
|
|
||||||
Managed
|
|
||||||
</label>`}
|
|
||||||
</span>`}
|
</span>`}
|
||||||
${isPro && (activeProfile === 'custom' || FILTER_PROFILES[activeProfile].cols['precip']) && html`<button class=${`col-toggle${visibleCols.precip ? ' on' : ''}`} onClick=${() => toggleCol('precip')}>Precip</button>`}
|
${isPro && (activeProfile === 'custom' || FILTER_PROFILES[activeProfile].cols['precip']) && html`<button class=${`col-toggle${visibleCols.precip ? ' on' : ''}`} onClick=${() => toggleCol('precip')}>Precip</button>`}
|
||||||
|
|
||||||
@@ -1456,4 +1463,3 @@ export function UTCIForecast() {
|
|||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
// ════════════════════════════════════════════════════════════════════════
|
// ════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
import { h, Fragment } from '../vendor/preact.js';
|
import { h, Fragment } from '../vendor/preact.js';
|
||||||
|
import { useState, useEffect, useRef } from '../vendor/preact-hooks.js';
|
||||||
import htm from '../vendor/htm.js';
|
import htm from '../vendor/htm.js';
|
||||||
import {
|
import {
|
||||||
skyGradientForElev, skyFillForElev, grassFillForElev,
|
skyGradientForElev, skyFillForElev, grassFillForElev,
|
||||||
@@ -22,6 +23,113 @@ import {
|
|||||||
|
|
||||||
const html = htm.bind(h);
|
const html = htm.bind(h);
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════════
|
||||||
|
// CUSTOMSELECT — cross-platform styled dropdown pill.
|
||||||
|
// ───────────────────────────────────────────────────────────────────
|
||||||
|
// props:
|
||||||
|
// value — current value string
|
||||||
|
// options — array of { value, label, disabled }
|
||||||
|
// onChange — fn(newValue)
|
||||||
|
// hideLabel — label shown when value === 'off' (e.g. 'Burn')
|
||||||
|
// hidingLabel — label shown for the 'off' option when active (e.g. 'Hide Burn')
|
||||||
|
// isOn — bool — whether the pill shows as active (brass)
|
||||||
|
// groupedLeft — bool — fuse right side with a VentPill
|
||||||
|
// isLastChild — bool — restore right border-radius when no sibling follows
|
||||||
|
// ═══════════════════════════════════════════════════════════════════
|
||||||
|
export function CustomSelect({ value, options, onChange, hideLabel, hidingLabel, isOn, groupedLeft, isLastChild }) {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const wrapRef = useRef(null);
|
||||||
|
|
||||||
|
// Close on outside click or Escape
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open) return;
|
||||||
|
const onDown = (e) => {
|
||||||
|
if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(false);
|
||||||
|
};
|
||||||
|
const onKey = (e) => { if (e.key === 'Escape') setOpen(false); };
|
||||||
|
document.addEventListener('mousedown', onDown);
|
||||||
|
document.addEventListener('touchstart', onDown);
|
||||||
|
document.addEventListener('keydown', onKey);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('mousedown', onDown);
|
||||||
|
document.removeEventListener('touchstart', onDown);
|
||||||
|
document.removeEventListener('keydown', onKey);
|
||||||
|
};
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
|
// Build the label shown on the button
|
||||||
|
const activeOpt = options.find(o => o.value === value);
|
||||||
|
const btnLabel = isOn
|
||||||
|
? (activeOpt ? activeOpt.label : hideLabel)
|
||||||
|
: hideLabel;
|
||||||
|
|
||||||
|
const btnClass = [
|
||||||
|
'cs-btn',
|
||||||
|
isOn ? 'on' : '',
|
||||||
|
groupedLeft ? 'grouped-left' : '',
|
||||||
|
(groupedLeft && isLastChild) ? 'last-child' : '',
|
||||||
|
].filter(Boolean).join(' ');
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<span class=${`cs-wrap${open ? ' open' : ''}`} ref=${wrapRef}>
|
||||||
|
<button
|
||||||
|
class=${btnClass}
|
||||||
|
onClick=${(e) => { e.stopPropagation(); setOpen(o => !o); }}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
${btnLabel}
|
||||||
|
<span class="cs-arrow">▾</span>
|
||||||
|
</button>
|
||||||
|
${open && html`
|
||||||
|
<div class="cs-panel" role="listbox">
|
||||||
|
${isOn && html`
|
||||||
|
<span
|
||||||
|
class="cs-option"
|
||||||
|
role="option"
|
||||||
|
onClick=${() => { onChange('off'); setOpen(false); }}
|
||||||
|
>${hidingLabel || ('Hide ' + hideLabel)}</span>
|
||||||
|
<div class="cs-divider"></div>
|
||||||
|
`}
|
||||||
|
${options.map(opt => html`
|
||||||
|
<span
|
||||||
|
key=${opt.value}
|
||||||
|
class=${`cs-option${opt.value === value && isOn ? ' selected' : ''}${opt.disabled ? ' disabled' : ''}`}
|
||||||
|
role="option"
|
||||||
|
onClick=${() => {
|
||||||
|
if (opt.disabled) return;
|
||||||
|
onChange(opt.value);
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
>${opt.label}</span>
|
||||||
|
`)}
|
||||||
|
</div>
|
||||||
|
`}
|
||||||
|
</span>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════════
|
||||||
|
// VENTPILL — the checkbox pill fused to the right of a CustomSelect.
|
||||||
|
// ───────────────────────────────────────────────────────────────────
|
||||||
|
// props:
|
||||||
|
// checked — bool
|
||||||
|
// onChange — fn()
|
||||||
|
// label — string
|
||||||
|
// title — tooltip string
|
||||||
|
// ═══════════════════════════════════════════════════════════════════
|
||||||
|
export function VentPill({ checked, onChange, label, title }) {
|
||||||
|
return html`
|
||||||
|
<label class=${`cs-vent${checked ? ' on' : ''}`} title=${title}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked=${checked}
|
||||||
|
onChange=${onChange}
|
||||||
|
style=${{ position: 'absolute', opacity: 0, width: 0, height: 0, pointerEvents: 'none' }}
|
||||||
|
/>
|
||||||
|
<span class=${`cs-checkbox${checked ? ' checked' : ''}`}></span>
|
||||||
|
${label}
|
||||||
|
</label>`;
|
||||||
|
}
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════
|
||||||
// SKYSCOPE — the little porthole circle next to each hour.
|
// SKYSCOPE — the little porthole circle next to each hour.
|
||||||
// ───────────────────────────────────────────────────────────────────
|
// ───────────────────────────────────────────────────────────────────
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
// Paste your Stripe SECRET key below (never the publishable key).
|
// Paste your Stripe SECRET key below (never the publishable key).
|
||||||
// Swap sk_test_... for sk_live_... when you go live.
|
// Swap sk_test_... for sk_live_... when you go live.
|
||||||
|
|
||||||
define('STRIPE_SECRET_KEY', 'sk_test_51TD27GEwik8ohlUdzs6LZx9mu3NOIERBnlzPoOcdWA1wfxM8YqVnIEowEpCJI7nMIrXNZuPMbwnLz9CgYbUrORkV00Yzf1MfVI');
|
define('STRIPE_SECRET_KEY', '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');
|
||||||
define('SUNSCOPE_URL', 'https://sunscope.net');
|
define('SUNSCOPE_URL', 'https://sunscope.net');
|
||||||
|
|
||||||
$error = '';
|
$error = '';
|
||||||
|
|||||||
Reference in New Issue
Block a user