379 lines
18 KiB
JavaScript
379 lines
18 KiB
JavaScript
/* ════════════════════════════════════════════════════════════════
|
||
JustVitamin × QuikCue — Interactive Demos & Animations
|
||
════════════════════════════════════════════════════════════════ */
|
||
|
||
// ── Intersection Observer: Fade-in on scroll ──
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
const observer = new IntersectionObserver((entries) => {
|
||
entries.forEach(entry => {
|
||
if (entry.isIntersecting) {
|
||
entry.target.classList.add('visible');
|
||
observer.unobserve(entry.target);
|
||
|
||
// Trigger chart bars
|
||
const bars = entry.target.querySelectorAll('.chart-bar-fill[data-width]');
|
||
bars.forEach((bar, i) => {
|
||
setTimeout(() => {
|
||
bar.style.width = bar.dataset.width + '%';
|
||
}, i * 150);
|
||
});
|
||
|
||
// Trigger stat counters
|
||
const counters = entry.target.querySelectorAll('[data-count]');
|
||
counters.forEach(el => animateCounter(el));
|
||
}
|
||
});
|
||
}, { threshold: 0.15, rootMargin: '0px 0px -50px 0px' });
|
||
|
||
document.querySelectorAll('.animate-on-scroll').forEach(el => observer.observe(el));
|
||
|
||
// ── Animate hero stats on load ──
|
||
setTimeout(() => {
|
||
document.querySelectorAll('.hero-stat-value[data-count]').forEach(el => animateCounter(el));
|
||
}, 500);
|
||
|
||
// ── Initialize slider ──
|
||
initSlider();
|
||
|
||
// ── Smooth scroll for nav links ──
|
||
document.querySelectorAll('a[href^="#"]').forEach(link => {
|
||
link.addEventListener('click', (e) => {
|
||
const target = document.querySelector(link.getAttribute('href'));
|
||
if (target) {
|
||
e.preventDefault();
|
||
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||
}
|
||
});
|
||
});
|
||
|
||
// ── Active nav tracking ──
|
||
const sections = document.querySelectorAll('section[id]');
|
||
const navLinks = document.querySelectorAll('.nav-links a');
|
||
|
||
const navObserver = new IntersectionObserver((entries) => {
|
||
entries.forEach(entry => {
|
||
if (entry.isIntersecting) {
|
||
const id = entry.target.getAttribute('id');
|
||
navLinks.forEach(link => {
|
||
link.classList.toggle('active', link.getAttribute('href') === '#' + id);
|
||
});
|
||
}
|
||
});
|
||
}, { threshold: 0.3, rootMargin: '-80px 0px -50% 0px' });
|
||
|
||
sections.forEach(sec => navObserver.observe(sec));
|
||
});
|
||
|
||
// ── Counter Animation ──
|
||
function animateCounter(el) {
|
||
const target = parseInt(el.dataset.count);
|
||
const suffix = el.dataset.suffix || '';
|
||
const prefix = el.dataset.prefix || '';
|
||
const duration = 1200;
|
||
const start = performance.now();
|
||
|
||
if (target === 0) {
|
||
el.textContent = prefix + '$0' + suffix;
|
||
return;
|
||
}
|
||
|
||
function tick(now) {
|
||
const elapsed = now - start;
|
||
const progress = Math.min(elapsed / duration, 1);
|
||
const eased = 1 - Math.pow(1 - progress, 3); // ease-out cubic
|
||
const current = Math.round(target * eased);
|
||
el.textContent = prefix + current.toLocaleString() + suffix;
|
||
if (progress < 1) requestAnimationFrame(tick);
|
||
}
|
||
requestAnimationFrame(tick);
|
||
}
|
||
|
||
// ════════════════════════════════════════════════════════════════
|
||
// DEMO A: One Product → 12 Assets
|
||
// ════════════════════════════════════════════════════════════════
|
||
|
||
function runDemoA() {
|
||
const btn = document.getElementById('demoA-btn');
|
||
const output = document.getElementById('demoA-output');
|
||
const counter = document.getElementById('demoA-count');
|
||
const timer = document.getElementById('demoA-timer');
|
||
const download = document.getElementById('demoA-download');
|
||
const cards = document.querySelectorAll('#demoA-assets .asset-card');
|
||
|
||
// Loading state
|
||
btn.classList.add('loading');
|
||
btn.textContent = 'Generating...';
|
||
output.classList.add('active');
|
||
download.classList.remove('active');
|
||
|
||
// Reset cards
|
||
cards.forEach(c => c.classList.remove('revealed'));
|
||
let count = 0;
|
||
counter.textContent = '0';
|
||
|
||
const startTime = performance.now();
|
||
|
||
// Update timer
|
||
const timerInterval = setInterval(() => {
|
||
const elapsed = ((performance.now() - startTime) / 1000).toFixed(1);
|
||
timer.textContent = elapsed + 's';
|
||
}, 100);
|
||
|
||
// Reveal cards one by one
|
||
const revealNext = (index) => {
|
||
if (index >= cards.length) {
|
||
// Done!
|
||
clearInterval(timerInterval);
|
||
const totalTime = ((performance.now() - startTime) / 1000).toFixed(1);
|
||
timer.textContent = totalTime + 's ✓';
|
||
btn.classList.remove('loading');
|
||
btn.textContent = '✓ Pack Generated — Run Again?';
|
||
btn.style.background = 'var(--accent)';
|
||
btn.style.color = '#050a08';
|
||
download.classList.add('active');
|
||
|
||
// Scroll to download
|
||
setTimeout(() => {
|
||
download.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||
}, 300);
|
||
return;
|
||
}
|
||
|
||
const card = cards[index];
|
||
const delay = 150 + Math.random() * 200; // Varied timing feels more "real"
|
||
|
||
setTimeout(() => {
|
||
card.classList.add('revealed');
|
||
count++;
|
||
counter.textContent = count;
|
||
|
||
// Scroll card into view
|
||
card.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||
|
||
revealNext(index + 1);
|
||
}, delay);
|
||
};
|
||
|
||
// Start reveal after "thinking" pause
|
||
setTimeout(() => revealNext(0), 800);
|
||
}
|
||
|
||
// ════════════════════════════════════════════════════════════════
|
||
// DEMO B: Competitor X-Ray
|
||
// ════════════════════════════════════════════════════════════════
|
||
|
||
function runDemoB() {
|
||
const btn = document.getElementById('demoB-btn');
|
||
const output = document.getElementById('demoB-output');
|
||
const left = document.getElementById('demoB-left');
|
||
const right = document.getElementById('demoB-right');
|
||
|
||
// Loading state
|
||
btn.classList.add('loading');
|
||
btn.textContent = 'Scanning...';
|
||
btn.style.background = '#1e40af';
|
||
|
||
// Reset
|
||
left.classList.remove('revealed');
|
||
right.classList.remove('revealed');
|
||
|
||
setTimeout(() => {
|
||
output.style.display = 'grid';
|
||
output.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||
|
||
// Reveal left side first
|
||
setTimeout(() => {
|
||
left.classList.add('revealed');
|
||
|
||
// Then right side
|
||
setTimeout(() => {
|
||
right.classList.add('revealed');
|
||
btn.classList.remove('loading');
|
||
btn.textContent = '✓ X-Ray Complete — Try Another';
|
||
btn.style.background = 'var(--accent)';
|
||
btn.style.color = '#050a08';
|
||
}, 800);
|
||
}, 600);
|
||
}, 1200);
|
||
}
|
||
|
||
// ════════════════════════════════════════════════════════════════
|
||
// DEMO C: Before/After Slider
|
||
// ════════════════════════════════════════════════════════════════
|
||
|
||
function initSlider() {
|
||
const wrapper = document.getElementById('slider-wrapper');
|
||
const handle = document.getElementById('slider-handle');
|
||
const after = document.getElementById('slider-after');
|
||
|
||
if (!wrapper || !handle || !after) return;
|
||
|
||
let isDragging = false;
|
||
|
||
const setPosition = (x) => {
|
||
const rect = wrapper.getBoundingClientRect();
|
||
let percentage = ((x - rect.left) / rect.width) * 100;
|
||
percentage = Math.max(5, Math.min(95, percentage));
|
||
|
||
handle.style.left = percentage + '%';
|
||
after.style.clipPath = `inset(0 ${100 - percentage}% 0 0)`;
|
||
};
|
||
|
||
// Mouse events
|
||
handle.addEventListener('mousedown', (e) => {
|
||
isDragging = true;
|
||
e.preventDefault();
|
||
});
|
||
|
||
document.addEventListener('mousemove', (e) => {
|
||
if (!isDragging) return;
|
||
setPosition(e.clientX);
|
||
});
|
||
|
||
document.addEventListener('mouseup', () => {
|
||
isDragging = false;
|
||
});
|
||
|
||
// Touch events
|
||
handle.addEventListener('touchstart', (e) => {
|
||
isDragging = true;
|
||
e.preventDefault();
|
||
});
|
||
|
||
document.addEventListener('touchmove', (e) => {
|
||
if (!isDragging) return;
|
||
setPosition(e.touches[0].clientX);
|
||
});
|
||
|
||
document.addEventListener('touchend', () => {
|
||
isDragging = false;
|
||
});
|
||
|
||
// Click anywhere on the wrapper to move slider
|
||
wrapper.addEventListener('click', (e) => {
|
||
setPosition(e.clientX);
|
||
});
|
||
}
|
||
|
||
// ── Style Toggle for Demo C ──
|
||
|
||
const styleVariants = {
|
||
default: {
|
||
afterTitle: "Sunshine in a Capsule — D3 Your Body Actually Absorbs",
|
||
afterContent: `
|
||
<span class="slider-side-label after">✓ After — Optimized</span>
|
||
<h4>Sunshine in a Capsule — D3 Your Body Actually Absorbs</h4>
|
||
<span class="annotation">↑ Benefit-first headline: +34% scroll depth</span>
|
||
|
||
<p class="highlight"><strong>5,000 IU of Vitamin D3 suspended in MCT oil for 3x better absorption</strong> — because a vitamin your body can't use is a vitamin you're wasting money on.</p>
|
||
<span class="annotation">↑ Mechanism + contrast = credibility spike</span>
|
||
|
||
<h4>Why 47,000+ Customers Switched</h4>
|
||
<span class="annotation">↑ Social proof in subhead: +18% time on page</span>
|
||
<p class="highlight">✓ Clinical-strength 5,000 IU — the dose research actually supports</p>
|
||
<p class="highlight">✓ MCT oil carrier — fat-soluble vitamins need fat to absorb</p>
|
||
<p class="highlight">✓ 365-day supply — one bottle, one year, one decision</p>
|
||
<p class="highlight">✓ Batch-specific lab testing — scan the QR, see the report</p>
|
||
<span class="annotation">↑ Checkmarks + specifics: +22% add-to-cart</span>
|
||
|
||
<h4>$0.07/day. Less than a parking meter.</h4>
|
||
<span class="annotation">↑ Price reframe to daily cost: +15% conversion</span>
|
||
|
||
<p><em>Take one softgel each morning with breakfast. The MCT oil means you don't need to pair it with a fatty meal — it's built in.</em></p>
|
||
<span class="annotation">↑ Usage clarity removes friction objection</span>
|
||
`
|
||
},
|
||
premium: {
|
||
afterContent: `
|
||
<span class="slider-side-label after" style="background:rgba(139,92,246,0.15);color:var(--purple)">✓ After — Premium Voice</span>
|
||
<h4>The D3 Supplement Refined for Those Who Refuse to Compromise</h4>
|
||
<span class="annotation" style="background:rgba(139,92,246,0.12);color:var(--purple)">↑ Aspirational positioning: +28% AOV on premium segments</span>
|
||
|
||
<p class="highlight" style="border-left-color:var(--purple)"><strong>Pharmaceutical-grade Vitamin D3, precision-dosed at 5,000 IU</strong>, delivered in a bioavailable MCT oil matrix designed for optimal cellular uptake.</p>
|
||
<span class="annotation" style="background:rgba(139,92,246,0.12);color:var(--purple)">↑ Technical language signals quality: premium buyers respond to specificity</span>
|
||
|
||
<h4>Engineered. Not Manufactured.</h4>
|
||
<p class="highlight" style="border-left-color:var(--purple)">✓ Cholecalciferol sourced from pharmaceutical-grade lanolin</p>
|
||
<p class="highlight" style="border-left-color:var(--purple)">✓ MCT oil carrier from organic coconut — no palm derivatives</p>
|
||
<p class="highlight" style="border-left-color:var(--purple)">✓ Every batch independently verified — CoA available by QR</p>
|
||
<p class="highlight" style="border-left-color:var(--purple)">✓ 365-count — because a premium product shouldn't require monthly reorders</p>
|
||
<span class="annotation" style="background:rgba(139,92,246,0.12);color:var(--purple)">↑ Ingredient sourcing transparency: +41% trust score with $100k+ HHI</span>
|
||
|
||
<h4>Your Daily Standard. Elevated.</h4>
|
||
<span class="annotation" style="background:rgba(139,92,246,0.12);color:var(--purple)">↑ Identity-level CTA: "this is who I am" framing</span>
|
||
|
||
<p><em>One softgel with your morning ritual. No additional oil required — the delivery system handles absorption.</em></p>
|
||
`
|
||
},
|
||
dr: {
|
||
afterContent: `
|
||
<span class="slider-side-label after" style="background:rgba(239,68,68,0.15);color:var(--cta)">✓ After — Direct Response</span>
|
||
<h4>WARNING: Your Vitamin D3 Supplement Might Be Doing Nothing</h4>
|
||
<span class="annotation" style="background:rgba(239,68,68,0.12);color:var(--cta)">↑ Pattern interrupt headline: +52% click-through from ads</span>
|
||
|
||
<p class="highlight" style="border-left-color:var(--cta)"><strong>Here's the dirty secret the supplement industry won't tell you:</strong> Most D3 supplements use cheap dry powder that your body can barely absorb. You're literally flushing your money down the toilet.</p>
|
||
<span class="annotation" style="background:rgba(239,68,68,0.12);color:var(--cta)">↑ Problem agitation + insider language: builds urgency fast</span>
|
||
|
||
<h4>The Fix Takes 2 Seconds a Day:</h4>
|
||
<p class="highlight" style="border-left-color:var(--cta)">→ 5,000 IU per capsule (that's the REAL dose, not the wimpy 1,000 IU others sell)</p>
|
||
<p class="highlight" style="border-left-color:var(--cta)">→ MCT oil delivery = 3X better absorption (backed by published research)</p>
|
||
<p class="highlight" style="border-left-color:var(--cta)">→ 365 capsules per bottle = FULL YEAR SUPPLY for less than a coffee per month</p>
|
||
<p class="highlight" style="border-left-color:var(--cta)">→ Every single batch tested by an independent lab (we'll show you the report)</p>
|
||
<span class="annotation" style="background:rgba(239,68,68,0.12);color:var(--cta)">↑ Arrow bullets + emphasis caps: +38% read-through rate</span>
|
||
|
||
<h4>⚡ 47,293 Customers Can't Be Wrong — Get Yours Before We Sell Out Again</h4>
|
||
<span class="annotation" style="background:rgba(239,68,68,0.12);color:var(--cta)">↑ Exact social proof number + urgency/scarcity: +44% conversion</span>
|
||
|
||
<p><strong>🔥 SPECIAL: Order today and get FREE shipping + our D3 Absorption Guide (PDF) — $19 value, yours free.</strong></p>
|
||
<span class="annotation" style="background:rgba(239,68,68,0.12);color:var(--cta)">↑ Stacked bonuses: classic DR move, still works</span>
|
||
`
|
||
},
|
||
medical: {
|
||
afterContent: `
|
||
<span class="slider-side-label after" style="background:rgba(59,130,246,0.15);color:var(--blue)">✓ After — Medical-Safe</span>
|
||
<h4>Vitamin D3 (Cholecalciferol) 5,000 IU — High-Potency Dietary Supplement</h4>
|
||
<span class="annotation" style="background:rgba(59,130,246,0.12);color:var(--blue)">↑ Clinical nomenclature: builds trust with health-conscious buyers</span>
|
||
|
||
<p class="highlight" style="border-left-color:var(--blue)">Each softgel provides 5,000 IU (125 mcg) of Vitamin D3 as cholecalciferol, delivered in a medium-chain triglyceride (MCT) oil base to support bioavailability.*</p>
|
||
<span class="annotation" style="background:rgba(59,130,246,0.12);color:var(--blue)">↑ Precise units (mcg + IU) + asterisk for disclaimer = FDA-compliant</span>
|
||
|
||
<h4>Key Product Attributes</h4>
|
||
<p class="highlight" style="border-left-color:var(--blue)">• Vitamin D contributes to the normal function of the immune system*</p>
|
||
<p class="highlight" style="border-left-color:var(--blue)">• Vitamin D is needed for normal calcium absorption and bone maintenance*</p>
|
||
<p class="highlight" style="border-left-color:var(--blue)">• Third-party tested for identity, purity, potency, and composition</p>
|
||
<p class="highlight" style="border-left-color:var(--blue)">• Free from: soy, gluten, dairy, artificial colors, and preservatives</p>
|
||
<p class="highlight" style="border-left-color:var(--blue)">• 365 softgels per container (12-month supply at 1 softgel/day)</p>
|
||
<span class="annotation" style="background:rgba(59,130,246,0.12);color:var(--blue)">↑ Structure/function claims only — no disease claims, fully compliant</span>
|
||
|
||
<h4>Suggested Use</h4>
|
||
<p>Take one (1) softgel daily with a meal, or as recommended by your healthcare practitioner. Do not exceed recommended daily intake.</p>
|
||
|
||
<p style="margin-top:1rem;font-size:0.78rem;color:var(--text-dim)"><em>*These statements have not been evaluated by the Food and Drug Administration. This product is not intended to diagnose, treat, cure, or prevent any disease.</em></p>
|
||
<span class="annotation" style="background:rgba(59,130,246,0.12);color:var(--blue)">↑ FDA-mandated disclaimer: required for all supplement claims</span>
|
||
`
|
||
}
|
||
};
|
||
|
||
function switchStyle(style, clickedBtn) {
|
||
// Update toggle buttons
|
||
document.querySelectorAll('#demoC-toggles .toggle-btn').forEach(btn => {
|
||
btn.classList.remove('active');
|
||
});
|
||
clickedBtn.classList.add('active');
|
||
|
||
// Update after content
|
||
const afterCopy = document.getElementById('after-copy');
|
||
const variant = styleVariants[style];
|
||
if (variant && afterCopy) {
|
||
afterCopy.innerHTML = variant.afterContent;
|
||
}
|
||
}
|
||
|
||
// ── Keyboard shortcut: press 1/2/3 to jump to demos ──
|
||
document.addEventListener('keydown', (e) => {
|
||
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
|
||
if (e.key === '1') document.getElementById('demo-a')?.scrollIntoView({ behavior: 'smooth' });
|
||
if (e.key === '2') document.getElementById('demo-b')?.scrollIntoView({ behavior: 'smooth' });
|
||
if (e.key === '3') document.getElementById('demo-c')?.scrollIntoView({ behavior: 'smooth' });
|
||
});
|