feat: GDPR — add privacy link under forms, update privacy policy with Brevo and retention

This commit is contained in:
Roberto Musso
2026-04-11 19:41:51 +02:00
parent 48110f8be6
commit 98f973316d
2 changed files with 171 additions and 93 deletions

View File

@@ -418,21 +418,16 @@
} }
/* ═══════════════════════════════════════ /* ═══════════════════════════════════════
APP WALKTHROUGH — Sticky Scroll APP WALKTHROUGH — Sticky + IntersectionObserver
═══════════════════════════════════════ */ ═══════════════════════════════════════ */
.walkthrough { .walkthrough {
position: relative; position: relative;
z-index: 2; z-index: 2;
height: 100vh; padding: 80px 0 40px;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
} }
.walkthrough-inner { .walkthrough-inner {
display: flex; display: flex;
align-items: center; align-items: flex-start;
justify-content: center;
gap: 64px; gap: 64px;
padding: 0 48px; padding: 0 48px;
max-width: 1200px; max-width: 1200px;
@@ -440,23 +435,43 @@
width: 100%; width: 100%;
} }
/* Text column (left) */ /* Scroll progress line — left edge */
.wt-progress {
position: absolute;
left: 0;
top: 0;
width: 3px;
height: 0%;
background: linear-gradient(180deg, var(--primary), var(--primary-deep));
border-radius: 0 2px 2px 0;
transition: height 0.4s cubic-bezier(0.16, 1, 0.3, 1);
opacity: 0.6;
}
/* Text column — flows naturally, each step creates scroll distance */
.wt-text { .wt-text {
flex: 0 0 320px; flex: 0 0 360px;
position: relative; position: relative;
min-height: 200px;
} }
.wt-step { .wt-step {
position: absolute; min-height: 80vh;
top: 0; display: flex;
left: 0; flex-direction: column;
right: 0; justify-content: center;
opacity: 0; padding: 40px 0;
transform: translateY(20px); opacity: 0.12;
transform: translateY(16px);
filter: blur(1px);
transition: opacity 0.6s cubic-bezier(0.16, 1, 0.3, 1),
transform 0.6s cubic-bezier(0.16, 1, 0.3, 1),
filter 0.6s cubic-bezier(0.16, 1, 0.3, 1);
} }
.wt-step:first-child { padding-top: 0; }
.wt-step:last-child { min-height: 60vh; }
.wt-step.active { .wt-step.active {
opacity: 1; opacity: 1;
transform: none; transform: none;
filter: blur(0px);
} }
.wt-step-num { .wt-step-num {
font-size: 0.68rem; font-size: 0.68rem;
@@ -505,11 +520,13 @@
transform: scale(1.25); transform: scale(1.25);
} }
/* Device column (right) */ /* Device column — sticky, pins below nav while text scrolls */
.wt-device { .wt-device {
flex: 1; flex: 1;
max-width: 640px; max-width: 640px;
position: relative; position: sticky;
top: calc(50vh - 240px);
align-self: flex-start;
} }
.wt-device-frame { .wt-device-frame {
border-radius: 14px; border-radius: 14px;
@@ -520,6 +537,7 @@
0 8px 20px rgba(0,0,0,0.06), 0 8px 20px rgba(0,0,0,0.06),
0 24px 48px rgba(0,0,0,0.08), 0 24px 48px rgba(0,0,0,0.08),
0 48px 80px rgba(0,0,0,0.05); 0 48px 80px rgba(0,0,0,0.05);
position: relative;
} }
.wt-device-frame::before { .wt-device-frame::before {
content: ''; content: '';
@@ -552,7 +570,7 @@
color: #1a1a1a; color: #1a1a1a;
} }
/* Sidebar */ /* Sidebar — CSS-driven reveal */
.wt-sidebar { .wt-sidebar {
width: 44px; width: 44px;
background: #f4f0f4; background: #f4f0f4;
@@ -563,6 +581,14 @@
padding: 10px 0; padding: 10px 0;
gap: 4px; gap: 4px;
opacity: 0; opacity: 0;
transform: translateX(-20px);
transition: opacity 0.5s ease, transform 0.5s ease;
}
.walkthrough[data-step="1"] .wt-sidebar,
.walkthrough[data-step="2"] .wt-sidebar,
.walkthrough[data-step="3"] .wt-sidebar {
opacity: 1;
transform: none;
} }
.wt-sidebar-logo { .wt-sidebar-logo {
width: 24px; width: 24px;
@@ -606,10 +632,20 @@
flex-direction: column; flex-direction: column;
background: #fafafa; background: #fafafa;
} }
/* Greeting — CSS-driven reveal */
.wt-greeting { .wt-greeting {
opacity: 0; opacity: 0;
transform: translateY(-10px);
transition: opacity 0.5s ease 0.1s, transform 0.5s ease 0.1s;
margin-bottom: 4px; margin-bottom: 4px;
} }
.walkthrough[data-step="1"] .wt-greeting,
.walkthrough[data-step="2"] .wt-greeting,
.walkthrough[data-step="3"] .wt-greeting {
opacity: 1;
transform: none;
}
.wt-greeting-label { .wt-greeting-label {
font-size: 14px; font-size: 14px;
color: #8a8ea9; color: #8a8ea9;
@@ -629,18 +665,35 @@
font-size: 28px; font-size: 28px;
line-height: 1; line-height: 1;
} }
/* Brief — CSS-driven reveal */
.wt-brief { .wt-brief {
opacity: 0; opacity: 0;
transform: translateY(10px);
transition: opacity 0.5s ease 0.1s, transform 0.5s ease 0.1s;
margin: 16px 0 20px; margin: 16px 0 20px;
font-size: 13px; font-size: 13px;
line-height: 1.6; line-height: 1.6;
color: #555; color: #555;
max-width: 480px; max-width: 480px;
} }
.walkthrough[data-step="2"] .wt-brief,
.walkthrough[data-step="3"] .wt-brief {
opacity: 1;
transform: none;
}
/* Chat — CSS-driven reveal */
.wt-chat { .wt-chat {
opacity: 0; opacity: 0;
transform: translateY(15px);
transition: opacity 0.5s ease, transform 0.5s ease;
margin-top: auto; margin-top: auto;
} }
.walkthrough[data-step="3"] .wt-chat {
opacity: 1;
transform: none;
}
.wt-chat-input { .wt-chat-input {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -667,13 +720,21 @@
flex-shrink: 0; flex-shrink: 0;
} }
.wt-chat-send i { width: 14px; height: 14px; color: #e5a94e; } .wt-chat-send i { width: 14px; height: 14px; color: #e5a94e; }
/* Suggestions — CSS-driven reveal */
.wt-suggestions { .wt-suggestions {
opacity: 0; opacity: 0;
transform: translateY(15px);
transition: opacity 0.5s ease 0.1s, transform 0.5s ease 0.1s;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 6px; gap: 6px;
margin-top: 12px; margin-top: 12px;
} }
.walkthrough[data-step="3"] .wt-suggestions {
opacity: 1;
transform: none;
}
.wt-suggestion { .wt-suggestion {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -684,7 +745,7 @@
} }
.wt-suggestion i { width: 14px; height: 14px; flex-shrink: 0; } .wt-suggestion i { width: 14px; height: 14px; flex-shrink: 0; }
/* Pillar pills (below device) */ /* Pillar pills — CSS-driven reveal */
.wt-pillars { .wt-pillars {
display: flex; display: flex;
gap: 16px; gap: 16px;
@@ -692,24 +753,62 @@
flex-wrap: wrap; flex-wrap: wrap;
margin-top: 32px; margin-top: 32px;
opacity: 0; opacity: 0;
transform: translateY(10px);
transition: opacity 0.5s ease 0.15s, transform 0.5s ease 0.15s;
}
.walkthrough[data-step="3"] .wt-pillars {
opacity: 1;
transform: none;
} }
/* Walkthrough — Tablet */
@media (max-width: 900px) { @media (max-width: 900px) {
.walkthrough { padding: 40px 0 24px; }
.walkthrough-inner { .walkthrough-inner {
flex-direction: column; flex-direction: column;
gap: 32px; gap: 0;
padding: 80px 20px 24px; padding: 0 20px;
} }
.wt-text { flex: none; width: 100%; min-height: 120px; } .wt-progress { display: none; }
.wt-device { max-width: 100%; } .wt-device {
.wt-app { min-height: 280px; } order: -1;
position: sticky;
top: 72px;
max-width: 100%;
z-index: 1;
padding-bottom: 16px;
}
.wt-text {
flex: none;
width: 100%;
position: relative;
z-index: 2;
}
.wt-step {
min-height: 70vh;
padding: 32px 0;
background: linear-gradient(180deg, rgba(244,237,243,0.95) 0%, rgba(244,237,243,0.8) 80%, transparent 100%);
border-radius: 16px;
padding-left: 16px;
padding-right: 16px;
}
.wt-step:first-child { padding-top: 24px; }
.wt-step.active { filter: blur(0px); }
.wt-app { min-height: 260px; }
.wt-main { padding: 20px 24px; } .wt-main { padding: 20px 24px; }
.wt-greeting-name { font-size: 24px; } .wt-greeting-name { font-size: 24px; }
} }
/* Walkthrough — Mobile */
@media (max-width: 640px) { @media (max-width: 640px) {
.walkthrough-inner { padding: 72px 16px 16px; gap: 20px; } .walkthrough { padding: 24px 0 16px; }
.wt-text { flex: none; } .walkthrough-inner { padding: 0 16px; }
.wt-app { min-height: 240px; font-size: 11px; } .wt-device { top: 68px; }
.wt-step {
min-height: 55vh;
padding: 20px 12px;
}
.wt-app { min-height: 200px; font-size: 11px; }
.wt-main { padding: 16px 18px; } .wt-main { padding: 16px 18px; }
.wt-greeting-name { font-size: 20px; } .wt-greeting-name { font-size: 20px; }
.wt-greeting-name .wt-sparkle { font-size: 18px; } .wt-greeting-name .wt-sparkle { font-size: 18px; }
@@ -935,8 +1034,10 @@
transition-duration: 0.01ms !important; transition-duration: 0.01ms !important;
} }
.gs-reveal { opacity: 1; transform: none; } .gs-reveal { opacity: 1; transform: none; }
.wt-step { opacity: 1 !important; transform: none !important; } .wt-step { opacity: 1 !important; transform: none !important; filter: none !important; }
.wt-sidebar, .wt-greeting, .wt-brief, .wt-chat, .wt-suggestions, .wt-pillars { opacity: 1 !important; transform: none !important; } .wt-sidebar, .wt-greeting, .wt-brief, .wt-chat, .wt-suggestions, .wt-pillars { opacity: 1 !important; transform: none !important; }
.wt-device { position: relative !important; top: auto !important; transform: none !important; }
.wt-progress { display: none !important; }
.particle { display: none; } .particle { display: none; }
} }
</style> </style>
@@ -989,7 +1090,7 @@
<input type="email" name="email" placeholder="your@email.com" required autocomplete="email" aria-label="Email address"> <input type="email" name="email" placeholder="your@email.com" required autocomplete="email" aria-label="Email address">
<button type="submit" class="btn btn-primary">Get early access</button> <button type="submit" class="btn btn-primary">Get early access</button>
</form> </form>
<p class="form-note hero-el">Free to start · No credit card · Early adopters get priority</p> <p class="form-note hero-el">Free to start · No credit card · <a href="./privacy.html" style="color:var(--text-muted);text-decoration:underline;text-underline-offset:2px;">Privacy policy</a></p>
<div class="form-success" id="success-hero" role="status"> <div class="form-success" id="success-hero" role="status">
<div class="check-icon"><i data-lucide="mail" style="color: var(--primary-deep); width:24px; height:24px;"></i></div> <div class="check-icon"><i data-lucide="mail" style="color: var(--primary-deep); width:24px; height:24px;"></i></div>
@@ -1003,15 +1104,9 @@
</div> </div>
</section> </section>
<!-- ═══════ SOCIAL PROOF BAR ═══════ -->
<div class="proof-bar" role="complementary">
Gmail, Outlook &amp; Teams
<span class="sep">·</span>
100% on your device
</div>
<!-- ═══════ APP WALKTHROUGH — Sticky Scroll ═══════ --> <!-- ═══════ APP WALKTHROUGH — Sticky Scroll ═══════ -->
<section class="walkthrough" id="walkthrough"> <section class="walkthrough" id="walkthrough" data-step="0">
<div class="wt-progress" id="wt-progress"></div>
<div class="walkthrough-inner"> <div class="walkthrough-inner">
<!-- Text column --> <!-- Text column -->
@@ -1224,7 +1319,7 @@
<input type="email" name="email" placeholder="your@email.com" required autocomplete="email" aria-label="Email address"> <input type="email" name="email" placeholder="your@email.com" required autocomplete="email" aria-label="Email address">
<button type="submit" class="btn btn-primary">Join the waitlist</button> <button type="submit" class="btn btn-primary">Join the waitlist</button>
</form> </form>
<p class="form-note gs-reveal">No spam, ever. Unsubscribe anytime.</p> <p class="form-note gs-reveal">No spam, ever. <a href="./privacy.html" style="color:var(--text-muted);text-decoration:underline;text-underline-offset:2px;">Privacy policy</a></p>
<div class="form-success" id="success-footer" role="status"> <div class="form-success" id="success-footer" role="status">
<div class="check-icon"><i data-lucide="mail" style="color: var(--primary-deep); width:24px; height:24px;"></i></div> <div class="check-icon"><i data-lucide="mail" style="color: var(--primary-deep); width:24px; height:24px;"></i></div>
@@ -1330,63 +1425,44 @@
}); });
}); });
// ─── Walkthrough: sticky scroll with progressive app reveal ─── // ─── Walkthrough: CSS sticky + IntersectionObserver ───
const wtSection = document.getElementById('walkthrough'); const wtSection = document.getElementById('walkthrough');
const wtSteps = gsap.utils.toArray('.wt-step'); const wtSteps = document.querySelectorAll('.wt-step');
const wtSidebar = document.getElementById('wt-sidebar'); const wtProgress = document.getElementById('wt-progress');
const wtGreeting = document.getElementById('wt-greeting');
const wtBrief = document.getElementById('wt-brief');
const wtChat = document.getElementById('wt-chat');
const wtSuggestions = document.getElementById('wt-suggestions');
const wtPillars = document.getElementById('wt-pillars');
if (wtSection) { if (wtSection && wtSteps.length) {
// Set initial states // Set first step active
gsap.set(wtSteps, { opacity: 0, y: 40 }); wtSteps[0].classList.add('active');
gsap.set(wtSteps[0], { opacity: 1, y: 0 });
gsap.set([wtSidebar], { opacity: 0, x: -30 });
gsap.set([wtGreeting], { opacity: 0, y: -20 });
gsap.set([wtBrief], { opacity: 0, y: 20 });
gsap.set([wtChat], { opacity: 0, y: 30 });
gsap.set([wtSuggestions], { opacity: 0, y: 30 });
gsap.set([wtPillars], { opacity: 0, y: 20 });
const wtTl = gsap.timeline({ // IntersectionObserver for step activation
scrollTrigger: { const stepObserver = new IntersectionObserver((entries) => {
trigger: wtSection, entries.forEach(entry => {
start: 'top top', if (entry.isIntersecting) {
end: () => '+=' + (window.innerHeight * 3), const step = entry.target.getAttribute('data-step');
scrub: 0.6, wtSteps.forEach(s => s.classList.remove('active'));
pin: true, entry.target.classList.add('active');
anticipatePin: 1, wtSection.setAttribute('data-step', step);
invalidateOnRefresh: true, }
}, });
}, {
rootMargin: '-40% 0px -40% 0px',
threshold: 0,
}); });
// Use normalized 0-1 positions so timing scales with any scroll distance wtSteps.forEach(step => stepObserver.observe(step));
wtTl
// Step 0 visible at start → sidebar flies in // Scroll progress indicator
.to(wtSidebar, { opacity: 1, x: 0, duration: 0.15, ease: 'power2.out' }, 0) if (wtProgress) {
.to(wtSteps[0], { opacity: 0, y: -30, duration: 0.08 }, 0.13) const updateProgress = () => {
// Step 1: greeting const rect = wtSection.getBoundingClientRect();
.to(wtSteps[1], { opacity: 1, y: 0, duration: 0.1, ease: 'power2.out' }, 0.18) const sectionH = wtSection.offsetHeight;
.to(wtGreeting, { opacity: 1, y: 0, duration: 0.1, ease: 'power2.out' }, 0.18) const scrolled = -rect.top;
// Hold step 1 const pct = Math.max(0, Math.min(100, (scrolled / (sectionH - window.innerHeight)) * 100));
.to({}, { duration: 0.08 }, 0.28) wtProgress.style.height = pct + '%';
// Step 2: brief };
.to(wtSteps[1], { opacity: 0, y: -30, duration: 0.08 }, 0.36) window.addEventListener('scroll', updateProgress, { passive: true });
.to(wtSteps[2], { opacity: 1, y: 0, duration: 0.1, ease: 'power2.out' }, 0.42) updateProgress();
.to(wtBrief, { opacity: 1, y: 0, duration: 0.1, ease: 'power2.out' }, 0.42) }
// Hold step 2
.to({}, { duration: 0.08 }, 0.52)
// Step 3: chat + suggestions + pillars
.to(wtSteps[2], { opacity: 0, y: -30, duration: 0.08 }, 0.60)
.to(wtSteps[3], { opacity: 1, y: 0, duration: 0.1, ease: 'power2.out' }, 0.66)
.to(wtChat, { opacity: 1, y: 0, duration: 0.08, ease: 'power2.out' }, 0.66)
.to(wtSuggestions, { opacity: 1, y: 0, duration: 0.08, ease: 'power2.out' }, 0.72)
.to(wtPillars, { opacity: 1, y: 0, duration: 0.08, ease: 'power2.out' }, 0.78)
// Hold step 3 visible
.to({}, { duration: 0.14 }, 0.86);
} }
// ─── Generic scroll reveals for gs-reveal ─── // ─── Generic scroll reveals for gs-reveal ───

View File

@@ -259,6 +259,7 @@
<p>We do not sell, rent, or trade your personal information. We share data only with:</p> <p>We do not sell, rent, or trade your personal information. We share data only with:</p>
<ul> <ul>
<li><strong>Stripe</strong> — for payment processing (PCI DSS Level 1 compliant)</li> <li><strong>Stripe</strong> — for payment processing (PCI DSS Level 1 compliant)</li>
<li><strong>Brevo (Sendinblue SAS)</strong> — for transactional emails (waitlist confirmation, product updates). Your email address is shared with Brevo solely to deliver these messages. Brevo acts as a data processor under GDPR and stores data in the EU. <a href="https://www.brevo.com/legal/privacypolicy/">Brevo Privacy Policy</a></li>
<li><strong>LLM providers</strong> (OpenAI, Anthropic) — text snippets for AI processing, under no-training data agreements</li> <li><strong>LLM providers</strong> (OpenAI, Anthropic) — text snippets for AI processing, under no-training data agreements</li>
<li><strong>Cloud infrastructure</strong> (hosting provider) — encrypted data only for cloud backup/sync features</li> <li><strong>Cloud infrastructure</strong> (hosting provider) — encrypted data only for cloud backup/sync features</li>
<li><strong>Law enforcement</strong> — only when required by law, and limited to data we actually possess (account info, not your local content)</li> <li><strong>Law enforcement</strong> — only when required by law, and limited to data we actually possess (account info, not your local content)</li>
@@ -286,7 +287,8 @@
<ul> <ul>
<li><strong>Account data:</strong> Retained while your account is active. Deleted within 30 days of account deletion request.</li> <li><strong>Account data:</strong> Retained while your account is active. Deleted within 30 days of account deletion request.</li>
<li><strong>Encrypted backups:</strong> Deleted within 30 days of account deletion, or on your request.</li> <li><strong>Encrypted backups:</strong> Deleted within 30 days of account deletion, or on your request.</li>
<li><strong>Waitlist emails:</strong> Retained until beta launch, then migrated to account data or deleted.</li> <li><strong>Waitlist (confirmed):</strong> Retained until beta launch, then migrated to account data or deleted on request. Every email includes an unsubscribe link that immediately anonymizes your data.</li>
<li><strong>Waitlist (unconfirmed):</strong> Automatically anonymized after 48 hours. The anonymized record (signup date, source) is retained for aggregate analytics but contains no personal data.</li>
<li><strong>Server logs:</strong> Retained for 90 days, then purged.</li> <li><strong>Server logs:</strong> Retained for 90 days, then purged.</li>
<li><strong>Local data:</strong> Under your control — persists until you delete it or uninstall the app.</li> <li><strong>Local data:</strong> Under your control — persists until you delete it or uninstall the app.</li>
</ul> </ul>