New welcome note & subscribe reminders
This commit is contained in:
fraxle
2026-08-16 11:37:28 +01:00
parent ff8a9950f4
commit 7c604ae2be
6 changed files with 242 additions and 17 deletions
+3 -3
View File
@@ -22,9 +22,9 @@ import { Wordmark } from './Wordmark.js';
const html = htm.bind(h);
const SUBSCRIBE_URL = 'https://buy.stripe.com/9B63cw7vl8k15Ei9DQd7q00';
const SUBSCRIBE_URL_ONEOFF = 'https://buy.stripe.com/14A14odTJ6bT0jY4jwd7q02';
const MANAGE_URL = 'https://billing.stripe.com/p/login/9B63cw7vl8k15Ei9DQd7q00';
export const SUBSCRIBE_URL = 'https://buy.stripe.com/9B63cw7vl8k15Ei9DQd7q00';
export const SUBSCRIBE_URL_ONEOFF = 'https://buy.stripe.com/14A14odTJ6bT0jY4jwd7q02';
export const MANAGE_URL = 'https://billing.stripe.com/p/login/9B63cw7vl8k15Ei9DQd7q00';
export function SubscribeModal({ title, detail, onClose, openRestore }) {
// Dismiss on Esc.
+112
View File
@@ -0,0 +1,112 @@
// ------------------------------------------------------------------------
// components/WeeklyNudge.js - Friendly weekly reminder for free users.
//
// Shown at most once every 7 days to non-subscribers (never on the very
// first visit - the WelcomeModal owns that moment). Leads with a small
// sun-themed fact so it reads as a bit of sunshine trivia rather than a
// nag, then offers SunScope Extra.
//
// Reuses the .welcome-* overlay/card styling. Purely presentational - the
// open/close state lives in useAppState (weeklyNudgeOpen).
//
// Props:
// onClose - dismiss and stamp the "shown" date (7-day cooldown)
// openRestore - opens the "Already subscribed?" restore modal
// ------------------------------------------------------------------------
import { h } from '../../vendor/preact.js';
import { useEffect, useMemo } from '../../vendor/preact-hooks.js';
import htm from '../../vendor/htm.js';
import { Wordmark } from './Wordmark.js';
import { SUBSCRIBE_URL, SUBSCRIBE_URL_ONEOFF } from './SubscribeModal.js';
const html = htm.bind(h);
// One of these is picked at random each time the nudge appears, so a
// regular user gets a different bit of sun trivia week to week.
const SUN_FACTS = [
{
icon: '☀️',
title: 'Sunlight is eight minutes old',
body: html`Every ray that warms your face left the Sun eight and a half minutes ago and
crossed 93 million miles to get here. The least you can do is dress for it.`,
},
{
icon: '🌡️',
title: 'Shade can feel 15°C cooler',
body: html`Air temperature barely changes when you step into shade — but what your body
feels can drop by ten or fifteen degrees. That gap is exactly what SunSoak measures.`,
},
{
icon: '🧴',
title: 'Clouds are not sunscreen',
body: html`Up to 80% of UV sails straight through thin cloud, which is why overcast days
catch people out. SunScope's UV and sunburn-time columns keep score for you.`,
},
{
icon: '🚗',
title: 'A parked car beats the desert',
body: html`On a mild 22°C afternoon a car's interior can pass 50°C in under an hour —
hotter than the Sahara. SunScope can show you that number before you park.`,
},
{
icon: '💨',
title: 'Wind steals your warmth',
body: html`A brisk breeze can strip several degrees off how cold you feel without moving
the thermometer at all. Wind chill is baked into every SunSoak reading.`,
},
{
icon: '🌅',
title: 'The golden hour is real',
body: html`Low sun travels through far more atmosphere, so early and late light is gentler
on skin — a good reason to check the hourly view before heading out.`,
},
];
export function WeeklyNudge({ onClose, openRestore }) {
const fact = useMemo(() => SUN_FACTS[Math.floor(Math.random() * SUN_FACTS.length)], []);
// Dismiss on Esc.
useEffect(() => {
const onKey = (e) => { if (e.key === 'Escape') onClose(); };
document.addEventListener('keydown', onKey);
return () => document.removeEventListener('keydown', onKey);
}, [onClose]);
return html`
<div class="welcome-overlay" onClick=${onClose}>
<div class="welcome-card" role="dialog" aria-modal="true" aria-labelledby="nudge-title"
onClick=${(e) => e.stopPropagation()} style=${{ maxWidth: '440px' }}>
<button class="welcome-close" aria-label="Close" onClick=${onClose}>×</button>
<div class="welcome-head">
<${Wordmark} />
<div class="welcome-kicker" style=${{ marginTop: '10px' }}>Your weekly ray of sun</div>
</div>
<div class="nudge-fact">
<span class="nudge-fact-icon" aria-hidden="true">${fact.icon}</span>
<h2 class="welcome-title" id="nudge-title">${fact.title}</h2>
<p class="nudge-fact-body">${fact.body}</p>
</div>
<p class="welcome-remember">
✨ <strong>SunScope Extra</strong> unlocks the 🔒 profiles — hiking, sailing, fishing,
photography, winter sports and the whole Work set — plus Show All and custom columns,
so you can build your own view. £2 a month.
</p>
<a class="welcome-cta" href=${SUBSCRIBE_URL} target="_blank" rel="noopener noreferrer"
style=${{ textDecoration: 'none', textAlign: 'center', marginBottom: '10px' }}>
Subscribe — £2/month
</a>
<div class="nudge-links">
<a href=${SUBSCRIBE_URL_ONEOFF} target="_blank" rel="noopener noreferrer">
Or make a one-off payment →
</a>
<button type="button" onClick=${() => { onClose(); openRestore(); }}>
Already subscribed? Restore access →
</button>
<button type="button" class="nudge-dismiss" onClick=${onClose}>
Maybe next week
</button>
</div>
</div>
</div>`;
}
+15 -14
View File
@@ -21,23 +21,21 @@ const html = htm.bind(h);
const STEPS = [
{
icon: '☀️',
title: 'SunSoak is the number to watch',
body: html`Pick a <strong>Solar model</strong> (open, urban, beach…) in the SunSoak dropdown
to match where you are. It shows what your body actually feels in the sun — not just the
air temperature.`,
title: 'Watch SunSoak',
body: html`It's how the sun actually feels, not just air temperature. Pick the
<strong>Solar model</strong> that matches where you are.`,
},
{
icon: '🎯',
title: 'Choose a profile',
body: html`Tap <strong>Places</strong>, <strong>Activities</strong> or <strong>Work</strong>
to instantly load the columns that matter for your situation — like Beach, Cycling or
Farming.`,
title: 'Pick a profile',
body: html`<strong>Places</strong>, <strong>Activities</strong> or <strong>Work</strong>
Beach, Cycling, Farming and more — loads the right columns instantly.`,
},
{
icon: '⚙️',
title: 'Make it yours',
body: html`Hit <strong>Edit columns</strong> to add or remove any data you like — UV and
sunburn time, vehicle interior, soil, air quality and more.`,
body: html`Every profile brings its own columns — UV, sunburn time, vehicle interior, soil,
air quality. <strong>Edit columns</strong> (Extra) lets you pick your own.`,
},
];
@@ -59,9 +57,8 @@ export function WelcomeModal({ onClose }) {
<h2 class="welcome-title" id="welcome-title">How to read the sky in 3 steps</h2>
</div>
<p class="welcome-intro">
This isn't your usual weather forecast. SunScope goes deeper than temperature alone —
it shows how the environment affects you: sun exposure, wind chill, UV intensity, and more.
For the best results, pick the <strong>environment</strong> that matches where you'll be.
Not your usual forecast SunScope shows how the weather affects
<strong>you</strong>: sun, wind chill, UV and more.
</p>
<ol class="welcome-steps">
${STEPS.map((s, i) => html`
@@ -74,7 +71,11 @@ export function WelcomeModal({ onClose }) {
</span>
</li>`)}
</ol>
<p class="welcome-remember">💾 Don't worry — SunScope remembers your choices for next time.</p>
<p class="welcome-remember">
✨ <strong>SunScope Extra</strong> unlocks the 🔒 profiles and custom columns,
£2/month — tap any locked one to subscribe.
</p>
<p class="welcome-note">💾 SunScope remembers your choices for next time.</p>
<button class="welcome-cta" onClick=${onClose}>Got it</button>
</div>
</div>`;