mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-23 11:26:42 +02:00
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>
385 lines
No EOL
15 KiB
Text
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> |