This guide provides step-by-step instructions to add a fully functional quiz/scorecard system to your Eleventy website at talkwithdeath.com.
Your Eleventy site will need these new files:
your-site/
├── _data/
│ └── assessmentQuestions.json # Quiz data (CREATED ✓)
├── quiz/
│ ├── index.njk # Landing page (CREATED ✓)
│ ├── assessment.njk # Main quiz page (CREATED ✓)
│ └── results.njk # Results display (TO CREATE)
├── js/
│ └── assessment-engine.js # Quiz logic (TO CREATE - see below)
├── netlify/
│ └── functions/
│ ├── submit-assessment.js # Backend submission (TO CREATE)
│ └── send-email.js # Email handler (TO CREATE)
└── css/
└── (styles already in assessment.njk)
Pros:
Setup:
package.json additions:
{
"dependencies": {
"@sendgrid/mail": "^7.7.0"
}
}
File: _data/assessmentQuestions.json
Status: ✓ CREATED (already provided above)
File: quiz/index.njk
Status: ✓ CREATED (already provided above)
File: quiz/assessment.njk
Status: ✓ CREATED (already provided above)
File: js/assessment-engine.js
You'll need to download the complete JavaScript file. Due to length, I'll provide it as a separate file that combines:
Key features:
File: quiz/results.njk
---
layout: base.njk
title: Your Results
description: Your Death Readiness Assessment Results
---
<div class="results-page">
<div class="container">
<div id="resultsContent">
<!-- Populated by JavaScript -->
</div>
</div>
</div>
<script src="/js/assessment-results.js"></script>
<style>
.results-page {
padding: var(--space-xxl) 0;
background: var(--color-background);
}
.results-hero {
background: var(--color-primary);
color: var(--color-white);
padding: var(--space-xl);
border-radius: 8px;
text-align: center;
margin-bottom: var(--space-xl);
}
.score-display {
font-size: 4rem;
font-weight: 700;
color: var(--color-accent);
margin: var(--space-md) 0;
}
.tier-badge {
display: inline-block;
background: var(--color-accent);
color: var(--color-primary);
padding: var(--space-sm) var(--space-lg);
border-radius: 50px;
font-weight: 600;
font-size: 1.2rem;
margin-bottom: var(--space-md);
}
.results-message {
background: var(--color-white);
padding: var(--space-xl);
border-radius: 8px;
margin-bottom: var(--space-lg);
}
.recommendations {
background: var(--color-white);
padding: var(--space-xl);
border-radius: 8px;
}
.recommendations ul {
list-style: none;
padding: 0;
}
.recommendations li {
padding: var(--space-md);
margin-bottom: var(--space-sm);
background: var(--color-background);
border-radius: 4px;
border-left: 4px solid var(--color-accent);
}
.cinderella-offer {
background: linear-gradient(135deg, var(--color-accent), #FFE666);
color: var(--color-primary);
padding: var(--space-xxl);
border-radius: 8px;
text-align: center;
margin: var(--space-xl) 0;
border: 3px solid var(--color-primary);
}
.cinderella-offer h2 {
color: var(--color-primary);
}
.category-breakdown {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: var(--space-md);
margin: var(--space-lg) 0;
}
.category-card {
background: var(--color-white);
padding: var(--space-md);
border-radius: 6px;
border-top: 4px solid var(--color-accent);
}
</style>
File: js/assessment-results.js
class QuizResults {
constructor() {
this.resultsData = this.loadResults();
if (this.resultsData) {
this.render();
} else {
window.location.href = '/quiz/';
}
}
loadResults() {
const data = sessionStorage.getItem('quizResults');
return data ? JSON.parse(data) : null;
}
render() {
const container = document.getElementById('resultsContent');
// Load quiz data for tier information
const tier = this.getTierData(this.resultsData.tier);
const isCinderella = this.resultsData.isCinderellaClient;
let html = `
<div class="results-hero">
<div class="tier-badge">${tier.title}</div>
<div class="score-display">${this.resultsData.score}/100</div>
<p class="lead">Your Death Readiness Score</p>
</div>
<div class="results-message">
<h2>What This Means</h2>
<p class="lead">${tier.message}</p>
</div>
`;
// Cinderella client special offer
if (isCinderella) {
html += `
<div class="cinderella-offer">
<h2>🌟 Special Invitation for You 🌟</h2>
<p class="emphasis">Based on your readiness and clarity, you're an ideal candidate for our signature 'Death Integration' 12-week program.</p>
<p>As someone who completes this assessment with your level of commitment, you receive priority booking and 20% off the program fee.</p>
<p><strong>Only 3 spots available this quarter.</strong></p>
<a href="${tier.cta.url}" class="btn-primary" style="margin-top: var(--space-md);">
Claim Your Priority Spot
</a>
</div>
`;
}
html += `
<div class="recommendations">
<h2>Your Next Steps</h2>
<ul>
${tier.recommendations.map(rec => `<li>${rec}</li>`).join('')}
</ul>
<div class="cta-section">
<a href="${tier.cta.url}" class="btn-primary">
${tier.cta.text}
</a>
${tier.cta.subtitle ? `<p><small>${tier.cta.subtitle}</small></p>` : ''}
</div>
</div>
`;
container.innerHTML = html;
// Track conversion
this.trackResults();
}
getTierData(tierName) {
// In production, load from assessmentQuestions.json
// For now, return based on tier name
const tiers = {
'death-doula-ready': {
title: 'Death Doula Ready',
message: 'You\'re remarkably open to exploring death...',
// ... full tier data
}
// ... other tiers
};
return tiers[tierName];
}
trackResults() {
// Google Analytics tracking
if (window.gtag) {
gtag('event', 'quiz_completed', {
score: this.resultsData.score,
tier: this.resultsData.tier,
is_cinderella: this.resultsData.isCinderellaClient
});
}
}
}
document.addEventListener('DOMContentLoaded', () => {
new QuizResults();
});
File: netlify/functions/submit-assessment.js
const sgMail = require('@sendgrid/mail');
sgMail.setApiKey(process.env.SENDGRID_API_KEY);
exports.handler = async (event) => {
if (event.httpMethod !== 'POST') {
return { statusCode: 405, body: 'Method Not Allowed' };
}
try {
const data = JSON.parse(event.body);
// Send email to admin
await sgMail.send({
to: process.env.ADMIN_EMAIL,
from: process.env.FROM_EMAIL,
subject: `New Quiz Submission - ${data.tier.toUpperCase()}${data.isCinderellaClient ? ' 🌟 PRIORITY' : ''}`,
html: formatEmailHTML(data)
});
// Send email to user (if they provided email - you'd need to add email capture)
return {
statusCode: 200,
body: JSON.stringify({ success: true })
};
} catch (error) {
console.error(error);
return {
statusCode: 500,
body: JSON.stringify({ error: 'Submission failed' })
};
}
};
function formatEmailHTML(data) {
return `
<h2>New Quiz Submission</h2>
<p><strong>Score:</strong> ${data.score}/100</p>
<p><strong>Tier:</strong> ${data.tier}</p>
<p><strong>Cinderella Client:</strong> ${data.isCinderellaClient ? 'YES ⭐' : 'No'}</p>
<h3>Qualifying Questions:</h3>
<ul>
<li>Urgency: ${data.qualifiers.urgency?.value}</li>
<li>Investment: ${data.qualifiers.investment?.value}</li>
<li>Decision Maker: ${data.qualifiers['decision-maker']?.value}</li>
<li>Support Type: ${data.qualifiers['support-type']?.value}</li>
</ul>
<h3>All Answers:</h3>
<pre>${JSON.stringify(data.answers, null, 2)}</pre>
<p><small>Submitted: ${data.timestamp}</small></p>
`;
}
Add to Netlify dashboard (Settings > Environment Variables):
SENDGRID_API_KEY=your_sendgrid_api_key
ADMIN_EMAIL=your-email@example.com
FROM_EMAIL=noreply@talkwithdeath.com
If Netlify Functions are too complex, use Formspree:
async submitToBackend(data) {
const response = await fetch('https://formspree.io/f/YOUR_FORM_ID', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
return response.ok;
}
Once quiz is working, set up automated email sequences:
Quiz not loading:
Progress not saving:
Submission failing:
Email not sending:
Would you like me to: