Archiv-Suche lief auf grossen Datenbestaenden langsam und war im Browser komplett tot: app.js startete ein eigenes Alpine, Livewire injizierte zusaetzlich seins - der DOM-Morph brach mit "Alpine.transaction is not a function" ab (keine Rubrik klickbar, Suchbegriff nicht aenderbar). - app.js buendelt jetzt Livewire + Alpine aus livewire.esm, @livewireScriptConfig im Web-Layout stoppt die Auto-Injektion - MySQL-Volltext-Index (Boolean-Mode, +wort*) statt LIKE ueber alles; LIKE-Fallback fuer SQLite-Tests und kurze Tokens - Firmen-/Rubrik-Treffer als ID-Listen statt orWhereHas (OR + EXISTS hebelte den Volltext-Index aus) Co-authored-by: Cursor <cursoragent@cursor.com>
139 lines
5.3 KiB
PHP
139 lines
5.3 KiB
PHP
<!DOCTYPE html>
|
||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||
|
||
@php
|
||
$siteName = $domainName ?? config('app.name', 'Laravel');
|
||
// Inline-@section('title', …)-Werte kommen von Blade bereits escaped an —
|
||
// einmal dekodieren, damit {{ }} unten nicht doppelt escapt („&amp;").
|
||
$pageTitle = html_entity_decode(trim($__env->yieldContent('title')), ENT_QUOTES, 'UTF-8') ?: $siteName;
|
||
$pageDescription = html_entity_decode(trim($__env->yieldContent('meta_description')), ENT_QUOTES, 'UTF-8') ?: $siteName;
|
||
// Self-Canonical als Default aus der Portal-URL + Request-Pfad (ohne Query):
|
||
// pro Portal/Domain getrennt und unabhängig von URL::forceRootUrl – verhindert Cross-Portal-Duplicate.
|
||
$portalBase = rtrim($domainUrl ?? config('app.url'), '/');
|
||
$canonicalUrl = trim($__env->yieldContent('canonical')) ?: ($portalBase.request()->getPathInfo());
|
||
$ogType = trim($__env->yieldContent('og_type')) ?: 'website';
|
||
$ogImage = trim($__env->yieldContent('og_image'));
|
||
@endphp
|
||
|
||
<meta name="description" content="{{ $pageDescription }}">
|
||
|
||
<title>{{ $pageTitle }}</title>
|
||
|
||
@php
|
||
$robotsDirective = trim($__env->yieldContent('robots'));
|
||
@endphp
|
||
|
||
{{-- Robots-Anweisung (z. B. noindex für private Vorschauen) --}}
|
||
@if ($robotsDirective !== '')
|
||
<meta name="robots" content="{{ $robotsDirective }}">
|
||
@endif
|
||
|
||
{{-- Kanonische URL (entfällt bei noindex — es gibt keine öffentliche URL) --}}
|
||
@if (! str_contains($robotsDirective, 'noindex'))
|
||
<link rel="canonical" href="{{ $canonicalUrl }}">
|
||
@endif
|
||
|
||
{{-- Sprach-Alternates (de/en) – pro Seite optional gesetzt --}}
|
||
@hasSection('hreflang')
|
||
@yield('hreflang')
|
||
@endif
|
||
|
||
{{-- OpenGraph / Social --}}
|
||
<meta property="og:type" content="{{ $ogType }}">
|
||
<meta property="og:site_name" content="{{ $siteName }}">
|
||
<meta property="og:title" content="{{ $pageTitle }}">
|
||
<meta property="og:description" content="{{ $pageDescription }}">
|
||
<meta property="og:url" content="{{ $canonicalUrl }}">
|
||
@if ($ogImage)
|
||
<meta property="og:image" content="{{ $ogImage }}">
|
||
@endif
|
||
<meta name="twitter:card" content="{{ $ogImage ? 'summary_large_image' : 'summary' }}">
|
||
<meta name="twitter:title" content="{{ $pageTitle }}">
|
||
<meta name="twitter:description" content="{{ $pageDescription }}">
|
||
|
||
<!-- Domain-spezifisches Favicon -->
|
||
<link rel="icon" href="{{ asset(\App\Helpers\ThemeHelper::getFaviconPath()) }}">
|
||
|
||
<!-- Fonts -->
|
||
@include('partials.local-fonts')
|
||
|
||
@php
|
||
$font = \App\Helpers\ThemeHelper::getFont();
|
||
$theme = config('app.theme', 'businessportal24');
|
||
@endphp
|
||
|
||
{{-- app.js bündelt Livewire + Alpine (livewire.esm) — @livewireScriptConfig
|
||
unten verhindert, dass Livewire zusätzlich sein eigenes Script injiziert
|
||
(sonst zwei Alpine-Instanzen und kaputte wire:model-Bindings). --}}
|
||
@livewireStyles
|
||
@vite([\App\Helpers\ThemeHelper::getThemeCssPath(), 'resources/js/app.js'], $domainConfig['assets_dir'] ?? 'build/web')
|
||
|
||
<!-- Sticky Header Script -->
|
||
<script>
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
const topbar = document.getElementById('topbar');
|
||
const header = document.getElementById('header');
|
||
|
||
if (!topbar || !header) return;
|
||
|
||
let topbarHeight = topbar.offsetHeight;
|
||
let isHeaderSticky = false;
|
||
|
||
function updateHeaderPosition() {
|
||
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
||
|
||
if (scrollTop >= topbarHeight && !isHeaderSticky) {
|
||
// TopBar ist nicht mehr sichtbar - Header wird sticky
|
||
header.classList.remove('header-normal');
|
||
header.classList.add('header-sticky');
|
||
isHeaderSticky = true;
|
||
} else if (scrollTop < topbarHeight && isHeaderSticky) {
|
||
// TopBar ist wieder sichtbar - Header wird normal
|
||
header.classList.remove('header-sticky');
|
||
header.classList.add('header-normal');
|
||
isHeaderSticky = false;
|
||
}
|
||
}
|
||
|
||
// Initial check
|
||
updateHeaderPosition();
|
||
|
||
// Listen for scroll events
|
||
window.addEventListener('scroll', updateHeaderPosition);
|
||
|
||
// Listen for resize events (in case topbar height changes)
|
||
window.addEventListener('resize', function() {
|
||
topbarHeight = topbar.offsetHeight;
|
||
updateHeaderPosition();
|
||
});
|
||
});
|
||
</script>
|
||
|
||
<!-- Additional Styles -->
|
||
@stack('styles')
|
||
|
||
<x-web.umami />
|
||
|
||
</head>
|
||
|
||
<body class="antialiased" style="background-color: hsl(var(--background)); color: hsl(var(--foreground));">
|
||
|
||
<!-- TopBar - statisch oben -->
|
||
|
||
@yield('content')
|
||
|
||
{{-- Schwebendes Kontakt-Widget (unten rechts, alle Web-Domains) --}}
|
||
<x-web.contact-launcher />
|
||
|
||
<!-- Additional Scripts -->
|
||
@stack('scripts')
|
||
|
||
@livewireScriptConfig
|
||
</body>
|
||
|
||
</html>
|