checkflo/app/src/routes/+page.svelte

749 lines
20 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script lang="ts">
import logo from '$lib/assets/checkflo-logo.png';
import hafnerLogo from '$lib/assets/hafner-logo.png';
import { pb } from '$lib/pb';
import { onMount } from 'svelte';
import { browser } from '$app/environment';
const DEMO_TENANT = 'mengbzc3ajxpccz';
const STATUS_LABEL: Record<string, string> = { ok: 'OK', abweichung: 'Abweichung', kritisch: 'Kritisch' };
const STATUS_CLASS: Record<string, string> = { ok: 'ok', abweichung: 'warn', kritisch: 'crit' };
let liveLogs = $state<any[]>([]);
let liveStats = $state({ total: 0, ok: 0, warn: 0, crit: 0 });
let lastUpdate = $state('');
async function fetchLiveData() {
try {
const today = new Date();
today.setHours(0, 0, 0, 0);
const dateStr = today.toISOString().slice(0, 19).replace('T', ' ');
const result = await pb.collection('check_logs').getList(1, 3, {
filter: `tenant = '${DEMO_TENANT}' && created >= '${dateStr}'`,
expand: 'station',
sort: '-created'
});
liveLogs = result.items;
liveStats.total = result.totalItems;
liveStats.ok = result.items.filter((l: any) => l.status === 'ok').length;
liveStats.warn = result.items.filter((l: any) => l.status === 'abweichung').length;
liveStats.crit = result.items.filter((l: any) => l.status === 'kritisch').length;
lastUpdate = new Date().toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' });
} catch { /* public endpoint, sollte immer klappen */ }
}
onMount(() => {
if (!browser) return;
fetchLiveData();
const iv = setInterval(fetchLiveData, 30_000);
return () => clearInterval(iv);
});
function formatTime(iso: string) {
if (!iso) return '';
const d = new Date(iso);
return isNaN(d.getTime()) ? '' : d.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' });
}
const features = [
{
icon: '🏷️',
title: 'Ihr Name, Ihr Brand',
text: 'Ihre Kunden sehen nur Ihr Logo, Ihre Farben, Ihre Domain. checkflo bleibt unsichtbar im Hintergrund.'
},
{
icon: '📶',
title: 'Keine Installation nötig',
text: 'Ihre Kunden scannen den QR-Code und füllen die Checkliste direkt im Browser aus — kein App-Store, kein MDM-Problem.'
},
{
icon: '📄',
title: 'Protokolle auf Knopfdruck',
text: 'Monatsprotokolle als PDF für Lebensmittelkontrollen — automatisch generiert, rechtssicher, ohne Aufwand für Sie.'
}
];
</script>
<!-- NAV -->
<nav>
<a href="/" class="logo">
<img src={logo} alt="checkflo" height="36" />
</a>
<a href="/admin" class="btn-nav">Anmelden</a>
</nav>
<!-- HERO -->
<section class="hero">
<div class="hero-inner">
<p class="eyebrow">White-Label-Software für Prüf- und Wartungsbetriebe</p>
<h1>Ihre eigene<br />HACCP-App —<br />unter Ihrem Namen.</h1>
<p class="subtitle">
Bieten Sie Ihren Kunden digitale Prüfprotokolle als gebrandete App an.
Kein App-Store. Keine Installation. Läuft offline.
Und Ihre Kunden sehen nur Ihr Logo.
</p>
<div class="hero-actions">
<a href="mailto:hallo@checkflo.de" class="btn-primary">Kostenlose Demo vereinbaren</a>
<a href="#funktionen" class="btn-ghost">So funktioniert es ↓</a>
</div>
</div>
</section>
<!-- MOCKUPS -->
<section class="mockups">
<div class="container">
<p class="eyebrow-dark">So sieht Ihre gebrandete App aus</p>
<h2>Dashboard für Sie — Checkliste für Ihre Kunden</h2>
<div class="devices">
<!-- Desktop: CSS Dashboard Schmidt Hygiene -->
<div class="device-browser">
<div class="browser-bar">
<div class="browser-dots">
<span></span><span></span><span></span>
</div>
<div class="browser-url">app.hafner-pruefdienste.de/admin</div>
</div>
<div class="browser-screen">
<div class="dash-sidebar">
<img src={hafnerLogo} alt="Hafner Prüftechnik" class="dash-brand-logo" />
<div class="dash-nav-item active schmidt">Dashboard</div>
<div class="dash-nav-item">Protokoll</div>
<div class="dash-nav-item">Stationen</div>
</div>
<div class="dash-main">
<div class="dash-title">Dashboard</div>
<div class="dash-date">
{new Date().toLocaleDateString('de-DE', { weekday: 'long', day: 'numeric', month: 'long' })}
{#if lastUpdate}<span class="dash-refresh">· {lastUpdate}</span>{/if}
</div>
<div class="dash-stats">
<div class="dash-stat">
<div class="dash-num">{liveStats.total}</div>
<div class="dash-label">Heute</div>
</div>
<div class="dash-stat ok">
<div class="dash-num">{liveStats.ok}</div>
<div class="dash-label">OK</div>
</div>
<div class="dash-stat warn">
<div class="dash-num">{liveStats.warn}</div>
<div class="dash-label">Abw.</div>
</div>
<div class="dash-stat crit">
<div class="dash-num">{liveStats.crit}</div>
<div class="dash-label">Kritisch</div>
</div>
</div>
<div class="dash-entries-title">Letzte Einträge</div>
{#each liveLogs as log}
<div class="dash-entry">
<span class="entry-badge {STATUS_CLASS[log.status] ?? 'ok'}">{STATUS_LABEL[log.status] ?? log.status}</span>
<span class="entry-station">{log.expand?.station?.name ?? '—'}</span>
<span class="entry-temp">{log.temperature ? log.temperature + ' °C' : ''}</span>
<span class="entry-who">{log.checked_by} · {formatTime(log.created)}</span>
</div>
{:else}
<div class="dash-empty">Noch keine Einträge heute</div>
{/each}
</div>
</div>
</div>
<!-- Mobile: Live-Iframe Schmidt Hygiene (blau) -->
<div class="device-phone">
<div class="phone-frame">
<div class="phone-notch"></div>
<div class="phone-screen">
<iframe
src="https://checkflo.de/s/b6b30daf-8bfe-4648-ba2b-6c42916d2264"
title="Musterküche Demo"
scrolling="no"
></iframe>
</div>
<div class="phone-home"></div>
</div>
<p class="phone-caption">Ihr Kunde erfasst — Sie sehen es sofort</p>
</div>
</div>
</div>
</section>
<!-- HOW IT WORKS -->
<section class="steps" id="funktionen">
<div class="container">
<h2>In 3 Schritten zur eigenen HACCP-App</h2>
<div class="steps-grid">
<div class="step">
<div class="step-num">1</div>
<h3>Lizenz buchen & branden</h3>
<p>Sie erhalten Ihre App unter Ihrem Logo, Ihren Farben und Ihrer Domain — fertig in 48 Stunden.</p>
</div>
<div class="step">
<div class="step-num">2</div>
<h3>Kunden einrichten</h3>
<p>Stationen anlegen, QR-Codes ausdrucken, beim Kunden aufkleben. Ihr Kunde braucht nichts installieren.</p>
</div>
<div class="step">
<div class="step-num">3</div>
<h3>Protokolle abrufen</h3>
<p>Ihre Kunden erfassen täglich — Sie sehen alles in Echtzeit und exportieren Protokolle per Knopfdruck als PDF.</p>
</div>
</div>
</div>
</section>
<!-- FEATURES -->
<section class="features">
<div class="container">
<h2>Was checkflo für Sie leistet</h2>
<div class="features-grid">
{#each features as f}
<div class="feature-card">
<span class="feature-icon">{f.icon}</span>
<h3>{f.title}</h3>
<p>{f.text}</p>
</div>
{/each}
</div>
</div>
</section>
<!-- PRICING -->
<section class="pricing">
<div class="container">
<div class="pricing-intro">
<h2>White-Label-Lizenz für Prüf- und Wartungsbetriebe</h2>
<p class="pricing-sub">
Sie treten gegenüber Ihren Kunden mit einer <strong>eigenen gebrandeten App</strong> auf —
checkflo läuft im Hintergrund. Ihre Kunden sehen nur Ihr Logo, Ihre Farben, Ihre Domain.
</p>
<div class="pricing-math">
<span class="math-example">10 Kunden × 30 €/Monat</span>
<span class="math-arrow"></span>
<span class="math-result">300 € MRR bei 249 € Lizenz</span>
</div>
</div>
<div class="pricing-grid">
<div class="plan">
<div class="plan-name">Basic White-Label</div>
<div class="plan-price">249 €<span>/Monat</span></div>
<p class="plan-desc">Für den Einstieg — bis zu 10 Kundenbetriebe</p>
<ul class="plan-features">
<li>✓ Ihr Logo & CI-Farben in der App</li>
<li>✓ Subdomain (app.ihr-betrieb.de)</li>
<li>✓ Bis zu 500 Assets / QR-Tags gesamt</li>
<li>✓ HACCP-Protokoll-Templates</li>
<li>✓ PDF-Export für Ihre Kunden</li>
<li>✓ Offline-fähige PWA</li>
</ul>
<a href="mailto:hallo@checkflo.de?subject=Basic White-Label Anfrage" class="btn-plan">Demo vereinbaren</a>
</div>
<div class="plan plan-featured">
<div class="plan-badge">Empfohlen</div>
<div class="plan-name">Pro White-Label</div>
<div class="plan-price">499 €<span>/Monat</span></div>
<p class="plan-desc">Für wachsende Betriebe ohne Asset-Limit</p>
<ul class="plan-features">
<li>✓ Eigene Custom Domain Ihrer Kunden</li>
<li>✓ Unlimitierte Assets & Kundenbetriebe</li>
<li>✓ Individueller Formular-Generator</li>
<li>✓ API-Zugang (ERP, Excel-Export)</li>
<li>✓ Dynamisches Branding je Kunde</li>
<li>✓ Prioritäts-Support</li>
</ul>
<a href="mailto:hallo@checkflo.de?subject=Pro White-Label Anfrage" class="btn-plan btn-plan-featured">Demo vereinbaren</a>
</div>
<div class="plan">
<div class="plan-name">Enterprise</div>
<div class="plan-price">ab 1.500 €<span>/Monat</span></div>
<p class="plan-desc">Für Ketten, Verbände & große Facility-Manager</p>
<ul class="plan-features">
<li>✓ Dedizierte Server-Infrastruktur</li>
<li>✓ SAP / MS Dynamics Anbindung</li>
<li>✓ On-Premise Installation möglich</li>
<li>✓ SLA & dedizierter Ansprechpartner</li>
<li>✓ Individuelle Entwicklung</li>
<li>✓ Schulung & Onboarding</li>
</ul>
<a href="mailto:hallo@checkflo.de?subject=Enterprise Anfrage" class="btn-plan">Angebot anfragen</a>
</div>
</div>
<p class="pricing-note">Keine Einrichtungsgebühr · Monatlich kündbar · Alle Pläne inkl. Updates</p>
</div>
</section>
<!-- CTA -->
<section class="cta">
<div class="container">
<h2>Bieten Sie Ihren Kunden eine eigene HACCP-App an.</h2>
<p>Für Prüfbetriebe, Ingenieurbüros und Facility-Management-Firmen.</p>
<a href="mailto:hallo@checkflo.de" class="btn-primary btn-large">Kostenlose Demo vereinbaren</a>
</div>
</section>
<!-- FOOTER -->
<footer>
<div class="container">
<span class="logo"><span class="logo-check">check</span><span class="logo-flo">flo</span></span>
<span>© 2026 checkflo · <a href="mailto:hallo@checkflo.de">hallo@checkflo.de</a></span>
</div>
</footer>
<style>
/* NAV */
nav {
display: flex;
justify-content: space-between;
align-items: center;
height: 72px;
padding: 0 2rem;
border-bottom: 1px solid #f0f0f0;
position: sticky;
top: 0;
background: #fff;
z-index: 10;
}
.logo { display: flex; align-items: center; }
.logo img { height: 44px; width: auto; }
.btn-nav {
padding: 0.5rem 1.25rem;
border: 1.5px solid #0B1023;
border-radius: 6px;
font-weight: 600;
font-size: 0.9rem;
transition: all 0.15s;
}
.btn-nav:hover { background: #0B1023; color: #fff; }
/* MOCKUPS */
.mockups {
padding: 5rem 2rem;
background: #0B1023;
color: #fff;
overflow: hidden;
}
.mockups .eyebrow-dark {
font-size: 0.8rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1.5px;
color: #F97316;
text-align: center;
margin-bottom: 1rem;
}
.mockups h2 { color: #fff; text-align: center; margin-bottom: 3rem; }
.devices {
display: flex;
align-items: flex-start;
justify-content: center;
gap: 3rem;
flex-wrap: wrap;
}
/* Browser Mockup */
.device-browser {
flex: 1;
min-width: 320px;
max-width: 580px;
background: #1a2035;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 24px 60px rgba(0,0,0,0.5);
border: 1px solid #2a3347;
}
.browser-bar {
background: #252d42;
padding: 0.6rem 1rem;
display: flex;
align-items: center;
gap: 0.75rem;
}
.browser-dots { display: flex; gap: 5px; }
.browser-dots span {
width: 10px; height: 10px;
border-radius: 50%;
background: #3a4055;
}
.browser-url {
flex: 1;
background: #1a2035;
border-radius: 4px;
padding: 0.2rem 0.6rem;
font-size: 0.7rem;
color: #6b7a9a;
font-family: monospace;
}
.browser-screen { display: flex; height: 300px; overflow: hidden; }
/* CSS Dashboard */
.dash-sidebar {
width: 120px;
background: #0B1023;
padding: 1rem 0.75rem;
flex-shrink: 0;
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.dash-brand-logo { width: 100%; margin-bottom: 1rem; }
.dash-nav-item.schmidt { background: rgba(42,155,87,0.15); color: #2A9B57; }
.dash-nav-item {
padding: 0.4rem 0.5rem;
border-radius: 5px;
font-size: 0.65rem;
color: #6b7a9a;
}
.dash-nav-item.active {
background: rgba(249,115,22,0.15);
color: #F97316;
}
.dash-main {
flex: 1;
padding: 1rem;
background: #F5F7FA;
overflow: hidden;
}
.dash-title { font-size: 0.9rem; font-weight: 800; color: #0B1023; }
.dash-date { font-size: 0.65rem; color: #888; margin-bottom: 0.75rem; }
.dash-stats {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 0.4rem;
margin-bottom: 0.75rem;
}
.dash-stat {
background: #fff;
border-radius: 6px;
padding: 0.4rem;
border-left: 2.5px solid #ddd;
}
.dash-stat.ok { border-color: #16a34a; }
.dash-stat.warn { border-color: #ea580c; }
.dash-stat.crit { border-color: #dc2626; }
.dash-num { font-size: 1rem; font-weight: 800; color: #0B1023; line-height: 1; }
.dash-label { font-size: 0.5rem; color: #888; margin-top: 0.15rem; }
.dash-refresh { font-size: 0.55rem; color: #aaa; }
.dash-entries-title { font-size: 0.7rem; font-weight: 700; color: #0B1023; margin-bottom: 0.4rem; }
.dash-empty { font-size: 0.65rem; color: #aaa; padding: 0.5rem 0; }
.dash-entry {
background: #fff;
border-radius: 6px;
padding: 0.4rem 0.5rem;
font-size: 0.6rem;
display: flex;
align-items: center;
gap: 0.4rem;
margin-bottom: 0.3rem;
}
.entry-badge {
padding: 0.1rem 0.35rem;
border-radius: 10px;
font-weight: 700;
font-size: 0.55rem;
white-space: nowrap;
}
.entry-badge.ok { background: #dcfce7; color: #16a34a; }
.entry-badge.warn { background: #ffedd5; color: #ea580c; }
.entry-station { flex: 1; font-weight: 600; color: #0B1023; }
.entry-temp { color: #555; }
.entry-who { color: #aaa; white-space: nowrap; }
/* Phone Mockup */
.device-phone {
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
}
.phone-frame {
width: 220px;
background: #1a1a2e;
border-radius: 36px;
padding: 12px;
box-shadow: 0 24px 60px rgba(0,0,0,0.6), inset 0 0 0 1px #2a2a4a;
}
.phone-notch {
width: 60px;
height: 20px;
background: #1a1a2e;
border-radius: 10px;
margin: 0 auto 8px;
border: 1.5px solid #2a2a4a;
}
.phone-screen {
background: #fff;
border-radius: 24px;
overflow: hidden;
height: 480px;
position: relative;
}
.phone-screen iframe {
position: absolute;
top: 0;
left: 0;
width: 390px;
height: 960px;
border: none;
transform: scale(0.502);
transform-origin: top left;
pointer-events: auto;
}
.phone-home {
width: 60px;
height: 4px;
background: #3a3a5a;
border-radius: 2px;
margin: 10px auto 0;
}
.phone-caption {
font-size: 0.8rem;
color: #6b7a9a;
text-align: center;
}
@media (max-width: 768px) {
.device-browser { display: none; }
.devices { justify-content: center; }
}
/* HERO */
.hero {
background: linear-gradient(135deg, #0B1023 0%, #16213e 100%);
color: #fff;
padding: 5rem 2rem 6rem;
text-align: center;
}
.hero-inner { max-width: 720px; margin: 0 auto; }
.eyebrow {
font-size: 0.85rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1.5px;
color: #F97316;
margin-bottom: 1.5rem;
}
h1 {
font-size: clamp(2.2rem, 6vw, 3.5rem);
font-weight: 800;
line-height: 1.15;
margin-bottom: 1.5rem;
}
.subtitle {
font-size: 1.15rem;
color: #b0b8cc;
max-width: 560px;
margin: 0 auto 2.5rem;
}
.hero-actions {
display: flex;
gap: 1rem;
justify-content: center;
flex-wrap: wrap;
}
/* BUTTONS */
.btn-primary {
background: #F97316;
color: #fff;
padding: 0.85rem 2rem;
border-radius: 8px;
font-weight: 700;
font-size: 1rem;
transition: background 0.15s;
display: inline-block;
}
.btn-primary:hover { background: #ea6c10; }
.btn-large { padding: 1rem 2.5rem; font-size: 1.1rem; }
.btn-ghost {
color: #b0b8cc;
padding: 0.85rem 1.5rem;
font-size: 1rem;
transition: color 0.15s;
display: inline-block;
}
.btn-ghost:hover { color: #fff; }
/* STEPS */
.steps {
padding: 5rem 2rem;
background: #F5F7FA;
}
.container { max-width: 1000px; margin: 0 auto; }
h2 {
font-size: 1.9rem;
font-weight: 800;
text-align: center;
margin-bottom: 3rem;
}
.steps-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 2rem;
}
.step {
background: #fff;
border-radius: 12px;
padding: 2rem;
box-shadow: 0 2px 12px rgba(0,0,0,0.06);
}
.step-num {
width: 2.5rem;
height: 2.5rem;
background: #F97316;
color: #fff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 800;
font-size: 1.1rem;
margin-bottom: 1rem;
}
.step h3 { font-size: 1.1rem; margin-bottom: 0.5rem; }
.step p { color: #555; font-size: 0.95rem; }
/* FEATURES */
.features { padding: 5rem 2rem; }
.features-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 2rem;
}
.feature-card {
border: 1.5px solid #eee;
border-radius: 12px;
padding: 2rem;
transition: border-color 0.15s;
}
.feature-card:hover { border-color: #F97316; }
.feature-icon { font-size: 2rem; display: block; margin-bottom: 1rem; }
.feature-card h3 { font-size: 1.1rem; margin-bottom: 0.5rem; }
.feature-card p { color: #555; font-size: 0.95rem; }
/* PRICING */
.pricing { padding: 5rem 2rem; background: #F5F7FA; }
.pricing-intro { text-align: center; margin-bottom: 3rem; }
.pricing-sub { color: #555; margin-top: 1rem; margin-bottom: 1.5rem; font-size: 1.05rem; max-width: 640px; margin-left: auto; margin-right: auto; }
.pricing-math {
display: inline-flex;
align-items: center;
gap: 0.75rem;
background: #fff;
border: 1.5px solid #F97316;
border-radius: 10px;
padding: 0.6rem 1.25rem;
font-size: 0.9rem;
margin-top: 0.5rem;
}
.math-example { color: #555; }
.math-arrow { color: #F97316; font-size: 1.2rem; }
.math-result { font-weight: 700; color: #0B1023; }
.plan-desc { font-size: 0.85rem; color: #888; margin-bottom: 1.25rem; margin-top: -0.75rem; }
.pricing-note { text-align: center; margin-top: 2rem; font-size: 0.85rem; color: #aaa; }
.pricing-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1.5rem;
align-items: stretch;
}
.plan {
background: #fff;
border-radius: 16px;
padding: 2rem;
border: 1.5px solid #eee;
display: flex;
flex-direction: column;
position: relative;
}
.plan-featured {
border-color: #F97316;
box-shadow: 0 4px 24px rgba(249,115,22,0.12);
}
.plan-badge {
position: absolute;
top: -12px;
left: 50%;
transform: translateX(-50%);
background: #F97316;
color: #fff;
padding: 0.2rem 0.9rem;
border-radius: 20px;
font-size: 0.75rem;
font-weight: 700;
}
.plan-name { font-size: 0.85rem; font-weight: 700; color: #888; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 0.75rem; }
.plan-price { font-size: 2rem; font-weight: 800; color: #0B1023; margin-bottom: 1.5rem; }
.plan-price span { font-size: 1rem; font-weight: 400; color: #888; }
.plan-features { list-style: none; margin: 0 0 2rem; padding: 0; display: flex; flex-direction: column; gap: 0.6rem; flex: 1; }
.plan-features li { font-size: 0.9rem; color: #444; }
.btn-plan {
display: block;
text-align: center;
padding: 0.75rem;
border-radius: 8px;
font-weight: 700;
font-size: 0.95rem;
border: 1.5px solid #0B1023;
color: #0B1023;
transition: all 0.15s;
}
.btn-plan:hover { background: #0B1023; color: #fff; }
.btn-plan-featured { background: #F97316; border-color: #F97316; color: #fff; }
.btn-plan-featured:hover { background: #ea6c10; border-color: #ea6c10; }
/* CTA */
.cta {
background: #0B1023;
color: #fff;
padding: 5rem 2rem;
text-align: center;
}
.cta h2 { color: #fff; margin-bottom: 1rem; }
.cta p { color: #b0b8cc; margin-bottom: 2rem; }
/* FOOTER */
footer {
padding: 1.5rem 2rem;
border-top: 1px solid #f0f0f0;
}
footer .container {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 0.5rem;
font-size: 0.9rem;
color: #888;
}
footer a:hover { color: #F97316; }
</style>