3.5.5
Update desktop profile hover button Recalibrated vehicle ventilation calcs
This commit is contained in:
@@ -55,6 +55,7 @@ export function ConfigPanel({
|
||||
}) {
|
||||
const profileScrollRef = useRef(null);
|
||||
const profileWrapRef = useRef(null);
|
||||
const dropdownRef = useRef(null);
|
||||
|
||||
const { mainConfigKey, mainLabel } = deriveProfileMain(activeProfile, outdoorsVariant);
|
||||
|
||||
@@ -77,6 +78,34 @@ export function ConfigPanel({
|
||||
return () => document.removeEventListener('keydown', onKey);
|
||||
}, [open, onClose]);
|
||||
|
||||
// From tablet up the panel drops *out of* the active-profile-row rather
|
||||
// than sliding up from the floating button (which is mobile-only), so it
|
||||
// has to be anchored to that row's live bottom edge — it's sticky on
|
||||
// desktop and scrolls away on tablet, hence remeasuring on scroll/resize.
|
||||
// Mobile (<=640px) ignores the var entirely and keeps the bottom sheet.
|
||||
useEffect(() => {
|
||||
const el = dropdownRef.current;
|
||||
if (!el) return;
|
||||
const measure = () => {
|
||||
const row = document.querySelector('.active-profile-row');
|
||||
const top = row ? Math.max(0, row.getBoundingClientRect().bottom) : 0;
|
||||
el.style.setProperty('--profile-anchor-top', `${Math.round(top)}px`);
|
||||
// Not enough room below the row for the panel's content? Fall back to
|
||||
// the mobile bottom-sheet layout, which gets the full viewport height.
|
||||
const body = el.querySelector('.profile-dropdown-body');
|
||||
const content = body ? body.scrollHeight : 0;
|
||||
el.classList.toggle('is-bottom-sheet', content > window.innerHeight - top - 16);
|
||||
};
|
||||
measure();
|
||||
if (!open) return;
|
||||
window.addEventListener('scroll', measure, true);
|
||||
window.addEventListener('resize', measure);
|
||||
return () => {
|
||||
window.removeEventListener('scroll', measure, true);
|
||||
window.removeEventListener('resize', measure);
|
||||
};
|
||||
}, [open, activeTab, activeProfile, outdoorsVariant]);
|
||||
|
||||
// Drag-to-scroll + fade edges for the horizontal profile card row.
|
||||
useEffect(() => {
|
||||
const el = profileScrollRef.current;
|
||||
@@ -226,7 +255,7 @@ export function ConfigPanel({
|
||||
return html`
|
||||
<${Fragment}>
|
||||
<div class=${`profile-dropdown-overlay${open ? ' is-open' : ''}`} onClick=${onClose}></div>
|
||||
<div class=${`profile-dropdown${open ? ' is-open' : ''}`} role="dialog" aria-modal="true" aria-label="Profile and configuration" aria-hidden=${!open}>
|
||||
<div ref=${dropdownRef} class=${`profile-dropdown${open ? ' is-open' : ''}`} role="dialog" aria-modal="true" aria-label="Profile and configuration" aria-hidden=${!open}>
|
||||
<button class="profile-dropdown-close" aria-label="Close" onClick=${onClose}>×</button>
|
||||
<div class="profile-dropdown-body">
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ import { h, Fragment } from '../../vendor/preact.js';
|
||||
import htm from '../../vendor/htm.js';
|
||||
import { confidenceBand, utciCategory, VEHICLE_SPEEDS, VEHICLE_TYPES, BUILDING_TYPES, FUR_COLORS } from '../utils.js';
|
||||
import { FREE_DAYS, UTCI_ENVIRONMENTS, deriveProfileMain, FILTER_PROFILES, variantIcons } from '../config.js';
|
||||
import { calcVehicleInteriorTemp } from '../physics.js';
|
||||
import { calcVehicleInteriorTempPass } from '../physics.js';
|
||||
import { CloudIcon, PrecipIcon, HouseIcon, CarIcon, FogIcon, IceIcon } from '../components.js';
|
||||
import { SubscribeModal } from './SubscribeModal.js';
|
||||
|
||||
@@ -222,9 +222,12 @@ export function DayTabs({
|
||||
let shadeHi = null, shadeLo = null;
|
||||
if (isVehicleProfile) {
|
||||
const speedMph = (VEHICLE_SPEEDS[vehicleSpeed] ?? VEHICLE_SPEEDS.static).mph;
|
||||
const ventVals = d.rows
|
||||
.map(r => calcVehicleInteriorTemp(r.Ta, r.glob, r.elev, vehicleType, true, speedMph))
|
||||
.filter(v => isFinite(v));
|
||||
// Run the lagged pass over the whole day, not per-hour equilibrium,
|
||||
// so this hi/lo is directly comparable with the vehicleT column.
|
||||
const ventVals = calcVehicleInteriorTempPass(
|
||||
d.rows.map(r => r.Ta), d.rows.map(r => r.glob), d.rows.map(r => r.elev),
|
||||
d.rows.map(r => r.va), vehicleType, true, speedMph
|
||||
).filter(v => v != null && isFinite(v));
|
||||
shadeHi = ventVals.length ? Math.round(Math.max(...ventVals)) : null;
|
||||
shadeLo = ventVals.length ? Math.round(Math.min(...ventVals)) : null;
|
||||
} else if (secondaryField) {
|
||||
|
||||
@@ -23,13 +23,7 @@ import { Wordmark } from './Wordmark.js';
|
||||
const html = htm.bind(h);
|
||||
|
||||
const SUBSCRIBE_URL = 'https://buy.stripe.com/9B63cw7vl8k15Ei9DQd7q00';
|
||||
// TODO: replace with the one-time Payment Link created in the Stripe
|
||||
// Dashboard (see plan Part 3) - a flat-rate product with a few selectable
|
||||
// price options (e.g. £3 / £5 / £10), since Stripe Payment Links don't
|
||||
// support true customer-chosen amounts. Configure its after-payment
|
||||
// redirect to https://sunscope.net/?session_id={CHECKOUT_SESSION_ID}
|
||||
// the same way the monthly link should be.
|
||||
const SUBSCRIBE_URL_ONEOFF = 'https://buy.stripe.com/REPLACE_WITH_ONEOFF_PAYMENT_LINK';
|
||||
const SUBSCRIBE_URL_ONEOFF = 'https://buy.stripe.com/14A14odTJ6bT0jY4jwd7q02';
|
||||
const MANAGE_URL = 'https://billing.stripe.com/p/login/9B63cw7vl8k15Ei9DQd7q00';
|
||||
|
||||
export function SubscribeModal({ title, detail, onClose, openRestore }) {
|
||||
|
||||
Reference in New Issue
Block a user