@php $__locale = app()->getLocale(); $__locales = config('locales.supported', ['en' => ['name' => 'English', 'flag' => '🇬🇧', 'rtl' => false]]); $__isRtl = (bool) ($__locales[$__locale]['rtl'] ?? false); // Operator-editable SEO meta + Open Graph / Twitter card image. // // Set at /super-admin/landing-page-editor → SEO tab (LandingContent.seo_*). // Every field is optional: when empty the layout keeps the stock translator // string / bundled brand image it rendered before, so a fresh OR unmigrated // install is unchanged. (The previous code read GeneralSettings::og_image_url, // a property that never existed on that class, so the OG image was never // actually operator-settable.) try/catch keeps this green if the settings // table isn't migrated yet. try { $__lcSeo = app(\App\Settings\LandingContent::class); $seoTitle = trim((string) ($__lcSeo->seo_meta_title ?? '')); $seoDescription = trim((string) ($__lcSeo->seo_meta_description ?? '')); $seoKeywords = trim((string) ($__lcSeo->seo_meta_keywords ?? '')); $seoOgImage = trim((string) ($__lcSeo->seo_og_image_url ?? '')); } catch (\Throwable) { $seoTitle = $seoDescription = $seoKeywords = $seoOgImage = ''; } $ogImage = $seoOgImage !== '' ? $seoOgImage : url('/leadhub-brand.svg'); // SA-global branding overrides for the public marketing site. // BrandingSettings.logo_url / favicon_url are set at // /super-admin/branding. When unset (or the settings table isn't // migrated yet) we fall back to the bundled /favicon.svg so a fresh // install still renders a mark. Customer report: "logo dont change // on the whole site" — the public pages used to hardcode favicon.svg // and never read the operator's uploaded logo. try { $__brand = app(\App\Settings\BrandingSettings::class); $brandLogo = \App\Support\PublicMedia::url($__brand->logo_url) ?? '/favicon.svg'; $brandFavicon = \App\Support\PublicMedia::url($__brand->favicon_url) ?? '/favicon.svg'; } catch (\Throwable) { $brandLogo = '/favicon.svg'; $brandFavicon = '/favicon.svg'; } @endphp @yield('title', $seoTitle !== '' ? $seoTitle : $appName) @if($seoKeywords !== '') @endif {{-- No explicit type= so an operator-uploaded PNG / ICO favicon is sniffed correctly (the bundled default is still an SVG). --}} {{-- Follow an operator-uploaded favicon; fall back to the bundled PNG when none is set ($brandFavicon defaults to the bundled /favicon.svg). --}} {{-- Inter font self-hosted under public/vendor/ — no Google Fonts CDN load. Loaded as a at the top of so the font-display: swap in @font-face fires before the page paints. --}} {{-- Open Graph --}} {{-- Twitter Card --}} {{-- JSON-LD schema. The array is built inside a PHP block first so the JSON-LD keys that start with the at sign do not collide with Blade directives during compile. The encoded result is then echoed raw into the script tag. --}} @php $__jsonLd = [ 'CONTEXT_KEY' => 'https://schema.org', 'TYPE_KEY' => 'SoftwareApplication', 'applicationCategory' => 'BusinessApplication', 'operatingSystem' => 'Web', 'description' => __('marketing.jsonld_description'), 'offers' => [ 'TYPE_KEY' => 'Offer', 'price' => '0', 'priceCurrency' => 'USD', 'description' => __('marketing.jsonld_offer_description'), ], ]; // Swap placeholder keys back to the @ form via a recursive walk // because Blade's directive matcher treats '@context' / '@type' // as directive calls when they appear inline in the template. $__jsonLdSwap = static function (array $arr) use (&$__jsonLdSwap): array { $out = []; foreach ($arr as $k => $v) { $k = $k === 'CONTEXT_KEY' ? '@context' : ($k === 'TYPE_KEY' ? '@type' : $k); $out[$k] = is_array($v) ? $__jsonLdSwap($v) : $v; } return $out; }; @endphp {{-- Defense-in-depth: JSON_HEX_TAG escapes `<` and `>` as `<` / `>` so a translator-edited `__('marketing.jsonld_description')` string containing `` cannot close this @stack('head') @php // Landing content drives nav visibility toggles; null = show. $lc = app(\App\Settings\LandingContent::class); // Custom static pages (About, Contact, etc.) for the marketing nav. // Fresh query per request instead of Cache::remember — a stale // cache entry with a wrong-shape payload was causing runtime // "Attempt to read property 'slug' on string" errors when the // iteration variable deserialised as a plain string. The one // extra SELECT per landing-page view is cheap; the bug isn't. $navStaticPages = \App\Models\StaticPage::forNav() ->get(['id', 'slug', 'title', 'translations']); @endphp @yield('content') @php // Fresh query — see the comment above the nav version for why // we abandoned Cache::remember here. $footerStaticPages = \App\Models\StaticPage::forFooter() ->get(['id', 'slug', 'title', 'translations']); // Show-by-default semantics: null = show, false = hide. $footerShowNavLinks = $lc->footer_show_nav_links ?? true; $footerShowBrand = $lc->footer_show_brand_column ?? true; $footerShowStatic = $lc->footer_show_static_pages ?? true; $footerShowSocial = $lc->footer_show_social ?? true; // Social URLs — null = default "#" placeholder so icons render // out of the box. Empty string after trim = operator chose to // hide that one icon. $fX = $lc->footer_social_x_url; $fX = $fX === null ? '#' : trim((string) $fX); $fGh = $lc->footer_social_github_url; $fGh = $fGh === null ? '#' : trim((string) $fGh); $fLi = $lc->footer_social_linkedin_url; $fLi = $fLi === null ? '#' : trim((string) $fLi); // Facebook / Instagram / YouTube are opt-in: default to '' (hidden) when // unset so a fresh install doesn't show dead '#' links for them. $fFb = $lc->footer_social_facebook_url; $fFb = $fFb === null ? '' : trim((string) $fFb); $fIg = $lc->footer_social_instagram_url; $fIg = $fIg === null ? '' : trim((string) $fIg); $fYt = $lc->footer_social_youtube_url; $fYt = $fYt === null ? '' : trim((string) $fYt); $footerHasSocial = $footerShowSocial && ($fX !== '' || $fGh !== '' || $fLi !== '' || $fFb !== '' || $fIg !== '' || $fYt !== ''); $footerCopyright = $lc->footer_copyright ?: __('marketing.footer_default_copyright', ['year' => date('Y'), 'app' => e($appName)]); $footerTagline = $lc->t('footer_tagline') ?: __('marketing.footer_default_tagline'); @endphp {{-- Nav elevation on scroll + mobile hamburger — shared across every public page. --}} @include('marketing.partials.cookie-consent')