2.7
New profile tabs Moved Solar model to Why box
This commit is contained in:
+18
-8
@@ -7,21 +7,15 @@
|
|||||||
.utci-header {
|
.utci-header {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr auto 1fr; /* left | dial | why */
|
grid-template-columns: 1fr auto 1fr; /* left | dial | why */
|
||||||
grid-template-areas:
|
grid-template-areas: "hleft dial hright";
|
||||||
"hleft dial hright"
|
|
||||||
". env .";
|
|
||||||
column-gap: 32px;
|
column-gap: 32px;
|
||||||
row-gap: 6px;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding-bottom: 6px;
|
padding-bottom: 6px;
|
||||||
margin-bottom: 6px;
|
margin-bottom: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Side columns occupy only the dial's row, so they vertically centre against
|
|
||||||
the dial itself; the environment selector drops to its own row beneath. */
|
|
||||||
.header-left { grid-area: hleft; }
|
.header-left { grid-area: hleft; }
|
||||||
.scope-col { grid-area: dial; }
|
.scope-col { grid-area: dial; }
|
||||||
.scope-env-bar { grid-area: env; }
|
|
||||||
.header-right { grid-area: hright; }
|
.header-right { grid-area: hright; }
|
||||||
|
|
||||||
/* Left column — wordmark, tagline, search and location stacked together */
|
/* Left column — wordmark, tagline, search and location stacked together */
|
||||||
@@ -276,10 +270,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.scope-env-row .cs-panel,
|
.scope-env-row .cs-panel,
|
||||||
.scope-env-row .cs-option {
|
.scope-env-row .cs-option,
|
||||||
|
.insight-env-footer .cs-panel,
|
||||||
|
.insight-env-footer .cs-option {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.insight-env-footer .cs-btn.on:hover .cs-btn-overlay { opacity: 1; }
|
||||||
|
.insight-env-footer .cs-btn .cs-btn-label { position: relative; z-index: 1; }
|
||||||
|
|
||||||
/* Per-environment gradient themes — !important keeps the gradient fixed
|
/* Per-environment gradient themes — !important keeps the gradient fixed
|
||||||
under the brass hover overlay (ui.css :hover rules load later). */
|
under the brass hover overlay (ui.css :hover rules load later). */
|
||||||
.scope-env-row[data-env="open"] .cs-btn.on { background: linear-gradient(135deg, #5a8a28 0%, #b8d860 100%) !important; border-color: #4a7818 !important; color: #fff; }
|
.scope-env-row[data-env="open"] .cs-btn.on { background: linear-gradient(135deg, #5a8a28 0%, #b8d860 100%) !important; border-color: #4a7818 !important; color: #fff; }
|
||||||
@@ -291,8 +290,19 @@
|
|||||||
.scope-env-row[data-env="openwater"] .cs-btn.on { background: linear-gradient(135deg, #0c4878 0%, #2898d8 100%) !important; border-color: #0c4878 !important; color: #fff; }
|
.scope-env-row[data-env="openwater"] .cs-btn.on { background: linear-gradient(135deg, #0c4878 0%, #2898d8 100%) !important; border-color: #0c4878 !important; color: #fff; }
|
||||||
.scope-env-row[data-env="urban"] .cs-btn.on { background: linear-gradient(135deg, #4a5568 0%, #a89880 100%) !important; border-color: #3a4455 !important; color: #fff; }
|
.scope-env-row[data-env="urban"] .cs-btn.on { background: linear-gradient(135deg, #4a5568 0%, #a89880 100%) !important; border-color: #3a4455 !important; color: #fff; }
|
||||||
|
|
||||||
|
/* Duplicate gradient rules for the Solar model button inside the insight panel */
|
||||||
|
.insight-env-footer[data-env="open"] .cs-btn.on { background: linear-gradient(135deg, #5a8a28 0%, #b8d860 100%) !important; border-color: #4a7818 !important; color: #fff; }
|
||||||
|
.insight-env-footer[data-env="forest"] .cs-btn.on { background: linear-gradient(135deg, #1b5e3b 0%, #57bf8a 100%) !important; border-color: #1b5e3b !important; color: #fff; }
|
||||||
|
.insight-env-footer[data-env="alpine"] .cs-btn.on { background: linear-gradient(135deg, #a8cfe8 0%, #dff0fa 100%) !important; border-color: #6aaedd !important; color: #1a3a58; }
|
||||||
|
.insight-env-footer[data-env="beach"] .cs-btn.on { background: linear-gradient(135deg, #2a8fc8 0%, #e8d87a 100%) !important; border-color: #1e78b0 !important; color: #fff; }
|
||||||
|
.insight-env-footer[data-env="river"] .cs-btn.on { background: linear-gradient(135deg, #2e6e5e 0%, #78c4a8 100%) !important; border-color: #2e6e5e !important; color: #fff; }
|
||||||
|
.insight-env-footer[data-env="desert"] .cs-btn.on { background: linear-gradient(135deg, #b86820 0%, #e8c458 100%) !important; border-color: #a05818 !important; color: #fff; }
|
||||||
|
.insight-env-footer[data-env="openwater"] .cs-btn.on { background: linear-gradient(135deg, #0c4878 0%, #2898d8 100%) !important; border-color: #0c4878 !important; color: #fff; }
|
||||||
|
.insight-env-footer[data-env="urban"] .cs-btn.on { background: linear-gradient(135deg, #4a5568 0%, #a89880 100%) !important; border-color: #3a4455 !important; color: #fff; }
|
||||||
|
|
||||||
/* On hover the brass overlay covers the interior — force white text for contrast */
|
/* On hover the brass overlay covers the interior — force white text for contrast */
|
||||||
.scope-env-row .cs-btn.on:hover { color: #fff; }
|
.scope-env-row .cs-btn.on:hover { color: #fff; }
|
||||||
|
.insight-env-footer .cs-btn.on:hover { color: #fff; }
|
||||||
|
|
||||||
/* The search box on the right of the header */
|
/* The search box on the right of the header */
|
||||||
.utci-search-wrap {
|
.utci-search-wrap {
|
||||||
|
|||||||
+51
-4
@@ -11,7 +11,7 @@
|
|||||||
/* Wrapper that hosts the scrollable strip, edge fades, and chevrons. */
|
/* Wrapper that hosts the scrollable strip, edge fades, and chevrons. */
|
||||||
.utci-day-tabs-wrap {
|
.utci-day-tabs-wrap {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-top: 14px;
|
margin-top: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Edge fade gradients — only show when there's content to scroll to. */
|
/* Edge fade gradients — only show when there's content to scroll to. */
|
||||||
@@ -132,7 +132,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0;
|
gap: 0;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
padding: 0 14px 0;
|
padding: 0px;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
@@ -160,8 +160,8 @@
|
|||||||
justify-content: safe center; /* fall back to flex-start if it overflows */
|
justify-content: safe center; /* fall back to flex-start if it overflows */
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
overflow-y: auto; /* forced by browser when overflow-x is non-visible */
|
overflow-y: auto; /* forced by browser when overflow-x is non-visible */
|
||||||
padding-bottom: 400px; /* extends clip boundary so the tallest dropdown (Activities) isn't cut off */
|
padding-bottom: 6px;
|
||||||
margin-bottom: -400px; /* cancel that extra space in the layout */
|
margin-bottom: 0;
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
-ms-overflow-style: none;
|
-ms-overflow-style: none;
|
||||||
padding-right: 4px; /* breathing room at right edge */
|
padding-right: 4px; /* breathing room at right edge */
|
||||||
@@ -173,6 +173,12 @@
|
|||||||
.profile-scroll-wrap {
|
.profile-scroll-wrap {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
padding-top:8px;
|
||||||
|
padding-bottom:4px;
|
||||||
|
background-color: #fffcf2;
|
||||||
|
border-left: 1.5px solid #c9b08a;
|
||||||
|
border-right: 1.5px solid #c9b08a;
|
||||||
|
border-bottom: 1.5px solid #c9b08a;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-scroll-wrap::before,
|
.profile-scroll-wrap::before,
|
||||||
@@ -285,6 +291,47 @@
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Tab strip above the profile scroll row */
|
||||||
|
.profile-tabs {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
border-bottom: 1.5px solid #c9b08a;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
}
|
||||||
|
.profile-tab {
|
||||||
|
flex: 1;
|
||||||
|
padding: 7px 4px;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 2.5px solid transparent;
|
||||||
|
border-left: 1.5px solid #f5edd6;
|
||||||
|
border-right: 1.5px solid #f5edd6;
|
||||||
|
border-top: 1.5px solid #f5edd6;
|
||||||
|
margin-bottom: -1.5px;
|
||||||
|
font-family: Manrope, sans-serif;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
color: #9a7a4a;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.15s, border-color 0.15s;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.profile-tab:hover {
|
||||||
|
color: #c8922a;
|
||||||
|
background: #f0e4c4;
|
||||||
|
}
|
||||||
|
.profile-tab.active {
|
||||||
|
color: #c8922a;
|
||||||
|
background-color: #fffcf2;
|
||||||
|
border-bottom-color: #fffcf2;
|
||||||
|
border-left: 1.5px solid #c9b08a;
|
||||||
|
border-right: 1.5px solid #c9b08a;
|
||||||
|
border-top: 1.5px solid #c9b08a;
|
||||||
|
border-radius:7px 7px 0px 0px
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ── 6. COLUMN TOGGLES (the "Columns: Hour Air RH ..." bar) ────────── */
|
/* ── 6. COLUMN TOGGLES (the "Columns: Hour Air RH ..." bar) ────────── */
|
||||||
|
|
||||||
|
|||||||
+54
-1
@@ -1176,12 +1176,64 @@
|
|||||||
Sits between the header dial and the day tabs.
|
Sits between the header dial and the day tabs.
|
||||||
════════════════════════════════════════════════════════════════════════ */
|
════════════════════════════════════════════════════════════════════════ */
|
||||||
|
|
||||||
/* "Why it feels like this" — sits in the header's right column, beside the dial */
|
/* "Why it feels like this" - sits in the header's right column, beside the dial */
|
||||||
.insight-panel--why {
|
.insight-panel--why {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 300px;
|
max-width: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Solar model selector inside the Why it feels like panel */
|
||||||
|
.insight-env-footer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
border-bottom: 1px solid #ede4cc;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
.insight-env-footer-label {
|
||||||
|
font-family: Manrope, sans-serif;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
color: #b09870;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.insight-env-footer .scope-env-row {
|
||||||
|
width: 100%;
|
||||||
|
border-top: none;
|
||||||
|
padding: 0;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
.insight-env-footer .cs-wrap,
|
||||||
|
.insight-env-footer .cs-btn {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.insight-env-footer .cs-btn {
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
isolation: isolate;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.insight-env-footer .cs-btn .cs-btn-overlay {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
border-radius: inherit;
|
||||||
|
background: #c8922a;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.35s ease;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
.insight-env-footer .cs-arrow {
|
||||||
|
position: absolute;
|
||||||
|
right: 12px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
/* Hourly table paired with the "Today at a glance" rail on its right */
|
/* Hourly table paired with the "Today at a glance" rail on its right */
|
||||||
.table-with-rail {
|
.table-with-rail {
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -1222,6 +1274,7 @@
|
|||||||
letter-spacing: 0.1em;
|
letter-spacing: 0.1em;
|
||||||
color: #b09870;
|
color: #b09870;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Each factor row */
|
/* Each factor row */
|
||||||
|
|||||||
+14
-17
@@ -441,26 +441,23 @@ export function UTCIForecast() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="scope-env-bar">
|
|
||||||
<div class="scope-env-row" data-env=${utciEnv}>
|
|
||||||
<span class="scope-env-label">Environment</span>
|
|
||||||
<${CustomSelect}
|
|
||||||
value=${utciEnv}
|
|
||||||
isOn=${true}
|
|
||||||
noHide=${true}
|
|
||||||
grpClass="grp-felt"
|
|
||||||
options=${Object.entries(UTCI_ENVIRONMENTS).map(([k, v]) => ({
|
|
||||||
value: k,
|
|
||||||
label: v.label,
|
|
||||||
}))}
|
|
||||||
onChange=${(v) => setUtciEnv(v)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
${forecast && currentRow && whyFeelsLike && html`
|
${forecast && currentRow && whyFeelsLike && html`
|
||||||
<div class="insight-panel insight-panel--why">
|
<div class="insight-panel insight-panel--why">
|
||||||
|
<div class="insight-env-footer" data-env=${utciEnv}>
|
||||||
|
<span class="insight-env-footer-label">Solar model</span>
|
||||||
|
<${CustomSelect}
|
||||||
|
value=${utciEnv}
|
||||||
|
isOn=${true}
|
||||||
|
noHide=${true}
|
||||||
|
grpClass="grp-felt"
|
||||||
|
options=${Object.entries(UTCI_ENVIRONMENTS).map(([k, v]) => ({
|
||||||
|
value: k,
|
||||||
|
label: v.label,
|
||||||
|
}))}
|
||||||
|
onChange=${(v) => setUtciEnv(v)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div class="insight-panel-title">Why it feels like this</div>
|
<div class="insight-panel-title">Why it feels like this</div>
|
||||||
${[
|
${[
|
||||||
{ label: 'Sun and sky', icon: '☀', value: whyFeelsLike.sunAndSky },
|
{ label: 'Sun and sky', icon: '☀', value: whyFeelsLike.sunAndSky },
|
||||||
|
|||||||
+98
-125
@@ -42,7 +42,7 @@
|
|||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
import { h, Fragment } from '../../vendor/preact.js';
|
import { h, Fragment } from '../../vendor/preact.js';
|
||||||
import { useRef, useEffect } from '../../vendor/preact-hooks.js';
|
import { useRef, useEffect, useState } from '../../vendor/preact-hooks.js';
|
||||||
import htm from '../../vendor/htm.js';
|
import htm from '../../vendor/htm.js';
|
||||||
import { confidenceBand } from '../utils.js';
|
import { confidenceBand } from '../utils.js';
|
||||||
import { FREE_DAYS, FILTER_PROFILES, OUTDOORS_VARIANTS, profileButtonOrder, activityVariantKeys, placeVariantKeys, workVariantKeys } from '../config.js';
|
import { FREE_DAYS, FILTER_PROFILES, OUTDOORS_VARIANTS, profileButtonOrder, activityVariantKeys, placeVariantKeys, workVariantKeys } from '../config.js';
|
||||||
@@ -96,6 +96,17 @@ export function DayTabs({
|
|||||||
const profileScrollRef = useRef(null);
|
const profileScrollRef = useRef(null);
|
||||||
const profileWrapRef = useRef(null);
|
const profileWrapRef = useRef(null);
|
||||||
|
|
||||||
|
// Active tab: common - places - activities - work.
|
||||||
|
// Auto-derived from the current profile on mount and on change.
|
||||||
|
const getTab = () => {
|
||||||
|
if (activeProfile === 'outdoors' && placeVariantKeys.includes(outdoorsVariant)) return 'places';
|
||||||
|
if (activeProfile === 'outdoors' && activityVariantKeys.includes(outdoorsVariant)) return 'activities';
|
||||||
|
if (activeProfile === 'farming' || (activeProfile === 'outdoors' && workVariantKeys.includes(outdoorsVariant))) return 'work';
|
||||||
|
return 'common';
|
||||||
|
};
|
||||||
|
const [activeTab, setActiveTab] = useState(getTab);
|
||||||
|
useEffect(() => { setActiveTab(getTab()); }, [activeProfile, outdoorsVariant]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const el = profileScrollRef.current;
|
const el = profileScrollRef.current;
|
||||||
const wrap = profileWrapRef.current;
|
const wrap = profileWrapRef.current;
|
||||||
@@ -155,139 +166,98 @@ export function DayTabs({
|
|||||||
<${Fragment}>
|
<${Fragment}>
|
||||||
|
|
||||||
<div class="filter-profiles">
|
<div class="filter-profiles">
|
||||||
|
<div class="profile-tabs">
|
||||||
|
${[['common','Common'],['places','Places'],['activities','Activities'],['work','Work']].map(([key, label]) => html`
|
||||||
|
<button
|
||||||
|
key=${key}
|
||||||
|
class=${`profile-tab${activeTab === key ? ' active' : ''}`}
|
||||||
|
onClick=${() => setActiveTab(key)}
|
||||||
|
>${label}</button>
|
||||||
|
`)}
|
||||||
|
</div>
|
||||||
<div class="profile-scroll-wrap" ref=${profileWrapRef}>
|
<div class="profile-scroll-wrap" ref=${profileWrapRef}>
|
||||||
<div class="profile-scroll" ref=${profileScrollRef}>
|
<div class="profile-scroll" ref=${profileScrollRef}>
|
||||||
${profileButtonOrder.slice(0, 3).map((key) => {
|
|
||||||
const profile = FILTER_PROFILES[key];
|
${activeTab === 'common' && profileButtonOrder.map((key) => {
|
||||||
const locked = profile.proOnly && !isPro;
|
const profile = FILTER_PROFILES[key];
|
||||||
return html`
|
const locked = profile.proOnly && !isPro;
|
||||||
|
return html`
|
||||||
|
<button
|
||||||
|
key=${key}
|
||||||
|
class=${`profile-btn${activeProfile === key ? ' active' : ''}${locked ? ' locked' : ''}`}
|
||||||
|
style=${{ opacity: locked ? 0.6 : 1, cursor: locked ? 'not-allowed' : 'pointer', position: 'relative' }}
|
||||||
|
title=${locked ? `Profile ${profile.label} is part of SunScope Extra` : ''}
|
||||||
|
onClick=${() => {
|
||||||
|
if (locked) { setProPromptSource(`profile:${key}`); setProPromptDay(0); return; }
|
||||||
|
activateProfile(key);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span class="profile-btn-scene" style=${{ background: profile.scene }}>${profile.scene.includes('url(') ? null : profile.icon}</span>
|
||||||
|
<span class="profile-btn-label">${profile.label}${locked ? ' locked' : ''}</span>
|
||||||
|
</button>`;
|
||||||
|
})}
|
||||||
|
|
||||||
|
${activeTab === 'places' && placeOptions.map((opt) => html`
|
||||||
<button
|
<button
|
||||||
key=${key}
|
key=${opt.value}
|
||||||
class=${`profile-btn${activeProfile === key ? ' active' : ''}${locked ? ' locked' : ''}`}
|
class=${`profile-btn${placeValue === opt.value ? ' active' : ''}${opt.disabled ? ' locked' : ''}`}
|
||||||
style=${{ opacity: locked ? 0.6 : 1, cursor: locked ? 'not-allowed' : 'pointer', position: 'relative' }}
|
style=${{ opacity: opt.disabled ? 0.6 : 1, cursor: opt.disabled ? 'not-allowed' : 'pointer', position: 'relative' }}
|
||||||
title=${locked ? `🔒 ${profile.label} is part of SunScope Extra` : ''}
|
|
||||||
onClick=${() => {
|
onClick=${() => {
|
||||||
if (locked) {
|
if (opt.disabled) { setProPromptSource(`variant:${opt.value}`); setProPromptDay(0); return; }
|
||||||
setProPromptSource(`profile:${key}`);
|
try { localStorage.setItem('sunscope_profile', 'outdoors'); } catch(e) {}
|
||||||
setProPromptDay(0);
|
setActiveProfile('outdoors');
|
||||||
return;
|
setOutdoorsVariantAndSave(opt.value);
|
||||||
}
|
setVisibleCols({ ...OUTDOORS_VARIANTS[opt.value].cols });
|
||||||
activateProfile(key);
|
setIndoorMode('off');
|
||||||
|
setIndoorManaged(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span class="profile-btn-scene" style=${{ background: profile.scene }}>${profile.scene.includes('url(') ? null : profile.icon}</span>
|
<span class="profile-btn-scene" style=${{ background: opt.scene }}></span>
|
||||||
<span class="profile-btn-label">${profile.label}${locked ? ' 🔒' : ''}</span>
|
<span class="profile-btn-label">${opt.name}</span>
|
||||||
</button>`;
|
</button>
|
||||||
})}
|
`)}
|
||||||
<span class="profile-divider" aria-hidden="true"></span>
|
|
||||||
<${CustomSelect}
|
${activeTab === 'activities' && activityOptions.map((opt) => html`
|
||||||
key="places"
|
|
||||||
value=${placeValue}
|
|
||||||
isOn=${activeProfile === 'outdoors' && placeVariantKeys.includes(outdoorsVariant)}
|
|
||||||
noHide=${true}
|
|
||||||
hideLabel="Places"
|
|
||||||
buttonLabel=${placeValue === 'off' ? 'Places' : `${placeLabel}`}
|
|
||||||
grpClass="profile-card"
|
|
||||||
sceneStyle=${{ background: placeValue !== 'off' && OUTDOORS_VARIANTS[placeValue]?.scene ? OUTDOORS_VARIANTS[placeValue].scene : "url('assets/images/profiles/places.png') center / cover no-repeat, linear-gradient(160deg,#98ccc0,#c4e4dc)" }}
|
|
||||||
sceneContent=${null}
|
|
||||||
options=${placeOptions}
|
|
||||||
onChange=${(v) => {
|
|
||||||
if (v === 'off') { activateProfile('basic'); return; }
|
|
||||||
if (OUTDOORS_VARIANTS[v]?.proOnly && !isPro) {
|
|
||||||
setProPromptSource(`variant:${v}`);
|
|
||||||
setProPromptDay(0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try { localStorage.setItem('sunscope_profile', 'outdoors'); } catch (e) { /* ignore */ }
|
|
||||||
setActiveProfile('outdoors');
|
|
||||||
setOutdoorsVariantAndSave(v);
|
|
||||||
setVisibleCols({ ...OUTDOORS_VARIANTS[v].cols });
|
|
||||||
setIndoorMode('off');
|
|
||||||
setIndoorManaged(false);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<${CustomSelect}
|
|
||||||
key="activities"
|
|
||||||
value=${activityValue}
|
|
||||||
isOn=${activeProfile === 'outdoors' && activityVariantKeys.includes(outdoorsVariant)}
|
|
||||||
noHide=${true}
|
|
||||||
hideLabel="Activities"
|
|
||||||
buttonLabel=${activityValue === 'off' ? 'Activities' : `${activityLabel}`}
|
|
||||||
grpClass="profile-card"
|
|
||||||
sceneStyle=${{ background: activityValue !== 'off' && OUTDOORS_VARIANTS[activityValue]?.scene ? OUTDOORS_VARIANTS[activityValue].scene : "url('assets/images/profiles/activities.png') center / cover no-repeat, linear-gradient(160deg,#e0c0a0,#f0d8c0)" }}
|
|
||||||
sceneContent=${null}
|
|
||||||
options=${activityOptions}
|
|
||||||
onChange=${(v) => {
|
|
||||||
if (v === 'off') { activateProfile('basic'); return; }
|
|
||||||
if (OUTDOORS_VARIANTS[v]?.proOnly && !isPro) {
|
|
||||||
setProPromptSource(`variant:${v}`);
|
|
||||||
setProPromptDay(0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try { localStorage.setItem('sunscope_profile', 'outdoors'); } catch (e) { /* ignore */ }
|
|
||||||
setActiveProfile('outdoors');
|
|
||||||
setOutdoorsVariantAndSave(v);
|
|
||||||
setVisibleCols({ ...OUTDOORS_VARIANTS[v].cols });
|
|
||||||
setIndoorMode('off');
|
|
||||||
setIndoorManaged(false);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<${CustomSelect}
|
|
||||||
key="work"
|
|
||||||
value=${workValue}
|
|
||||||
isOn=${activeProfile === 'farming' || (activeProfile === 'outdoors' && workVariantKeys.includes(outdoorsVariant))}
|
|
||||||
noHide=${true}
|
|
||||||
hideLabel="Work"
|
|
||||||
buttonLabel=${workValue === 'off' ? 'Work' : `${workLabel}`}
|
|
||||||
grpClass="profile-card"
|
|
||||||
sceneStyle=${{ background: workValue === 'farming' ? FILTER_PROFILES.farming.scene : workValue !== 'off' && OUTDOORS_VARIANTS[workValue]?.scene ? OUTDOORS_VARIANTS[workValue].scene : "url('assets/images/profiles/work.png') center / cover no-repeat, linear-gradient(160deg,#b0b0d4,#d0d0e8)" }}
|
|
||||||
sceneContent=${null}
|
|
||||||
options=${workOptions}
|
|
||||||
onChange=${(v) => {
|
|
||||||
if (v === 'off') { activateProfile('basic'); return; }
|
|
||||||
if (v === 'farming') { activateProfile('farming'); return; }
|
|
||||||
if (OUTDOORS_VARIANTS[v]?.proOnly && !isPro) {
|
|
||||||
setProPromptSource(`variant:${v}`);
|
|
||||||
setProPromptDay(0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try { localStorage.setItem('sunscope_profile', 'outdoors'); } catch (e) { /* ignore */ }
|
|
||||||
setActiveProfile('outdoors');
|
|
||||||
setOutdoorsVariantAndSave(v);
|
|
||||||
setVisibleCols({ ...OUTDOORS_VARIANTS[v].cols });
|
|
||||||
if (v === 'office') {
|
|
||||||
setBuildingType('office');
|
|
||||||
setIndoorMode('on');
|
|
||||||
setIndoorManaged(false);
|
|
||||||
} else {
|
|
||||||
setIndoorMode('off');
|
|
||||||
setIndoorManaged(false);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<span class="profile-divider" aria-hidden="true"></span>
|
|
||||||
${profileButtonOrder.slice(3).map((key) => {
|
|
||||||
const profile = FILTER_PROFILES[key];
|
|
||||||
const locked = profile.proOnly && !isPro;
|
|
||||||
return html`
|
|
||||||
<button
|
<button
|
||||||
key=${key}
|
key=${opt.value}
|
||||||
class=${`profile-btn${activeProfile === key ? ' active' : ''}${locked ? ' locked' : ''}`}
|
class=${`profile-btn${activityValue === opt.value ? ' active' : ''}${opt.disabled ? ' locked' : ''}`}
|
||||||
style=${{ opacity: locked ? 0.6 : 1, cursor: locked ? 'not-allowed' : 'pointer', position: 'relative' }}
|
style=${{ opacity: opt.disabled ? 0.6 : 1, cursor: opt.disabled ? 'not-allowed' : 'pointer', position: 'relative' }}
|
||||||
title=${locked ? `🔒 ${profile.label} is part of SunScope Extra` : ''}
|
|
||||||
onClick=${() => {
|
onClick=${() => {
|
||||||
if (locked) {
|
if (opt.disabled) { setProPromptSource(`variant:${opt.value}`); setProPromptDay(0); return; }
|
||||||
setProPromptSource(`profile:${key}`);
|
try { localStorage.setItem('sunscope_profile', 'outdoors'); } catch(e) {}
|
||||||
setProPromptDay(0);
|
setActiveProfile('outdoors');
|
||||||
return;
|
setOutdoorsVariantAndSave(opt.value);
|
||||||
}
|
setVisibleCols({ ...OUTDOORS_VARIANTS[opt.value].cols });
|
||||||
activateProfile(key);
|
setIndoorMode('off');
|
||||||
|
setIndoorManaged(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span class="profile-btn-scene" style=${{ background: profile.scene }}>${profile.scene.includes('url(') ? null : profile.icon}</span>
|
<span class="profile-btn-scene" style=${{ background: opt.scene }}></span>
|
||||||
<span class="profile-btn-label">${profile.label}${locked ? ' 🔒' : ''}</span>
|
<span class="profile-btn-label">${opt.name}</span>
|
||||||
</button>`;
|
</button>
|
||||||
})}
|
`)}
|
||||||
|
|
||||||
|
${activeTab === 'work' && workOptions.map((opt) => html`
|
||||||
|
<button
|
||||||
|
key=${opt.value}
|
||||||
|
class=${`profile-btn${workValue === opt.value ? ' active' : ''}${opt.disabled ? ' locked' : ''}`}
|
||||||
|
style=${{ opacity: opt.disabled ? 0.6 : 1, cursor: opt.disabled ? 'not-allowed' : 'pointer', position: 'relative' }}
|
||||||
|
onClick=${() => {
|
||||||
|
if (opt.disabled) { setProPromptSource(`variant:${opt.value}`); setProPromptDay(0); return; }
|
||||||
|
if (opt.value === 'farming') { activateProfile('farming'); return; }
|
||||||
|
try { localStorage.setItem('sunscope_profile', 'outdoors'); } catch(e) {}
|
||||||
|
setActiveProfile('outdoors');
|
||||||
|
setOutdoorsVariantAndSave(opt.value);
|
||||||
|
setVisibleCols({ ...OUTDOORS_VARIANTS[opt.value].cols });
|
||||||
|
if (opt.value === 'office') { setBuildingType('office'); setIndoorMode('on'); setIndoorManaged(false); }
|
||||||
|
else { setIndoorMode('off'); setIndoorManaged(false); }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span class="profile-btn-scene" style=${{ background: opt.scene }}></span>
|
||||||
|
<span class="profile-btn-label">${opt.name}</span>
|
||||||
|
</button>
|
||||||
|
`)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -363,7 +333,10 @@ export function DayTabs({
|
|||||||
style=${{
|
style=${{
|
||||||
background: isActive ? wBgActive : wBg,
|
background: isActive ? wBgActive : wBg,
|
||||||
color: wText,
|
color: wText,
|
||||||
border: '1px solid #c9b08a',
|
borderTop: '1px solid #c9b08a',
|
||||||
|
borderRight: '1px solid #c9b08a',
|
||||||
|
borderBottom: '1px solid #c9b08a',
|
||||||
|
borderLeft: i === 0 ? '1px solid #c9b08a' : 'none',
|
||||||
opacity: locked ? 0.5 : 1,
|
opacity: locked ? 0.5 : 1,
|
||||||
cursor: locked ? 'not-allowed' : 'pointer',
|
cursor: locked ? 'not-allowed' : 'pointer',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
|
|||||||
@@ -117,9 +117,11 @@ export function buildHourlyRows({ forecast, airQuality, location, vehicleType, v
|
|||||||
// Suggested multipliers (tune with real data):
|
// Suggested multipliers (tune with real data):
|
||||||
// Resting: 1.0 (no change)
|
// Resting: 1.0 (no change)
|
||||||
// Walking: 0.85
|
// Walking: 0.85
|
||||||
// Cycling: 0.75
|
// Cycling: 0.75 *This will need a airflow calc as going at speed is cooling
|
||||||
// Running: 0.60
|
// Running: 0.60 *This will need a airflow calc as going at speed is cooling
|
||||||
// Sport: 0.50
|
// Sport: 0.50 *This will need a airflow calc as going at speed is cooling
|
||||||
|
//
|
||||||
|
// *Danny Notes: These faster speed activities might need intergrating into the future "Vehicle Speed" calcs somehow?
|
||||||
//
|
//
|
||||||
// heatStressLevel - a simple label: 'Low' | 'Moderate' | 'High' | 'Very High'
|
// heatStressLevel - a simple label: 'Low' | 'Moderate' | 'High' | 'Very High'
|
||||||
// Derived from UTCI + activity heat load. A runner at
|
// Derived from UTCI + activity heat load. A runner at
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 121 KiB After Width: | Height: | Size: 53 KiB |
Reference in New Issue
Block a user