soames-astro-theme 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +29 -0
- package/README.md +67 -0
- package/package.json +68 -0
- package/src/components/Bio.tsx +36 -0
- package/src/components/BlogSidebar.tsx +48 -0
- package/src/components/DocsSidebar.tsx +45 -0
- package/src/components/Footer.tsx +59 -0
- package/src/components/FooterMenu.tsx +36 -0
- package/src/components/Header.tsx +57 -0
- package/src/components/HeaderMenu.tsx +77 -0
- package/src/components/HeroHeader.tsx +66 -0
- package/src/components/Logo.tsx +37 -0
- package/src/components/shortcodes/RemoveContentAreaPadding.tsx +13 -0
- package/src/components/shortcodes/Shortcodes.tsx +319 -0
- package/src/components/shortcodes/SoamesFeature.tsx +54 -0
- package/src/components/shortcodes/SoamesGalleryMenu.tsx +90 -0
- package/src/components/shortcodes/SoamesIconList.tsx +90 -0
- package/src/components/shortcodes/SoamesSoundCloud.tsx +71 -0
- package/src/components/shortcodes/SoamesTextBlock.tsx +27 -0
- package/src/components/shortcodes/SoamesTextList.tsx +27 -0
- package/src/components/shortcodes/SoamesTitle.tsx +24 -0
- package/src/components/shortcodes/SoamesTitleBar.tsx +22 -0
- package/src/components/shortcodes/SoamesTitleBarLg.tsx +56 -0
- package/src/components/shortcodes/SoamesVideo.tsx +35 -0
- package/src/components/shortcodes/getAttributes.ts +19 -0
- package/src/components/shortcodes/getContent.ts +4 -0
- package/src/integration.ts +147 -0
- package/src/layouts/Base.astro +93 -0
- package/src/lib/wp.ts +351 -0
- package/src/routes/[...uri].astro +41 -0
- package/src/routes/blog/[...page].astro +88 -0
- package/src/routes/blog/post/[...slug].astro +79 -0
- package/src/routes/docs/[...slug].astro +58 -0
- package/src/routes/docs/index.astro +66 -0
- package/src/routes/index.astro +39 -0
- package/src/scripts/soames-nav.js +72 -0
- package/src/styles/site-overrides.css +4 -0
- package/src/styles/soames/base.css +593 -0
- package/src/styles/soames/components.css +1556 -0
- package/src/styles/soames/layout.css +209 -0
- package/src/styles/soames/overrides.css +2138 -0
- package/src/styles/soames/typography.css +23 -0
- package/src/styles/theme.css +8 -0
- package/src/styles/vendor/normalize.css +343 -0
- package/src/styles/vendor/wordpress-blocks.css +3451 -0
- package/src/theme-shadow.ts +39 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// PORTED VERBATIM from soames-gatsby-theme/src/components/shortcodes/SoamesTitle.tsx
|
|
2
|
+
import React from "react";
|
|
3
|
+
|
|
4
|
+
interface SoamesTitleProps {
|
|
5
|
+
title: React.ReactNode;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const SoamesTitle: React.FC<SoamesTitleProps> = ({ title }) => {
|
|
9
|
+
return (
|
|
10
|
+
<section className="soames-section soames-title">
|
|
11
|
+
<div className="container">
|
|
12
|
+
<div className="media-container-row">
|
|
13
|
+
<div className="title col-12 col-md-8">
|
|
14
|
+
<h2 className="align-center mbr-fonts-style display-2">
|
|
15
|
+
{title}
|
|
16
|
+
</h2>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
</section>
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default SoamesTitle;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// PORTED VERBATIM from soames-gatsby-theme/src/components/shortcodes/SoamesTitleBar.tsx
|
|
2
|
+
import React from "react";
|
|
3
|
+
|
|
4
|
+
interface SoamesTitleBarProps {
|
|
5
|
+
title: React.ReactNode;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const SoamesTitleBar: React.FC<SoamesTitleBarProps> = ({ title }) => {
|
|
9
|
+
return (
|
|
10
|
+
<section className="soames-section soames-title-bar mt-5 mb-3">
|
|
11
|
+
<div className="container">
|
|
12
|
+
<div className="media-container-row">
|
|
13
|
+
<div className="title col-12 col-md-8">
|
|
14
|
+
<h2 className="align-center mbr-fonts-style display-2">{title}</h2>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
</section>
|
|
19
|
+
);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default SoamesTitleBar;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
interface SoamesTitleBarLgProps {
|
|
4
|
+
title: React.ReactNode;
|
|
5
|
+
attributes: {
|
|
6
|
+
subtitle?: string[];
|
|
7
|
+
background?: string[];
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const SoamesTitleBarLg: React.FC<SoamesTitleBarLgProps> = ({ title, attributes }) => {
|
|
12
|
+
const subtitle = attributes?.subtitle?.[0];
|
|
13
|
+
const background = attributes?.background?.[0];
|
|
14
|
+
const defaultBackground = 'https://picsum.photos/1080/720';
|
|
15
|
+
|
|
16
|
+
const css = `
|
|
17
|
+
.soames-background-title-bar-lg::after {
|
|
18
|
+
background: url(${background || defaultBackground});
|
|
19
|
+
background-position: 50% 50%;
|
|
20
|
+
background-size: cover;
|
|
21
|
+
background-attachment: fixed;
|
|
22
|
+
background-repeat: no-repeat;
|
|
23
|
+
left: 0px;
|
|
24
|
+
height: 176px;
|
|
25
|
+
overflow: hidden;
|
|
26
|
+
pointer-events: none;
|
|
27
|
+
margin-top: 0px;
|
|
28
|
+
transform: translate3d(0px, 0px, 0px);
|
|
29
|
+
}
|
|
30
|
+
`;
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<>
|
|
34
|
+
<style>{css}</style>
|
|
35
|
+
<section className="soames-header-sm soames-parallax soames-background-title-bar-lg mt-5 mb-3">
|
|
36
|
+
<div className="soames-overlay" style={{ opacity: 0.6, backgroundColor: 'rgb(46, 46, 46)' }}></div>
|
|
37
|
+
<div className="container">
|
|
38
|
+
<div className="row justify-content-md-center">
|
|
39
|
+
<div className="soames-white col-md-10">
|
|
40
|
+
<h1 className="soames-section-title align-center soames-bold mbr-fonts-style display-1">
|
|
41
|
+
{title}
|
|
42
|
+
</h1>
|
|
43
|
+
{subtitle && (
|
|
44
|
+
<h3 className="soames-section-subtitle align-center soames-light soames-white mbr-fonts-style display-5">
|
|
45
|
+
{subtitle}
|
|
46
|
+
</h3>
|
|
47
|
+
)}
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
</section>
|
|
52
|
+
</>
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export default SoamesTitleBarLg;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
interface SoamesVideoProps {
|
|
4
|
+
attributes: {
|
|
5
|
+
link: string;
|
|
6
|
+
title?: string;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const SoamesVideo: React.FC<SoamesVideoProps> = ({ attributes }) => {
|
|
11
|
+
const { link, title } = attributes;
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<section className="soames-video-container">
|
|
15
|
+
<figure className="soames-figure align-center container">
|
|
16
|
+
<div className="video-block">
|
|
17
|
+
<div className="video-wrapper">
|
|
18
|
+
<iframe
|
|
19
|
+
height="580"
|
|
20
|
+
width="360"
|
|
21
|
+
src={link}
|
|
22
|
+
title={title ?? "Embedded video"}
|
|
23
|
+
frameBorder="0"
|
|
24
|
+
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
|
25
|
+
referrerPolicy="strict-origin-when-cross-origin"
|
|
26
|
+
allowFullScreen
|
|
27
|
+
></iframe>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
</figure>
|
|
31
|
+
</section>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default SoamesVideo;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// src/utils/shortcodes/getAttributes.ts
|
|
2
|
+
|
|
3
|
+
export function getAttributes(match: RegExpMatchArray | null): Record<string, string[]> | null {
|
|
4
|
+
if (!match || !match[1]) return null;
|
|
5
|
+
|
|
6
|
+
const attributes: Record<string, string[]> = {};
|
|
7
|
+
const attrString = match[1];
|
|
8
|
+
|
|
9
|
+
const regex = /(\w+)=["']?((?:.(?!["']?\s+(?:\S+)=|\s*\/?[>"']))+.)["']?/g;
|
|
10
|
+
let attrMatch;
|
|
11
|
+
|
|
12
|
+
while ((attrMatch = regex.exec(attrString)) !== null) {
|
|
13
|
+
const key = attrMatch[1];
|
|
14
|
+
const value = attrMatch[2].replace(/[″”]+/g, '').split(',');
|
|
15
|
+
attributes[key] = value;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return attributes;
|
|
19
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import type { AstroIntegration } from 'astro';
|
|
2
|
+
// Returns react() alongside the theme integration; Astro flattens integration arrays.
|
|
3
|
+
import react from '@astrojs/react';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { themeShadow } from './theme-shadow.js';
|
|
7
|
+
|
|
8
|
+
// html-react-parser is CommonJS and pulls a CJS subtree; these must be bundled
|
|
9
|
+
// (noExternal) AND pre-bundled (optimizeDeps) so React dedupes to one copy and
|
|
10
|
+
// dev SSR doesn't choke on CJS (`exports is not defined`).
|
|
11
|
+
const PARSER_CJS_DEPS = [
|
|
12
|
+
'html-react-parser',
|
|
13
|
+
'html-dom-parser',
|
|
14
|
+
'domhandler',
|
|
15
|
+
'domelementtype',
|
|
16
|
+
'domutils',
|
|
17
|
+
'dom-serializer',
|
|
18
|
+
'htmlparser2',
|
|
19
|
+
'entities',
|
|
20
|
+
'style-to-js',
|
|
21
|
+
'style-to-object',
|
|
22
|
+
'inline-style-parser',
|
|
23
|
+
'react-property',
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
// Browser-ish UA — this WP install's Wordfence 403s some default UAs (see lib/wp.ts).
|
|
27
|
+
const WP_UA =
|
|
28
|
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36';
|
|
29
|
+
|
|
30
|
+
// Resolve the blog's base slug from the WP "Posts page" (Settings → Reading) at
|
|
31
|
+
// config time, so the blog archive route can be injected at that slug. Falls back
|
|
32
|
+
// to 'blog' if unset or WP is unreachable, so builds never hang or break on it.
|
|
33
|
+
async function fetchPostsSlug(graphqlUrl: string): Promise<string> {
|
|
34
|
+
if (!graphqlUrl) return 'blog';
|
|
35
|
+
try {
|
|
36
|
+
const ctrl = new AbortController();
|
|
37
|
+
const timer = setTimeout(() => ctrl.abort(), 5000);
|
|
38
|
+
const res = await fetch(graphqlUrl, {
|
|
39
|
+
method: 'POST',
|
|
40
|
+
headers: { 'Content-Type': 'application/json', 'User-Agent': WP_UA },
|
|
41
|
+
body: JSON.stringify({ query: '{ wpPages: pages(first: 100) { nodes { slug isPostsPage } } }' }),
|
|
42
|
+
signal: ctrl.signal,
|
|
43
|
+
});
|
|
44
|
+
clearTimeout(timer);
|
|
45
|
+
if (!res.ok) return 'blog';
|
|
46
|
+
const json = (await res.json()) as {
|
|
47
|
+
data?: { wpPages?: { nodes?: Array<{ slug: string; isPostsPage: boolean }> } };
|
|
48
|
+
};
|
|
49
|
+
const pp = (json?.data?.wpPages?.nodes ?? []).find((n) => n.isPostsPage);
|
|
50
|
+
return pp?.slug || 'blog';
|
|
51
|
+
} catch {
|
|
52
|
+
return 'blog';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface SoamesThemeOptions {
|
|
57
|
+
/** WordPress GraphQL endpoint, e.g. http://soames.orbivision.net/graphql */
|
|
58
|
+
wordpressUrl?: string;
|
|
59
|
+
/** Optional WordPress base URL (defaults to wordpressUrl minus /graphql). */
|
|
60
|
+
wordpressBaseUrl?: string;
|
|
61
|
+
/** Hostnames whose images Astro may optimize. */
|
|
62
|
+
imageDomains?: string[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// The Soames Astro theme integration — successor to the Gatsby theme.
|
|
66
|
+
// Responsibilities:
|
|
67
|
+
// 1) register @astrojs/react (React islands),
|
|
68
|
+
// 2) install the theme-shadow resolver so sites override via src/overrides/,
|
|
69
|
+
// 3) inject the page-template routes the theme provides,
|
|
70
|
+
// 4) authorize WP image domains,
|
|
71
|
+
// 5) expose WP config to the theme via env + make the package transpilable.
|
|
72
|
+
export default function soamesTheme(options: SoamesThemeOptions = {}): AstroIntegration[] {
|
|
73
|
+
const themeSrc = fileURLToPath(new URL('.', import.meta.url)); // <pkg>/src/
|
|
74
|
+
const wpUrl = options.wordpressUrl || process.env.WORDPRESS_GRAPHQL_URL || '';
|
|
75
|
+
const wpBase =
|
|
76
|
+
options.wordpressBaseUrl ||
|
|
77
|
+
process.env.WORDPRESS_BASE_URL ||
|
|
78
|
+
wpUrl.replace(/\/graphql\/?$/, '');
|
|
79
|
+
const imageHost = (() => {
|
|
80
|
+
try {
|
|
81
|
+
return wpBase ? new URL(wpBase).hostname : undefined;
|
|
82
|
+
} catch {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
})();
|
|
86
|
+
const imageDomains = options.imageDomains ?? (imageHost ? [imageHost] : []);
|
|
87
|
+
|
|
88
|
+
const theme: AstroIntegration = {
|
|
89
|
+
name: 'soames-astro-theme',
|
|
90
|
+
hooks: {
|
|
91
|
+
'astro:config:setup': async ({ config, updateConfig, injectRoute }) => {
|
|
92
|
+
const overrideDir = path.join(fileURLToPath(config.srcDir), 'overrides');
|
|
93
|
+
|
|
94
|
+
updateConfig({
|
|
95
|
+
image: { domains: imageDomains },
|
|
96
|
+
vite: {
|
|
97
|
+
plugins: [themeShadow({ themeDir: themeSrc, overrideDir })],
|
|
98
|
+
// Ship source from this package; bundle it AND the html-react-parser
|
|
99
|
+
// subtree so everything is transpiled, shares the deduped React, and
|
|
100
|
+
// the parser's CJS deps (domelementtype et al.) get proper interop
|
|
101
|
+
// under SSR (otherwise "does not provide an export named 'default'").
|
|
102
|
+
ssr: {
|
|
103
|
+
noExternal: ['soames-astro-theme', ...PARSER_CJS_DEPS],
|
|
104
|
+
// SSR dep pre-bundling — without this, dev SSR throws "exports is
|
|
105
|
+
// not defined" on the CommonJS parser modules.
|
|
106
|
+
optimizeDeps: { include: PARSER_CJS_DEPS },
|
|
107
|
+
},
|
|
108
|
+
// Client-side pre-bundle too (for the parser when used in islands).
|
|
109
|
+
optimizeDeps: { include: PARSER_CJS_DEPS },
|
|
110
|
+
// Force a single React copy — the theme ships html-react-parser, which
|
|
111
|
+
// must build elements with the SAME React the renderer uses (otherwise
|
|
112
|
+
// "Objects are not valid as a React child" under file:/linked installs).
|
|
113
|
+
resolve: { dedupe: ['react', 'react-dom'] },
|
|
114
|
+
define: {
|
|
115
|
+
'import.meta.env.WORDPRESS_GRAPHQL_URL': JSON.stringify(wpUrl),
|
|
116
|
+
'import.meta.env.WORDPRESS_BASE_URL': JSON.stringify(wpBase),
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Blog base slug from the WP "Posts page" (Settings → Reading), or 'blog'.
|
|
122
|
+
const postsSlug = await fetchPostsSlug(wpUrl);
|
|
123
|
+
|
|
124
|
+
// Theme-provided routes. A site file at the same path takes precedence
|
|
125
|
+
// over an injected route, so sites can still override pages directly.
|
|
126
|
+
injectRoute({ pattern: '/', entrypoint: 'soames-astro-theme/routes/index.astro' });
|
|
127
|
+
injectRoute({ pattern: '/[...uri]', entrypoint: 'soames-astro-theme/routes/[...uri].astro' });
|
|
128
|
+
// Blog archive lives at the Posts-page slug (default /blog/). Individual
|
|
129
|
+
// posts stay at /blog/post/<slug>/ regardless.
|
|
130
|
+
injectRoute({ pattern: `/${postsSlug}/[...page]`, entrypoint: 'soames-astro-theme/routes/blog/[...page].astro' });
|
|
131
|
+
injectRoute({ pattern: '/blog/post/[...slug]', entrypoint: 'soames-astro-theme/routes/blog/post/[...slug].astro' });
|
|
132
|
+
// Documentation (weDocs). Optional — generates 0 doc pages when weDocs
|
|
133
|
+
// isn't installed (getDocs() returns []); /docs/ then shows an empty state.
|
|
134
|
+
injectRoute({ pattern: '/docs', entrypoint: 'soames-astro-theme/routes/docs/index.astro' });
|
|
135
|
+
injectRoute({ pattern: '/docs/[...slug]', entrypoint: 'soames-astro-theme/routes/docs/[...slug].astro' });
|
|
136
|
+
|
|
137
|
+
// When the blog moved off /blog/, redirect the old base path to it.
|
|
138
|
+
if (postsSlug !== 'blog') {
|
|
139
|
+
updateConfig({ redirects: { '/blog': `/${postsSlug}` } });
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// react() first so its renderer is registered; Astro flattens this array.
|
|
146
|
+
return [react(), theme];
|
|
147
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Soames theme base layout. Header & Footer are imported through `@theme/` so a
|
|
3
|
+
// site can shadow them via src/overrides/. All WordPress data is fetched at build
|
|
4
|
+
// time here (replaces Gatsby useStaticQuery) and passed down as props.
|
|
5
|
+
|
|
6
|
+
// Global styles (cascade order matches the Gatsby setup: vendor → theme → overrides).
|
|
7
|
+
import "bootstrap/dist/css/bootstrap.min.css";
|
|
8
|
+
import "../styles/vendor/wordpress-blocks.css";
|
|
9
|
+
import "../styles/theme.css"; // @imports soames/{base,layout,typography,components,overrides}
|
|
10
|
+
// Site global overrides — empty in the theme; a site shadows this via
|
|
11
|
+
// src/overrides/styles/site-overrides.css. Imported LAST so site rules win.
|
|
12
|
+
import "@theme/styles/site-overrides.css";
|
|
13
|
+
|
|
14
|
+
import Header from "@theme/components/Header";
|
|
15
|
+
import Footer from "@theme/components/Footer";
|
|
16
|
+
import {
|
|
17
|
+
getGeneralSettings,
|
|
18
|
+
getSoamesSettings,
|
|
19
|
+
getMenuByLocation,
|
|
20
|
+
} from "../lib/wp";
|
|
21
|
+
|
|
22
|
+
interface Props {
|
|
23
|
+
pageTitle: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
isHomePage?: boolean;
|
|
26
|
+
}
|
|
27
|
+
const { pageTitle, description, isHomePage = false } = Astro.props;
|
|
28
|
+
|
|
29
|
+
const [settings, soames, headerMenu, footerMenu] = await Promise.all([
|
|
30
|
+
getGeneralSettings(),
|
|
31
|
+
getSoamesSettings(),
|
|
32
|
+
getMenuByLocation("HEADER"),
|
|
33
|
+
getMenuByLocation("FOOTER"),
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
const metaDescription = description || settings.description || "";
|
|
37
|
+
const fullTitle = `${pageTitle} | ${settings.title}`;
|
|
38
|
+
const showCompanyName = soames.showCompanyName ?? true;
|
|
39
|
+
---
|
|
40
|
+
<!doctype html>
|
|
41
|
+
<html lang="en">
|
|
42
|
+
<head>
|
|
43
|
+
<meta charset="utf-8" />
|
|
44
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
45
|
+
<title>{fullTitle}</title>
|
|
46
|
+
{metaDescription && <meta name="description" content={metaDescription} />}
|
|
47
|
+
{/* SEO/OG/Twitter — replaces the Gatsby Seo (react-helmet) component */}
|
|
48
|
+
<meta property="og:title" content={pageTitle} />
|
|
49
|
+
<meta property="og:description" content={metaDescription} />
|
|
50
|
+
<meta property="og:type" content="website" />
|
|
51
|
+
<meta name="twitter:card" content="summary" />
|
|
52
|
+
<meta name="twitter:title" content={pageTitle} />
|
|
53
|
+
<meta name="twitter:description" content={metaDescription} />
|
|
54
|
+
{soames.faviconUrl && <link rel="icon" href={soames.faviconUrl} type="image/png" />}
|
|
55
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
56
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="anonymous" />
|
|
57
|
+
<link
|
|
58
|
+
rel="stylesheet"
|
|
59
|
+
href="https://fonts.googleapis.com/css2?family=Rubik:ital,wght@0,300;0,400;0,500;0,700;0,900;1,300;1,400;1,500;1,700;1,900&display=swap"
|
|
60
|
+
/>
|
|
61
|
+
</head>
|
|
62
|
+
<body>
|
|
63
|
+
{/* `.global-wrapper` matches the Gatsby Layout structure. Crucially, the theme
|
|
64
|
+
CSS makes <main> the parallax scroll container (height:100vh; overflow-y:auto;
|
|
65
|
+
perspective:2px), so EVERYTHING that scrolls — content AND footer — must live
|
|
66
|
+
INSIDE <main>, exactly as the Gatsby layout had it. */}
|
|
67
|
+
<div class="global-wrapper" data-is-root-path={isHomePage}>
|
|
68
|
+
<Header
|
|
69
|
+
title={settings.title}
|
|
70
|
+
logoUrl={soames.logoUrl}
|
|
71
|
+
logoAlt={soames.logoAlt}
|
|
72
|
+
companyName={soames.companyName}
|
|
73
|
+
showCompanyName={showCompanyName}
|
|
74
|
+
menuItems={headerMenu}
|
|
75
|
+
/>
|
|
76
|
+
<main>
|
|
77
|
+
<slot />
|
|
78
|
+
<Footer
|
|
79
|
+
title={settings.title}
|
|
80
|
+
contactBlurb={soames.contactBlurb}
|
|
81
|
+
companyName={soames.companyName}
|
|
82
|
+
menuItems={footerMenu}
|
|
83
|
+
/>
|
|
84
|
+
</main>
|
|
85
|
+
</div>
|
|
86
|
+
{/* Bootstrap 5 bundle (Popper included, no jQuery) handles the mobile
|
|
87
|
+
hamburger collapse; soames-nav.js covers submenu dropdowns + scroll state. */}
|
|
88
|
+
<script>
|
|
89
|
+
import "bootstrap/dist/js/bootstrap.bundle.min.js";
|
|
90
|
+
import "../scripts/soames-nav.js";
|
|
91
|
+
</script>
|
|
92
|
+
</body>
|
|
93
|
+
</html>
|