<!DOCTYPE html>
{{-- `data-chrome` keys the global heading font-family rule in app.css
     (`[data-chrome="marketing"] :where(h1,h2,h3) { font-family: var(--font-display) }`)
     so admin / editor surfaces stay Inter even when admin picks Lora as the
     display font, while marketing pages still render headlines in the picked
     display face. Inertia's `$page['component']` is the source of truth —
     same classifier mirrored client-side via `useForceLightTheme` /
     `useThemeFromPreference` for Inertia navigations after hydrate. --}}
@php
    // `data-skin` selects the marketing design system (default | geometry).
    // It rides the active theme's `skin` knob and follows the SAME source as
    // the CSS-var injection below — Settings::activeTheme(), which the
    // PreviewTheme middleware swaps to the staged draft under
    // `?theme_preview=1`, so the Appearance live-preview iframe reflects a
    // drafted skin. Defensive (install wizard must not 500) + slug-clamped so
    // only [a-z0-9-] reaches the attribute; anything weird -> default. CSS
    // scoping (html[data-skin][data-chrome='marketing']) does the app
    // isolation — the attribute is harmless on app/editor/admin surfaces.
    try {
        $skinKnob = (string) (\App\Services\Settings::activeTheme()['knobs']['skin'] ?? 'default');
        $dataSkin = preg_replace('/[^a-z0-9-]/', '', strtolower($skinKnob));
        if ($dataSkin === null || $dataSkin === '') {
            $dataSkin = 'default';
        }
    } catch (\Throwable) {
        $dataSkin = 'default';
    }
@endphp
<html
    lang="{{ str_replace('_', '-', app()->getLocale()) }}"
    dir="ltr"
    data-chrome="{{ \App\Support\ChromeMode::forComponent($page['component'] ?? null) }}"
    data-skin="{{ $dataSkin }}"
>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="csrf-token" content="{{ csrf_token() }}">

    {{-- Server-side SEO for public marketing pages. Client-rendered Inertia
         (no SSR — the no-Node Hostinger constraint) leaves a blank body +
         no meta for non-JS crawlers/scrapers, so we resolve + emit it here.
         Tags carry `data-server-seo`; app.tsx strips them before mount so
         Inertia's <Head> (PageSeo) owns meta for SPA nav. Null for non-
         marketing surfaces (admin / editor / auth) — emits nothing. --}}
    @php
        $serverSeo = \App\Support\MarketingSeoServer::forPage($page ?? null, request());
    @endphp

    @php
        try {
            $activeTheme = \App\Services\Settings::activeTheme();
            $derived = $activeTheme['derived'] ?? null;
            $themeKnobs = $activeTheme['knobs'] ?? [];
            // Build the union of weights from the active theme's typography knobs
            // so an admin who sets weight_display=900 actually loads the 900 woff2.
            $themeWeights = array_values(array_unique([
                (int) ($themeKnobs['weight_body'] ?? 400),
                (int) ($themeKnobs['weight_subheading'] ?? 600),
                (int) ($themeKnobs['weight_heading'] ?? 700),
                (int) ($themeKnobs['weight_display'] ?? 800),
            ]));
            // Display + subheading fonts also load italic axis so `<em>`
            // accent text in marketing headlines ("a *simple* screenshot
            // maker") renders in real italic glyphs (Lora Italic, Playfair
            // Italic, etc.) rather than browser-faked obliquing. font_sans
            // skips italic — body copy rarely uses <em> on purpose and the
            // 4 extra woff2 files aren't worth it for body bandwidth.
            $googleFontsUrl = \App\Support\GoogleFontUrl::build(
                [
                    $themeKnobs['font_sans'] ?? null,
                    $themeKnobs['font_display'] ?? null,
                    $themeKnobs['font_subheading'] ?? null,
                ],
                $themeWeights,
                [
                    $themeKnobs['font_display'] ?? null,
                    $themeKnobs['font_subheading'] ?? null,
                ],
            );
        } catch (\Throwable) {
            $derived = null;
            $googleFontsUrl = null;
        }
    @endphp
    @if ($googleFontsUrl)
        <link rel="preconnect" href="https://fonts.googleapis.com">
        <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
        <link rel="stylesheet" href="{{ $googleFontsUrl }}">
    @endif

    @php
        $themeAssets = [];
        try {
            $themeRow = \App\Models\ThemeVersion::query()->active()->first(['assets']);
            $themeAssets = is_array($themeRow?->assets) ? $themeRow->assets : [];
        } catch (\Throwable) {
            $themeAssets = [];
        }
        $resolvePublicUrl = static function (?string $path): ?string {
            return is_string($path) && $path !== ''
                ? \Illuminate\Support\Facades\Storage::disk('public')->url($path)
                : null;
        };
        $faviconUrl = $resolvePublicUrl($themeAssets['favicon'] ?? null);
        // theme's og_image still flows into PageSeo's fallback chain via
        // marketing.seo.themeOgImageUrl (server-resolved in seoPayload()).
        // Blade no longer emits og:image directly — PageSeo on marketing
        // pages handles the full chain with proper per-page overrides.
        try {
            $siteName = (string) (\App\Services\Settings::get('site.name', config('app.name')) ?? config('app.name'));
        } catch (\Throwable) {
            $siteName = (string) config('app.name');
        }
        $modePref = (string) ($themeKnobs['mode_pref'] ?? 'system');
    @endphp
    @if ($faviconUrl)
        <link rel="icon" href="{{ $faviconUrl }}">
    @endif
    {{-- Open Graph site name. Per-page og:image / twitter:card / etc come
         from the React `<PageSeo>` component on marketing pages, which runs
         the full fallback chain (page > site SEO > theme og_image > logo).
         Blade keeps just `og:site_name` here since it doesn't change per
         page and is harmless to emit on admin/editor surfaces too. --}}
    <meta property="og:site_name" content="{{ $siteName }}">
    {{-- Surface the admin's mode_pref knob to theme-init.js as the fallback
         when the user hasn't explicitly toggled. localStorage still wins so
         a user's manual choice persists across visits. --}}
    <script nonce="{{ \Illuminate\Support\Facades\Vite::cspNonce() }}">
        window.__themeModePref = @json($modePref);
    </script>

    @php
        // Admin-editable site name (set at /admin/settings → branding).
        // Falls back to APP_NAME env when the setting hasn't been written
        // yet (fresh install, install-wizard time). Wrapped in try/catch
        // for the install-wizard boot path where the settings table
        // doesn't exist yet.
        try {
            $siteName = (string) \App\Services\Settings::get('site.name', config('app.name', 'AppShot AI'));
        } catch (\Throwable) {
            $siteName = (string) config('app.name', 'AppShot AI');
        }
    @endphp
    <title inertia>{{ $serverSeo['title'] ?? $siteName }}</title>
    {{-- Surface the admin-editable site name to the client at boot so
         `app.tsx`'s Inertia title callback (which runs outside React and
         can't read shared props via hooks) appends the right brand suffix
         to every page title. --}}
    <meta name="app-name" content="{{ $siteName }}">
    <meta name="app-version" content="{{ trim((string) @file_get_contents(base_path('VERSION'))) ?: 'dev' }}">

    <script src="{{ asset('theme-init.js') }}"></script>

    @viteReactRefresh
    @vite(['resources/css/app.css', 'resources/js/app.tsx'])

    {{-- Dynamic theme block MUST come after @vite so it overrides any
         static `:root` declarations in the compiled CSS file. Always
         rendered (even on bare /install pages) so `--font-sans` etc.
         are guaranteed to resolve regardless of cascade-layer quirks. --}}
    <style nonce="{{ \Illuminate\Support\Facades\Vite::cspNonce() }}">
        :root {
            @if ($derived && !empty($derived['css_vars_light']))
                @foreach ($derived['css_vars_light'] as $name => $value)
                    {!! $name !!}: {!! $value !!};
                @endforeach
            @else
                --font-sans: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;
                --font-display: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;
                --font-mono: 'JetBrains Mono', ui-monospace, 'SFMono-Regular', Menlo, monospace;
            @endif
        }
        @if ($derived && !empty($derived['css_vars_dark']))
            [data-theme='dark'] {
                @foreach ($derived['css_vars_dark'] as $name => $value)
                    {!! $name !!}: {!! $value !!};
                @endforeach
            }
        @endif
    </style>

    @inertiaHead

    {{-- Server-resolved SEO meta + JSON-LD for non-JS crawlers/scrapers.
         See \App\Support\MarketingSeoServer. Stripped client-side by
         app.tsx so PageSeo.tsx owns these for SPA navigation. --}}
    @if ($serverSeo)
        @if ($serverSeo['description'] !== '')
            <meta name="description" content="{{ $serverSeo['description'] }}" data-server-seo>
        @endif
        @if ($serverSeo['keywords'] !== '')
            <meta name="keywords" content="{{ $serverSeo['keywords'] }}" data-server-seo>
        @endif
        @if ($serverSeo['noindex'])
            <meta name="robots" content="noindex,nofollow" data-server-seo>
        @endif
        <link rel="canonical" href="{{ $serverSeo['canonical'] }}" data-server-seo>
        <meta property="og:type" content="website" data-server-seo>
        <meta property="og:title" content="{{ $serverSeo['title'] }}" data-server-seo>
        @if ($serverSeo['description'] !== '')
            <meta property="og:description" content="{{ $serverSeo['description'] }}" data-server-seo>
        @endif
        <meta property="og:url" content="{{ $serverSeo['ogUrl'] }}" data-server-seo>
        @if ($serverSeo['ogImage'])
            <meta property="og:image" content="{{ $serverSeo['ogImage'] }}" data-server-seo>
        @endif
        <meta name="twitter:card" content="{{ $serverSeo['ogImage'] ? 'summary_large_image' : 'summary' }}" data-server-seo>
        <meta name="twitter:title" content="{{ $serverSeo['title'] }}" data-server-seo>
        @if ($serverSeo['description'] !== '')
            <meta name="twitter:description" content="{{ $serverSeo['description'] }}" data-server-seo>
        @endif
        @if ($serverSeo['ogImage'])
            <meta name="twitter:image" content="{{ $serverSeo['ogImage'] }}" data-server-seo>
        @endif
        @if ($serverSeo['twitterHandle'] !== '')
            <meta name="twitter:site" content="{{ $serverSeo['twitterHandle'] }}" data-server-seo>
        @endif
        @foreach ($serverSeo['jsonLd'] as $ld)
            <script type="application/ld+json" data-server-seo nonce="{{ \Illuminate\Support\Facades\Vite::cspNonce() }}">{!! $ld !!}</script>
        @endforeach
    @endif

    {{-- Admin-pasted analytics scripts (head slot). Only emit when scripts
         are configured AND the current request is on the public marketing
         surface — admin / editor / auth pages stay script-free regardless.
         The matching permissive CSP profile is emitted by SecurityHeaders
         using the same gate. Raw HTML is admin-trusted (gated by
         /admin/marketing/scripts route + admin role); no nonce injection
         because permissive CSP uses 'unsafe-inline' there. --}}
    @php
        $msScripts = \App\Support\MarketingScripts::payload();
        $msEmit = $msScripts['enabled'] && \App\Support\MarketingScripts::isPublicMarketingRequest(request());
        // Cookie-consent gate: when the banner is enabled, the analytics
        // scripts (non-essential) only emit once the visitor accepted. The
        // `cookie_consent` cookie is JS-set + EncryptCookies-excepted, so it
        // arrives in plaintext here. Banner off → emit as before (opt-in).
        $ccEnabled = false;
        try {
            $ccConfig = \App\Services\Settings::get('marketing.cookie_consent');
            $ccEnabled = is_array($ccConfig) && ! empty($ccConfig['enabled']);
        } catch (\Throwable) {
            // Settings unavailable (e.g. pre-migration boot) — don't gate.
        }
        if ($msEmit && $ccEnabled && request()->cookie('cookie_consent') !== 'accepted') {
            $msEmit = false;
        }
    @endphp
    {{-- Sentinel marking this document as carrying admin-pasted analytics
         scripts. Inertia SPA navigations don't reload <head>, so those scripts
         (and any analytics runtime they boot) would keep running after a
         client-side visit into app/admin chrome. app.tsx watches this meta and
         forces a full reload when a navigation lands on an app-chrome page,
         dropping them. Emitted whenever scripts are configured for this
         request, even if cookie-consent is currently deferring the injection. --}}
    @if ($msScripts['enabled'] && \App\Support\MarketingScripts::isPublicMarketingRequest(request()))
        <meta name="marketing-scripts-active" content="1">
    @endif
    @if ($msEmit && trim($msScripts['head']) !== '')
        {!! $msScripts['head'] !!}
    @endif
</head>
<body class="font-sans antialiased">
    @inertia

    {{-- Crawlable body for non-JS bots/scrapers/AI fetchers. Hidden via the
         external `.seo-ssr-body` class (in app.css) rather than <noscript>:
         HTML→text converters strip <noscript>, but read a normal <div>. Real
         browsers load app.css → it's display:none → no flash of unstyled
         content; app.tsx then removes it on mount. Agents that don't apply
         external CSS still read the text. See \App\Support\MarketingSeoServer. --}}
    @if ($serverSeo && $serverSeo['body'] !== '')
        <div class="seo-ssr-body" aria-hidden="true">{!! $serverSeo['body'] !!}</div>
    @endif

    @if ($msEmit && trim($msScripts['body']) !== '')
        {!! $msScripts['body'] !!}
    @endif
</body>
</html>
