3076 lines
145 KiB
HTML
3076 lines
145 KiB
HTML
<!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 2–3 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 1–2",
|
||
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 2–3-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 1–2",
|
||
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: '3–4 people', de: '3–4 Personen' }, sub: { en: '', de: '' } },
|
||
{ val: '6', label: { en: '5–6 people', de: '5–6 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: '1–3 days buffer', de: '1–3 Tage Puffer' } },
|
||
{ val: 'filter', label: { en: 'Filter + some storage', de: 'Filter + etwas Vorrat' }, sub: { en: '1–2 week capability', de: '1–2 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: '1–3 months', de: '1–3 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: '~€280–350', 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: '~€30–35/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>
|