Files
deepstock/index.original.html
2026-05-18 08:48:38 -05:00

3076 lines
145 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0"/>
<title data-i18n="page_title">Plan-B — Survival Preparedness Advisor</title>
<link rel="preconnect" href="https://fonts.googleapis.com"/>
<link href="https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=DM+Serif+Display&family=Barlow:wght@300;400;500;600&display=swap" rel="stylesheet"/>
<style>
:root {
--black: #FAFAFA;
--deep: #F2F2F0;
--panel: #FAFAFA;
--card: #FAFAFA;
--border: #E4E4E0;
--muted: #C8C8C4;
--text-dim: #8A8A84;
--text: #5A5A54;
--bright: #3A3A34;
--white: #1A1A18;
--red: #5A9A78;
--red-dim: #E8F2EC;
--orange: #4A8A68;
--orange-dim: #E8F2EC;
--yellow: #5A9A78;
--green: #5A9A78;
--green-dim: #E8F2EC;
--green-bright: #4A8A68;
--teal: #5A9090;
--accent: #5A9A78;
--accent-dim: #E8F2EC;
--font-display: 'DM Serif Display', serif;
--font-body: 'Barlow', sans-serif;
--font-mono: 'Space Mono', monospace;
--radius: 10px;
--radius-lg: 16px;
--trans: all 0.25s cubic-bezier(0.4,0,0.2,1);
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; -webkit-tap-highlight-color: transparent;
-webkit-overflow-scrolling: touch; }
body { background: #FAFAFA; color: var(--text); font-family: var(--font-body); font-size: 16px; line-height: 1.6; height: 100vh; overflow: hidden; }
/* ── HEADER ── */
.site-header {
position: fixed; top: 0; left: 0; right: 0; z-index: 100;
background: rgba(250,250,250,0.92);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border-bottom: 1px solid var(--border);
padding: 0 24px;
height: 60px;
display: flex; align-items: center; justify-content: space-between;
}
@media (min-width: 768px) {
.site-header { padding: 0 28px; }
}
.logo {
font-family: var(--font-display);
font-weight: 400;
font-size: 20px;
letter-spacing: 0.02em;
color: var(--white);
}
.logo .b { color: var(--red); }
.header-right { display: flex; align-items: center; gap: 12px; }
.lang-toggle {
display: flex;
background: var(--panel);
border: 1px solid var(--border);
border-radius: 0;
overflow: hidden;
}
.lang-btn {
padding: 5px 12px;
border: none;
background: none;
font-family: var(--font-mono);
font-size: 11px;
font-weight: 700;
letter-spacing: 0.1em;
color: var(--text-dim);
cursor: pointer;
transition: var(--trans);
}
.lang-btn.active { background: var(--red); color: #FFFFFF; }
/* ── APP ── */
.app { padding-top: 0; height: 100vh; display: flex; flex-direction: column; overflow: hidden; }
/* ── HERO ── */
.hero {
position: relative;
/* asymmetric vertical padding shifts the centered content past the fixed header
and compensates for the scenario-strip below — net effect: visually centered on the viewport */
padding: 100px 16px 24px;
text-align: center;
background: url('public/images/bg-1.jpg') center/cover no-repeat;
overflow: hidden;
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.hero-eyebrow {
position: relative; z-index: 1;
font-family: var(--font-body);
font-size: 11px;
letter-spacing: 0.3em;
text-transform: uppercase;
color: #0a0a0a;
margin-bottom: 12px;
animation: fadeUp 1s ease both;
font-weight: 400;
}
.hero h1 {
position: relative; z-index: 1;
font-family: var(--font-display);
font-weight: 400;
/* Mobile (default): fill viewport minus 16px gutters each side.
Divisor 5 ≈ measured ratio of "Plan-B" rendered width to font-size
in DM Serif Display at 0.05em tracking. Tweak if the text overflows
or under-fills its container. */
/* Divisor 7 leaves enough horizontal room for the wider "Plan-B"
wordmark without overflow at the mobile/tablet breakpoint
transition (~704px was clipping at /6). */
font-size: calc((100vw - 48px) / 7);
line-height: 1;
letter-spacing: 0.05em;
color: #1A1A18;
margin-bottom: 40px;
animation: fadeUp 1s 0.15s ease both;
}
/* Mobile — soften the bg image to 50% by overlaying the body fill */
@media (max-width: 767px) {
.hero {
background: linear-gradient(rgba(250,250,250,0.5), rgba(250,250,250,0.5)), url('public/images/bg-1.jpg') center/cover no-repeat;
}
}
/* Tablet — 32px gutters each side */
@media (min-width: 768px) {
.hero h1 { font-size: calc((100vw - 64px) / 7); }
}
@media (min-width: 900px) {
.hero h1 { font-size: clamp(120px, 14vw, 176px); }
}
.hero h1 .b { color: #5A9A78; }
/* Trademark mark — only auto-attached to the small header logo. The
hero H1 uses an explicit sibling .brand-tm span instead, since CSS
filters on the H1 (paint-3d) would otherwise pull ™ into the gloss. */
.logo[data-i18n="brand"]::after {
content: "™";
font-size: 0.4em;
vertical-align: super;
letter-spacing: 0;
}
/* Hero brand line — flex wrapper so ™ can sit next to the painted H1
without being inside the H1's filter scope. The wrapper takes over
the H1's bottom margin and entrance animation so the line moves
together. */
.hero .brand-line {
display: inline-block;
white-space: nowrap;
position: relative; z-index: 1;
margin: 0 auto 40px;
animation: fadeUp 1s 0.15s ease both;
}
.hero .brand-line h1.paint-3d {
display: inline-block;
vertical-align: top;
margin-bottom: 0;
animation: none;
}
.brand-tm {
display: inline-block;
vertical-align: super;
font-family: var(--font-display);
font-weight: 400;
/* 1/5 of H1 size to match the small superscript scale used elsewhere */
font-size: calc((100vw - 32px) / 25);
line-height: 1;
color: #1A1A18;
letter-spacing: 0;
margin-left: 0.05em;
}
@media (min-width: 768px) {
.brand-tm { font-size: calc((100vw - 64px) / 25); }
}
@media (min-width: 900px) {
.brand-tm { font-size: clamp(24px, 2.8vw, 35px); }
}
/* Glossy 3D paint effect on the brand title */
.hero h1.paint-3d {
filter: url(#paintGloss);
-webkit-filter: url(#paintGloss);
/* subtle vertical sheen — top catches light, bottom sinks back into the paint */
background: linear-gradient(180deg, #2a2a26 0%, #1A1A18 45%, #060604 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
color: transparent;
padding: 0.12em 0.08em 0.2em;
transition: filter 0.6s ease;
}
.hero h1.paint-3d:hover {
filter: url(#paintGlossHover);
-webkit-filter: url(#paintGlossHover);
}
@media (prefers-reduced-motion: reduce) {
.hero h1.paint-3d { transition: none; }
}
.hero-sub {
position: relative; z-index: 1;
font-family: var(--font-body);
font-size: 13px;
letter-spacing: 0.3em;
text-transform: uppercase;
font-weight: 400;
color: #0a0a0a;
max-width: 520px;
margin: 0 auto 40px;
line-height: 1.6;
animation: fadeUp 1s 0.3s ease both;
}
.cta-btn {
display: inline-flex;
align-items: center;
gap: 10px;
font-family: var(--font-body);
font-weight: 400;
font-size: 13px;
letter-spacing: 0.2em;
text-transform: uppercase;
padding: 18px 48px;
border-radius: 0;
cursor: pointer;
transition: color 0.3s ease, var(--trans);
animation: fadeUp 1s 0.45s ease both;
/* Lift above .hero::after radial fade so the button isn't washed out */
z-index: 2;
}
.cta-btn span { position: relative; }
/* ── EMAIL CAPTURE ── */
.email-section {
background: var(--panel);
border-bottom: 1px solid var(--border);
padding: 28px 24px;
}
.email-inner {
max-width: 540px;
margin: 0 auto;
}
.email-label {
font-family: var(--font-mono);
font-size: 10px;
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--red);
margin-bottom: 8px;
}
.email-title {
font-family: var(--font-display);
font-weight: 800;
font-size: 20px;
color: var(--white);
margin-bottom: 6px;
}
.email-sub {
font-size: 13px;
color: var(--text-dim);
margin-bottom: 16px;
line-height: 1.6;
}
.email-form {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.email-input {
flex: 1;
min-width: 200px;
padding: 13px 16px;
background: var(--card);
border: 1.5px solid var(--border);
border-radius: var(--radius);
color: var(--white);
font-family: var(--font-body);
font-size: 15px;
outline: none;
transition: var(--trans);
}
.email-input:focus { border-color: var(--red); }
.email-input::placeholder { color: var(--muted); }
.email-submit {
padding: 13px 24px;
background: var(--red);
border: none;
border-radius: var(--radius);
color: #0C0C0E;
font-family: var(--font-body);
font-weight: 600;
font-size: 14px;
letter-spacing: 0.1em;
text-transform: uppercase;
cursor: pointer;
transition: var(--trans);
white-space: nowrap;
}
.email-submit:hover { background: #7AB498; }
.email-success {
display: none;
padding: 14px 20px;
background: var(--green-dim);
border: 1px solid var(--green);
border-radius: var(--radius);
color: var(--green-bright);
font-size: 14px;
font-weight: 600;
margin-top: 10px;
}
.email-success.show { display: block; }
.email-note {
font-size: 11px;
color: var(--muted);
margin-top: 10px;
}
/* ── SCENARIO STRIP ── */
.scenario-strip {
display: flex;
overflow-x: auto;
gap: 12px;
padding: 20px 24px;
background: var(--panel);
border-bottom: 1px solid var(--border);
-webkit-overflow-scrolling: touch;
scrollbar-width: none;
}
.scenario-strip::-webkit-scrollbar { display: none; }
.scenario-pill {
flex-shrink: 0;
display: flex;
align-items: center;
gap: 8px;
padding: 10px 18px;
border-radius: 40px;
border: 1px solid rgba(255,255,255,0.06);
font-family: var(--font-body);
font-weight: 500;
font-size: 13px;
letter-spacing: 0.03em;
white-space: nowrap;
color: var(--text-dim);
background: rgba(255,255,255,0.02);
}
.pill-1 { border-color: rgba(90,154,120,0.2); color: var(--red); }
.pill-2 { border-color: rgba(90,154,120,0.15); color: var(--text); }
.pill-3 { border-color: rgba(90,154,120,0.15); color: var(--text); }
.pill-4 { border-color: rgba(106,170,138,0.2); color: var(--green); }
.pill-dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; opacity: 0.6; }
/* ── QUESTIONNAIRE ── */
.quiz-section {
width: 100%;
min-height: 100vh;
display: flex;
flex-direction: column;
}
.quiz-section.hidden { display: none !important; }
.quiz-progress-bar {
position: fixed;
top: 0;
left: 0; right: 0;
background: rgba(250,250,250,0.92);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border-bottom: 1px solid var(--border);
/* Match .site-header dimensions: 60px tall, 24px mobile / 28px desktop padding */
height: 60px;
padding: 0 24px;
z-index: 110;
}
@media (min-width: 768px) {
.quiz-progress-bar { padding: 0 28px; }
}
body.quiz-active .site-header { display: none; }
.quiz-progress-bar .progress-wrap { max-width: 540px; margin: 0 auto; }
.quiz-progress-bar .progress-header { margin-bottom: 6px; }
.quiz-content {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
padding: 80px 24px 110px;
width: 100%;
}
.quiz-content #question-container {
width: 100%;
max-width: 540px;
margin: 0 auto;
}
.quiz-footer {
position: fixed;
bottom: 0;
left: 0; right: 0;
background: rgba(250,250,250,0.92);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border-top: 1px solid var(--border);
padding: 14px 24px max(14px, env(safe-area-inset-bottom));
z-index: 90;
}
.quiz-footer .q-nav { max-width: 540px; margin: 0 auto; }
.quiz-footer:empty { display: none; }
.progress-wrap { margin-bottom: 0; }
.progress-header { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 10px; }
.progress-label { font-family: var(--font-mono); font-size: 11px; color: var(--text-dim); letter-spacing: 0.1em; text-transform: uppercase; }
.progress-count { font-family: var(--font-mono); font-size: 13px; color: var(--bright); }
.progress-bar { height: 3px; background: var(--muted); border-radius: 99px; overflow: hidden; }
.progress-fill { height: 100%; background: var(--red); border-radius: 99px; transition: width 0.5s cubic-bezier(0.4,0,0.2,1); }
.question-card {
background: var(--card);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
padding: 28px 24px;
animation: fadeUp 0.35s ease both;
}
.q-step { font-family: var(--font-mono); font-size: 10px; color: var(--red); letter-spacing: 0.15em; text-transform: uppercase; margin-bottom: 12px; }
.q-text { font-family: var(--font-display); font-weight: 400; font-size: 24px; color: var(--white); line-height: 1.3; margin-bottom: 8px; }
.q-sub { font-size: 13px; color: var(--text-dim); line-height: 1.6; margin-bottom: 24px; }
.q-options { display: flex; flex-direction: column; gap: 10px; }
.q-opt {
display: flex; align-items: center; gap: 14px;
padding: 14px 16px;
background: var(--panel);
border: 1.5px solid var(--border);
border-radius: var(--radius);
cursor: pointer;
transition: var(--trans);
-webkit-tap-highlight-color: transparent;
-webkit-overflow-scrolling: touch;
}
.q-opt:hover { border-color: rgba(255,255,255,0.08); background: var(--deep); }
.q-opt.selected { border-color: var(--red); background: rgba(90,154,120,0.06); }
.q-opt.selected .opt-radio { border-color: var(--red); }
.q-opt.selected .opt-radio::after { display: block; }
.opt-radio {
width: 18px; height: 18px; border-radius: 50%;
border: 1.5px solid var(--muted);
flex-shrink: 0; position: relative; transition: var(--trans);
}
.opt-radio::after {
content: ''; display: none;
position: absolute; top: 50%; left: 50%;
transform: translate(-50%,-50%);
width: 8px; height: 8px;
background: var(--red); border-radius: 50%;
}
.opt-label { font-size: 15px; color: var(--bright); line-height: 1.4; }
.opt-sub { font-size: 12px; color: var(--text-dim); margin-top: 2px; }
.q-opt.multi .opt-radio { border-radius: 4px; }
.q-opt.multi.selected .opt-radio { border-color: var(--red); background: var(--red); border-radius: 4px; }
.q-opt.multi.selected .opt-radio::after {
display: block; content: '✓'; color: white; font-size: 11px; font-weight: bold;
background: none; width: auto; height: auto; top: 50%; left: 50%;
transform: translate(-50%,-55%); border-radius: 0;
}
.slider-wrap { padding: 8px 0; }
.slider-labels { display: flex; justify-content: space-between; margin-bottom: 12px; }
.slider-labels span { font-size: 12px; color: var(--text-dim); font-family: var(--font-mono); }
input[type=range] { width: 100%; -webkit-appearance: none; appearance: none; height: 4px; background: var(--muted); border-radius: 99px; outline: none; cursor: pointer; }
input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; width: 22px; height: 22px; border-radius: 50%; background: var(--red); cursor: pointer; box-shadow: 0 2px 16px rgba(90,154,120,0.3); }
.slider-val { text-align: center; font-family: var(--font-display); font-weight: 800; font-size: 32px; color: var(--white); margin-top: 16px; }
.slider-val span { font-size: 18px; color: var(--text-dim); margin-left: 4px; }
.q-nav { display: flex; gap: 12px; }
.btn-back { flex: 1; height: 52px; background: var(--panel); border: 1px solid var(--border); border-radius: 0; color: var(--text-dim); font-family: var(--font-body); font-weight: 600; font-size: 14px; letter-spacing: 0.1em; text-transform: uppercase; cursor: pointer; transition: var(--trans); display: flex; align-items: center; justify-content: center; }
.btn-back:hover { border-color: var(--muted); color: var(--text); }
.btn-next { flex: 1; height: 52px; background: var(--red); border: none; border-radius: 0; color: #FFFFFF; font-family: var(--font-body); font-weight: 600; font-size: 14px; letter-spacing: 0.1em; text-transform: uppercase; cursor: pointer; transition: var(--trans); box-shadow: 0 2px 20px rgba(90,154,120,0.15); }
.btn-next:hover { background: #C8A870; }
.btn-next:disabled { background: var(--muted); box-shadow: none; cursor: not-allowed; color: var(--deep); }
/* ── RESULTS ── */
.results-section { padding: 24px 20px 80px; max-width: 600px; margin: 0 auto; width: 100%; display: none; }
.results-section.active { display: block; }
.risk-banner { padding: 24px; border-radius: var(--radius-lg); margin-bottom: 24px; text-align: center; animation: fadeUp 0.4s ease both; }
.risk-level { font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.2em; text-transform: uppercase; margin-bottom: 8px; opacity: 0.8; }
.risk-title { font-family: var(--font-display); font-weight: 900; font-size: 36px; letter-spacing: -0.01em; line-height: 1; margin-bottom: 8px; }
.risk-desc { font-size: 14px; opacity: 0.85; line-height: 1.6; }
.risk-critical { background: var(--red-dim); border: 1px solid rgba(90,154,120,0.35); color: var(--white); }
.risk-high { background: var(--orange-dim); border: 1px solid rgba(74,138,104,0.35); color: var(--white); }
.risk-medium { background: var(--accent-dim); border: 1px solid rgba(90,154,120,0.25); color: var(--white); }
.risk-low { background: var(--green-dim); border: 1px solid rgba(90,154,120,0.35); color: var(--white); }
.scenario-tabs { display: flex; gap: 0; background: var(--panel); border: 1px solid var(--border); border-radius: 0; padding: 4px; margin-bottom: 24px; overflow: hidden; animation: fadeUp 0.4s 0.1s ease both; }
.s-tab { flex: 1; padding: 10px 6px; border: none; background: none; border-radius: 0; font-family: var(--font-display); font-weight: 700; font-size: 12px; letter-spacing: 0.04em; color: var(--text-dim); cursor: pointer; transition: var(--trans); text-align: center; line-height: 1.3; }
.s-tab.active-s1 { background: rgba(90,154,120,0.1); color: var(--red); }
.s-tab.active-s2 { background: rgba(184,152,106,0.1); color: var(--orange); }
.s-tab.active-s3 { background: rgba(90,154,120,0.08); color: var(--yellow); }
.s-tab.active-s4 { background: rgba(106,170,138,0.1); color: var(--green-bright); }
.rec-cards { animation: fadeUp 0.4s 0.15s ease both; }
.rec-card { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-lg); margin-bottom: 16px; overflow: hidden; }
.rec-header { padding: 16px 20px; display: flex; align-items: center; gap: 12px; border-bottom: 1px solid var(--border); }
.rec-icon { width: 40px; height: 40px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 20px; flex-shrink: 0; }
.rec-cat { font-family: var(--font-mono); font-size: 10px; color: var(--text-dim); letter-spacing: 0.12em; text-transform: uppercase; }
.rec-title { font-family: var(--font-display); font-weight: 400; font-size: 18px; color: var(--white); line-height: 1.2; }
.priority-badge { margin-left: auto; padding: 4px 10px; border-radius: 99px; font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.1em; font-weight: 700; flex-shrink: 0; }
.p-critical { background: rgba(90,154,120,0.08); color: var(--red); border: 1px solid rgba(90,154,120,0.2); }
.p-high { background: rgba(184,152,106,0.08); color: var(--orange); border: 1px solid rgba(184,152,106,0.2); }
.p-medium { background: rgba(90,154,120,0.06); color: var(--yellow); border: 1px solid rgba(90,154,120,0.15); }
.rec-body { padding: 16px 20px; }
.rec-items { display: flex; flex-direction: column; gap: 12px; }
.rec-item { display: flex; flex-direction: column; gap: 8px; padding-bottom: 12px; border-bottom: 1px solid var(--border); }
.rec-item:last-child { border-bottom: none; padding-bottom: 0; }
.item-name { font-weight: 600; font-size: 15px; color: var(--bright); }
.item-why { font-size: 13px; color: var(--text-dim); line-height: 1.55; }
.item-cost { font-family: var(--font-mono); font-size: 13px; color: var(--green-bright); }
.affiliate-btn { display: inline-flex; align-items: center; gap: 6px; padding: 8px 14px; background: var(--accent-dim); border: 1px solid var(--accent); border-radius: 0; color: #88BBFF; font-size: 12px; font-weight: 600; text-decoration: none; cursor: pointer; transition: var(--trans); width: fit-content; }
.affiliate-btn:hover { background: rgba(74,143,224,0.2); }
.budget-meter { background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 20px; margin-bottom: 20px; animation: fadeUp 0.4s 0.2s ease both; }
.bm-title { font-family: var(--font-display); font-weight: 700; font-size: 16px; color: var(--white); margin-bottom: 16px; }
.budget-cats { display: flex; flex-direction: column; gap: 10px; }
.bcat { display: flex; align-items: center; gap: 10px; }
.bcat-label { font-size: 13px; color: var(--text-dim); width: 100px; flex-shrink: 0; }
.bcat-bar-wrap { flex: 1; height: 6px; background: var(--muted); border-radius: 99px; overflow: hidden; }
.bcat-bar { height: 100%; border-radius: 99px; transition: width 0.8s cubic-bezier(0.4,0,0.2,1); }
.bcat-cost { font-family: var(--font-mono); font-size: 12px; color: var(--bright); width: 60px; text-align: right; flex-shrink: 0; }
.timeline { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 20px; margin-bottom: 20px; animation: fadeUp 0.4s 0.25s ease both; }
.tl-title { font-family: var(--font-display); font-weight: 700; font-size: 16px; color: var(--white); margin-bottom: 16px; }
.tl-items { display: flex; flex-direction: column; gap: 0; }
.tl-item { display: flex; gap: 16px; padding-bottom: 16px; position: relative; }
.tl-item:last-child { padding-bottom: 0; }
.tl-item:not(:last-child)::before { content: ''; position: absolute; left: 15px; top: 32px; bottom: 0; width: 2px; background: var(--border); }
.tl-dot { width: 32px; height: 32px; border-radius: 50%; background: var(--panel); border: 2px solid; display: flex; align-items: center; justify-content: center; font-size: 14px; flex-shrink: 0; position: relative; z-index: 1; }
.tl-when { font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--text-dim); margin-bottom: 4px; }
.tl-action { font-size: 14px; color: var(--bright); line-height: 1.5; }
.tl-cost { font-family: var(--font-mono); font-size: 12px; color: var(--green-bright); margin-top: 4px; }
/* Results email capture */
.results-email {
background: linear-gradient(135deg, var(--red-dim), #141210);
border: 1px solid rgba(90,154,120,0.2);
border-radius: var(--radius-lg);
padding: 24px;
margin-bottom: 20px;
animation: fadeUp 0.4s 0.3s ease both;
}
.results-email .email-label { color: var(--red); }
.results-email .email-title { font-size: 18px; }
.restart-btn { width: 100%; padding: 16px; background: var(--panel); border: 1px solid var(--border); border-radius: 0; color: var(--text-dim); font-family: var(--font-display); font-weight: 700; font-size: 16px; letter-spacing: 0.06em; text-transform: uppercase; cursor: pointer; transition: var(--trans); margin-top: 8px; }
.restart-btn:hover { border-color: var(--muted); color: var(--text); }
/* ── ABOUT ── */
.about-section { background: var(--deep); border-top: 1px solid rgba(255,255,255,0.04); padding: 56px 24px; text-align: center; }
.about-section h2 { font-family: var(--font-display); font-weight: 400; font-size: 26px; color: var(--white); margin-bottom: 14px; }
.about-section p { font-size: 14px; color: var(--text-dim); max-width: 380px; margin: 0 auto 20px; line-height: 1.8; font-weight: 300; }
.affiliate-note { font-family: var(--font-mono); font-size: 10px; color: var(--muted); letter-spacing: 0.08em; line-height: 1.8; }
@keyframes fadeUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
.hidden { display: none !important; }
/* ── SINGLE FOLD: hide non-essential sections on load ──
(header stays visible with a transparent background on the homepage) */
.site-header { background: transparent; backdrop-filter: none; -webkit-backdrop-filter: none; border-bottom: none; }
.scenario-strip,
.email-section,
.about-section { display: none !important; }
/* Language switcher — wears the white-paint look (#F0F0F0 fill, soft
light-grey ambient shadow + faint top highlight to match the H1
white paint). Inner buttons stay transparent so the fill reads as
one unit. */
.lang-toggle {
background: #F0F0F0 !important;
border: 1px solid rgba(0,0,0,0.06) !important;
border-radius: 4px;
box-shadow: 0 3px 5px rgba(0,0,0,0.07), inset 0 1px 0 rgba(255,255,255,0.6);
overflow: hidden;
}
.lang-btn { background: transparent !important; color: var(--text-dim); }
.lang-btn.active { background: transparent !important; color: var(--red); }
/* ── CAPTURE FORM ── */
/* Capture form container — wears the same white-paint look as the inner
selectors, but a touch whiter (#FAFAFA vs #F0F0F0) so it reads as the
outer container while keeping the H1's dy=7 / blur≈10 / 0.12 alpha drop
and faint inset top highlight for the gloss feel. */
.capture-form-wrap{background:#FAFAFA;border:1px solid rgba(0,0,0,0.06);border-radius:var(--radius-lg);padding:28px 24px;margin-bottom:20px;animation:fadeUp 0.4s 0.3s ease both;box-shadow:0 7px 10.6px rgba(0,0,0,0.12), inset 0 1px 0 rgba(255,255,255,0.7);}
/* Country/region indicator hidden — not needed in this build. */
#region-indicator { display: none !important; }
/* ── Paint variants (ported from paint.html for in-page experimentation) ── */
body.paint-green .hero h1.paint-3d {
background: #2a3010;
-webkit-background-clip: text;
background-clip: text;
}
body.paint-green .cta-btn::before {
background: #2a3010;
}
body.paint-white .hero h1.paint-3d {
filter: url(#paintGlossWhite);
-webkit-filter: url(#paintGlossWhite);
background: #F0F0F0;
-webkit-background-clip: text;
background-clip: text;
}
body.paint-white .hero h1.paint-3d:hover {
filter: url(#paintGlossWhiteHover);
-webkit-filter: url(#paintGlossWhiteHover);
}
body.paint-white .cta-btn { color: #1A1A18; }
body.paint-white .cta-btn:hover { color: #060604; }
body.paint-white .cta-btn::before {
background: #F0F0F0;
filter: url(#paintGlossWhiteBtn);
-webkit-filter: url(#paintGlossWhiteBtn);
}
/* Paint picker UI — top-right swatches */
.paint-picker { display: flex; gap: 8px; align-items: center; }
.paint-swatch {
width: 28px; height: 28px;
border-radius: 50%;
border: 1px solid var(--border);
cursor: pointer;
padding: 0;
transition: transform 0.15s ease;
}
.paint-swatch:hover { transform: scale(1.08); }
.paint-swatch.swatch-dark { background: linear-gradient(180deg, #2a2a26, #060604); }
.paint-swatch.swatch-green { background: #2a3010; }
.paint-swatch.swatch-white { background: linear-gradient(180deg, #FFFFFF, #DDDDDD); }
.paint-swatch.active {
outline: 2px solid var(--red);
outline-offset: 2px;
}
/* Modifier toggle ⚙ — wears the same white-paint look as the lang-toggle */
.mod-open-btn {
background: #F0F0F0;
border: 1px solid rgba(0,0,0,0.06);
border-radius: 4px;
box-shadow: 0 7px 10.6px rgba(0,0,0,0.12), inset 0 1px 0 rgba(255,255,255,0.7);
width: 28px; height: 28px;
cursor: pointer;
font-size: 14px;
line-height: 1;
padding: 0;
color: var(--text-dim);
transition: var(--trans);
}
.mod-open-btn:hover { color: var(--text); }
/* Quiz progress bar — flex row with logo (left), progress (center), lang (right).
Logo matches the site-header .logo: same 20px DM Serif Display. */
.quiz-progress-bar { display: flex; align-items: center; gap: 16px; }
@media (min-width: 768px) {
.quiz-progress-bar { gap: 32px; }
}
.quiz-progress-bar .qpb-logo {
font-family: var(--font-display);
font-weight: 400;
font-size: 20px;
letter-spacing: 0.02em;
color: var(--white);
white-space: nowrap;
text-decoration: none;
}
.quiz-progress-bar .progress-wrap { flex: 1; max-width: none; margin: 0; }
.quiz-progress-bar .lang-toggle { flex-shrink: 0; }
@media (max-width: 767px) {
.quiz-progress-bar .lang-toggle { display: none; }
}
/* Active language button — bolded red so the current selection stands
out against the white-paint container fill. */
.lang-btn.active { font-weight: 800 !important; color: var(--red) !important; }
/* Outer question-card wears the glossy white-paint look at #FAFAFA —
a touch whiter than the inner .q-opt (#F0F0F0) so it reads as the
surrounding container while keeping the same drop-shadow + inset
top highlight as .capture-form-wrap. */
.question-card {
background: #FAFAFA !important;
border: 1px solid rgba(0,0,0,0.06) !important;
box-shadow: 0 7px 10.6px rgba(0,0,0,0.12), inset 0 1px 0 rgba(255,255,255,0.7);
}
/* Inner selection options wear the white-paint look: #F0F0F0 fill,
soft light-grey ambient shadow + faint inset top highlight.
Shadow kept lighter than the outer .question-card so the depth
reads as outer-elevated > inner-resting. */
.q-opt {
background: #F0F0F0 !important;
border: 1px solid rgba(0,0,0,0.06) !important;
box-shadow: 0 3px 6px rgba(0,0,0,0.06), inset 0 1px 0 rgba(255,255,255,0.7);
}
.q-opt:hover { border-color: rgba(0,0,0,0.12) !important; background: #F4F4F4 !important; }
.q-opt.selected { border-color: var(--red) !important; }
/* Modifier panel — hidden by default, opens when body has .mod-open */
.mod-panel {
position: fixed;
top: 72px;
right: 12px;
width: 280px;
max-height: calc(100vh - 84px);
overflow-y: auto;
background: rgba(255,255,255,0.96);
border: 1px solid var(--border);
border-radius: 6px;
box-shadow: 0 8px 32px rgba(0,0,0,0.08);
z-index: 200;
font-family: var(--font-mono);
font-size: 11px;
padding: 8px;
display: none;
}
body.mod-open .mod-panel { display: block; }
.mod-header {
display: flex; align-items: center; justify-content: space-between;
padding: 4px 8px;
border-bottom: 1px solid var(--border);
margin-bottom: 8px;
}
.mod-header h3 {
font-family: var(--font-display);
font-size: 14px; font-weight: 400; margin: 0;
}
.mod-paint-label {
font-family: var(--font-mono); font-size: 10px;
color: var(--red); letter-spacing: 0.1em;
text-transform: uppercase; margin-left: 8px;
}
.mod-toggle {
background: none; border: 1px solid var(--border); border-radius: 3px;
width: 22px; height: 22px; cursor: pointer; font-size: 14px; line-height: 1;
padding: 0;
}
.mod-fieldset {
border: 1px solid var(--border); border-radius: 4px;
padding: 6px 8px; margin: 0 0 8px;
}
.mod-fieldset legend {
padding: 0 4px;
font-weight: 600; font-size: 10px;
letter-spacing: 0.05em; text-transform: uppercase;
color: var(--text-dim);
}
.mod-row {
display: grid;
grid-template-columns: 56px 1fr 38px;
gap: 6px; align-items: center; margin: 4px 0;
}
.mod-row > span:first-child {
font-size: 10px; color: var(--text-dim);
}
.mod-row input[type="range"] { width: 100%; }
.mod-row input[type="color"] {
width: 100%; height: 22px; padding: 0;
border: 1px solid var(--border); border-radius: 3px;
grid-column: 2 / span 2;
}
.mod-row output {
font-size: 10px; text-align: right;
font-variant-numeric: tabular-nums;
}
.mod-actions {
display: flex; align-items: center; gap: 8px;
padding-top: 8px; border-top: 1px solid var(--border);
}
.mod-actions button {
flex: 1; padding: 6px 12px;
background: var(--red); color: #fff;
border: none; border-radius: 3px; cursor: pointer;
font-family: var(--font-mono); font-size: 11px;
font-weight: 600; text-transform: uppercase; letter-spacing: 0.1em;
}
.mod-copy-status {
font-size: 10px; color: var(--green); white-space: nowrap;
}
.mod-fieldset[data-hide] { display: none; }
.capture-label{font-family:var(--font-mono);font-size:10px;color:var(--red);letter-spacing:0.15em;text-transform:uppercase;margin-bottom:12px;display:block;}
.capture-title{font-family:var(--font-display);font-weight:900;font-size:24px;color:var(--white);margin-bottom:6px;}
.capture-sub{font-size:13px;color:var(--text-dim);line-height:1.6;margin-bottom:20px;}
.form-grid{display:flex;flex-direction:column;gap:12px;}
.form-row{display:flex;gap:10px;flex-wrap:wrap;}
.form-field{display:flex;flex-direction:column;gap:5px;flex:1;min-width:140px;}
.field-label{font-size:11px;color:var(--text-dim);font-family:var(--font-mono);letter-spacing:0.06em;text-transform:uppercase;}
.form-input,.form-select{width:100%;padding:12px 14px;background:var(--card);border:1.5px solid var(--border);border-radius:var(--radius);color:var(--white);font-family:var(--font-body);font-size:14px;outline:none;transition:var(--trans);}
.form-input:focus,.form-select:focus{border-color:rgba(90,154,120,0.4);}
.form-input::placeholder{color:var(--muted);}
.form-select{cursor:pointer;-webkit-appearance:none;appearance:none;}
.form-select option{background:var(--card);color:var(--white);}
.form-input[rows]{resize:vertical;min-height:80px;line-height:1.5;}
.capture-submit{width:100%;padding:16px;background:var(--red);border:none;border-radius:0;color:#0C0C0E;font-family:var(--font-body);font-weight:600;font-size:14px;letter-spacing:0.1em;text-transform:uppercase;cursor:pointer;transition:var(--trans);margin-top:4px;box-shadow:0 4px 24px rgba(90,154,120,0.2);}
.capture-submit:hover{background:#7AB498;transform:translateY(-1px);}
/* ── NARRATIVE SECTION ── */
.narrative-section {
background: var(--card);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
margin-bottom: 24px;
overflow: hidden;
animation: fadeUp 0.4s ease both;
}
.narrative-header {
padding: 16px 20px;
background: rgba(90,154,120,0.03);
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
gap: 10px;
}
.narrative-title {
font-family: var(--font-display);
font-weight: 800;
font-size: 17px;
color: var(--white);
}
.narrative-tag {
margin-left: auto;
font-family: var(--font-mono);
font-size: 10px;
color: var(--red);
letter-spacing: 0.12em;
text-transform: uppercase;
border: 1px solid var(--red-dim);
padding: 3px 8px;
border-radius: 4px;
}
.narrative-body {
padding: 20px 24px;
font-size: 14px;
line-height: 1.75;
color: var(--text);
}
.narrative-body h4 {
font-family: var(--font-display);
font-weight: 800;
font-size: 16px;
color: var(--white);
margin: 20px 0 8px;
letter-spacing: 0.02em;
}
.narrative-body h4:first-child { margin-top: 0; }
.narrative-body p { margin: 0 0 12px; }
.narrative-body ul { margin: 8px 0 12px; padding-left: 20px; }
.narrative-body li { margin-bottom: 6px; }
.narrative-body strong { color: var(--bright); }
.narrative-loading {
padding: 32px 24px;
text-align: center;
}
.loading-dots {
display: inline-flex;
gap: 6px;
margin-bottom: 12px;
}
.loading-dots span {
width: 6px; height: 6px;
background: var(--red);
border-radius: 50%;
animation: bounce 1.4s infinite;
}
.loading-dots span:nth-child(2) { animation-delay: 0.2s; }
.loading-dots span:nth-child(3) { animation-delay: 0.4s; }
@keyframes bounce {
0%, 80%, 100% { transform: scale(0.6); opacity: 0.4; }
40% { transform: scale(1); opacity: 1; }
}
.loading-text {
font-family: var(--font-mono);
font-size: 11px;
color: var(--text-dim);
letter-spacing: 0.1em;
text-transform: uppercase;
}
.scroll-hint {
text-align: center;
padding: 12px;
font-family: var(--font-mono);
font-size: 11px;
color: var(--text-dim);
letter-spacing: 0.1em;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 0.4; }
50% { opacity: 1; }
}
/* ── REQUIRED BADGE ── */
.capture-required-badge {
background: rgba(90,154,120,0.06);
border: 1px solid rgba(90,154,120,0.15);
border-radius: var(--radius);
padding: 10px 14px;
font-family: var(--font-mono);
font-size: 10px;
color: var(--red);
letter-spacing: 0.1em;
text-transform: uppercase;
margin-bottom: 20px;
text-align: center;
}
/* ── NEWSLETTER CHECKBOX ── */
.newsletter-check {
display: flex;
align-items: flex-start;
gap: 12px;
cursor: pointer;
padding: 12px 14px;
background: var(--panel);
border: 1.5px solid var(--border);
border-radius: var(--radius);
transition: var(--trans);
margin-top: 4px;
}
.newsletter-check:hover { border-color: var(--muted); }
.newsletter-check input[type=checkbox] { display: none; }
.check-box {
width: 20px; height: 20px;
border: 2px solid var(--muted);
border-radius: 4px;
flex-shrink: 0;
margin-top: 1px;
position: relative;
transition: var(--trans);
background: transparent;
}
.newsletter-check input:checked ~ .check-box {
background: rgba(90,154,120,0.9);
border-color: var(--red);
}
.newsletter-check input:checked ~ .check-box::after {
content: '✓';
position: absolute;
top: 50%; left: 50%;
transform: translate(-50%, -55%);
color: white;
font-size: 12px;
font-weight: 700;
}
.check-label {
font-size: 13px;
color: var(--text);
line-height: 1.5;
}
/* ── NARRATIVE PROGRESS BAR ── */
.narrative-progress {
height: 3px;
background: var(--muted);
margin: 0 24px 0;
border-radius: 99px;
overflow: hidden;
}
.narrative-progress-fill {
height: 100%;
width: 0%;
background: var(--red);
border-radius: 99px;
transition: width 0.5s linear;
}
/* ── STAGED REVEAL ── */
.reveal-section {
display: none;
}
.reveal-section.revealed {
display: block;
animation: fadeUp 0.5s ease both;
}
/* ── NARRATIVE CTA ── */
.narrative-cta {
padding: 16px 20px;
border-top: 1px solid var(--border);
text-align: center;
}
.narrative-cta-btn {
display: inline-flex;
align-items: center;
gap: 8px;
background: var(--red);
color: #0C0C0E;
border: none;
border-radius: 0;
padding: 14px 24px;
font-family: var(--font-body);
font-weight: 600;
font-size: 14px;
letter-spacing: 0.1em;
text-transform: uppercase;
cursor: pointer;
transition: var(--trans);
width: 100%;
justify-content: center;
line-height: 1.4;
text-align: center;
}
.narrative-cta-btn:hover { transform: translateY(-1px); box-shadow: 0 4px 24px rgba(90,154,120,0.25); background: #7AB498; }
/* ── CAPTURE FORM HEADER ── */
.capture-form-header {
font-family: var(--font-display);
font-weight: 400;
font-size: 22px;
color: var(--white);
letter-spacing: 0.01em;
padding-bottom: 16px;
border-bottom: 1px solid rgba(90,154,120,0.2);
margin-bottom: 20px;
}
/* ── PROPRIETARY PROTEIN OFFER ── */
.protein-offer {
background: var(--green-dim);
border: 1px solid rgba(90,154,120,0.45);
border-radius: var(--radius-lg);
padding: 24px;
margin-bottom: 20px;
animation: fadeUp 0.5s ease both;
display: none;
}
.protein-offer.show { display: block; }
.protein-offer-badge {
display: inline-flex;
align-items: center;
gap: 6px;
background: rgba(40,160,96,0.15);
border: 1px solid rgba(40,160,96,0.4);
border-radius: 4px;
padding: 4px 10px;
font-family: var(--font-mono);
font-size: 10px;
color: var(--green-bright);
letter-spacing: 0.12em;
text-transform: uppercase;
margin-bottom: 12px;
}
.protein-offer-title {
font-family: var(--font-display);
font-weight: 900;
font-size: 22px;
color: var(--white);
margin-bottom: 8px;
line-height: 1.2;
}
.protein-offer-body {
font-size: 14px;
color: var(--text-dim);
line-height: 1.7;
margin-bottom: 16px;
}
.protein-offer-btn {
display: inline-flex;
align-items: center;
gap: 8px;
background: var(--green);
color: white;
border: none;
border-radius: 0;
padding: 14px 24px;
font-family: var(--font-display);
font-weight: 800;
font-size: 16px;
letter-spacing: 0.04em;
text-transform: uppercase;
cursor: pointer;
text-decoration: none;
transition: var(--trans);
width: 100%;
justify-content: center;
}
.protein-offer-btn:hover { background: #32C070; transform: translateY(-1px); }
.scenario-strip{
overflow:hidden;
padding:16px 0;
background:var(--panel);
border-bottom:1px solid var(--border);
cursor:grab;
position:relative;
}
.scenario-strip:active{cursor:grabbing;}
.scenario-strip-track{
display:flex;
gap:12px;
padding:4px 24px;
width:max-content;
animation:scroll-pills 28s linear infinite;
-webkit-overflow-scrolling:touch;
}
.scenario-strip:hover .scenario-strip-track,
.scenario-strip:focus-within .scenario-strip-track {
animation-play-state:paused;
}
@keyframes scroll-pills {
0% { transform: translateX(0); }
100% { transform: translateX(-50%); }
}
/* ── Painted button system ──────────────────────────────────────────
Shared embossed-paint fill for primary CTAs (.cta-btn, .btn-next,
.narrative-cta-btn, .capture-submit). Secondary actions
(.btn-back, .restart-btn) keep their original flat panel style. */
.cta-btn,
.btn-next,
.narrative-cta-btn,
.capture-submit {
position: relative;
isolation: isolate;
background: transparent;
border: none;
box-shadow: none;
}
.cta-btn::before,
.btn-next::before,
.narrative-cta-btn::before,
.capture-submit::before {
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
z-index: -1;
transition: filter 0.6s ease;
}
/* Primary — dark fill */
.cta-btn,
.btn-next,
.narrative-cta-btn,
.capture-submit {
color: #f4ecd8;
}
.cta-btn::before,
.btn-next::before,
.narrative-cta-btn::before,
.capture-submit::before {
background: linear-gradient(180deg, #2a2a26 0%, #1A1A18 45%, #060604 100%);
filter: url(#paintGlossBtn);
-webkit-filter: url(#paintGlossBtn);
}
.cta-btn:hover::before,
.btn-next:hover::before,
.narrative-cta-btn:hover::before,
.capture-submit:hover::before {
filter: url(#paintGlossBtnHover);
-webkit-filter: url(#paintGlossBtnHover);
}
.cta-btn:hover,
.btn-next:hover,
.narrative-cta-btn:hover,
.capture-submit:hover {
background: transparent;
color: #fff8e8;
transform: none;
box-shadow: none;
}
.cta-btn:active,
.btn-next:active,
.narrative-cta-btn:active,
.capture-submit:active { opacity: 0.9; }
.btn-next:disabled,
.btn-next:disabled:hover {
opacity: 0.4;
cursor: not-allowed;
background: transparent;
color: #f4ecd8;
box-shadow: none;
}
.btn-next:disabled::before,
.btn-next:disabled:hover::before {
filter: url(#paintGlossBtn);
-webkit-filter: url(#paintGlossBtn);
}
</style>
</head>
<body>
<!-- SVG filter defs for the glossy-paint hero title -->
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
<defs>
<filter id="paintGloss" x="-12%" y="-30%" width="124%" height="160%" color-interpolation-filters="sRGB">
<feMorphology in="SourceAlpha" operator="dilate" radius="2.5" result="dilated"/>
<feGaussianBlur in="dilated" stdDeviation="6" result="puddle"/>
<feFlood flood-color="#fff4dc" flood-opacity="0.85" result="puddleColor"/>
<feComposite in="puddleColor" in2="puddle" operator="in" result="puddleHi"/>
<feFlood flood-color="#1A1A18" flood-opacity="0.18" result="puddleShadowColor"/>
<feComposite in="puddleShadowColor" in2="puddle" operator="in" result="puddleShadow"/>
<feOffset in="puddleShadow" dx="0" dy="3" result="puddleShadowOff"/>
<feGaussianBlur in="SourceAlpha" stdDeviation="1.4" result="bump"/>
<feSpecularLighting in="bump" surfaceScale="6" specularConstant="1.7"
specularExponent="28" lighting-color="#ffffff" result="spec">
<fePointLight x="-300" y="-400" z="420"/>
</feSpecularLighting>
<feComposite in="spec" in2="SourceAlpha" operator="in" result="specClip"/>
<feSpecularLighting in="bump" surfaceScale="4" specularConstant="0.9"
specularExponent="40" lighting-color="#fff2d4" result="spec2">
<fePointLight x="600" y="-200" z="380"/>
</feSpecularLighting>
<feComposite in="spec2" in2="SourceAlpha" operator="in" result="specClip2"/>
<feGaussianBlur in="SourceAlpha" stdDeviation="4" result="dsBlur"/>
<feOffset in="dsBlur" dx="0" dy="6" result="dsOff"/>
<feComponentTransfer in="dsOff" result="dsFinal">
<feFuncA type="linear" slope="0.45"/>
</feComponentTransfer>
<feMerge>
<feMergeNode in="dsFinal"/>
<!-- warm halo glow (puddleHi + puddleShadowOff) disabled — uncomment both nodes below to restore the cream bloom around the text -->
<!-- <feMergeNode in="puddleHi"/> -->
<!-- <feMergeNode in="puddleShadowOff"/> -->
<feMergeNode in="SourceGraphic"/>
<feMergeNode in="specClip"/>
<feMergeNode in="specClip2"/>
</feMerge>
</filter>
<filter id="paintGlossHover" x="-12%" y="-30%" width="124%" height="160%" color-interpolation-filters="sRGB">
<feMorphology in="SourceAlpha" operator="dilate" radius="2.5" result="dilated"/>
<feGaussianBlur in="dilated" stdDeviation="6" result="puddle"/>
<feFlood flood-color="#fff4dc" flood-opacity="0.85" result="puddleColor"/>
<feComposite in="puddleColor" in2="puddle" operator="in" result="puddleHi"/>
<feFlood flood-color="#1A1A18" flood-opacity="0.18" result="puddleShadowColor"/>
<feComposite in="puddleShadowColor" in2="puddle" operator="in" result="puddleShadow"/>
<feOffset in="puddleShadow" dx="0" dy="3" result="puddleShadowOff"/>
<feGaussianBlur in="SourceAlpha" stdDeviation="1.4" result="bump"/>
<feSpecularLighting in="bump" surfaceScale="6" specularConstant="1.7"
specularExponent="28" lighting-color="#ffffff" result="spec">
<fePointLight x="500" y="-400" z="420"/>
</feSpecularLighting>
<feComposite in="spec" in2="SourceAlpha" operator="in" result="specClip"/>
<feSpecularLighting in="bump" surfaceScale="4" specularConstant="0.9"
specularExponent="40" lighting-color="#fff2d4" result="spec2">
<fePointLight x="-200" y="-200" z="380"/>
</feSpecularLighting>
<feComposite in="spec2" in2="SourceAlpha" operator="in" result="specClip2"/>
<feGaussianBlur in="SourceAlpha" stdDeviation="4" result="dsBlur"/>
<feOffset in="dsBlur" dx="0" dy="6" result="dsOff"/>
<feComponentTransfer in="dsOff" result="dsFinal">
<feFuncA type="linear" slope="0.45"/>
</feComponentTransfer>
<feMerge>
<feMergeNode in="dsFinal"/>
<!-- warm halo glow disabled — see #paintGloss above for the matching restore note -->
<!-- <feMergeNode in="puddleHi"/> -->
<!-- <feMergeNode in="puddleShadowOff"/> -->
<feMergeNode in="SourceGraphic"/>
<feMergeNode in="specClip"/>
<feMergeNode in="specClip2"/>
</feMerge>
</filter>
<!-- Soft glossy embossed fill for buttons — generous bump, soft warm halo -->
<filter id="paintGlossBtn" x="-8%" y="-25%" width="116%" height="150%" color-interpolation-filters="sRGB">
<feMorphology in="SourceAlpha" operator="dilate" radius="0.6" result="dilated"/>
<feGaussianBlur in="dilated" stdDeviation="2.5" result="puddle"/>
<feFlood flood-color="#fff4dc" flood-opacity="0.4" result="puddleColor"/>
<feComposite in="puddleColor" in2="puddle" operator="in" result="puddleHi"/>
<feGaussianBlur in="SourceAlpha" stdDeviation="1.2" result="bump"/>
<feSpecularLighting in="bump" surfaceScale="4" specularConstant="1.6"
specularExponent="50" lighting-color="#ffffff" result="spec">
<fePointLight x="-150" y="-300" z="280"/>
</feSpecularLighting>
<feComposite in="spec" in2="SourceAlpha" operator="in" result="specClip"/>
<feSpecularLighting in="bump" surfaceScale="3" specularConstant="0.7"
specularExponent="60" lighting-color="#fff2d4" result="spec2">
<fePointLight x="400" y="-150" z="260"/>
</feSpecularLighting>
<feComposite in="spec2" in2="SourceAlpha" operator="in" result="specClip2"/>
<feGaussianBlur in="SourceAlpha" stdDeviation="2.5" result="dsBlur"/>
<feOffset in="dsBlur" dx="0" dy="3" result="dsOff"/>
<feComponentTransfer in="dsOff" result="dsFinal">
<feFuncA type="linear" slope="0.4"/>
</feComponentTransfer>
<feMerge>
<feMergeNode in="dsFinal"/>
<feMergeNode in="puddleHi"/>
<feMergeNode in="SourceGraphic"/>
<feMergeNode in="specClip"/>
<feMergeNode in="specClip2"/>
</feMerge>
</filter>
<filter id="paintGlossBtnHover" x="-8%" y="-25%" width="116%" height="150%" color-interpolation-filters="sRGB">
<feMorphology in="SourceAlpha" operator="dilate" radius="0.6" result="dilated"/>
<feGaussianBlur in="dilated" stdDeviation="2.5" result="puddle"/>
<feFlood flood-color="#fff4dc" flood-opacity="0.4" result="puddleColor"/>
<feComposite in="puddleColor" in2="puddle" operator="in" result="puddleHi"/>
<feGaussianBlur in="SourceAlpha" stdDeviation="1.2" result="bump"/>
<feSpecularLighting in="bump" surfaceScale="4" specularConstant="1.6"
specularExponent="50" lighting-color="#ffffff" result="spec">
<fePointLight x="350" y="-300" z="280"/>
</feSpecularLighting>
<feComposite in="spec" in2="SourceAlpha" operator="in" result="specClip"/>
<feSpecularLighting in="bump" surfaceScale="3" specularConstant="0.7"
specularExponent="60" lighting-color="#fff2d4" result="spec2">
<fePointLight x="-150" y="-150" z="260"/>
</feSpecularLighting>
<feComposite in="spec2" in2="SourceAlpha" operator="in" result="specClip2"/>
<feGaussianBlur in="SourceAlpha" stdDeviation="2.5" result="dsBlur"/>
<feOffset in="dsBlur" dx="0" dy="3" result="dsOff"/>
<feComponentTransfer in="dsOff" result="dsFinal">
<feFuncA type="linear" slope="0.4"/>
</feComponentTransfer>
<feMerge>
<feMergeNode in="dsFinal"/>
<feMergeNode in="puddleHi"/>
<feMergeNode in="SourceGraphic"/>
<feMergeNode in="specClip"/>
<feMergeNode in="specClip2"/>
</feMerge>
</filter>
<!-- Glossy white paint H1 filter (matches paint.html's tuned defaults) -->
<filter id="paintGlossWhite" x="-12%" y="-30%" width="124%" height="160%" color-interpolation-filters="sRGB">
<feGaussianBlur in="SourceAlpha" stdDeviation="0.8" result="bump"/>
<feSpecularLighting in="bump" surfaceScale="7" specularConstant="3.5"
specularExponent="21" lighting-color="#ffffff" result="spec">
<fePointLight x="-200" y="-350" z="380"/>
</feSpecularLighting>
<feComposite in="spec" in2="SourceAlpha" operator="in" result="specClip"/>
<feSpecularLighting in="bump" surfaceScale="7" specularConstant="3.3"
specularExponent="19" lighting-color="#ffffff" result="spec2">
<fePointLight x="500" y="-200" z="320"/>
</feSpecularLighting>
<feComposite in="spec2" in2="SourceAlpha" operator="in" result="specClip2"/>
<feGaussianBlur in="SourceAlpha" stdDeviation="5.3" result="dsBlur"/>
<feOffset in="dsBlur" dx="0" dy="7" result="dsOff"/>
<feComponentTransfer in="dsOff" result="dsFinal">
<feFuncA type="linear" slope="0.12"/>
</feComponentTransfer>
<feMerge>
<feMergeNode in="dsFinal"/>
<feMergeNode in="SourceGraphic"/>
<feMergeNode in="specClip"/>
<feMergeNode in="specClip2"/>
</feMerge>
</filter>
<filter id="paintGlossWhiteHover" x="-12%" y="-30%" width="124%" height="160%" color-interpolation-filters="sRGB">
<feGaussianBlur in="SourceAlpha" stdDeviation="0.8" result="bump"/>
<feSpecularLighting in="bump" surfaceScale="7" specularConstant="3.5"
specularExponent="21" lighting-color="#ffffff" result="spec">
<fePointLight x="500" y="-350" z="380"/>
</feSpecularLighting>
<feComposite in="spec" in2="SourceAlpha" operator="in" result="specClip"/>
<feSpecularLighting in="bump" surfaceScale="7" specularConstant="3.3"
specularExponent="19" lighting-color="#ffffff" result="spec2">
<fePointLight x="-200" y="-200" z="320"/>
</feSpecularLighting>
<feComposite in="spec2" in2="SourceAlpha" operator="in" result="specClip2"/>
<feGaussianBlur in="SourceAlpha" stdDeviation="5.3" result="dsBlur"/>
<feOffset in="dsBlur" dx="0" dy="7" result="dsOff"/>
<feComponentTransfer in="dsOff" result="dsFinal">
<feFuncA type="linear" slope="0.12"/>
</feComponentTransfer>
<feMerge>
<feMergeNode in="dsFinal"/>
<feMergeNode in="SourceGraphic"/>
<feMergeNode in="specClip"/>
<feMergeNode in="specClip2"/>
</feMerge>
</filter>
<filter id="paintGlossWhiteBtn" x="-8%" y="-25%" width="116%" height="150%" color-interpolation-filters="sRGB">
<feGaussianBlur in="SourceAlpha" stdDeviation="0.8" result="bump"/>
<feSpecularLighting in="bump" surfaceScale="7" specularConstant="3.5"
specularExponent="21" lighting-color="#ffffff" result="spec">
<fePointLight x="-150" y="-300" z="280"/>
</feSpecularLighting>
<feComposite in="spec" in2="SourceAlpha" operator="in" result="specClip"/>
<feSpecularLighting in="bump" surfaceScale="7" specularConstant="3.3"
specularExponent="19" lighting-color="#ffffff" result="spec2">
<fePointLight x="400" y="-150" z="260"/>
</feSpecularLighting>
<feComposite in="spec2" in2="SourceAlpha" operator="in" result="specClip2"/>
<feGaussianBlur in="SourceAlpha" stdDeviation="5.3" result="dsBlur"/>
<feOffset in="dsBlur" dx="0" dy="7" result="dsOff"/>
<feComponentTransfer in="dsOff" result="dsFinal">
<feFuncA type="linear" slope="0.12"/>
</feComponentTransfer>
<feMerge>
<feMergeNode in="dsFinal"/>
<feMergeNode in="SourceGraphic"/>
<feMergeNode in="specClip"/>
<feMergeNode in="specClip2"/>
</feMerge>
</filter>
</defs>
</svg>
<header class="site-header">
<div class="logo" data-i18n="brand">Plan-B</div>
<div class="header-right">
<button class="mod-open-btn" id="modOpenBtn" type="button" title="Open style modifier" aria-label="Open style modifier"></button>
<div id="region-indicator" style="font-family:var(--font-mono);font-size:10px;color:var(--text-dim);padding:4px 8px;background:var(--panel);border:1px solid var(--border);border-radius:4px;white-space:nowrap;cursor:pointer;" onclick="showRegionPicker()" title="Click to change region"></div>
<div class="lang-toggle">
<button class="lang-btn active" onclick="setLang('en')" id="btn-en">EN</button>
<button class="lang-btn" onclick="setLang('de')" id="btn-de">DE</button>
</div>
</div>
</header>
<aside class="mod-panel" id="modPanel">
<header class="mod-header">
<h3>Modifier <span class="mod-paint-label" id="modPaintLabel">Dark</span></h3>
<button class="mod-toggle" id="modCloseBtn" type="button" aria-label="Close modifier">×</button>
</header>
<div class="mod-body">
<fieldset class="mod-fieldset">
<legend>Paint</legend>
<div class="paint-picker" role="radiogroup" aria-label="Paint color" style="padding: 4px 0;">
<button class="paint-swatch swatch-dark active" data-paint="" title="Dark (default)" aria-label="Dark"></button>
<button class="paint-swatch swatch-green" data-paint="paint-green" title="Army green" aria-label="Army green"></button>
<button class="paint-swatch swatch-white" data-paint="paint-white" title="Glossy white" aria-label="Glossy white"></button>
</div>
</fieldset>
<fieldset class="mod-fieldset" data-paint-only="green,white">
<legend>Fill</legend>
<label class="mod-row"><span>color</span><input type="color" data-var="fill"/></label>
</fieldset>
<fieldset class="mod-fieldset">
<legend>Drop Shadow</legend>
<label class="mod-row"><span>dy</span><input type="range" min="0" max="20" step="0.5" data-var="dsDy"/><output></output></label>
<label class="mod-row"><span>blur</span><input type="range" min="0" max="10" step="0.1" data-var="dsBlur"/><output></output></label>
<label class="mod-row"><span>alpha</span><input type="range" min="0" max="1" step="0.01" data-var="dsSlope"/><output></output></label>
</fieldset>
<fieldset class="mod-fieldset">
<legend>Bump</legend>
<label class="mod-row"><span>stdDev</span><input type="range" min="0" max="3" step="0.1" data-var="bumpStd"/><output></output></label>
</fieldset>
<fieldset class="mod-fieldset">
<legend>Specular 1 (upper-left)</legend>
<label class="mod-row"><span>scale</span><input type="range" min="0" max="30" step="1" data-var="spec1Scale"/><output></output></label>
<label class="mod-row"><span>constant</span><input type="range" min="0" max="8" step="0.1" data-var="spec1Const"/><output></output></label>
<label class="mod-row"><span>exponent</span><input type="range" min="0" max="100" step="1" data-var="spec1Exp"/><output></output></label>
<label class="mod-row"><span>color</span><input type="color" data-var="spec1Color"/></label>
</fieldset>
<fieldset class="mod-fieldset">
<legend>Specular 2 (upper-right)</legend>
<label class="mod-row"><span>scale</span><input type="range" min="0" max="30" step="1" data-var="spec2Scale"/><output></output></label>
<label class="mod-row"><span>constant</span><input type="range" min="0" max="8" step="0.1" data-var="spec2Const"/><output></output></label>
<label class="mod-row"><span>exponent</span><input type="range" min="0" max="100" step="1" data-var="spec2Exp"/><output></output></label>
<label class="mod-row"><span>color</span><input type="color" data-var="spec2Color"/></label>
</fieldset>
<div class="mod-actions">
<button id="modCopy" type="button">Copy snippet</button>
<span class="mod-copy-status" id="modCopyStatus"></span>
</div>
</div>
</aside>
<main class="app" id="app">
<!-- HERO -->
<section class="hero" id="hero-section">
<h1 class="paint-3d" data-i18n="brand">Plan-B</h1>
<p class="hero-sub">Preparedness, refined.</p>
<button class="cta-btn" onclick="startQuiz()">
<span>Begin</span>
</button>
</section>
<!-- SCENARIO STRIP -->
<div class="scenario-strip" id="scenario-strip">
<div class="scenario-strip-track" id="scenario-track">
<div class="scenario-pill pill-s1"><span class="pill-dot"></span><span data-i18n="pill_s1">📦 Supply Shock</span></div>
<div class="scenario-pill pill-s2"><span class="pill-dot"></span><span data-i18n="pill_s2">🌾 Food Crisis</span></div>
<div class="scenario-pill pill-s3"><span class="pill-dot"></span><span data-i18n="pill_s3">📈 Hyperinflation</span></div>
<div class="scenario-pill pill-s4"><span class="pill-dot"></span><span data-i18n="pill_s4">🏦 Bank Crisis</span></div>
<div class="scenario-pill pill-s5"><span class="pill-dot"></span><span data-i18n="pill_s5">⚡ Power Outage</span></div>
<div class="scenario-pill pill-s6"><span class="pill-dot"></span><span data-i18n="pill_s6">💧 Water Failure</span></div>
<div class="scenario-pill pill-s7"><span class="pill-dot"></span><span data-i18n="pill_s7">⚠️ Partial Collapse</span></div>
<div class="scenario-pill pill-s8"><span class="pill-dot"></span><span data-i18n="pill_s8">🪖 War / Conflict</span></div>
<div class="scenario-pill pill-s9"><span class="pill-dot"></span><span data-i18n="pill_s9">💀 Total Collapse</span></div>
<!-- Duplicate for seamless loop -->
<div class="scenario-pill pill-s1" aria-hidden="true"><span class="pill-dot"></span><span data-i18n="pill_s1">📦 Supply Shock</span></div>
<div class="scenario-pill pill-s2" aria-hidden="true"><span class="pill-dot"></span><span data-i18n="pill_s2">🌾 Food Crisis</span></div>
<div class="scenario-pill pill-s3" aria-hidden="true"><span class="pill-dot"></span><span data-i18n="pill_s3">📈 Hyperinflation</span></div>
<div class="scenario-pill pill-s4" aria-hidden="true"><span class="pill-dot"></span><span data-i18n="pill_s4">🏦 Bank Crisis</span></div>
<div class="scenario-pill pill-s5" aria-hidden="true"><span class="pill-dot"></span><span data-i18n="pill_s5">⚡ Power Outage</span></div>
<div class="scenario-pill pill-s6" aria-hidden="true"><span class="pill-dot"></span><span data-i18n="pill_s6">💧 Water Failure</span></div>
<div class="scenario-pill pill-s7" aria-hidden="true"><span class="pill-dot"></span><span data-i18n="pill_s7">⚠️ Partial Collapse</span></div>
<div class="scenario-pill pill-s8" aria-hidden="true"><span class="pill-dot"></span><span data-i18n="pill_s8">🪖 War / Conflict</span></div>
<div class="scenario-pill pill-s9" aria-hidden="true"><span class="pill-dot"></span><span data-i18n="pill_s9">💀 Total Collapse</span></div>
</div>
</div>
<!-- QUESTIONNAIRE -->
<section class="quiz-section hidden" id="quiz-section">
<div class="quiz-progress-bar">
<a class="qpb-logo logo" data-i18n="brand" href="#" aria-label="Back to landing page" onclick="event.preventDefault(); restartQuiz();">Plan-B</a>
<div class="progress-wrap">
<div class="progress-header">
<span class="progress-label" data-i18n="progress_label">Assessment Progress</span>
<span class="progress-count" id="progress-count">1 / 8</span>
</div>
<div class="progress-bar"><div class="progress-fill" id="progress-fill" style="width:12.5%"></div></div>
</div>
<div class="lang-toggle" aria-label="Language">
<button class="lang-btn" data-lang="en" onclick="setLang('en')">EN</button>
<button class="lang-btn" data-lang="de" onclick="setLang('de')">DE</button>
</div>
</div>
<div class="quiz-content">
<div id="question-container"></div>
</div>
<div class="quiz-footer" id="quiz-footer"></div>
</section>
<!-- RESULTS -->
<section class="results-section" id="results-section">
<div id="risk-banner-container"></div>
<!-- PROTEIN OFFER — shown only if protein_access === uncertain -->
<div class="protein-offer" id="protein-offer-section">
<div class="protein-offer-badge">🔒 <span data-i18n="protein_offer_badge">Exclusive — DACH Region Priority Access</span></div>
<div class="protein-offer-title" data-i18n="protein_offer_title">Secure your high-grade animal protein source</div>
<div class="protein-offer-body" data-i18n="protein_offer_body">You indicated that your protein supply may not be secure in a crisis. We have identified an exclusive, verified source of high-grade animal protein — available with priority access for Plan-B members in Germany, Austria, and Switzerland.</div>
<a class="protein-offer-btn" href="PROTEIN_OFFER_URL" target="_blank" rel="noopener noreferrer" data-i18n="protein_offer_btn">→ Secure My Protein Source Now</a>
</div>
<!-- AI NARRATIVE SECTION -->
<div class="narrative-section" id="narrative-section">
<div class="narrative-header">
<span style="font-size:20px">🧠</span>
<div class="narrative-title" data-i18n="narrative_title">Your Personal Analysis</div>
<div class="narrative-tag" data-i18n="narrative_tag">AI Generated</div>
</div>
<div id="narrative-content">
<div class="narrative-loading" id="narrative-loading">
<div class="narrative-progress"><div class="narrative-progress-fill" id="narrative-progress"></div></div>
<div class="loading-dots"><span></span><span></span><span></span></div>
<div class="loading-text" data-i18n="narrative_loading">Generating your personalised analysis...</div>
<div style="font-size:10px;color:var(--muted);margin-top:8px;font-family:var(--font-mono);letter-spacing:0.06em" data-i18n="narrative_loading_sub">Takes up to 10 seconds</div>
</div>
<div class="narrative-body hidden" id="narrative-text"></div>
</div>
<!-- CTA after narrative completes -->
<div class="narrative-cta hidden" id="narrative-cta">
<button class="narrative-cta-btn" onclick="scrollToForm()" data-i18n="cta_scroll">
↓ Fill in your details below to receive your personalised plan by email
</button>
</div>
</div>
<!-- CAPTURE FORM — appears first after narrative -->
<div class="capture-form-wrap reveal-section" id="capture-form-wrap">
<div class="capture-title" data-i18n="capture_title">Personal Details</div>
<div class="capture-sub" data-i18n="capture_sub">Enter your details and we'll email your complete survival plan immediately — including all recommendations below.</div>
<form id="capture-form" onsubmit="submitCapture(event)">
<input type="hidden" id="h_location" name="location"/>
<input type="hidden" id="h_household" name="household_size"/>
<input type="hidden" id="h_water" name="water_access"/>
<input type="hidden" id="h_food" name="food_reserves"/>
<input type="hidden" id="h_medical" name="medical_needs"/>
<input type="hidden" id="h_sanitation" name="sanitation"/>
<input type="hidden" id="h_budget" name="budget_eur"/>
<input type="hidden" id="h_priority" name="priorities"/>
<input type="hidden" id="h_protein" name="protein_access"/>
<input type="hidden" id="h_risk_score" name="risk_score"/>
<input type="hidden" id="h_risk_level" name="risk_level"/>
<input type="hidden" id="h_lang" name="language_used"/>
<input type="hidden" id="h_timestamp" name="submitted_at"/>
<input type="hidden" id="h_scenarios" name="scenarios"/>
<input type="hidden" id="h_protein_pref" name="protein_preference"/>
<input type="hidden" id="h_protein_security" name="protein_security"/>
<div class="form-grid">
<div class="form-row">
<div class="form-field">
<label class="field-label"><span data-i18n="field_name">First Name</span> <span style="color:var(--red)">*</span></label>
<input class="form-input" type="text" id="f_name" name="first_name" required placeholder="First name"/>
</div>
<div class="form-field">
<label class="field-label"><span data-i18n="field_lastname">Last Name</span> <span style="color:var(--red)">*</span></label>
<input class="form-input" type="text" id="f_lastname" name="last_name" required placeholder="Last name"/>
</div>
</div>
<div class="form-field">
<label class="field-label"><span data-i18n="field_email">Email</span> <span style="color:var(--red)">*</span></label>
<input class="form-input" type="email" id="f_email" name="email" required placeholder="your@email.com"/>
</div>
<div class="form-row">
<div class="form-field">
<label class="field-label"><span data-i18n="field_city">City</span> <span style="color:var(--red)">*</span></label>
<input class="form-input" type="text" id="f_city" name="city" required placeholder="Your city"/>
</div>
<div class="form-field">
<label class="field-label"><span data-i18n="field_country">Country</span> <span style="color:var(--red)">*</span></label>
<select class="form-select" id="f_country" name="country" required onchange="updateRegionFromCountry(this.value); updateRegionIndicator();">
<option value=""></option>
<option value="AT">🇦🇹 Austria</option>
<option value="DE">🇩🇪 Germany</option>
<option value="CH">🇨🇭 Switzerland</option>
<option value="US">🇺🇸 United States</option>
<option value="CA">🇨🇦 Canada</option>
<option value="GB">🇬🇧 United Kingdom</option>
<option value="FR">🇫🇷 France</option>
<option value="NL">🇳🇱 Netherlands</option>
<option value="BE">🇧🇪 Belgium</option>
<option value="ES">🇪🇸 Spain</option>
<option value="IT">🇮🇹 Italy</option>
<option value="PL">🇵🇱 Poland</option>
<option value="SE">🇸🇪 Sweden</option>
<option value="NO">🇳🇴 Norway</option>
<option value="DK">🇩🇰 Denmark</option>
<option value="OTHER">Other</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-field">
<label class="field-label"><span data-i18n="field_phone">Phone (optional)</span></label>
<input class="form-input" type="tel" id="f_phone" name="phone" placeholder="+43 or +1 ..."/>
</div>
<div class="form-field">
<label class="field-label"><span data-i18n="field_pref_lang">Preferred language</span></label>
<select class="form-select" id="f_pref_lang" name="preferred_language">
<option value="en">English</option>
<option value="de">Deutsch</option>
<option value="fr">Français</option>
<option value="es">Español</option>
</select>
</div>
</div>
<div class="form-field">
<label class="field-label"><span data-i18n="field_protein_label">🥩 Protein access (optional)</span></label>
<textarea class="form-input" id="f_protein_detail" name="protein_detail" rows="3"
placeholder="e.g. free-range eggs from neighbour, fishing lake nearby, hunter in family..."></textarea>
<div style="font-size:11px;color:var(--text-dim);margin-top:4px;font-style:italic" data-i18n="protein_note">Helps us personalise your food recommendations.</div>
</div>
<label class="newsletter-check">
<input type="checkbox" id="f_newsletter" name="newsletter" value="yes" checked/>
<span class="check-box"></span>
<span class="check-label" data-i18n="newsletter_label">Yes, send me weekly preparedness updates from Plan-B</span>
</label>
<button class="capture-submit" type="submit" id="capture-submit-btn">
<span data-i18n="capture_btn">Send Me My Plan →</span>
</button>
</div>
</form>
<div class="capture-success hidden" id="capture-success">
<div style="font-size:36px;margin-bottom:8px;text-align:center"></div>
<div style="font-family:var(--font-display);font-weight:800;font-size:22px;color:var(--green-bright);margin-bottom:8px;text-align:center" data-i18n="success_title">Plan sent!</div>
<div style="font-size:14px;color:var(--text-dim);line-height:1.6;text-align:center" data-i18n="success_text">Check your inbox — your personalised plan is on its way.</div>
<div style="margin-top:14px;padding:12px 16px;background:rgba(212,168,32,0.1);border:1px solid rgba(212,168,32,0.35);border-radius:8px;text-align:center">
<div style="font-size:13px;color:#D4A820;line-height:1.7" data-i18n="success_spam">📬 Don't see it? Check your <strong>spam or junk folder</strong>. If it's there, please mark it as <strong>"Not Spam"</strong> — this ensures future updates reach your inbox directly, and helps us improve deliverability for everyone.</div>
</div>
<div style="margin-top:14px;text-align:center">
<button class="narrative-cta-btn" onclick="scrollToRecs()" data-i18n="success_explore">↓ Now explore your full recommendations below</button>
</div>
</div>
<div style="font-size:11px;color:var(--muted);margin-top:12px;text-align:center;line-height:1.7" data-i18n="privacy_note">🔒 Your data is never sold. Unsubscribe anytime. GDPR compliant.</div>
</div>
<!-- RECOMMENDATIONS + SCENARIO TABS — shown after form -->
<div id="recs-anchor"></div>
<div id="budget-meter-container" class="reveal-section"></div>
<div class="scenario-tabs reveal-section" id="scenario-tabs">
<button class="s-tab" id="stab-1" data-s="1" onclick="showScenario(1)"><span data-i18n="tab_s1">💀 Total</span></button>
<button class="s-tab" id="stab-2" data-s="2" onclick="showScenario(2)"><span data-i18n="tab_s2">⚠️ Partial</span></button>
<button class="s-tab" id="stab-3" data-s="3" onclick="showScenario(3)"><span data-i18n="tab_s3">📈 Inflation</span></button>
<button class="s-tab" id="stab-4" data-s="4" onclick="showScenario(4)"><span data-i18n="tab_s4">🌿 Food</span></button>
</div>
<div class="rec-cards reveal-section" id="rec-cards-container"></div>
<div class="timeline reveal-section" id="timeline-container">
<div class="tl-title" data-i18n="timeline_title">⏱ Your Action Timeline</div>
<div class="tl-items" id="timeline-items"></div>
</div>
<button class="restart-btn" onclick="restartQuiz()" data-i18n="restart_btn">↩ Retake Assessment</button>
</section>
<!-- ABOUT -->
<section class="about-section" id="about-section">
<h2 data-i18n="about_title">Why Plan-B?</h2>
<p data-i18n="about_text">Built by preparedness researchers and city-dwelling practitioners. Every recommendation is sourced, tested, and city-apartment-compatible.</p>
<p class="affiliate-note" data-i18n="affiliate_note">* This site uses affiliate links. When you purchase through our links, we may earn a commission at no extra cost to you. This helps keep the platform free.</p>
</section>
</main>
<script>
// ══════════════════════════════════════
// TRANSLATIONS
// ══════════════════════════════════════
const T = {
en: {
brand: "Plan-B",
page_title: "Plan-B — Survival Preparedness Advisor",
hero_eyebrow: "Crisis Preparedness Advisor",
hero_sub: "AI-Assisted Preparedness for Urban Households",
hero_cta: "Start Assessment",
stat_scenarios: "Scenarios",
stat_questions: "Questions",
stat_free: "Free Forever",
email_label: "Stay Prepared",
email_title: "Get your weekly survival briefing",
email_sub: "Crisis updates, product recommendations, and preparedness tips. Free. No spam.",
email_btn: "Subscribe",
email_success: "✓ You're in. First briefing coming shortly.",
email_note: "No spam. Unsubscribe anytime. Your data is never sold.",
results_email_title: "Save your plan & get updates",
results_email_sub: "We'll email you your personalised plan plus weekly preparedness updates.",
pill_s1:"📦 Supply Shock", pill_s2:"🌾 Food Crisis",
pill_s3:"📈 Hyperinflation", pill_s4:"🏦 Bank Crisis",
pill_s5:"⚡ Power Outage", pill_s6:"💧 Water Failure",
pill_s7:"⚠️ Partial Collapse", pill_s8:"🪖 War / Conflict",
pill_s9:"💀 Total Collapse",
pill_1: "Total Collapse",
pill_2: "Partial Collapse",
pill_3: "Hyperinflation",
pill_4: "Food Shortage",
progress_label: "Assessment Progress",
tab_s1: "💀 Total\nCollapse",
tab_s2: "⚠️ Partial\nCollapse",
tab_s3: "📈 Hyper-\ninflation",
tab_s4: "🌿 Food\nShortage",
timeline_title: "⏱ Your Action Timeline",
restart_btn: "↩ Retake Assessment",
about_title: "Why Plan-B?",
about_text: "Built by preparedness researchers and city-dwelling practitioners. Every recommendation is sourced, tested, and city-apartment-compatible.",
affiliate_note: "* This site uses affiliate links. When you purchase through our links, we may earn a commission at no extra cost to you. This helps keep the platform free.",
budget_title: "📊 Budget Allocation",
risk_label: "Preparedness Assessment",
risk_critical_title: "CRITICAL",
risk_critical_desc: "You have almost no buffer. A 48-hour supply disruption would put your household in danger.",
risk_high_title: "HIGH RISK",
risk_high_desc: "Significant gaps in your preparedness. A 1-week disruption would be very difficult to manage.",
risk_medium_title: "MODERATE",
risk_medium_desc: "Some preparation in place. A 23 week disruption would stretch your resources.",
risk_low_title: "PREPARED",
risk_low_desc: "Good foundation. Focus on extending your buffer and filling specific gaps.",
q_step: "Question",
back_btn: "Back",
continue_btn: "Continue",
finish_btn: "Get My Plan",
narrative_title: "Your Personal Analysis",
narrative_tag: "AI Generated",
narrative_loading: "Generating your personalised analysis...",
narrative_loading_sub: "Takes up to 10 seconds",
field_lastname: "Last Name",
scroll_hint:"↓ COMPLETE YOUR PROFILE BELOW TO RECEIVE YOUR PLAN",
capture_required:"",
newsletter_label:"Yes, send me weekly preparedness updates from Plan-B",
capture_label:"📋 Get Your Full Report",
capture_title:"Personal Details",
capture_sub:"We send your complete survival plan to your inbox plus weekly updates.",
field_email:"Email",field_name:"First Name",field_city:"City",field_country:"Country",
field_pref_lang:"Preferred language",field_phone:"Phone (optional)",
field_protein_label:"Protein access (optional)",
protein_note:"High-quality non-commercial protein sources help us personalise your food plan.",
capture_btn:"Send Me My Plan",
success_title:"Plan sent to your inbox!",
success_text:"Check your email. We included your personalised recommendations, action timeline, and product links.",
privacy_note:"Your data is never sold. Unsubscribe anytime. GDPR compliant.",
protein_offer_badge:"Exclusive — DACH Region Priority Access",
protein_offer_title:"Secure your high-grade animal protein source",
protein_offer_body:"You indicated your protein supply may not be secure in a crisis. We have identified an exclusive, verified source of high-grade animal protein — available with priority access for Plan-B members in Germany, Austria, and Switzerland.",
protein_offer_btn:"→ Secure My Protein Source Now",
capture_header:"Personal Details",
cta_scroll:"↓ Fill in your details below to receive your personalised plan by email",
success_explore:"↓ Now explore your full recommendations below",
success_spam:"📬 Don't see it? Check your spam or junk folder. If it's there, please mark it as Not Spam — this ensures future updates reach your inbox directly, and helps us improve deliverability for everyone.",
view_amazon: "View on Amazon",
bcat_water: "Water",
bcat_food: "Food",
bcat_growing: "Growing",
bcat_energy: "Energy",
bcat_medical: "Medical",
bcat_sanitation: "Sanitation",
tl_week1: "This week",
tl_week2: "Week 12",
tl_month1: "Month 1",
tl_month2: "Month 2",
tl_month3: "Month 3",
tl_a1: "Buy sprouting seeds + mason jars. Start sprouting TODAY.",
tl_a2: "Stock 2-week dry food reserve: rice, lentils, oats, oil, canned fish.",
tl_a3: "Purchase AWG water unit + basic first aid kit + antidiarrheal/ORS.",
tl_a4: "Extend food to 3 months. Add hydroponic tower. Buy solar power bank.",
tl_a5: "Solar generator (500Wh) + sleeping bags + Baofeng radios + cash reserve.",
},
de: {
brand: "Plan-B",
page_title: "Plan-B — Krisenvorsorge-Berater",
hero_eyebrow: "⚡ Krisenvorsorge-Berater",
hero_sub: "KI-gestützte Vorsorge für urbane Haushalte",
hero_cta: "Jetzt starten",
stat_scenarios: "Szenarien",
stat_questions: "Fragen",
stat_free: "Kostenlos",
email_label: "Vorbereitet bleiben",
email_title: "Dein wöchentliches Survival-Briefing",
email_sub: "Krisenaktualisierungen, Produktempfehlungen und Vorsorge-Tipps. Kostenlos. Kein Spam.",
email_btn: "Abonnieren",
email_success: "✓ Du bist dabei. Erstes Briefing folgt in Kürze.",
email_note: "Kein Spam. Jederzeit abmeldbar. Deine Daten werden nie verkauft.",
results_email_title: "Plan speichern & Updates erhalten",
results_email_sub: "Wir senden dir deinen personalisierten Plan sowie wöchentliche Vorsorge-Updates per E-Mail.",
pill_s1:"📦 Versorgungsengpass", pill_s2:"🌾 Lebensmittelkrise",
pill_s3:"📈 Hyperinflation", pill_s4:"🏦 Bankenkrise",
pill_s5:"⚡ Stromausfall", pill_s6:"💧 Wasserversorgung",
pill_s7:"⚠️ Partieller Kollaps", pill_s8:"🪖 Krieg / Konflikt",
pill_s9:"💀 Totaler Kollaps",
pill_1: "Totaler Kollaps",
pill_2: "Partieller Kollaps",
pill_3: "Hyperinflation",
pill_4: "Lebensmittelkrise",
progress_label: "Fortschritt",
tab_s1: "💀 Totaler\nKollaps",
tab_s2: "⚠️ Partieller\nKollaps",
tab_s3: "📈 Hyper-\ninflation",
tab_s4: "🌿 Lebens-\nmittelkrise",
timeline_title: "⏱ Dein Aktionsplan",
restart_btn: "↩ Neu starten",
about_title: "Warum Plan-B?",
about_text: "Entwickelt von Vorsorge-Forschern und Stadtbewohnern. Jede Empfehlung ist recherchiert, getestet und für Stadtwohnungen geeignet.",
affiliate_note: "* Diese Website nutzt Affiliate-Links. Beim Kauf über unsere Links erhalten wir eine Provision ohne Mehrkosten für dich. So bleibt die Plattform kostenlos.",
budget_title: "📊 Budgetverteilung",
risk_label: "Vorsorge-Bewertung",
risk_critical_title: "KRITISCH",
risk_critical_desc: "Du hast fast keinen Puffer. Eine 48-stündige Versorgungsunterbrechung würde deinen Haushalt gefährden.",
risk_high_title: "HOHES RISIKO",
risk_high_desc: "Erhebliche Lücken in deiner Vorbereitung. Eine 1-wöchige Unterbrechung wäre sehr schwer zu bewältigen.",
risk_medium_title: "MÄSSIG",
risk_medium_desc: "Einige Vorbereitungen vorhanden. Eine 23-wöchige Unterbrechung würde deine Ressourcen strapazieren.",
risk_low_title: "VORBEREITET",
risk_low_desc: "Gute Grundlage. Fokussiere auf die Erweiterung deines Puffers und das Schließen spezifischer Lücken.",
q_step: "Frage",
back_btn: "Zurück",
continue_btn: "Weiter",
finish_btn: "Meinen Plan abrufen",
narrative_title: "Deine persönliche Analyse",
narrative_tag: "KI-Generiert",
narrative_loading: "Deine personalisierte Analyse wird erstellt...",
narrative_loading_sub: "Dauert bis zu 10 Sekunden",
field_lastname: "Nachname",
scroll_hint:"↓ PROFIL AUSFÜLLEN UM DEINEN PLAN ZU ERHALTEN",
capture_required:"",
newsletter_label:"Ja, ich möchte wöchentliche Vorsorge-Updates von Plan-B erhalten",
capture_label:"",
capture_title:"Persönliche Angaben",
capture_sub:"Gib deine Daten ein — wir senden dir deinen vollständigen Plan sofort per E-Mail.",
field_email:"E-Mail",field_name:"Vorname",field_city:"Stadt",field_country:"Land",
field_pref_lang:"Bevorzugte Sprache",field_phone:"Telefon (optional)",
field_protein_label:"Proteinquellen (optional)",
protein_note:"Hochwertige nicht-kommerzielle Proteinquellen helfen uns deinen Ernaehrungsplan zu personalisieren.",
capture_btn:"Plan zusenden",
success_title:"Plan in dein Postfach gesendet!",
success_text:"Schau in dein E-Mail-Postfach. Wir haben deine personalisierten Empfehlungen und Aktionsplan beigefuegt.",
privacy_note:"Deine Daten werden nie verkauft. Jederzeit abmeldbar. DSGVO-konform.",
protein_offer_badge:"Exklusiv — DACH Region Prioritätszugang",
protein_offer_title:"Sichere deine hochwertige tierische Proteinquelle",
protein_offer_body:"Du hast angegeben, dass deine Proteinversorgung in einer Krise möglicherweise nicht gesichert ist. Wir haben eine exklusive, geprüfte Quelle für hochwertiges tierisches Protein identifiziert — mit Prioritätszugang für Plan-B Mitglieder in Deutschland, Österreich und der Schweiz.",
protein_offer_btn:"→ Jetzt Proteinquelle sichern",
capture_header:"Persönliche Daten",
cta_scroll:"↓ Fülle deine Daten aus um deinen personalisierten Plan per E-Mail zu erhalten",
success_explore:"↓ Entdecke jetzt deine vollständigen Empfehlungen unten",
success_spam:"📬 Nicht erhalten? Schau in deinen Spam-Ordner. Falls du es dort findest, markiere es bitte als Kein Spam — so landen zukünftige Updates direkt in deinem Posteingang und du hilfst uns die Zustellbarkeit für alle zu verbessern.",
view_amazon: "Bei Amazon ansehen",
bcat_water: "Wasser",
bcat_food: "Lebensmittel",
bcat_growing: "Anbau",
bcat_energy: "Energie",
bcat_medical: "Medizin",
bcat_sanitation: "Hygiene",
tl_week1: "Diese Woche",
tl_week2: "Woche 12",
tl_month1: "Monat 1",
tl_month2: "Monat 2",
tl_month3: "Monat 3",
tl_a1: "Keimsprossen-Samen + Einmachgläser kaufen. Heute mit dem Keimen beginnen.",
tl_a2: "2-Wochen-Trockenvorrat anlegen: Reis, Linsen, Hafer, Öl, Fischkonserven.",
tl_a3: "AWG-Wassergerät + Erste-Hilfe-Set + Durchfallmittel/ORS kaufen.",
tl_a4: "Vorrat auf 3 Monate erweitern. Hydroponik-Turm kaufen. Solar-Powerbank holen.",
tl_a5: "Solar-Generator (500Wh) + Schlafsäcke + Baofeng-Funkgeräte + Bargeldreserve.",
}
};
const WORKER_URL = 'https://planb-email.janwellmann.workers.dev/submit';
let currentLang = 'en';
function setLang(lang) {
currentLang = lang;
// Mark every .lang-btn (header + progress bar) — match by id fallback or data-lang
document.querySelectorAll('.lang-btn').forEach(btn => {
const matches = btn.id === 'btn-' + lang || btn.dataset.lang === lang;
btn.classList.toggle('active', matches);
});
document.documentElement.lang = lang;
document.querySelectorAll('[data-i18n]').forEach(el => {
const key = el.getAttribute('data-i18n');
if (T[lang][key]) el.textContent = T[lang][key];
});
if (T[lang].page_title) document.title = T[lang].page_title;
document.querySelectorAll('[data-ph-' + lang + ']').forEach(el => {
el.placeholder = el.getAttribute('data-ph-' + lang);
});
if (currentQ !== null && currentQ < QUESTIONS.length) renderQuestion();
}
function t(key) { return T[currentLang][key] || T['en'][key] || key; }
// ══════════════════════════════════════
// QUESTIONS
// ══════════════════════════════════════
const QUESTIONS = [
{
id: 'location', type: 'single',
text: { en: 'Where do you live?', de: 'Wo lebst du?' },
sub: { en: 'Your location affects water access, heating needs, and supply chain vulnerability.', de: 'Dein Standort beeinflusst Wasserzugang, Heizungsbedarf und Versorgungskettenanfälligkeit.' },
options: [
{ val: 'city_apt', label: { en: 'City apartment', de: 'Stadtwohnung' }, sub: { en: 'No outdoor space, shared building', de: 'Kein Außenbereich, Mehrfamilienhaus' } },
{ val: 'city_house', label: { en: 'City house', de: 'Stadthaus' }, sub: { en: 'Garden possible, urban area', de: 'Garten möglich, städtisches Gebiet' } },
{ val: 'suburban', label: { en: 'Suburban / small town', de: 'Vorstadt / Kleinstadt' }, sub: { en: 'Some outdoor space, near city', de: 'Etwas Außenbereich, stadtnah' } },
{ val: 'rural', label: { en: 'Rural / countryside', de: 'Ländlich / Land' }, sub: { en: 'Land available, remote', de: 'Grundstück vorhanden, abgelegen' } },
]
},
{
id: 'household', type: 'single',
text: { en: 'How many people in your household?', de: 'Wie viele Personen leben in deinem Haushalt?' },
sub: { en: 'Including children. This sets your calorie, water, and supply targets.', de: 'Einschließlich Kinder. Damit werden deine Kalorien-, Wasser- und Vorratsziele festgelegt.' },
options: [
{ val: '1', label: { en: '1 person', de: '1 Person' }, sub: { en: '', de: '' } },
{ val: '2', label: { en: '2 people', de: '2 Personen' }, sub: { en: '', de: '' } },
{ val: '4', label: { en: '34 people', de: '34 Personen' }, sub: { en: '', de: '' } },
{ val: '6', label: { en: '56 people', de: '56 Personen' }, sub: { en: '', de: '' } },
{ val: '8', label: { en: '7+ people', de: '7+ Personen' }, sub: { en: '', de: '' } },
]
},
{
id: 'water', type: 'single',
text: { en: 'What is your current water access?', de: 'Wie ist dein aktueller Wasserzugang?' },
sub: { en: 'If city supply failed tomorrow, what do you have?', de: 'Was hättest du, wenn die städtische Versorgung morgen ausfiele?' },
options: [
{ val: 'none', label: { en: 'Nothing stored', de: 'Nichts gelagert' }, sub: { en: 'Fully dependent on city supply', de: 'Vollständig abhängig von Stadtversorgung' } },
{ val: 'minimal', label: { en: 'A few days of bottled water', de: 'Einige Tage Flaschenwasser' }, sub: { en: '13 days buffer', de: '13 Tage Puffer' } },
{ val: 'filter', label: { en: 'Filter + some storage', de: 'Filter + etwas Vorrat' }, sub: { en: '12 week capability', de: '12 Wochen Kapazität' } },
{ val: 'solid', label: { en: 'AWG machine or large storage', de: 'AWG-Gerät oder großer Vorrat' }, sub: { en: '1+ month independent', de: '1+ Monat unabhängig' } },
]
},
{
id: 'food', type: 'single',
text: { en: 'How much food do you have stored?', de: 'Wie viele Lebensmittel hast du gelagert?' },
sub: { en: 'Not including fridge contents — only shelf-stable reserves.', de: 'Ohne Kühlschrankinhalt — nur haltbare Vorräte.' },
options: [
{ val: 'none', label: { en: 'Basically nothing', de: 'Praktisch nichts' }, sub: { en: 'Weekly grocery shopper', de: 'Wöchentlicher Einkäufer' } },
{ val: 'week', label: { en: 'About 1 week', de: 'Etwa 1 Woche' }, sub: { en: 'Some canned goods and pasta', de: 'Etwas Konserven und Nudeln' } },
{ val: 'month', label: { en: '13 months', de: '13 Monate' }, sub: { en: 'Conscious prepper basics', de: 'Bewusste Vorsorge-Grundlagen' } },
{ val: 'solid', label: { en: '3+ months', de: '3+ Monate' }, sub: { en: 'Serious preparedness', de: 'Ernsthafte Vorbereitung' } },
]
},
{
id: 'medical', type: 'single',
text: { en: 'Does your household have ongoing medical needs?', de: 'Hat dein Haushalt laufende medizinische Bedürfnisse?' },
sub: { en: 'Prescription medications, chronic conditions, infants, elderly.', de: 'Verschreibungspflichtige Medikamente, chronische Erkrankungen, Säuglinge, ältere Menschen.' },
options: [
{ val: 'none', label: { en: 'No — everyone healthy', de: 'Nein — alle gesund' }, sub: { en: 'No prescriptions needed', de: 'Keine Verschreibungen benötigt' } },
{ val: 'mild', label: { en: 'Minor — OTC medications only', de: 'Gering — nur rezeptfreie Medikamente' }, sub: { en: 'Vitamins, pain relief, allergy meds', de: 'Vitamine, Schmerzmittel, Allergiemittel' } },
{ val: 'moderate', label: { en: 'Moderate — some prescriptions', de: 'Mäßig — einige Verschreibungen' }, sub: { en: 'Manageable without hospital', de: 'Handhabbar ohne Krankenhaus' } },
{ val: 'high', label: { en: 'High — critical medications', de: 'Hoch — kritische Medikamente' }, sub: { en: 'Requires planning ahead', de: 'Erfordert vorausschauende Planung' } },
]
},
{
id: 'sanitation', type: 'single',
text: { en: 'What is your sanitation backup?', de: 'Wie ist deine Sanitär-Notlösung?' },
sub: { en: 'If your building\'s sewage and water stopped working today.', de: 'Wenn Abwasser und Wasser in deinem Gebäude heute ausfallen würden.' },
options: [
{ val: 'none', label: { en: 'No backup plan', de: 'Kein Notfallplan' }, sub: { en: 'Fully dependent on city', de: 'Vollständig von Stadt abhängig' } },
{ val: 'basic', label: { en: 'Basic supplies only', de: 'Nur Grundbedarf' }, sub: { en: 'Soap, disinfectant', de: 'Seife, Desinfektionsmittel' } },
{ val: 'bucket', label: { en: 'Bucket toilet + chemicals', de: 'Eimer-Toilette + Chemikalien' }, sub: { en: 'Can manage without sewage', de: 'Kann ohne Kanalisation auskommen' } },
{ val: 'full', label: { en: 'Full sanitation kit', de: 'Vollständiges Hygiene-Set' }, sub: { en: 'Ready for extended outage', de: 'Bereit für langen Ausfall' } },
]
},
{
id: 'budget', type: 'slider',
text: { en: 'What is your total preparedness budget?', de: 'Was ist dein Gesamtbudget für Vorsorge?' },
sub: { en: 'One-time investment for a 3-month baseline. You can build gradually.', de: 'Einmalige Investition für eine 3-Monats-Grundlage. Du kannst schrittweise aufbauen.' },
min: 200, max: 5000, step: 100, default: 1500,
unit: '€',
labels: { en: ['€200 (start)', '€5,000 (full)'], de: ['€200 (Start)', '€5.000 (voll)'] }
},
{
id: 'scenarios',
type: 'multi',
text: {
en: 'Which scenarios are you preparing for?',
de: 'Für welche Szenarien bereitest du dich vor?'
},
sub: {
en: 'Select all that apply. Your choices shape your personalised plan and recommendations.',
de: 'Alles Zutreffende auswählen. Deine Auswahl bestimmt deinen personalisierten Plan und die Empfehlungen.'
},
options: [
{ val: 'supply_shock', label: { en: '📦 Supply Side Shock', de: '📦 Versorgungsengpass' }, sub: { en: 'Empty shelves, import disruptions, rationing', de: 'Leere Regale, Importunterbrechungen, Rationierung' } },
{ val: 'food_crisis', label: { en: '🌾 Food Crisis / Shortage', de: '🌾 Lebensmittelkrise' }, sub: { en: 'Food scarcity, price spikes, distribution breakdown', de: 'Nahrungsknappheit, Preisspitzen, Verteilungsversagen' } },
{ val: 'hyperinflation', label: { en: '📈 Hyperinflation', de: '📈 Hyperinflation' }, sub: { en: 'Rapid currency devaluation, purchasing power collapse', de: 'Schnelle Geldentwertung, Kaufkraftverlust' } },
{ val: 'bank_crisis', label: { en: '🏦 Bank / Financial Crisis', de: '🏦 Banken- / Finanzkrise' }, sub: { en: 'Capital controls, frozen accounts, bank closures', de: 'Kapitalverkehrskontrollen, Kontosperrungen' } },
{ val: 'power_outage', label: { en: '⚡ Extended Power Outage', de: '⚡ Längerer Stromausfall' }, sub: { en: 'Grid failure, days to weeks without electricity', de: 'Netzausfall, Tage bis Wochen ohne Strom' } },
{ val: 'water_failure', label: { en: '💧 Water Supply Failure', de: '💧 Wasserversorgungsausfall' }, sub: { en: 'Municipal water disruption, contamination', de: 'Städtische Wasserunterbrechung, Kontamination' } },
{ val: 'partial_collapse', label: { en: '⚠️ Partial Collapse', de: '⚠️ Partieller Kollaps' }, sub: { en: 'Major infrastructure disruptions, weeks to months', de: 'Größere Infrastrukturunterbrechungen, Wochen bis Monate' } },
{ val: 'war', label: { en: '🪖 War / Armed Conflict', de: '🪖 Krieg / Bewaffneter Konflikt' }, sub: { en: 'Regional conflict affecting supply and safety', de: 'Regionaler Konflikt, der Versorgung und Sicherheit betrifft' } },
{ val: 'total_collapse', label: { en: '💀 Total Collapse', de: '💀 Totaler Kollaps' }, sub: { en: 'Full breakdown of infrastructure and social order', de: 'Vollständiger Zusammenbruch von Infrastruktur und Ordnung'} },
]
},
{
id: 'protein_pref',
type: 'multi',
text: { en: 'What is your top protein preference?', de: 'Was ist deine bevorzugte Proteinquelle?' },
sub: { en: 'Select all that apply. This shapes your food recommendations.', de: 'Alles Zutreffende auswählen. Beeinflusst deine Lebensmittelempfehlungen.' },
options: [
{ val: 'eggs', label: { en: '🥚 Free-range / pastured eggs', de: '🥚 Freiland- / Weidehaltungs-Eier' }, sub: { en: 'Local farm or own hens', de: 'Lokaler Bauernhof oder eigene Hühner' } },
{ val: 'dairy', label: { en: '🥛 Raw milk / grass-fed dairy', de: '🥛 Rohmilch / Weidemilchprodukte' }, sub: { en: 'Direct from farm', de: 'Direkt vom Bauernhof' } },
{ val: 'meat', label: { en: '🦌 Wild game / grass-fed meat', de: '🦌 Wild / Weidefleisch' }, sub: { en: 'Hunting, local butcher, own farm', de: 'Jagd, lokaler Metzger, eigener Hof' } },
{ val: 'fish', label: { en: '🐟 Wild-caught fish / seafood', de: '🐟 Wildfang-Fisch / Meeresfrüchte' }, sub: { en: 'Fishing access or trusted source', de: 'Angelzugang oder vertrauenswürdige Quelle' } },
{ val: 'legumes', label: { en: '🌱 Home-grown legumes / sprouts', de: '🌱 Selbst angebaute Hülsenfrüchte' }, sub: { en: 'Garden, balcony, or indoor', de: 'Garten, Balkon oder Indoor' } },
{ val: 'none', label: { en: '❌ No preference / not applicable', de: '❌ Keine Präferenz / trifft nicht zu' }, sub: { en: '', de: '' } },
]
},
{
id: 'protein_access',
type: 'single',
text: { en: 'Is this protein source reliably available in a crisis?', de: 'Ist diese Proteinquelle in einer Krise zuverlässig verfügbar?' },
sub: { en: 'Think: if supermarkets were closed for 4 weeks, could you still access your preferred protein?', de: 'Angenommen, Supermärkte sind 4 Wochen geschlossen — hättest du noch Zugang zu deiner bevorzugten Proteinquelle?' },
options: [
{ val: 'yes', label: { en: '✅ Yes — I have a trusted local source', de: '✅ Ja — ich habe eine verlässliche lokale Quelle' }, sub: { en: 'Farm, hunter, fisher, own livestock or garden', de: 'Hof, Jäger, Fischer, eigenes Vieh oder Garten' } },
{ val: 'uncertain', label: { en: '⚠️ Not sure — I depend on stores', de: '⚠️ Unsicher — ich bin auf Geschäfte angewiesen' }, sub: { en: 'No guaranteed access if supply chains fail', de: 'Kein gesicherter Zugang bei Versorgungsausfall' } },
]
},
];
// ══════════════════════════════════════
// STATE
// ══════════════════════════════════════
let currentQ = 0;
let answers = {};
let currentScenario = 1;
let riskScore = 0;
let riskLevelStr = '';
// ══════════════════════════════════════
// QUIZ FLOW
// ══════════════════════════════════════
function startQuiz() {
// Safe hide — null-check every element
const hide = id => { const el = document.getElementById(id) || document.querySelector('.' + id); if(el) el.classList.add('hidden'); };
const show = id => { const el = document.getElementById(id); if(el) { el.classList.remove('hidden'); el.style.display=''; } };
hide('hero-section');
hide('about-section');
// scenario-strip has class only, no id
const strip = document.querySelector('.scenario-strip');
if(strip) strip.classList.add('hidden');
// Unlock scroll for quiz/results
document.body.style.overflow = 'auto';
document.body.style.height = 'auto';
document.querySelector('.app').style.overflow = 'visible';
document.querySelector('.app').style.height = 'auto';
const quizEl = document.getElementById('quiz-section');
if(quizEl) quizEl.classList.remove('hidden');
document.body.classList.add('quiz-active');
window.scrollTo({ top: 0, behavior: 'smooth' });
currentQ = 0; answers = {};
renderQuestion();
}
function renderQuestion() {
if(!QUESTIONS[currentQ]) { console.error('No question at index', currentQ); return; }
const q = QUESTIONS[currentQ];
const pct = ((currentQ + 1) / QUESTIONS.length) * 100;
document.getElementById('progress-fill').style.width = pct + '%';
document.getElementById('progress-count').textContent = (currentQ + 1) + ' / ' + QUESTIONS.length;
let html = `<div class="question-card">
<div class="q-step">${t('q_step')} ${currentQ + 1}</div>
<div class="q-text">${q.text[currentLang]}</div>
<div class="q-sub">${q.sub[currentLang]}</div>`;
if (q.type === 'single') {
html += `<div class="q-options">`;
q.options.forEach(opt => {
const sel = answers[q.id] === opt.val ? 'selected' : '';
html += `<div class="q-opt ${sel}" onclick="selectOption('${q.id}','${opt.val}',this)">
<div class="opt-radio"></div>
<div>
<div class="opt-label">${opt.label[currentLang]}</div>
${opt.sub[currentLang] ? `<div class="opt-sub">${opt.sub[currentLang]}</div>` : ''}
</div>
</div>`;
});
html += `</div>`;
} else if (q.type === 'multi') {
html += `<div class="q-options">`;
const selArr = answers[q.id] || [];
q.options.forEach(opt => {
const sel = selArr.includes(opt.val) ? 'selected' : '';
html += `<div class="q-opt multi ${sel}" onclick="toggleMulti('${q.id}','${opt.val}',this)">
<div class="opt-radio"></div>
<div><div class="opt-label">${opt.label[currentLang]}</div></div>
</div>`;
});
html += `</div>`;
} else if (q.type === 'slider') {
const val = answers[q.id] || q.default;
const labels = q.labels[currentLang] || q.labels['en'];
html += `<div class="slider-wrap">
<div class="slider-labels"><span>${labels[0]}</span><span>${labels[1]}</span></div>
<input type="range" id="slider-input" min="${q.min}" max="${q.max}" step="${q.step}" value="${val}"
oninput="updateSlider(this.value,'${q.id}')"/>
<div class="slider-val" id="slider-display">${q.unit}${parseInt(val).toLocaleString()}</div>
</div>`;
if (!answers[q.id]) answers[q.id] = q.default;
}
html += `</div>`;
const isLast = currentQ === QUESTIONS.length - 1;
const nav = `<div class="q-nav">
${currentQ > 0 ? `<button class="btn-back" onclick="prevQ()">${t('back_btn')}</button>` : ''}
<button class="btn-next" id="btn-next" onclick="nextQ()" ${canProceed() ? '' : 'disabled'}>
${isLast ? t('finish_btn') : t('continue_btn')}
</button>
</div>`;
document.getElementById('question-container').innerHTML = html;
document.getElementById('quiz-footer').innerHTML = nav;
}
function canProceed() {
const q = QUESTIONS[currentQ];
if (q.type === 'slider' || q.type === 'multi') return true;
return !!answers[q.id];
}
function selectOption(qid, val, el) {
answers[qid] = val;
document.querySelectorAll('.q-opt').forEach(o => o.classList.remove('selected'));
el.classList.add('selected');
document.getElementById('btn-next').disabled = false;
}
function toggleMulti(qid, val, el) {
if (!answers[qid]) answers[qid] = [];
const idx = answers[qid].indexOf(val);
if (idx >= 0) { answers[qid].splice(idx, 1); el.classList.remove('selected'); }
else { answers[qid].push(val); el.classList.add('selected'); }
}
function updateSlider(val, qid) {
answers[qid] = parseInt(val);
document.getElementById('slider-display').innerHTML = `${parseInt(val).toLocaleString()}`;
}
function nextQ() {
if (currentQ < QUESTIONS.length - 1) { currentQ++; renderQuestion(); }
else showResults();
}
function prevQ() { if (currentQ > 0) { currentQ--; renderQuestion(); } }
function restartQuiz() {
const rs = document.getElementById('results-section');
if(rs) { rs.classList.remove('active'); rs.style.display = 'none'; }
const hero = document.getElementById('hero-section');
if(hero) hero.classList.remove('hidden');
const about = document.getElementById('about-section');
if(about) about.classList.remove('hidden');
const strip = document.querySelector('.scenario-strip');
if(strip) strip.classList.remove('hidden');
const quiz = document.getElementById('quiz-section');
if(quiz) quiz.classList.add('hidden');
document.body.classList.remove('quiz-active');
window.scrollTo({ top: 0, behavior: 'smooth' });
}
// ══════════════════════════════════════
// RESULTS ENGINE
// ══════════════════════════════════════
function calcRisk() {
let s = 0;
if (answers.water === 'none') s += 3; else if (answers.water === 'minimal') s += 2; else if (answers.water === 'filter') s += 1;
if (answers.food === 'none') s += 3; else if (answers.food === 'week') s += 2; else if (answers.food === 'month') s += 1;
if (answers.medical === 'high') s += 2; else if (answers.medical === 'moderate') s += 1;
if (answers.sanitation === 'none') s += 2; else if (answers.sanitation === 'basic') s += 1;
return s;
}
function getN() { const m = {'1':1,'2':2,'4':3.5,'6':5.5,'8':8}; return m[answers.household] || 4; }
function getScenarioRecs(scenario) {
const n = getN();
const isCity = answers.location === 'city_apt' || answers.location === 'city_house';
const lang = currentLang;
const amzDomain = lang === 'de' ? 'amazon.de' : 'amazon.com';
const data = {
1: {
label: { en: 'Total Supply Collapse', de: 'Totaler Versorgungskollaps' },
cards: [
{ icon: '💧', cat: { en: 'Water', de: 'Wasser' }, title: { en: 'Atmospheric Water Generator', de: 'Atmosphärischer Wassergenerator' },
priority: { en: 'CRITICAL', de: 'KRITISCH' }, pCls: 'p-critical', color: '#1A4A5A',
items: [
{ name: { en: 'Solaris WaterGen A10 (10L/day)', de: 'Solaris WaterGen A10 (10L/Tag)' },
why: { en: 'Extracts drinking water from air humidity. No plumbing needed. City apartment essential.', de: 'Gewinnt Trinkwasser aus Luftfeuchtigkeit. Kein Anschluss erforderlich. Unverzichtbar für Stadtwohnungen.' },
cost: '~€420', link: `https://www.${amzDomain}/s?k=atmospheric+water+generator+10L` },
{ name: { en: '5L food-grade jerrycans x6 (buffer)', de: '5L Lebensmittelkanister x6 (Puffer)' },
why: { en: 'AWG needs power. Store 3-day drinking buffer for power outage gaps.', de: 'AWG benötigt Strom. 3-Tage-Trinkwasserpuffer für Stromausfall-Lücken.' },
cost: '~€30', link: `https://www.${amzDomain}/s?k=food+grade+jerrycan+5L` },
]},
{ icon: '🌾', cat: { en: 'Food', de: 'Lebensmittel' }, title: { en: 'Caloric Base + Indoor Growing', de: 'Kalorienbasis + Indoor-Anbau' },
priority: { en: 'CRITICAL', de: 'KRITISCH' }, pCls: 'p-critical', color: '#3A1A00',
items: [
{ name: { en: `White rice 25kg x${Math.max(2,Math.round(n))} bags`, de: `Weißer Reis 25kg x${Math.max(2,Math.round(n))} Säcke` },
why: { en: '25-year shelf life sealed. 3,600 cal/kg. Caloric backbone of any long-term food reserve.', de: 'Versiegelt 25 Jahre haltbar. 3.600 kcal/kg. Kalorienbasis jeder Langzeitvorsorge.' },
cost: `~€${Math.round(n*20)}`, link: `https://www.${amzDomain}/s?k=white+rice+25kg+bulk` },
{ name: { en: '30-pod vertical hydroponic tower', de: '30-Pod-Vertikal-Hydroponik-Turm' },
why: { en: 'Continuous fresh greens year-round. Closes the vitamin gap that dried food cannot fill.', de: 'Kontinuierlich frisches Grünzeug ganzjährig. Schließt die Vitaminlücke, die Trockennahrung nicht füllt.' },
cost: '~€280350', link: `https://www.${amzDomain}/s?k=hydroponic+tower+indoor+garden` },
{ name: { en: 'Sprouting seeds (lentil, mung, alfalfa)', de: 'Keimsprossen-Samen (Linsen, Mungbohnen, Alfalfa)' },
why: { en: 'Fresh Vitamin C in 3 days. Zero equipment. Fastest food possible.', de: 'Frisches Vitamin C in 3 Tagen. Keine Ausrüstung. Die schnellste Nahrung überhaupt.' },
cost: '~€18', link: `https://www.${amzDomain}/s?k=sprouting+seeds+kit` },
]},
{ icon: '⚡', cat: { en: 'Energy', de: 'Energie' }, title: { en: 'Solar Power System', de: 'Solar-Energiesystem' },
priority: { en: 'CRITICAL', de: 'KRITISCH' }, pCls: 'p-critical', color: '#1A1A3A',
items: [
{ name: { en: 'Portable solar generator 500Wh (EcoFlow RIVER 2)', de: 'Tragbarer Solar-Generator 500Wh (EcoFlow RIVER 2)' },
why: { en: 'Powers AWG for 2.5 hrs, hydroponic LED for 10+ hrs, charges all devices. City energy backbone.', de: 'Betreibt AWG 2,5 Std., Hydroponik-LED 10+ Std., lädt alle Geräte. Städtische Energiegrundlage.' },
cost: '~€380', link: `https://www.${amzDomain}/s?k=EcoFlow+RIVER+2+solar+generator` },
{ name: { en: '60W foldable solar panel (balcony)', de: '60W Faltbares Solarpanel (Balkon)' },
why: { en: 'Charges generator via balcony railing. No installation needed.', de: 'Lädt Generator über Balkongeländer. Keine Installation erforderlich.' },
cost: '~€120', link: `https://www.${amzDomain}/s?k=60W+foldable+solar+panel+balcony` },
]},
]
},
2: {
label: { en: 'Partial Supply Collapse', de: 'Partieller Versorgungskollaps' },
cards: [
{ icon: '💧', cat: { en: 'Water', de: 'Wasser' }, title: { en: 'Water Buffer + Filtration', de: 'Wasserpuffer + Filtration' },
priority: { en: 'CRITICAL', de: 'KRITISCH' }, pCls: 'p-critical', color: '#1A3A4A',
items: [
{ name: { en: '5L food-grade jerrycans x10 (50L)', de: '5L Lebensmittelkanister x10 (50L)' },
why: { en: '50L covers 3 days drinking water for 4 people. Minimum safe buffer.', de: '50L decken 3 Tage Trinkwasser für 4 Personen. Mindest-Sicherheitspuffer.' },
cost: '~€50', link: `https://www.${amzDomain}/s?k=5L+jerrycan+food+grade` },
{ name: { en: 'Big Berkey water filter', de: 'Big Berkey Wasserfilter' },
why: { en: 'Gravity-fed, no electricity. Purifies any water source. Last-resort security.', de: 'Schwerkraftbetrieben, kein Strom. Reinigt jede Wasserquelle. Letzte Sicherheitslinie.' },
cost: '~€280', link: `https://www.${amzDomain}/s?k=Big+Berkey+water+filter` },
]},
{ icon: '🛒', cat: { en: 'Food', de: 'Lebensmittel' }, title: { en: '4-Week Pantry Reserve', de: '4-Wochen-Vorratskammer' },
priority: { en: 'HIGH', de: 'HOCH' }, pCls: 'p-high', color: '#2A1500',
items: [
{ name: { en: 'Rice, lentils, beans, oats — 4-week supply', de: 'Reis, Linsen, Bohnen, Hafer — 4-Wochen-Vorrat' },
why: { en: 'Complete protein: rice + legumes. Cheap, calorie-dense, long shelf life. Buy now.', de: 'Vollständiges Protein: Reis + Hülsenfrüchte. Günstig, kalorienreich, lange haltbar. Jetzt kaufen.' },
cost: `~€${Math.round(n*25)}`, link: `https://www.${amzDomain}/s?k=bulk+rice+lentils+oats` },
{ name: { en: 'Sprouting seeds (3-day fresh food)', de: 'Keimsprossen-Samen (3-Tage-Frischkost)' },
why: { en: 'Fresh produce disappears first in partial collapse. Sprouts give Vitamin C in 3 days.', de: 'Frische Produkte verschwinden zuerst. Sprossen liefern Vitamin C in 3 Tagen.' },
cost: '~€18', link: `https://www.${amzDomain}/s?k=sprouting+seeds+lentil+mung` },
]},
{ icon: '🕯️', cat: { en: 'Energy', de: 'Energie' }, title: { en: 'Power & Heat Backup', de: 'Strom- & Wärme-Backup' },
priority: { en: 'HIGH', de: 'HOCH' }, pCls: 'p-high', color: '#1A1A2A',
items: [
{ name: { en: 'Solar power bank 40,000mAh + 20W panel', de: 'Solar-Powerbank 40.000mAh + 20W-Panel' },
why: { en: 'Keeps phones, lights, radio running through multi-day outage.', de: 'Hält Handys, Lichter, Radio bei mehrtägigem Ausfall am Laufen.' },
cost: '~€90', link: `https://www.${amzDomain}/s?k=solar+power+bank+40000mAh` },
{ name: { en: `Thermal sleeping bags -10°C x${Math.round(n)}`, de: `Thermische Schlafsäcke -10°C x${Math.round(n)}` },
why: { en: 'City apartments lose heat fast without heating. A bag survives Central European winters.', de: 'Stadtwohnungen verlieren schnell Wärme. Ein Schlafsack übersteht mitteleuropäische Winter.' },
cost: `~€${Math.round(n)*45}`, link: `https://www.${amzDomain}/s?k=sleeping+bag+minus+10+degrees` },
]},
]
},
3: {
label: { en: 'Hyperinflation', de: 'Hyperinflation' },
cards: [
{ icon: '🌾', cat: { en: 'Food Hedge', de: 'Lebensmittel-Absicherung' }, title: { en: 'Buy Now While Prices Are Low', de: 'Jetzt kaufen, solange Preise niedrig sind' },
priority: { en: 'CRITICAL', de: 'KRITISCH' }, pCls: 'p-critical', color: '#2A1A00',
items: [
{ name: { en: `Rice 25kg x${Math.max(4,Math.round(n*1.5))} bags (buy immediately)`, de: `Reis 25kg x${Math.max(4,Math.round(n*1.5))} Säcke (sofort kaufen)` },
why: { en: 'In hyperinflation, food is the best hard asset. Rice at €0.80/kg today may cost €4/kg in 6 months.', de: 'Bei Hyperinflation ist Nahrung das beste Sachgut. Reis für €0,80/kg heute kann in 6 Monaten €4/kg kosten.' },
cost: `~€${Math.round(n*1.5*20)}`, link: `https://www.${amzDomain}/s?k=rice+25kg+white` },
{ name: { en: 'Freeze-dried meals x60 (25-year shelf life)', de: 'Gefriergetrocknete Mahlzeiten x60 (25 Jahre haltbar)' },
why: { en: "Locks in today's food cost for 25 years. Best inflation hedge in existence.", de: "Sichert die heutigen Lebensmittelkosten für 25 Jahre. Beste Inflationsabsicherung überhaupt." },
cost: '~€300', link: `https://www.${amzDomain}/s?k=freeze+dried+food+emergency+meals` },
{ name: { en: 'Physical silver coins (1oz)', de: 'Physische Silbermünzen (1 oz)' },
why: { en: 'Historically proven inflation hedge. Divisible and recognisable for local barter.', de: 'Historisch bewährte Inflationsabsicherung. Teilbar und für lokalen Tauschhandel erkennbar.' },
cost: '~€3035/coin', link: `https://www.${amzDomain}/s?k=silver+coin+1+oz` },
]},
{ icon: '🌿', cat: { en: 'Self-Sufficiency', de: 'Selbstversorgung' }, title: { en: 'Grow Your Own — City', de: 'Selbst anbauen — Stadt' },
priority: { en: 'HIGH', de: 'HOCH' }, pCls: 'p-high', color: '#0A1A0A',
items: [
{ name: { en: 'Indoor hydroponic tower (30 pods)', de: 'Indoor-Hydroponik-Turm (30 Pods)' },
why: { en: 'Eliminates grocery spend on herbs and greens permanently. €280 upfront vs. €15+/week at inflated prices.', de: 'Eliminiert dauerhaft Ausgaben für Kräuter und Salat. €280 einmalig vs. €15+/Woche bei Inflationspreisen.' },
cost: '~€280', link: `https://www.${amzDomain}/s?k=indoor+hydroponic+tower+30+pod` },
{ name: { en: 'Heirloom seed bank (50+ varieties)', de: 'Erbsamen-Bank (50+ Sorten)' },
why: { en: 'Ability to grow food is wealth in hyperinflation. Heirloom seeds replant each season forever.', de: 'Die Fähigkeit, Nahrung anzubauen, ist Reichtum bei Hyperinflation. Erbsamen können jede Saison neu gepflanzt werden.' },
cost: '~€35', link: `https://www.${amzDomain}/s?k=heirloom+vegetable+seed+bank` },
]},
]
},
4: {
label: { en: 'Food Shortage', de: 'Lebensmittelkrise' },
cards: [
{ icon: '🥦', cat: { en: 'Fresh Food', de: 'Frische Lebensmittel' }, title: { en: 'Indoor Food Production', de: 'Indoor-Lebensmittelproduktion' },
priority: { en: 'HIGH', de: 'HOCH' }, pCls: 'p-high', color: '#0A2A0A',
items: [
{ name: { en: '30-pod hydroponic tower', de: '30-Pod-Hydroponik-Turm' },
why: { en: 'Leafy greens, herbs, cherry tomatoes continuously. What disappears first from shelves = what you grow.', de: 'Kontinuierlich Blattgemüse, Kräuter, Kirschtomaten. Was zuerst aus den Regalen verschwindet = was du anbaust.' },
cost: '~€280', link: `https://www.${amzDomain}/s?k=indoor+hydroponic+tower` },
{ name: { en: 'Sprouting kit + seeds (5 varieties)', de: 'Keimsprossen-Set + Samen (5 Sorten)' },
why: { en: 'During shortages, fresh produce disappears first. Lentil/mung sprouts ready in 3 days, any time.', de: 'Bei Engpässen verschwinden frische Produkte zuerst. Linsen-/Mungbohnen-Sprossen in 3 Tagen fertig.' },
cost: '~€25', link: `https://www.${amzDomain}/s?k=sprouting+seeds+kit+jars` },
]},
{ icon: '📦', cat: { en: 'Storage', de: 'Vorratshaltung' }, title: { en: '90-Day Food Reserve', de: '90-Tage-Lebensmittelvorrat' },
priority: { en: 'HIGH', de: 'HOCH' }, pCls: 'p-high', color: '#1A1200',
items: [
{ name: { en: 'Rice + legumes 90-day supply (buy now)', de: 'Reis + Hülsenfrüchte 90-Tage-Vorrat (jetzt kaufen)' },
why: { en: 'Food shortage = prices rise before shelves empty. Buy 90-day supply this week at current prices.', de: 'Lebensmittelknappheit = Preise steigen bevor Regale leer sind. Jetzt kaufen zum aktuellen Preis.' },
cost: `~€${Math.round(n*40)}`, link: `https://www.${amzDomain}/s?k=bulk+rice+lentils+beans` },
{ name: { en: 'Mylar bags + oxygen absorbers (seal grains)', de: 'Mylar-Beutel + Sauerstoffabsorber (Getreide versiegeln)' },
why: { en: 'Seal bulk grains in Mylar = 20-year shelf life. One afternoon\'s work, permanent upgrade.', de: 'Getreide in Mylar versiegeln = 20 Jahre haltbar. Ein Nachmittag Arbeit, dauerhafter Vorteil.' },
cost: '~€30', link: `https://www.${amzDomain}/s?k=mylar+bags+oxygen+absorbers` },
]},
]
}
};
return data[scenario];
}
function showResults() {
document.getElementById('quiz-section').classList.add('hidden');
document.body.classList.remove('quiz-active');
const results = document.getElementById('results-section');
results.style.display = 'block';
results.classList.add('active');
window.scrollTo({ top: 0, behavior: 'smooth' });
riskScore = calcRisk();
checkProteinOffer();
const score = riskScore;
let riskCls, riskTitle, riskDesc, riskEmoji;
if (score >= 8) { riskCls='risk-critical'; riskEmoji='🔴'; riskTitle=t('risk_critical_title'); riskDesc=t('risk_critical_desc'); riskLevelStr='CRITICAL'; }
else if (score >= 5) { riskCls='risk-high'; riskEmoji='🟠'; riskTitle=t('risk_high_title'); riskDesc=t('risk_high_desc'); riskLevelStr='HIGH RISK'; }
else if (score >= 3) { riskCls='risk-medium'; riskEmoji='🟡'; riskTitle=t('risk_medium_title'); riskDesc=t('risk_medium_desc'); riskLevelStr='MODERATE'; }
else { riskCls='risk-low'; riskEmoji='🟢'; riskTitle=t('risk_low_title'); riskDesc=t('risk_low_desc'); riskLevelStr='PREPARED'; }
document.getElementById('risk-banner-container').innerHTML = `
<div class="risk-banner ${riskCls}">
<div class="risk-level">${riskEmoji} ${t('risk_label')}</div>
<div class="risk-title">${riskTitle}</div>
<div class="risk-desc">${riskDesc}</div>
</div>`;
// Default to scenario matching first user-selected crisis
const scenarioTabMap = {
total_collapse: 1, partial_collapse: 2, hyperinflation: 3,
supply_shock: 2, food_crisis: 4, bank_crisis: 3
};
const selectedScenarios = answers.scenarios || [];
const firstSelected = Array.isArray(selectedScenarios) ? selectedScenarios[0] : selectedScenarios.split(',')[0];
const defaultTab = scenarioTabMap[firstSelected] || 1;
showScenario(defaultTab);
updateRegionIndicator();
renderBudgetMeter();
fetchNarrative();
renderTimeline();
setTimeout(() => { document.getElementById('about-section').classList.remove('hidden'); }, 300);
}
function showScenario(n) {
currentScenario = n;
document.querySelectorAll('.s-tab').forEach(t => {
t.classList.remove('active-s1','active-s2','active-s3','active-s4');
if (parseInt(t.dataset.s) === n) t.classList.add(`active-s${n}`);
});
const rec = getScenarioRecs(n);
let html = '';
rec.cards.forEach((card, ci) => {
const viewTxt = t('view_amazon');
html += `<div class="rec-card" style="animation-delay:${ci*0.08}s">
<div class="rec-header">
<div class="rec-icon" style="background:${card.color}20;border:1px solid ${card.color}40">${card.icon}</div>
<div>
<div class="rec-cat">${card.cat[currentLang]}</div>
<div class="rec-title">${card.title[currentLang]}</div>
</div>
<span class="priority-badge ${card.pCls}">${card.priority[currentLang]}</span>
</div>
<div class="rec-body"><div class="rec-items">`;
card.items.forEach(item => {
html += `<div class="rec-item">
<div class="item-name">${item.name[currentLang]}</div>
<div class="item-why">${item.why[currentLang]}</div>
<div style="display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px">
<div class="item-cost">${item.cost}</div>
${item.link ? `<a class="affiliate-btn" href="${item.link}" target="_blank" rel="noopener noreferrer">🛒 ${viewTxt}</a>` : ''}
</div>
</div>`;
});
html += `</div></div></div>`;
});
document.getElementById('rec-cards-container').innerHTML = html;
}
function renderBudgetMeter() {
const budget = answers.budget || 1500;
const lang = currentLang;
const cats = [
{ label: t('bcat_water'), pct: 22, color: '#20A8A0' },
{ label: t('bcat_food'), pct: 28, color: '#E07B20' },
{ label: t('bcat_growing'), pct: 18, color: '#28A060' },
{ label: t('bcat_energy'), pct: 18, color: '#4A8FE0' },
{ label: t('bcat_medical'), pct: 9, color: '#E03A3A' },
{ label: t('bcat_sanitation'), pct: 5, color: '#D4A820' },
];
let html = `<div class="budget-meter">
<div class="bm-title">${t('budget_title')}${lang==='de'?'Gesamt':'Total'}: €${budget.toLocaleString()}</div>
<div class="budget-cats">`;
cats.forEach(c => {
html += `<div class="bcat">
<div class="bcat-label">${c.label}</div>
<div class="bcat-bar-wrap"><div class="bcat-bar" style="width:${c.pct}%;background:${c.color}"></div></div>
<div class="bcat-cost">€${Math.round(budget*c.pct/100)}</div>
</div>`;
});
html += `</div></div>`;
document.getElementById('budget-meter-container').innerHTML = html;
}
function renderTimeline() {
const colors = ['#E03A3A','#E07B20','#D4A820','#28A060','#4A8FE0'];
const items = [
{ when: t('tl_week1'), action: t('tl_a1'), cost: '~€25', emoji: '🌱' },
{ when: t('tl_week2'), action: t('tl_a2'), cost: `~€${Math.round(getN()*20)}`, emoji: '🛒' },
{ when: t('tl_month1'), action: t('tl_a3'), cost: '~€520', emoji: '💧' },
{ when: t('tl_month2'), action: t('tl_a4'), cost: '~€700', emoji: '⚡' },
{ when: t('tl_month3'), action: t('tl_a5'), cost: '~€600', emoji: '📡' },
];
let html = '';
items.forEach((item, i) => {
html += `<div class="tl-item">
<div class="tl-dot" style="border-color:${colors[i]};color:${colors[i]}">${item.emoji}</div>
<div class="tl-content">
<div class="tl-when">${item.when}</div>
<div class="tl-action">${item.action}</div>
<div class="tl-cost">${item.cost}</div>
</div>
</div>`;
});
document.getElementById('timeline-items').innerHTML = html;
}
// ── CAPTURE FORM SUBMIT ──
function submitCapture(e) {
e.preventDefault();
// Gather all form values safely
const g = id => { const el = document.getElementById(id); return el ? el.value.trim() : ''; };
const gc = id => { const el = document.getElementById(id); return el ? el.checked : false; };
const firstName = g('f_name');
const lastName = g('f_lastname');
const email = g('f_email');
const city = g('f_city');
const country = g('f_country');
const phone = g('f_phone');
const prefLang = g('f_pref_lang') || currentLang;
const protein = g('f_protein_detail');
const newsletter = gc('f_newsletter') ? 'yes' : 'no';
const score = calcRisk();
const lvl = score >= 8 ? 'CRITICAL' : score >= 5 ? 'HIGH RISK' : score >= 3 ? 'MODERATE' : 'PREPARED';
// Show loading state on button
const btn = document.getElementById('capture-submit-btn');
if(btn) { btn.disabled = true; btn.innerHTML = '<span>Sending...</span>'; }
const payload = {
first_name: firstName,
last_name: lastName,
email: email,
city: city,
country: country,
phone: phone,
preferred_language: prefLang,
newsletter: newsletter,
newsletter_subscriber: newsletter === 'yes' ? '✅ YES — send weekly updates' : '❌ NO — do not send',
protein_detail: protein,
// All quiz answers
location: answers.location || '',
household_size: answers.household || '',
water_access: answers.water || '',
food_reserves: answers.food || '',
medical_needs: answers.medical || '',
sanitation: answers.sanitation || '',
budget_eur: answers.budget || 1500,
priorities: (answers.scenarios || []).join(', '),
protein_access: (answers.protein || '') + (protein ? ' | ' + protein : ''),
scenarios: (answers.scenarios || []).join(', '),
protein_preference: (answers.protein_pref || []).join(', '),
protein_security: answers.protein_access || '',
protein_offer_sent: answers.protein_access === 'uncertain' ? 'yes' : 'no',
risk_score: score,
risk_level: lvl,
language_used: currentLang,
submitted_at: new Date().toISOString(),
send_email: 'yes',
_subject: (T[currentLang].brand || 'Plan-B') + ' — ' + lvl + ' — ' + firstName + ' ' + lastName + ' — ' + city + ', ' + country
};
const showSuccess = () => {
const form = document.getElementById('capture-form');
const success = document.getElementById('capture-success');
if(form) form.style.display = 'none';
if(success) { success.classList.remove('hidden'); success.style.display = 'block'; }
};
// STEP 1: Send to Formspree directly — guaranteed, no dependencies
fetch('https://formspree.io/f/maqlddel', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
body: JSON.stringify(payload)
}).catch(() => {}); // fire and forget
// STEP 2: Also send to Worker for DB storage + AI email (best effort, 12s timeout)
const workerTimeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error('timeout')), 12000)
);
Promise.race([
fetch(WORKER_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
mode: 'cors',
body: JSON.stringify(payload)
}),
workerTimeout
]).catch(() => {}); // fire and forget — Formspree already captured the data
// Brief delay before showing success — feels more intentional
setTimeout(showSuccess, 1500);
}
// ══════════════════════════════════════
// LOCATION-AWARE AFFILIATE SYSTEM
// ══════════════════════════════════════
// Detect user's likely country from browser
function detectUserCountry() {
const lang = navigator.language || navigator.userLanguage || 'en';
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone || '';
if (lang.startsWith('de') || tz.includes('Europe/Berlin') || tz.includes('Europe/Vienna') || tz.includes('Europe/Zurich')) return 'DACH';
if (lang.startsWith('fr') || tz.includes('Europe/Paris')) return 'FR';
if (lang.includes('GB') || lang.includes('en-GB') || tz.includes('Europe/London')) return 'UK';
if (tz.includes('America/Toronto') || tz.includes('America/Vancouver') || lang.includes('en-CA')) return 'CA';
if (tz.includes('Europe/Amsterdam') || tz.includes('Europe/Brussels') || tz.includes('Europe/Stockholm') || tz.includes('Europe/Oslo') || tz.includes('Europe/Copenhagen')) return 'NLNORDIC';
if (tz.includes('Europe/')) return 'EU_OTHER';
if (tz.includes('America/')) return 'US';
return 'US'; // default
}
let userRegion = detectUserCountry();
// Override region when country selected in form
function updateRegionFromCountry(countryCode) {
const dach = ['AT','DE','CH'];
const uk = ['GB'];
const fr = ['FR','BE'];
const ca = ['CA'];
const nlnordic = ['NL','SE','NO','DK','FI'];
const us = ['US'];
if (dach.includes(countryCode)) userRegion = 'DACH';
else if (uk.includes(countryCode)) userRegion = 'UK';
else if (fr.includes(countryCode)) userRegion = 'FR';
else if (ca.includes(countryCode)) userRegion = 'CA';
else if (nlnordic.includes(countryCode)) userRegion = 'NLNORDIC';
else if (us.includes(countryCode)) userRegion = 'US';
else if (countryCode && countryCode !== 'OTHER') userRegion = 'EU_OTHER';
}
// Get Amazon domain for current region
function getAmazonDomain() {
const map = { DACH:'amazon.de', UK:'amazon.co.uk', FR:'amazon.fr', CA:'amazon.ca', NLNORDIC:'amazon.de', EU_OTHER:'amazon.de', US:'amazon.com' };
return map[userRegion] || 'amazon.com';
}
// Get region label for display
function getRegionLabel() {
const map = { DACH:'🇩🇪 DE/AT/CH', UK:'🇬🇧 UK', FR:'🇫🇷 France', CA:'🇨🇦 Canada', NLNORDIC:'🇳🇱 NL/Nordic', EU_OTHER:'🇪🇺 Europe', US:'🇺🇸 USA' };
return map[userRegion] || '🌍 Global';
}
// Get affiliate links for a product keyword, region-aware
// Returns array of {store, url, label, badge} objects
function getAffiliateLinks(keyword, productType) {
const amz = getAmazonDomain();
const amzUrl = 'https://www.' + amz + '/s?k=' + encodeURIComponent(keyword);
const links = [];
// Always include Amazon for the detected region
const amzLabel = { 'amazon.de':'Amazon.de', 'amazon.co.uk':'Amazon UK', 'amazon.fr':'Amazon.fr', 'amazon.ca':'Amazon.ca', 'amazon.com':'Amazon.com' };
links.push({ store: amzLabel[amz] || 'Amazon', url: amzUrl, badge: '🛒' });
// Add specialist retailers by region + product type
if (userRegion === 'DACH') {
if (productType === 'outdoor' || productType === 'gear') {
links.push({ store: 'Globetrotter', url: 'https://www.globetrotter.de/search?q=' + encodeURIComponent(keyword), badge: '🏔' });
}
if (productType === 'food' || productType === 'survival') {
links.push({ store: 'Notfallshop.de', url: 'https://www.notfallshop.de/search?q=' + encodeURIComponent(keyword), badge: '🎒' });
}
if (productType === 'solar' || productType === 'energy') {
links.push({ store: 'Alternate.de', url: 'https://www.alternate.de/search?q=' + encodeURIComponent(keyword), badge: '⚡' });
}
}
if (userRegion === 'US' || userRegion === 'CA') {
if (productType === 'survival' || productType === 'food') {
links.push({ store: 'Survival Frog', url: 'https://www.survivalfrog.com/search?type=product&q=' + encodeURIComponent(keyword), badge: '🐸' });
}
if (productType === 'outdoor' || productType === 'gear') {
links.push({ store: 'REI', url: 'https://www.rei.com/search?q=' + encodeURIComponent(keyword), badge: '🏕' });
}
}
if (userRegion === 'UK') {
if (productType === 'outdoor' || productType === 'gear') {
links.push({ store: 'Go Outdoors', url: 'https://www.gooutdoors.co.uk/search?q=' + encodeURIComponent(keyword), badge: '⛺' });
}
}
if (userRegion === 'FR' || userRegion === 'NLNORDIC' || userRegion === 'EU_OTHER') {
if (productType === 'outdoor' || productType === 'gear' || productType === 'food') {
links.push({ store: 'Decathlon', url: 'https://www.decathlon.com/search?q=' + encodeURIComponent(keyword), badge: '🏃' });
}
}
return links;
}
// Render affiliate buttons HTML
function renderAffiliateButtons(keyword, productType) {
const links = getAffiliateLinks(keyword, productType);
return links.map(l =>
'<a class="affiliate-btn" href="' + l.url + '" target="_blank" rel="noopener noreferrer">' + l.badge + ' ' + l.store + '</a>'
).join('');
}
// Update region indicator in header
function updateRegionIndicator() {
const el = document.getElementById('region-indicator');
if (el) el.textContent = getRegionLabel();
}
// Auto-detect language on first load
function detectAndSetLanguage() {
try {
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone || '';
// navigator.languages gives full priority list e.g. ['de-AT', 'de', 'en']
const langs = navigator.languages
? navigator.languages.map(l => l.toLowerCase())
: [(navigator.language || 'en').toLowerCase()];
const primaryLang = langs[0] || 'en';
// ── German detection ──
// Timezones covering DE, AT, CH, LI, LU (partial)
const dachTZ = [
'Europe/Berlin','Europe/Vienna','Europe/Zurich',
'Europe/Busingen','Europe/Vaduz','Europe/Luxembourg'
];
const isTZGerman = dachTZ.some(z => tz === z);
const isLangGerman = langs.some(l => l.startsWith('de'));
if (isTZGerman || isLangGerman) {
setLang('de');
return;
}
// Default: English for everything else
// (extend here with 'fr', 'es', 'nl' when those translations are added)
setLang('en');
} catch(e) {
setLang('en');
}
}
// Init language + region on load
// Force English for local development
try { setLang('en'); } catch(e) {}
document.addEventListener('DOMContentLoaded', function() {
setLang('en');
updateRegionIndicator();
});
// ── FETCH + RENDER NARRATIVE ──
async function fetchNarrative() {
// Animate progress bar over 10 seconds
const bar = document.getElementById('narrative-progress');
let pct = 0;
const progressInterval = setInterval(() => {
pct = Math.min(pct + (100 / 20), 95); // fills in ~2.4 seconds
if (bar) bar.style.width = pct + '%';
}, 120);
const completeProgress = () => {
clearInterval(progressInterval);
if (bar) { bar.style.width = '100%'; bar.style.transition = 'width 0.3s'; }
setTimeout(() => {
const loading = document.getElementById('narrative-loading');
if (loading) loading.style.display = 'none';
}, 300);
};
const quizData = {
first_name: answers._first_name || '',
city: answers._city || '',
country: answers._country || '',
location: answers.location || '',
household_size: answers.household || '',
water_access: answers.water || '',
food_reserves: answers.food || '',
medical_needs: answers.medical || '',
sanitation: answers.sanitation || '',
budget_eur: answers.budget || 1500,
priorities: (answers.priority || []).join(', '),
protein_access: answers.protein || '',
scenarios: (answers.scenarios || []).join(', '),
risk_score: riskScore || 0,
risk_level: riskLevelStr || '',
language_used: currentLang,
preferred_language: currentLang
};
// Timeout after 14 seconds — show fallback
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error('timeout')), 8000)
);
try {
const fetchPromise = fetch('https://planb-email.janwellmann.workers.dev/narrative', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
mode: 'cors',
body: JSON.stringify(quizData)
});
const res = await Promise.race([fetchPromise, timeout]);
if (res.ok) {
const data = await res.json();
if (data.narrative) { completeProgress(); renderNarrative(data.narrative); return; }
}
completeProgress(); renderNarrative(getFallbackNarrativeText());
} catch (err) {
completeProgress(); renderNarrative(getFallbackNarrativeText());
}
}
function renderNarrative(text) {
document.getElementById('narrative-loading').classList.add('hidden');
// Show CTA button
const cta = document.getElementById('narrative-cta');
if (cta) cta.classList.remove('hidden');
revealSections();
const el = document.getElementById('narrative-text');
// Convert markdown-style text to HTML
const html = text
.split(/\n\n+/)
.filter(p => p.trim())
.map(p => {
// Bold headings **text**
if (p.startsWith('**') && p.split('\n').length <= 2) {
const heading = p.replace(/\*\*(.+?)\*\*/g, '$1').trim();
return `<h4>${heading}</h4>`;
}
// Bullet lists
if (p.includes('\n•') || p.trimStart().startsWith('•')) {
const lines = p.split('\n').filter(l => l.trim());
const items = lines.map(l => {
l = l.trim();
const text = l.startsWith('•') ? l.slice(1).trim() : l;
const bolded = text.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
return l.startsWith('•') ? `<li>${bolded}</li>` : `<p>${bolded}</p>`;
}).join('');
return `<ul>${items}</ul>`;
}
const bolded = p.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
return `<p>${bolded}</p>`;
}).join('');
el.innerHTML = html;
el.classList.remove('hidden');
}
function getFallbackNarrativeText() {
const isDE = currentLang === 'de';
return isDE
? '**DEINE LAGE**\n\nDein Assessment zeigt, wo die wichtigsten Lücken sind. Die gute Nachricht: Du hast den ersten Schritt gemacht.\n\n**NÄCHSTE SCHRITTE**\n\n• Wasser zuerst: AWG oder Berkey-Filter\n• Lebensmittelvorrat: 2 Wochen Grundvorrat\n• Energie: Solar-Powerbank für Stromausfall'
: '**YOUR SITUATION**\n\nYour assessment shows where the key gaps are. The good news: you\'ve taken the first step.\n\n**NEXT STEPS**\n\n• Water first: AWG or Berkey filter\n• Food reserve: 2-week dry goods\n• Energy: Solar power bank for outages';
}
// ── STAGED SECTION REVEAL ──
function revealSections() {
const sections = [
{ id:'scenario-tabs', display:'flex' },
{ id:'budget-meter-container',display:'block' },
{ id:'rec-cards-container', display:'block' },
{ id:'timeline-container', display:'block' },
{ id:'capture-form-wrap', display:'block' },
];
sections.forEach((s, i) => {
setTimeout(() => {
const el = document.getElementById(s.id);
if (!el) return;
el.style.display = s.display;
el.classList.add('revealed');
}, i * 200); // stagger 200ms apart
});
}
function scrollToForm() {
const el = document.getElementById('capture-form-wrap');
if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
function scrollToRecs() {
const el = document.getElementById('recs-anchor');
if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
// ── PROTEIN OFFER — show if protein_access === uncertain ──
function checkProteinOffer() {
const offerEl = document.getElementById('protein-offer-section');
if (!offerEl) return;
if (answers.protein_access === 'uncertain') {
offerEl.classList.add('show');
// Update offer text based on language
setLang(currentLang); // re-run translations on this new element
}
}
// ── SCENARIO STRIP TOUCH DRAG ──
(function() {
const strip = document.getElementById('scenario-strip');
const track = document.getElementById('scenario-track');
if (!strip || !track) return;
let isDragging = false, startX = 0, scrollLeft = 0;
// Pause animation on touch/mouse interaction
strip.addEventListener('mousedown', e => {
isDragging = true;
startX = e.pageX - strip.offsetLeft;
scrollLeft = strip.scrollLeft;
track.style.animationPlayState = 'paused';
});
strip.addEventListener('touchstart', e => {
track.style.animationPlayState = 'paused';
}, { passive: true });
strip.addEventListener('mouseleave', () => {
if (isDragging) {
isDragging = false;
track.style.animationPlayState = 'running';
}
});
strip.addEventListener('mouseup', () => {
isDragging = false;
track.style.animationPlayState = 'running';
});
strip.addEventListener('touchend', () => {
setTimeout(() => { track.style.animationPlayState = 'running'; }, 3000);
}, { passive: true });
})();
// ── Paint picker + modifier panel (in-page experimentation tool) ──
(function() {
const swatches = document.querySelectorAll('.paint-swatch');
if (!swatches.length) return;
const FILTER_IDS = {
dark: ['paintGloss', 'paintGlossHover'],
green: ['paintGloss', 'paintGlossHover'],
white: ['paintGlossWhite', 'paintGlossWhiteHover', 'paintGlossWhiteBtn']
};
const VARS = {
dsDy: { sel: 'feOffset[result="dsOff"]', attr: 'dy' },
dsBlur: { sel: 'feGaussianBlur[result="dsBlur"]', attr: 'stdDeviation' },
dsSlope: { sel: 'feFuncA', attr: 'slope' },
bumpStd: { sel: 'feGaussianBlur[result="bump"]', attr: 'stdDeviation' },
spec1Scale: { sel: 'feSpecularLighting[result="spec"]', attr: 'surfaceScale' },
spec1Const: { sel: 'feSpecularLighting[result="spec"]', attr: 'specularConstant' },
spec1Exp: { sel: 'feSpecularLighting[result="spec"]', attr: 'specularExponent' },
spec1Color: { sel: 'feSpecularLighting[result="spec"]', attr: 'lighting-color' },
spec2Scale: { sel: 'feSpecularLighting[result="spec2"]', attr: 'surfaceScale' },
spec2Const: { sel: 'feSpecularLighting[result="spec2"]', attr: 'specularConstant' },
spec2Exp: { sel: 'feSpecularLighting[result="spec2"]', attr: 'specularExponent' },
spec2Color: { sel: 'feSpecularLighting[result="spec2"]', attr: 'lighting-color' },
};
const PAINT_LABEL = { dark: 'Dark', green: 'Army Green', white: 'Glossy White' };
function activePaint() {
if (document.body.classList.contains('paint-white')) return 'white';
if (document.body.classList.contains('paint-green')) return 'green';
return 'dark';
}
function primaryFilter() { return document.getElementById(FILTER_IDS[activePaint()][0]); }
function applyVar(name, value) {
const def = VARS[name]; if (!def) return;
FILTER_IDS[activePaint()].forEach(id => {
const f = document.getElementById(id); if (!f) return;
const el = f.querySelector(def.sel); if (el) el.setAttribute(def.attr, value);
});
}
function readActiveFill() {
if (activePaint() === 'dark') return null;
const h1 = document.querySelector('.hero h1.paint-3d');
return h1 ? getComputedStyle(h1).backgroundColor : null;
}
function rgbToHex(rgb) {
if (!rgb) return '#000000';
if (rgb.startsWith('#')) return rgb;
const m = rgb.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
if (!m) return '#000000';
return '#' + [1,2,3].map(i => Number(m[i]).toString(16).padStart(2,'0')).join('');
}
function applyFill(hex) {
if (activePaint() === 'dark') return;
const h1 = document.querySelector('.hero h1.paint-3d');
if (h1) h1.style.background = hex;
document.body.style.setProperty('--mod-fill', hex);
if (!document.getElementById('modFillStyle')) {
const s = document.createElement('style');
s.id = 'modFillStyle';
s.textContent =
'body.paint-green .cta-btn::before, body.paint-white .cta-btn::before {' +
' background: var(--mod-fill, inherit) !important;' +
'}';
document.head.appendChild(s);
}
}
function clearModifierOverrides() {
const h1 = document.querySelector('.hero h1.paint-3d');
if (h1) h1.style.background = '';
document.body.style.removeProperty('--mod-fill');
}
function readAll() {
const filter = primaryFilter(); if (!filter) return;
Object.entries(VARS).forEach(([name, def]) => {
const input = document.querySelector('[data-var="' + name + '"]'); if (!input) return;
const el = filter.querySelector(def.sel);
if (el) { const v = el.getAttribute(def.attr); if (v !== null) input.value = v; }
});
const fillInput = document.querySelector('[data-var="fill"]');
if (fillInput) { const fill = readActiveFill(); if (fill) fillInput.value = rgbToHex(fill); }
document.querySelectorAll('.mod-row output').forEach(out => {
const input = out.previousElementSibling;
if (input && input.type === 'range') out.textContent = input.value;
});
}
function refreshSectionVisibility() {
const paint = activePaint();
document.querySelectorAll('.mod-fieldset[data-paint-only]').forEach(fs => {
const allowed = fs.dataset.paintOnly.split(',').map(s => s.trim());
if (allowed.includes(paint)) fs.removeAttribute('data-hide');
else fs.setAttribute('data-hide', 'true');
});
const label = document.getElementById('modPaintLabel');
if (label) label.textContent = PAINT_LABEL[paint];
}
swatches.forEach(btn => {
btn.addEventListener('click', () => {
document.body.classList.remove('paint-green', 'paint-white');
const paint = btn.dataset.paint;
if (paint) document.body.classList.add(paint);
swatches.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
clearModifierOverrides();
refreshSectionVisibility();
readAll();
});
});
document.querySelectorAll('[data-var]').forEach(input => {
input.addEventListener('input', () => {
const name = input.dataset.var;
if (name === 'fill') applyFill(input.value);
else applyVar(name, input.value);
const out = input.nextElementSibling;
if (out && out.tagName === 'OUTPUT') out.textContent = input.value;
});
});
// Header toggle button — open/close the modifier panel
const openBtn = document.getElementById('modOpenBtn');
const closeBtn = document.getElementById('modCloseBtn');
if (openBtn) {
openBtn.addEventListener('click', () => { document.body.classList.toggle('mod-open'); });
}
if (closeBtn) {
closeBtn.addEventListener('click', () => { document.body.classList.remove('mod-open'); });
}
const copyBtn = document.getElementById('modCopy');
const copyStatus = document.getElementById('modCopyStatus');
if (copyBtn) {
copyBtn.addEventListener('click', () => {
const filter = primaryFilter(); if (!filter) return;
const dump = { paint: activePaint(), filter: FILTER_IDS[activePaint()][0] };
Object.entries(VARS).forEach(([name, def]) => {
const el = filter.querySelector(def.sel);
if (el && el.hasAttribute(def.attr)) dump[name] = el.getAttribute(def.attr);
});
const fill = readActiveFill();
if (fill && activePaint() !== 'dark') dump.fill = rgbToHex(fill);
const text = JSON.stringify(dump, null, 2);
navigator.clipboard.writeText(text).then(() => {
if (copyStatus) {
copyStatus.textContent = 'Copied';
setTimeout(() => { copyStatus.textContent = ''; }, 1500);
}
}).catch(() => { if (copyStatus) copyStatus.textContent = 'Copy failed'; });
});
}
refreshSectionVisibility();
readAll();
})();
</script>
<script>(function(){function c(){var b=a.contentDocument||a.contentWindow.document;if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'9e711fc8eb4bbd8c',t:'MTc3NTMxNDIxMw=='};var a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body>
</html>