feat(phase-1): Premium design system, CapitoliumPreloader, IndexSectionIndicator, Recipe Showcase Stage

- Full CSS rewrite: Warm Ivory/Gold/Steinkohle design tokens, Floema grid, easing library
- AuraMarbleBackground: fixed grain texture layer
- CapitoliumPreloader: fullscreen counter animation with slide-up exit
- LiquidOverlayMenu: fixed hamburger → fullscreen clip-path overlay with SlideUpTextHover nav links
- IndexSectionIndicator: fixed left-side section tracker with animated fill line
- Recipe Showcase Stage: GSAP ScrollTrigger pinned stage (images enter from right, exit left)
  with parallax inner-image effect, intro title, progress dots, hint line
- LenisSmoothScroll: CDN integration synced with GSAP ticker
- CapitoliumRevealButton: scaleY fill reveal on hover for all CTAs
- SlideUpTextHover: overlay nav links
- UnderlineExpandHover: footer + overlay footer links
- head.php: GSAP 3.12.5 + ScrollTrigger + Lenis 1.0.42 CDN; Playfair Display + Manrope fonts
- recipe.php: updated to new design system (btn-reveal, pill--gold, Lenis init)
- impressum.php / datenschutz.php: migrated to shared head.php partial
This commit is contained in:
2026-05-21 11:52:40 +02:00
parent 025f41fd18
commit 6c00e1a00d
8 changed files with 2145 additions and 1176 deletions
+34 -35
View File
@@ -1,79 +1,78 @@
<?php
$langCode = (isset($lang) && $lang === 'de') ? 'de' : 'en';
$langCode = (isset($lang) && $lang === 'de') ? 'de' : 'en';
$langSuffix = $langCode === 'de' ? '?lang=de' : '';
$settings = function_exists('load_site_settings') ? load_site_settings() : null;
$settings = function_exists('load_site_settings') ? load_site_settings() : null;
$contactEmail = $settings['imprint'][$langCode]['email'] ?? 'contact@flixcooks.at';
?>
<footer class="site-footer">
<footer class="site-footer" role="contentinfo">
<div class="footer-meta">
<p>Website designed by Felix Brockers</p>
<a class="contact-link" href="mailto:<?php echo e($contactEmail); ?>">Contact me</a>
<p><?php echo $langCode === 'de' ? 'Designed &amp; entwickelt von Felix Brockers' : 'Designed &amp; built by Felix Brockers'; ?></p>
<a class="contact-link underline-link" href="mailto:<?php echo e($contactEmail); ?>">
<?php echo e($contactEmail); ?>
</a>
<small>© <?php echo date('Y'); ?> FlixCooks</small>
</div>
<div class="footer-links">
<a href="/impressum.php<?php echo $langSuffix; ?>"><?php echo $langCode === 'de' ? 'Impressum' : 'Imprint'; ?></a>
<a href="/datenschutz.php<?php echo $langSuffix; ?>"><?php echo $langCode === 'de' ? 'Datenschutz' : 'Privacy'; ?></a>
<a class="underline-link" href="/impressum.php<?php echo $langSuffix; ?>">
<?php echo $langCode === 'de' ? 'Impressum' : 'Imprint'; ?>
</a>
<a class="underline-link" href="/datenschutz.php<?php echo $langSuffix; ?>">
<?php echo $langCode === 'de' ? 'Datenschutz' : 'Privacy'; ?>
</a>
</div>
</footer>
<button id="backToTop" aria-label="Back to top">↑ Back to top</button>
<button id="backToTop" aria-label="<?php echo $langCode === 'de' ? 'Nach oben' : 'Back to top'; ?>">↑</button>
<!-- Fixed dark/light toggle — always visible, scrolls with page -->
<!-- Fixed dark/light toggle -->
<button class="theme-toggle" data-theme-toggle aria-label="Switch to dark mode">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
</button>
<script>
// ── Back to top ───────────────────────────────────────────────────────────────
// ── Back to top ────────────────────────────────────────────────
(function(){
const btn = document.getElementById('backToTop');
var btn = document.getElementById('backToTop');
if (!btn) return;
const check = () => btn.classList.toggle('show', window.scrollY > 240);
var check = function() { btn.classList.toggle('show', window.scrollY > 280); };
window.addEventListener('scroll', check, { passive: true });
btn.addEventListener('click', () => window.scrollTo({ top: 0, behavior: 'smooth' }));
btn.addEventListener('click', function() {
if (window.lenis) {
window.lenis.scrollTo(0, { duration: 1.4 });
} else {
window.scrollTo({ top: 0, behavior: 'smooth' });
}
});
check();
})();
// ── Dark / light mode toggle ─────────────────────────────────────────────────
// ── Dark / light mode toggle ───────────────────────────────────
(function(){
const r = document.documentElement;
let d = r.getAttribute('data-theme') || 'light';
var r = document.documentElement;
var d = localStorage.getItem('fc-theme') || r.getAttribute('data-theme') || 'light';
r.setAttribute('data-theme', d);
const SUN = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg>';
const MOON = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>';
var SUN = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg>';
var MOON = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>';
function setIcon() {
const icon = d === 'dark' ? SUN : MOON;
const label = 'Switch to ' + (d === 'dark' ? 'light' : 'dark') + ' mode';
document.querySelectorAll('[data-theme-toggle]').forEach(t => {
var icon = d === 'dark' ? SUN : MOON;
var label = 'Switch to ' + (d === 'dark' ? 'light' : 'dark') + ' mode';
document.querySelectorAll('[data-theme-toggle]').forEach(function(t) {
t.innerHTML = icon;
t.setAttribute('aria-label', label);
});
}
// Use event delegation so it works regardless of DOM order
document.addEventListener('click', function(e) {
if (e.target.closest('[data-theme-toggle]')) {
d = d === 'dark' ? 'light' : 'dark';
r.setAttribute('data-theme', d);
localStorage.setItem('fc-theme', d);
setIcon();
}
});
// Toggle adapts visually when scrolled off landing section
(function(){
const landing = document.getElementById('landing');
const toggle = document.querySelector('.theme-toggle');
if (!toggle) return;
function check() {
const past = !landing || window.scrollY > (landing.offsetHeight - 80);
toggle.classList.toggle('on-site', past);
}
window.addEventListener('scroll', check, { passive: true });
check();
})();
setIcon();
})();
</script>
+26 -3
View File
@@ -11,11 +11,34 @@ $description = $description ?? 'Seasonal recipes, tested tips, and approachable
<title><?php echo e($pageTitle); ?></title>
<link rel="icon" type="image/png" href="/assets/favicon.png">
<link rel="alternate icon" type="image/x-icon" sizes="16x16 32x32" href="/favicon.ico">
<!-- Google Fonts: Playfair Display + Manrope -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,500;0,600;0,700;1,500;1,600&family=Manrope:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600;1,700&family=Manrope:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
<!-- Main stylesheet -->
<link rel="stylesheet" href="/assets/style.css">
<!-- Always start light -->
<script>document.documentElement.setAttribute('data-theme','light');</script>
<!-- GSAP + ScrollTrigger (CDN) -->
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/ScrollTrigger.min.js"></script>
<!-- Lenis smooth scroll (CDN) -->
<script src="https://cdn.jsdelivr.net/npm/@studio-freight/lenis@1.0.42/bundled/lenis.min.js"></script>
<!-- Set theme instantly to avoid flash -->
<script>
(function(){
var t = localStorage.getItem('fc-theme') || 'light';
document.documentElement.setAttribute('data-theme', t);
})();
</script>
</head>
<body>
<!-- ── AuraMarbleBackground (fixed atmospheric grain layer) ── -->
<div class="bg-marble-container" aria-hidden="true">
<div class="bg-marble-grain"></div>
</div>
+108 -27
View File
@@ -1,32 +1,113 @@
<header class="site-header">
<div class="site-header__top">
<a class="logo" href="/index.php<?php echo $lang === 'de' ? '?lang=de' : ''; ?>">
<img class="logo--light" src="/assets/logo-white.png" alt="FlixCooks">
<img class="logo--dark" src="/assets/logo-black.png" alt="FlixCooks">
</a>
<div class="header-actions">
<a class="lang-toggle" href="<?php echo e($langSwitchHref ?? '#'); ?>"><?php echo e($langSwitchLabel ?? 'DE/EN'); ?></a>
<button class="menu-toggle" type="button" aria-expanded="false" aria-controls="siteNav">
<span class="menu-toggle__icon" aria-hidden="true">☰</span>
<span class="menu-toggle__label"><?php echo $lang === 'de' ? 'Menü' : 'Menu'; ?></span>
</button>
<?php
// Determine current page for active nav state
$currentPage = basename($_SERVER['PHP_SELF'] ?? 'index.php');
$langParam = isset($lang) && $lang === 'de' ? '?lang=de' : '';
$langParamAmp = isset($lang) && $lang === 'de' ? '&lang=de' : '';
?>
<!-- ── LiquidOverlayMenu ─────────────────────────────────────── -->
<!-- Fixed Logo (top-left) -->
<a class="nav-logo" href="/index.php<?php echo $langParam; ?>" aria-label="FlixCooks Home">
<img class="logo--light" src="/assets/logo-black.png" alt="FlixCooks">
<img class="logo--dark" src="/assets/logo-white.png" alt="FlixCooks">
</a>
<!-- Fixed Hamburger Button (top-right) -->
<button class="nav-trigger" id="navMenuBtn" type="button" aria-label="<?php echo isset($lang) && $lang === 'de' ? 'Menü öffnen' : 'Open menu'; ?>" aria-expanded="false" aria-controls="navOverlay">
<span class="nav-burger-lines" aria-hidden="true">
<span class="nav-line nav-line--top"></span>
<span class="nav-line nav-line--mid"></span>
<span class="nav-line nav-line--bot"></span>
</span>
</button>
<!-- Full-screen Overlay -->
<div class="nav-overlay" id="navOverlay" aria-hidden="true" role="dialog" aria-label="Navigation">
<div class="nav-overlay-inner">
<nav>
<ul class="nav-menu-list" role="list">
<li>
<a href="/index.php<?php echo $langParam; ?>" class="slide-hover-link" <?php echo $currentPage === 'index.php' ? 'aria-current="page"' : ''; ?>>
<span class="slide-hover-wrapper">
<span class="slide-text-main"><?php echo isset($lang) && $lang === 'de' ? 'Startseite' : 'Home'; ?></span>
<span class="slide-text-clone" aria-hidden="true"><?php echo isset($lang) && $lang === 'de' ? 'Startseite' : 'Home'; ?></span>
</span>
</a>
</li>
<li>
<a href="/index.php<?php echo $langParam; ?>#recipes-start" class="slide-hover-link">
<span class="slide-hover-wrapper">
<span class="slide-text-main"><?php echo isset($lang) && $lang === 'de' ? 'Rezepte' : 'Recipes'; ?></span>
<span class="slide-text-clone" aria-hidden="true"><?php echo isset($lang) && $lang === 'de' ? 'Rezepte' : 'Recipes'; ?></span>
</span>
</a>
</li>
<li>
<a href="/impressum.php<?php echo $langParam; ?>" class="slide-hover-link" <?php echo $currentPage === 'impressum.php' ? 'aria-current="page"' : ''; ?>>
<span class="slide-hover-wrapper">
<span class="slide-text-main"><?php echo isset($lang) && $lang === 'de' ? 'Impressum' : 'Imprint'; ?></span>
<span class="slide-text-clone" aria-hidden="true"><?php echo isset($lang) && $lang === 'de' ? 'Impressum' : 'Imprint'; ?></span>
</span>
</a>
</li>
</ul>
</nav>
<!-- Language & Footer links inside overlay -->
<div class="nav-overlay-footer">
<a href="<?php echo e($langSwitchHref ?? '#'); ?>" class="underline-link">
<?php echo isset($lang) && $lang === 'de' ? 'English' : 'Deutsch'; ?>
</a>
<a href="/datenschutz.php<?php echo $langParam; ?>" class="underline-link">
<?php echo isset($lang) && $lang === 'de' ? 'Datenschutz' : 'Privacy'; ?>
</a>
</div>
</div>
<nav id="siteNav" class="site-nav" data-collapsed="true">
<a href="/index.php<?php echo $lang === 'de' ? '?lang=de' : ''; ?>"><?php echo e($navHome ?? 'Home'); ?></a>
<a href="/index.php<?php echo $lang === 'de' ? '?lang=de' : ''; ?>#latest"><?php echo e($navLatest ?? 'Latest'); ?></a>
<a href="/index.php<?php echo $lang === 'de' ? '?lang=de' : ''; ?>#basics"><?php echo e($navBasics ?? 'Basics'); ?></a>
</nav>
</header>
</div>
<script>
(() => {
const toggle = document.currentScript.previousElementSibling.querySelector('.menu-toggle');
const nav = document.getElementById('siteNav');
if (!toggle || !nav) return;
toggle.addEventListener('click', () => {
const isCollapsed = nav.getAttribute('data-collapsed') === 'true';
nav.setAttribute('data-collapsed', String(!isCollapsed));
toggle.setAttribute('aria-expanded', String(isCollapsed));
(function() {
var btn = document.getElementById('navMenuBtn');
var overlay = document.getElementById('navOverlay');
if (!btn || !overlay) return;
function openMenu() {
btn.classList.add('active');
overlay.classList.add('open');
overlay.setAttribute('aria-hidden', 'false');
btn.setAttribute('aria-expanded', 'true');
document.body.classList.add('nav-open');
}
function closeMenu() {
btn.classList.remove('active');
overlay.classList.remove('open');
overlay.setAttribute('aria-hidden', 'true');
btn.setAttribute('aria-expanded', 'false');
document.body.classList.remove('nav-open');
}
btn.addEventListener('click', function() {
if (btn.classList.contains('active')) {
closeMenu();
} else {
openMenu();
}
});
// Close on Escape key
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' && overlay.classList.contains('open')) {
closeMenu();
btn.focus();
}
});
// Close on overlay link click
overlay.querySelectorAll('a').forEach(function(link) {
link.addEventListener('click', function() {
closeMenu();
});
})();
});
})();
</script>