managarten/memoro/apps/landing/src/components/ROICalculator.astro
Till-JS e7f5f942f3 chore: initial commit - consolidate 4 projects into monorepo
Projects included:
- maerchenzauber (NestJS backend + Expo mobile + SvelteKit web + Astro landing)
- manacore (Expo mobile + SvelteKit web + Astro landing)
- manadeck (NestJS backend + Expo mobile + SvelteKit web)
- memoro (Expo mobile + SvelteKit web + Astro landing)

This commit preserves the current state before monorepo restructuring.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-22 23:38:24 +01:00

385 lines
No EOL
15 KiB
Text

---
interface Props {
lang?: 'de' | 'en';
title?: string;
subtitle?: string;
accentColor?: string;
}
const {
lang = 'de',
title = lang === 'de' ? 'ROI-Rechner: Ihre Zeitersparnis mit Memoro' : 'ROI Calculator: Your Time Savings with Memoro',
subtitle = lang === 'de' ? 'Berechnen Sie, wie viel Zeit und Geld Sie mit automatischen Meeting-Protokollen sparen' : 'Calculate how much time and money you save with automatic meeting minutes',
accentColor = 'primary'
} = Astro.props;
const translations = {
de: {
meetingsPerWeek: 'Meetings pro Woche',
minutesPerMeeting: 'Dauer pro Meeting',
minutesPerProtocol: 'Zeit für Protokollerstellung',
hourlyRate: 'Ihr Stundensatz',
yourSavings: 'Ihre Ersparnis',
timeSaved: 'Eingesparte Zeit',
moneySaved: 'Eingesparte Kosten',
perWeek: 'pro Woche',
perMonth: 'pro Monat',
perYear: 'pro Jahr',
hours: 'Std.',
days: 'Tage',
roiText: 'Investition rechnet sich nach',
roiDays: '',
startNow: 'Jetzt kostenlos testen',
assumptions: 'Berechnung',
assumptionText: 'Basierend auf 80% Zeitersparnis durch automatische Protokollerstellung.',
},
en: {
meetingsPerWeek: 'Meetings per week',
minutesPerMeeting: 'Minutes per meeting',
minutesPerProtocol: 'Minutes for protocol',
hourlyRate: 'Hourly rate ($)',
yourSavings: 'Your Savings',
timeSaved: 'Time saved',
moneySaved: 'Money saved',
perWeek: 'per week',
perMonth: 'per month',
perYear: 'per year',
hours: 'hours',
days: 'days',
roiText: 'Return on Investment',
roiDays: 'ROI in',
startNow: 'Try Memoro now',
assumptions: 'Assumptions',
assumptionText: '80% time savings in protocol creation with Memoro.',
}
};
const t = translations[lang];
---
<div class="roi-calculator bg-background-page border border-border/50 rounded-2xl p-6 md:p-8 my-12" data-lang={lang}>
<div class="text-center mb-8">
<h2 class="text-2xl md:text-3xl font-bold text-text-primary mb-3">{title}</h2>
<p class="text-lg text-text-secondary max-w-2xl mx-auto">{subtitle}</p>
</div>
<div class="max-w-4xl mx-auto">
<div class="grid md:grid-cols-2 gap-8">
{/* Input Section */}
<div class="bg-background-card border border-border rounded-xl p-6">
<h3 class="text-lg font-semibold text-text-primary mb-4 flex items-center gap-2">
<svg class="w-5 h-5 text-yellow-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4" />
</svg>
<span>{lang === 'de' ? 'Ihre Eingaben' : 'Your Input'}</span>
</h3>
<div class="space-y-6">
{/* Meetings per Week */}
<div>
<label class="flex justify-between items-center mb-2">
<span class="text-sm font-medium text-text-secondary">{t.meetingsPerWeek}</span>
<span class="text-lg font-bold text-yellow-400" id="meetings-value">10</span>
</label>
<input
type="range"
id="meetings-slider"
min="1"
max="30"
value="10"
class="w-full h-2 bg-background-page rounded-lg appearance-none cursor-pointer slider"
/>
<div class="flex justify-between text-xs text-text-secondary/50 mt-1">
<span>1</span>
<span>30</span>
</div>
</div>
{/* Minutes per Meeting */}
<div>
<label class="flex justify-between items-center mb-2">
<span class="text-sm font-medium text-text-secondary">{t.minutesPerMeeting}</span>
<span class="text-lg font-bold text-yellow-400" id="meeting-duration-value">45</span>
</label>
<input
type="range"
id="meeting-duration-slider"
min="15"
max="120"
value="45"
step="15"
class="w-full h-2 bg-background-page rounded-lg appearance-none cursor-pointer slider"
/>
<div class="flex justify-between text-xs text-text-secondary/50 mt-1">
<span>15 min</span>
<span>120 min</span>
</div>
</div>
{/* Minutes for Protocol */}
<div>
<label class="flex justify-between items-center mb-2">
<span class="text-sm font-medium text-text-secondary">{t.minutesPerProtocol}</span>
<span class="text-lg font-bold text-yellow-400" id="protocol-time-value">30</span>
</label>
<input
type="range"
id="protocol-time-slider"
min="10"
max="90"
value="30"
step="5"
class="w-full h-2 bg-background-page rounded-lg appearance-none cursor-pointer slider"
/>
<div class="flex justify-between text-xs text-text-secondary/50 mt-1">
<span>10 min</span>
<span>90 min</span>
</div>
</div>
{/* Hourly Rate */}
<div>
<label class="flex justify-between items-center mb-2">
<span class="text-sm font-medium text-text-secondary">{t.hourlyRate}</span>
<span class="text-lg font-bold text-yellow-400">
<span id="hourly-rate-value">50</span>€
</span>
</label>
<input
type="range"
id="hourly-rate-slider"
min="20"
max="200"
value="50"
step="10"
class="w-full h-2 bg-background-page rounded-lg appearance-none cursor-pointer slider"
/>
<div class="flex justify-between text-xs text-text-secondary/50 mt-1">
<span>20€</span>
<span>200€</span>
</div>
</div>
</div>
</div>
{/* Results Section */}
<div class="bg-gradient-to-br from-yellow-400 to-amber-500 rounded-xl p-6 text-black">
<h3 class="text-xl font-bold mb-6 flex items-center gap-2">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 7h6m0 10v-3m-3 3h.01M9 17h.01M9 14h.01M12 14h.01M15 11h.01M12 11h.01M9 11h.01M7 21h10a2 2 0 002-2V5a2 2 0 00-2-2H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
</svg>
{t.yourSavings}
</h3>
<div class="space-y-4">
{/* Time Saved */}
<div class="bg-black/10 rounded-lg p-4">
<p class="text-sm font-medium mb-2">{t.timeSaved}</p>
<div class="grid grid-cols-3 gap-2">
<div>
<p class="text-2xl font-bold" id="time-week">5</p>
<p class="text-xs opacity-80">{t.hours} {t.perWeek}</p>
</div>
<div>
<p class="text-2xl font-bold" id="time-month">20</p>
<p class="text-xs opacity-75">{t.hours} {t.perMonth}</p>
</div>
<div>
<p class="text-2xl font-bold" id="time-year">10</p>
<p class="text-xs opacity-75">{t.days} {t.perYear}</p>
</div>
</div>
</div>
{/* Money Saved */}
<div class="bg-black/10 rounded-lg p-4">
<p class="text-sm font-medium mb-2">{t.moneySaved}</p>
<div class="grid grid-cols-3 gap-2">
<div>
<p class="text-2xl font-bold">€<span id="money-week">250</span></p>
<p class="text-xs opacity-75">{t.perWeek}</p>
</div>
<div>
<p class="text-2xl font-bold">€<span id="money-month">1000</span></p>
<p class="text-xs opacity-75">{t.perMonth}</p>
</div>
<div>
<p class="text-2xl font-bold">€<span id="money-year">12000</span></p>
<p class="text-xs opacity-75">{t.perYear}</p>
</div>
</div>
</div>
{/* ROI */}
<div class="bg-black/20 rounded-lg p-4 text-center">
<p class="text-sm font-medium mb-2">{t.roiText}</p>
<p class="text-3xl font-bold">
<span id="roi-days">3</span> <span id="roi-days-text">{lang === 'de' ? 'Tagen' : 'Days'}</span>
</p>
</div>
</div>
{/* CTA Button */}
<a href={`/${lang}/download`} class="mt-6 w-full bg-black text-yellow-400 py-3 px-6 rounded-lg font-semibold hover:bg-gray-900 transition-all hover:scale-105 hover:shadow-lg flex items-center justify-center gap-2">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
{t.startNow}
</a>
</div>
</div>
{/* Assumptions */}
<div class="mt-6 text-center">
<p class="text-sm text-text-secondary">
<span class="font-medium">{t.assumptions}:</span> {t.assumptionText}
</p>
</div>
</div>
</div>
<style>
.slider {
background: linear-gradient(to right, #fbbf24 0%, #fbbf24 var(--value, 50%), #1a1b26 var(--value, 50%), #1a1b26 100%);
}
.slider::-webkit-slider-thumb {
appearance: none;
width: 20px;
height: 20px;
background: linear-gradient(135deg, #fbbf24, #f59e0b);
border: 2px solid #fff;
cursor: pointer;
border-radius: 50%;
box-shadow: 0 2px 8px rgba(251,191,36,0.4);
transition: all 0.2s ease;
}
.slider::-webkit-slider-thumb:hover {
transform: scale(1.1);
box-shadow: 0 3px 12px rgba(251,191,36,0.6);
}
.slider::-moz-range-thumb {
width: 20px;
height: 20px;
background: linear-gradient(135deg, #fbbf24, #f59e0b);
border: 2px solid #fff;
cursor: pointer;
border-radius: 50%;
box-shadow: 0 2px 8px rgba(251,191,36,0.4);
transition: all 0.2s ease;
}
.slider::-moz-range-thumb:hover {
transform: scale(1.1);
box-shadow: 0 3px 12px rgba(251,191,36,0.6);
}
</style>
<script>
function initROICalculator() {
const calculator = document.querySelector('.roi-calculator');
if (!calculator) return;
const meetingsSlider = document.getElementById('meetings-slider') as HTMLInputElement;
const meetingDurationSlider = document.getElementById('meeting-duration-slider') as HTMLInputElement;
const protocolTimeSlider = document.getElementById('protocol-time-slider') as HTMLInputElement;
const hourlyRateSlider = document.getElementById('hourly-rate-slider') as HTMLInputElement;
const meetingsValue = document.getElementById('meetings-value');
const meetingDurationValue = document.getElementById('meeting-duration-value');
const protocolTimeValue = document.getElementById('protocol-time-value');
const hourlyRateValue = document.getElementById('hourly-rate-value');
const timeWeek = document.getElementById('time-week');
const timeMonth = document.getElementById('time-month');
const timeYear = document.getElementById('time-year');
const moneyWeek = document.getElementById('money-week');
const moneyMonth = document.getElementById('money-month');
const moneyYear = document.getElementById('money-year');
const roiDays = document.getElementById('roi-days');
const roiDaysText = document.getElementById('roi-days-text');
const lang = calculator.getAttribute('data-lang') || 'de';
function updateSliderBackground(slider: HTMLInputElement) {
const min = parseFloat(slider.min);
const max = parseFloat(slider.max);
const value = parseFloat(slider.value);
const percentage = ((value - min) / (max - min)) * 100;
slider.style.setProperty('--value', `${percentage}%`);
}
function calculateROI() {
const meetings = parseInt(meetingsSlider.value);
const meetingDuration = parseInt(meetingDurationSlider.value);
const protocolTime = parseInt(protocolTimeSlider.value);
const hourlyRate = parseInt(hourlyRateSlider.value);
// Update display values
if (meetingsValue) meetingsValue.textContent = meetings.toString();
if (meetingDurationValue) meetingDurationValue.textContent = meetingDuration.toString();
if (protocolTimeValue) protocolTimeValue.textContent = protocolTime.toString();
if (hourlyRateValue) hourlyRateValue.textContent = hourlyRate.toString();
// Calculate time saved (assuming 80% time saving with Memoro)
// Protocol time typically scales with meeting duration - longer meetings need longer documentation
// Use a scaling factor: base protocol time + percentage of meeting duration
const baseProtocolTime = protocolTime;
const scaledProtocolTime = Math.min(baseProtocolTime + (meetingDuration * 0.15), 90); // Cap at 90 minutes
const minutesSavedPerMeeting = scaledProtocolTime * 0.8;
const minutesSavedPerWeek = minutesSavedPerMeeting * meetings;
const hoursSavedPerWeek = minutesSavedPerWeek / 60;
const hoursSavedPerMonth = hoursSavedPerWeek * 4.33;
const hoursSavedPerYear = hoursSavedPerWeek * 52;
const daysSavedPerYear = hoursSavedPerYear / 8; // Assuming 8-hour workday
// Calculate money saved
const moneySavedPerWeek = hoursSavedPerWeek * hourlyRate;
const moneySavedPerMonth = hoursSavedPerMonth * hourlyRate;
const moneySavedPerYear = hoursSavedPerYear * hourlyRate;
// Calculate ROI (assuming Memoro costs 15€/month)
const memoroCostPerMonth = 15;
const daysToROI = Math.ceil((memoroCostPerMonth / (moneySavedPerMonth / 30)));
// Update results
if (timeWeek) timeWeek.textContent = hoursSavedPerWeek.toFixed(1);
if (timeMonth) timeMonth.textContent = hoursSavedPerMonth.toFixed(0);
if (timeYear) timeYear.textContent = daysSavedPerYear.toFixed(0);
if (moneyWeek) moneyWeek.textContent = Math.round(moneySavedPerWeek).toString();
if (moneyMonth) moneyMonth.textContent = Math.round(moneySavedPerMonth).toString();
if (moneyYear) moneyYear.textContent = Math.round(moneySavedPerYear).toString();
if (roiDays) roiDays.textContent = daysToROI.toString();
// Update days text based on singular/plural
if (roiDaysText) {
if (lang === 'de') {
roiDaysText.textContent = daysToROI === 1 ? 'Tag' : 'Tagen';
} else {
roiDaysText.textContent = daysToROI === 1 ? 'Day' : 'Days';
}
}
// Update slider backgrounds
updateSliderBackground(meetingsSlider);
updateSliderBackground(meetingDurationSlider);
updateSliderBackground(protocolTimeSlider);
updateSliderBackground(hourlyRateSlider);
}
// Add event listeners
meetingsSlider?.addEventListener('input', calculateROI);
meetingDurationSlider?.addEventListener('input', calculateROI);
protocolTimeSlider?.addEventListener('input', calculateROI);
hourlyRateSlider?.addEventListener('input', calculateROI);
// Initial calculation
calculateROI();
}
// Initialize on page load
document.addEventListener('DOMContentLoaded', initROICalculator);
// Also initialize after Astro page transitions
document.addEventListener('astro:page-load', initROICalculator);
</script>