Repository navigation
Expand file tree
/
Copy pathBaseLayout.astro
More file actions
239 lines (227 loc) · 8.55 KB
/
Copy pathBaseLayout.astro
File metadata and controls
239 lines (227 loc) · 8.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
---
import "@fontsource-variable/space-grotesk";
import "@fontsource-variable/inter";
import "@fontsource-variable/jetbrains-mono";
import "@/styles/global.css";
import Seo from "@/components/Seo.astro";
import Analytics from "@/components/Analytics.astro";
import ConsentBanner from "@/components/ConsentBanner.astro";
import JsonLd from "@/components/JsonLd.astro";
import SiteBackground from "@/components/SiteBackground.astro";
import Header from "@/components/Header.astro";
import Footer from "@/components/Footer.astro";
import ContactModal from "@/components/ContactModal.astro";
import SearchPalette from "@/components/SearchPalette.astro";
import EventBanner from "@/components/EventBanner.astro";
import PageLoader from "@/components/PageLoader.astro";
import { ClientRouter } from "astro:transitions";
import { tryEnv } from "@/lib/runtime";
import { getSettings, featuredEvent } from "@/lib/content";
import { resolveSite, isApexHost, BRAND, SOCIALS } from "@/lib/site";
import type { EventRow, Settings } from "@/lib/types";
interface Props {
title?: string;
description?: string;
canonicalPath?: string;
image?: string;
type?: "website" | "article" | "profile";
noindex?: boolean;
hideBanner?: boolean;
bare?: boolean; // no header/footer (e.g. links page)
publishedTime?: string;
modifiedTime?: string;
jsonLd?: Record<string, unknown> | Record<string, unknown>[];
/** Breadcrumb trail (excluding the implicit "Ana Sayfa" root, which is prepended). */
breadcrumbs?: { name: string; item: string }[];
}
const {
title,
description,
canonicalPath,
image,
type = "website",
noindex = false,
hideBanner = false,
bare = false,
publishedTime,
modifiedTime,
jsonLd,
breadcrumbs,
} = Astro.props;
let settings: Settings = {};
let banner: EventRow | null = null;
const env = tryEnv(Astro.locals);
if (env) {
try { settings = await getSettings(env); } catch { /* pre-seed: defaults */ }
if (!hideBanner && !bare) {
try { banner = await featuredEvent(env); } catch { /* ignore */ }
}
}
const site = resolveSite(settings);
const desc = description ?? site.description;
// Analytics runs ONLY on the canonical apex (devmultigroup.com). On the
// workers.dev staging URL, CF previews, www, and localhost the whole tracking
// layer stays inert — so it activates only once the apex serves this Worker.
// Mirrors the X-Robots staging guard in src/middleware.ts (BRAND.domain).
const analyticsActive = site.analyticsEnabled && isApexHost(Astro.url.host);
const orgLd = {
"@context": "https://schema.org",
"@type": "Organization",
"@id": `${BRAND.url}/#organization`,
name: BRAND.name,
alternateName: [BRAND.short, "DevMultiGroup"],
url: BRAND.url,
logo: `${BRAND.url}/dmg-logo.webp`,
image: `${BRAND.url}/og-default.png`,
description: site.description,
slogan: BRAND.tagline,
email: BRAND.email,
foundingDate: "2020",
knowsLanguage: ["tr", "en"],
areaServed: { "@type": "Country", name: "Türkiye" },
address: { "@type": "PostalAddress", addressCountry: "TR", addressLocality: "İstanbul" },
contactPoint: [
{ "@type": "ContactPoint", contactType: "customer support", email: "support@devmultigroup.com", availableLanguage: ["Turkish", "English"] },
{ "@type": "ContactPoint", contactType: "partnerships", email: "partner@devmultigroup.com", availableLanguage: ["Turkish", "English"] },
{ "@type": "ContactPoint", contactType: "sponsorship", email: "sponsor@devmultigroup.com", availableLanguage: ["Turkish", "English"] },
],
subOrganization: {
"@type": "EducationalOrganization",
name: "MultiAcademy",
url: "https://academy.devmultigroup.com",
description: "Developer MultiGroup'un öğrenme kolu. Ücretsiz, yoğun yazılım bootcamp'leri.",
},
sameAs: [
SOCIALS.multigroup.instagram,
SOCIALS.multigroup.twitter,
SOCIALS.multigroup.linkedin,
SOCIALS.multigroup.youtube,
SOCIALS.multigroup.github,
SOCIALS.multigroup.kommunity,
],
};
const breadcrumbLd =
breadcrumbs && breadcrumbs.length
? {
"@context": "https://schema.org",
"@type": "BreadcrumbList",
itemListElement: [
{ name: "Ana Sayfa", item: BRAND.url },
...breadcrumbs,
].map((c, i) => ({
"@type": "ListItem",
position: i + 1,
name: c.name,
item: c.item,
})),
}
: null;
const extra = jsonLd ? (Array.isArray(jsonLd) ? jsonLd : [jsonLd]) : [];
const structured = [orgLd, ...extra, ...(breadcrumbLd ? [breadcrumbLd] : [])];
---
<!doctype html>
<html lang="tr">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#0d0d0e" />
<meta name="generator" content={Astro.generator} />
<link rel="icon" href="/favicon.ico" sizes="any" />
<link rel="icon" href="/favicon.png" type="image/png" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" />
<link rel="alternate" type="application/rss+xml" title={`${BRAND.short} Blog`} href="/rss.xml" />
<Seo
title={title}
description={desc}
canonicalPath={canonicalPath}
image={image}
type={type}
noindex={noindex}
publishedTime={publishedTime}
modifiedTime={modifiedTime}
gscVerification={site.gscVerification}
/>
<JsonLd data={structured} />
<Analytics
measurementId={site.gaMeasurementId}
posthogKey={site.posthogKey}
posthogHost={site.posthogHost}
sentryDsn={site.sentryDsn}
environment={import.meta.env.DEV ? "development" : "production"}
enabled={analyticsActive}
/>
<ClientRouter />
</head>
<body>
<PageLoader />
<SiteBackground />
{site.bannerEnabled && banner && !hideBanner && !bare && <EventBanner event={banner} />}
{!bare && <Header variant={Astro.url.pathname.startsWith("/academy") ? "academy" : Astro.url.pathname.startsWith("/store") ? "store" : "multigroup"} />}
<main id="main">
<slot />
</main>
{!bare && <Footer />}
{!bare && <ContactModal />}
{!bare && <SearchPalette />}
{!bare && <ConsentBanner />}
{/* `html { scroll-behavior: smooth }` is wanted for the in-page anchors
(#gecmis, #logolar, #is-birlikleri…) but it also catches ClientRouter's
scroll reset after a swap: instead of jumping to the top, the page
ANIMATES there while the new content is already painting. That is the
lurch between pages. Suspend it for the duration of the swap only.
Set twice on purpose — the swap replaces <html>'s attributes with the
incoming document's, which wipes an inline style set before it. */}
<script is:inline>
(() => {
if (window.__dmgScrollFix) return;
window.__dmgScrollFix = true;
const root = document.documentElement;
const hold = () => { root.style.scrollBehavior = "auto"; };
const release = () => { root.style.scrollBehavior = ""; };
document.addEventListener("astro:before-swap", hold);
document.addEventListener("astro:after-swap", hold);
document.addEventListener("astro:page-load", release);
})();
</script>
<script is:inline>
(() => {
const clearSkeleton = (img) => {
const sk = img.closest(".skeleton") || img.parentElement;
if (sk) sk.classList.remove("skeleton");
};
const fail = (img) => {
img.style.display = "none";
clearSkeleton(img);
};
// reveal only once fully decoded, so nothing paints in progressively
const reveal = (img) => {
const show = () => {
img.classList.add("is-loaded");
clearSkeleton(img);
};
if (img.decode) img.decode().then(show).catch(show);
else show();
};
const initImgReveal = () => {
document.querySelectorAll("img.img-reveal").forEach((img) => {
if (img.dataset.revealBound) return;
img.dataset.revealBound = "1";
// already cached (e.g. bfcache or view-transition return)
if (img.complete) {
img.naturalWidth > 0 ? reveal(img) : fail(img);
return;
}
img.addEventListener("load", () => reveal(img), { once: true });
img.addEventListener("error", () => fail(img), { once: true });
});
};
if (!window.__dmgImgReveal) {
window.__dmgImgReveal = true;
document.addEventListener("astro:page-load", initImgReveal);
}
initImgReveal();
})();
</script>
</body>
</html>