CluPilotCloud/resources/views/components/layouts/site.blade.php

168 lines
9.2 KiB
PHP

@props([
'title' => null,
'description' => null,
'robots' => null,
])
<!DOCTYPE html>
<html lang="de" class="scroll-smooth">
<head>
@if ($description)<meta name="description" content="{{ $description }}">@endif
@if ($robots)<meta name="robots" content="{{ $robots }}">@endif
<x-shell.head :title="$title" />
{{-- Parts of the page arrive on scroll, which an observer decides. Without
JavaScript nothing is ever observed. --}}
<noscript><style>.rv,.logline{opacity:1!important;transform:none!important}</style></noscript>
</head>
{{--
The public website.
Two rewrites. The first only recoloured the old page same eight numbered
sections, same split hero, same comparison matrix and was correctly called
out as the old thing in new paint. A palette is not a design.
What actually changed on the second pass: the § numbering is gone (a
document convention, and the document voice was exactly what the redesign
dropped), the hero shows the product instead of describing it, the uniform
card rows became one asymmetric grid, the price matrix became price cards
with the full table folded away underneath, and the page now has one dark
hinge in the middle instead of a dark section among seven light ones.
--}}
<body class="flex min-h-screen flex-col bg-bg text-body antialiased">
<div id="site-progress" class="fixed inset-x-0 top-0 z-[120] h-0.5 w-0 bg-accent" aria-hidden="true"></div>
{{-- A floating bar rather than a full-width band: it reads as an object on
the page instead of a browser chrome bolted to the top, and it lets the
hero start at the very top of the viewport. --}}
<header x-data="{ open: false }" class="fixed inset-x-0 top-0 z-[110] px-4 pt-4 sm:px-6">
{{-- Opaque. Translucency plus backdrop-blur looks like glass over a
photograph and like a rendering fault over a dark panel, which is
exactly what scrolls under this bar. --}}
<div class="mx-auto max-w-[1120px] rounded-xl border border-line bg-surface shadow-sm">
<div class="flex h-14 items-center gap-8 pl-4 pr-3">
<a href="{{ route('home') }}"><x-ui.brand size="md" /></a>
<nav class="hidden items-center gap-1 text-sm text-muted lg:flex">
@foreach (['#produkt' => 'Produkt', '#ablauf' => 'Ablauf', '#sicherheit' => 'Sicherheit', '#preise' => 'Preise', '#fragen' => 'Fragen'] as $href => $label)
<a href="{{ route('home') }}{{ $href }}" class="rounded px-3 py-1.5 transition-colors hover:bg-surface-hover hover:text-ink">{{ $label }}</a>
@endforeach
</nav>
<div class="ml-auto flex items-center gap-2">
<a href="{{ route('login') }}" class="hidden rounded px-3 py-1.5 text-sm text-muted transition-colors hover:bg-surface-hover hover:text-ink sm:block">Anmelden</a>
<x-ui.button href="{{ route('home') }}#preise" variant="ink" size="sm">Preise</x-ui.button>
<button type="button" class="grid size-9 place-items-center rounded text-muted transition-colors hover:bg-surface-hover hover:text-ink lg:hidden" @click="open = !open" :aria-expanded="open" aria-label="Menü">
<x-ui.icon name="menu" class="size-5" x-show="!open" />
<x-ui.icon name="x" class="size-5" x-show="open" x-cloak />
</button>
</div>
</div>
<nav x-show="open" x-cloak @click="open = false" class="border-t border-line px-4 pb-2 lg:hidden">
@foreach (['#produkt' => 'Produkt', '#ablauf' => 'Ablauf', '#sicherheit' => 'Sicherheit', '#preise' => 'Preise', '#fragen' => 'Fragen', route('login') => 'Anmelden'] as $href => $label)
<a href="{{ Str::startsWith($href, '#') ? route('home').$href : $href }}" class="flex min-h-11 items-center border-b border-line text-sm text-body last:border-0">{{ $label }}</a>
@endforeach
</nav>
</div>
</header>
<main class="flex-1">
{{ $slot }}
</main>
<footer class="border-t border-line bg-surface">
<div class="mx-auto max-w-[1120px] px-5 py-14 sm:px-6">
<div class="grid gap-10 sm:grid-cols-2 lg:grid-cols-4">
<div class="lg:col-span-2">
<x-ui.brand size="md" suffix />
<p class="mt-4 max-w-[38ch] text-sm leading-relaxed text-muted">
Eine eigene, isolierte Cloud für Ihr Unternehmen eingerichtet, gesichert und
überwacht von uns, mit Serverstandort in der EU.
</p>
<a href="mailto:office&#64;clupilot.com" class="mt-5 inline-flex items-center gap-2 text-sm font-medium text-accent-text underline-offset-4 hover:underline">
<x-ui.icon name="mail" class="size-4" />office&#64;clupilot.com
</a>
</div>
<div>
<p class="lbl">Produkt</p>
<div class="mt-4 flex flex-col gap-2.5 text-sm text-muted">
<a href="{{ route('home') }}#produkt" class="transition-colors hover:text-ink">Was enthalten ist</a>
<a href="{{ route('home') }}#ablauf" class="transition-colors hover:text-ink">Ablauf</a>
<a href="{{ route('home') }}#sicherheit" class="transition-colors hover:text-ink">Sicherheit</a>
<a href="{{ route('home') }}#preise" class="transition-colors hover:text-ink">Preise</a>
</div>
</div>
<div>
<p class="lbl">Rechtliches &amp; Betrieb</p>
<div class="mt-4 flex flex-col gap-2.5 text-sm text-muted">
<a href="{{ route('legal.impressum') }}" class="transition-colors hover:text-ink">Impressum</a>
<a href="{{ route('legal.datenschutz') }}" class="transition-colors hover:text-ink">Datenschutz</a>
<a href="{{ route('legal.agb') }}" class="transition-colors hover:text-ink">AGB</a>
<a href="{{ route('status') }}" class="transition-colors hover:text-ink">Betriebsstatus</a>
<a href="{{ route('login') }}" class="transition-colors hover:text-ink">Anmelden</a>
</div>
</div>
</div>
<div class="mt-12 flex flex-col gap-2 border-t border-line pt-6 sm:flex-row sm:items-center">
<span class="lbl">© {{ date('Y') }} CluPilot Cloud · Österreich</span>
<span class="lbl sm:ml-auto">Serverstandort EU · Support auf Deutsch</span>
</div>
</div>
</footer>
@verbatim
<script>
(function () {
const reduce = matchMedia('(prefers-reduced-motion:reduce)').matches;
const progress = document.getElementById('site-progress');
addEventListener('scroll', () => {
const el = document.documentElement;
const max = el.scrollHeight - el.clientHeight;
progress.style.width = (max > 0 ? (scrollY / max) * 100 : 0) + '%';
}, { passive: true });
const reveal = new IntersectionObserver(entries => entries.forEach(entry => {
if (!entry.isIntersecting) return;
entry.target.classList.add('in');
reveal.unobserve(entry.target);
}), { threshold: 0.1, rootMargin: '0px 0px -40px 0px' });
document.querySelectorAll('.rv').forEach(el => reveal.observe(el));
// Tabular figures are set on the element, so counting never shifts
// the layout around it.
const count = new IntersectionObserver(entries => entries.forEach(entry => {
if (!entry.isIntersecting) return;
count.unobserve(entry.target);
const el = entry.target;
const target = parseFloat(el.dataset.count);
const decimals = parseInt(el.dataset.decimals || 0, 10);
const format = v => v.toFixed(decimals).replace('.', ',');
if (reduce) { el.textContent = format(target); return; }
const start = performance.now();
(function tick(now) {
const p = Math.min((now - start) / 1100, 1);
el.textContent = format(target * (1 - Math.pow(1 - p, 3)));
if (p < 1) requestAnimationFrame(tick);
})(start);
}), { threshold: 0.6 });
document.querySelectorAll('[data-count]').forEach(el => count.observe(el));
const log = document.getElementById('deploy-log');
if (log) {
const lines = new IntersectionObserver(entries => entries.forEach(entry => {
if (!entry.isIntersecting) return;
lines.unobserve(log);
log.querySelectorAll('.logline').forEach((row, i) =>
setTimeout(() => row.classList.add('in'), reduce ? 0 : 200 + i * 340));
}), { threshold: 0.3 });
lines.observe(log);
}
})();
</script>
@endverbatim
</body>
</html>