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.
Files changed (46) hide show
  1. package/LICENSE +29 -0
  2. package/README.md +67 -0
  3. package/package.json +68 -0
  4. package/src/components/Bio.tsx +36 -0
  5. package/src/components/BlogSidebar.tsx +48 -0
  6. package/src/components/DocsSidebar.tsx +45 -0
  7. package/src/components/Footer.tsx +59 -0
  8. package/src/components/FooterMenu.tsx +36 -0
  9. package/src/components/Header.tsx +57 -0
  10. package/src/components/HeaderMenu.tsx +77 -0
  11. package/src/components/HeroHeader.tsx +66 -0
  12. package/src/components/Logo.tsx +37 -0
  13. package/src/components/shortcodes/RemoveContentAreaPadding.tsx +13 -0
  14. package/src/components/shortcodes/Shortcodes.tsx +319 -0
  15. package/src/components/shortcodes/SoamesFeature.tsx +54 -0
  16. package/src/components/shortcodes/SoamesGalleryMenu.tsx +90 -0
  17. package/src/components/shortcodes/SoamesIconList.tsx +90 -0
  18. package/src/components/shortcodes/SoamesSoundCloud.tsx +71 -0
  19. package/src/components/shortcodes/SoamesTextBlock.tsx +27 -0
  20. package/src/components/shortcodes/SoamesTextList.tsx +27 -0
  21. package/src/components/shortcodes/SoamesTitle.tsx +24 -0
  22. package/src/components/shortcodes/SoamesTitleBar.tsx +22 -0
  23. package/src/components/shortcodes/SoamesTitleBarLg.tsx +56 -0
  24. package/src/components/shortcodes/SoamesVideo.tsx +35 -0
  25. package/src/components/shortcodes/getAttributes.ts +19 -0
  26. package/src/components/shortcodes/getContent.ts +4 -0
  27. package/src/integration.ts +147 -0
  28. package/src/layouts/Base.astro +93 -0
  29. package/src/lib/wp.ts +351 -0
  30. package/src/routes/[...uri].astro +41 -0
  31. package/src/routes/blog/[...page].astro +88 -0
  32. package/src/routes/blog/post/[...slug].astro +79 -0
  33. package/src/routes/docs/[...slug].astro +58 -0
  34. package/src/routes/docs/index.astro +66 -0
  35. package/src/routes/index.astro +39 -0
  36. package/src/scripts/soames-nav.js +72 -0
  37. package/src/styles/site-overrides.css +4 -0
  38. package/src/styles/soames/base.css +593 -0
  39. package/src/styles/soames/components.css +1556 -0
  40. package/src/styles/soames/layout.css +209 -0
  41. package/src/styles/soames/overrides.css +2138 -0
  42. package/src/styles/soames/typography.css +23 -0
  43. package/src/styles/theme.css +8 -0
  44. package/src/styles/vendor/normalize.css +343 -0
  45. package/src/styles/vendor/wordpress-blocks.css +3451 -0
  46. package/src/theme-shadow.ts +39 -0
@@ -0,0 +1,319 @@
1
+ import React, { ReactNode } from "react";
2
+ import parse, { domToReact, DOMNode, HTMLReactParserOptions, Element as DomElement } from "html-react-parser";
3
+
4
+ import { getAttributes } from "./getAttributes";
5
+ import { getContent } from "./getContent";
6
+
7
+ import RemoveContentAreaPadding from "./RemoveContentAreaPadding";
8
+ import SoamesTitle from "./SoamesTitle";
9
+ import SoamesTitleBar from "./SoamesTitleBar";
10
+ import SoamesTitleBarLg from "./SoamesTitleBarLg";
11
+ import SoamesTextBlock from "./SoamesTextBlock";
12
+ import SoamesIconList from "./SoamesIconList";
13
+ import SoamesFeature from "./SoamesFeature";
14
+ import SoamesGalleryMenu from "./SoamesGalleryMenu";
15
+ import SoamesVideo from "./SoamesVideo";
16
+ import SoamesTextList from "./SoamesTextList";
17
+ import SoamesSoundCloud from "./SoamesSoundCloud";
18
+
19
+ interface ShortcodesProps {
20
+ // Astro passes slotted children as rendered nodes, not a raw string, so the
21
+ // WP HTML comes in as an explicit `html` prop (only change for Astro).
22
+ html: string;
23
+ // Prose mode (docs/blog bodies): render Gutenberg headings/paragraphs as plain,
24
+ // left-aligned HTML instead of the centered, padded marketing transform.
25
+ prose?: boolean;
26
+ }
27
+
28
+ // Set per-render in <Shortcodes>. Safe as a module-level flag because parse()
29
+ // runs synchronously (no async boundary, single-threaded SSR/render), so one
30
+ // parse completes before another begins.
31
+ let proseMode = false;
32
+
33
+ type Attributes = {
34
+ [key: string]: string[];
35
+ };
36
+
37
+ const handleShortcodes: HTMLReactParserOptions["replace"] = (node) => {
38
+ if (node.type === "tag") {
39
+ const classes = ((node as DomElement).attribs?.class ?? "").split(" ");
40
+ const children = ((node as DomElement).children || []) as DOMNode[];
41
+ const opts = { replace: handleShortcodes };
42
+
43
+ // --- Gutenberg built-in block mappings ---
44
+
45
+ if (classes.includes("wp-block-heading")) {
46
+ if (proseMode) return undefined; // keep the plain <h2>/<h3>, left-aligned
47
+ return <SoamesTitle title={domToReact(children, opts)} />;
48
+ }
49
+
50
+ // Gutenberg paragraph blocks render as plain <p> tags (no wp-block-paragraph class in server HTML).
51
+ // Skip paragraphs whose first child text starts with "[" — those are classic editor shortcodes.
52
+ if ((node as DomElement).name === "p") {
53
+ const firstChild = children[0] as any;
54
+ const isShortcode = firstChild?.type === "text" && firstChild?.data?.trim().startsWith("[");
55
+ if (!isShortcode) {
56
+ if (proseMode) return undefined; // keep the plain <p>, left-aligned
57
+ return (
58
+ <section className="soames-section article soames-article">
59
+ <div className="container col-md-10">
60
+ <div className="inner-container" style={{ width: "100%" }}>
61
+ <div className="section-text align-center mbr-fonts-style display-7 pb-2">
62
+ <p className="block-text mbr-fonts-style display-7">
63
+ {domToReact(children, opts)}
64
+ </p>
65
+ </div>
66
+ </div>
67
+ </div>
68
+ </section>
69
+ );
70
+ }
71
+ }
72
+
73
+ // --- Custom Soames block mappings ---
74
+
75
+ if (classes.includes("wp-block-soames-title-bar")) {
76
+ return <SoamesTitleBar title={domToReact(children, opts)} />;
77
+ }
78
+
79
+ if (classes.includes("wp-block-soames-title-bar-lg")) {
80
+ const attrs = (node as DomElement).attribs;
81
+ return (
82
+ <SoamesTitleBarLg
83
+ title={attrs["data-title"] ?? ""}
84
+ attributes={{
85
+ subtitle: [attrs["data-subtitle"] ?? ""],
86
+ background: [attrs["data-background"] ?? ""],
87
+ }}
88
+ />
89
+ );
90
+ }
91
+
92
+ if (classes.includes("wp-block-soames-icon-list")) {
93
+ const attrs = (node as DomElement).attribs;
94
+ // ORBI-20: new blocks emit JSON `data-items`; old blocks/shortcodes use
95
+ // positional comma attrs. Prefer items, fall back to the legacy arrays.
96
+ if (attrs["data-items"]) {
97
+ try {
98
+ const items = JSON.parse(attrs["data-items"]);
99
+ if (Array.isArray(items)) return <SoamesIconList items={items} />;
100
+ } catch {
101
+ /* malformed JSON — fall through to legacy parsing below */
102
+ }
103
+ }
104
+ const csv = (key: string) => (attrs[key] ?? "").split(",");
105
+ return (
106
+ <SoamesIconList
107
+ attributes={{
108
+ images: csv("data-images"),
109
+ labels: csv("data-labels"),
110
+ links: csv("data-links"),
111
+ css: csv("data-css"),
112
+ }}
113
+ />
114
+ );
115
+ }
116
+
117
+ if (classes.includes("wp-block-soames-feature")) {
118
+ const attrs = (node as DomElement).attribs;
119
+ const image = attrs["data-image"];
120
+ const title = attrs["data-title"];
121
+ const css = attrs["data-css"];
122
+ return (
123
+ <section className="soames-features">
124
+ <div className="container">
125
+ <div className="col-md-12">
126
+ <div className="media-container-row">
127
+ <div className="align-left aside-content">
128
+ {title && (
129
+ <h2 className="mbr-title pt-2 mbr-fonts-style display-2">
130
+ <div>{title}</div>
131
+ </h2>
132
+ )}
133
+ <div className="block-content">
134
+ <div className={`card ${css ?? ""}`}>
135
+ <div className="media"><div className="media-body"></div></div>
136
+ <div className="card-box">
137
+ <p className="block-text mbr-fonts-style display-7">
138
+ {domToReact(children, opts)}
139
+ </p>
140
+ </div>
141
+ </div>
142
+ </div>
143
+ </div>
144
+ {image && (
145
+ <div className="soames-figure" style={{ width: "50%" }}>
146
+ <img src={image} alt={title ?? "Feature"} title={title ?? "Feature"} />
147
+ </div>
148
+ )}
149
+ </div>
150
+ </div>
151
+ </div>
152
+ </section>
153
+ );
154
+ }
155
+
156
+ if (classes.includes("wp-block-soames-gallery-menu")) {
157
+ const attrs = (node as DomElement).attribs;
158
+ // ORBI-20: prefer JSON `data-items`, fall back to legacy comma attrs.
159
+ if (attrs["data-items"]) {
160
+ try {
161
+ const items = JSON.parse(attrs["data-items"]);
162
+ if (Array.isArray(items)) return <SoamesGalleryMenu items={items} />;
163
+ } catch {
164
+ /* malformed JSON — fall through to legacy parsing below */
165
+ }
166
+ }
167
+ const csv = (key: string) => (attrs[key] ?? "").split(",");
168
+ return (
169
+ <SoamesGalleryMenu
170
+ attributes={{
171
+ images: csv("data-images"),
172
+ labels: csv("data-labels"),
173
+ links: csv("data-links"),
174
+ css: csv("data-css"),
175
+ }}
176
+ />
177
+ );
178
+ }
179
+
180
+ if (classes.includes("wp-block-soames-video")) {
181
+ const attrs = (node as DomElement).attribs;
182
+ return (
183
+ <SoamesVideo
184
+ attributes={{
185
+ link: attrs["data-link"] ?? "",
186
+ title: attrs["data-title"] ?? "",
187
+ }}
188
+ />
189
+ );
190
+ }
191
+
192
+ if (classes.includes("wp-block-soames-soundcloud")) {
193
+ const attrs = (node as DomElement).attribs;
194
+ return (
195
+ <SoamesSoundCloud
196
+ attributes={{
197
+ bandName: attrs["data-band-name"] ?? "",
198
+ siteLink: attrs["data-site-link"] ?? "",
199
+ playlistId: attrs["data-playlist-id"] ?? "",
200
+ albumLink: attrs["data-album-link"] ?? "",
201
+ albumName: attrs["data-album-name"] ?? "",
202
+ }}
203
+ />
204
+ );
205
+ }
206
+
207
+ if (classes.includes("wp-block-soames-text-list")) {
208
+ return (
209
+ <section className="soames-section article soames-list pb-0">
210
+ <div className="container">
211
+ <div className="media-container-row">
212
+ <div className="soames-text counter-container col-12 col-md-10 mbr-fonts-style pt-0 display-7">
213
+ {domToReact(children, opts)}
214
+ </div>
215
+ </div>
216
+ </div>
217
+ </section>
218
+ );
219
+ }
220
+
221
+ // --- Legacy text shortcode detection (unchanged) ---
222
+ }
223
+ if (node.type === "tag" && node.children && node.children.length > 0) {
224
+ const child = node.children[0];
225
+ if (child.type === "text") {
226
+ const shortcode = child.data.trim();
227
+
228
+ if (shortcode === "[soames-remove-content-area-padding]") {
229
+ return <RemoveContentAreaPadding />;
230
+ }
231
+
232
+ const shortcodeMappings: {
233
+ regex: RegExp;
234
+ component: React.FC<any>;
235
+ propsExtractor: (match: RegExpMatchArray) => any;
236
+ }[] = [
237
+ {
238
+ regex: /\[soames-title([^\]]*)\]([\s\S]*?)\[\/soames-title\]/,
239
+ component: SoamesTitle,
240
+ propsExtractor: (match) => ({ title: getContent(match) }),
241
+ },
242
+ {
243
+ regex: /\[soames-title-bar([^\]]*)\]([\s\S]*?)\[\/soames-title-bar\]/,
244
+ component: SoamesTitleBar,
245
+ propsExtractor: (match) => ({ title: getContent(match) }),
246
+ },
247
+ {
248
+ regex: /\[soames-title-bar-lg([^\]]*)\]([\s\S]*?)\[\/soames-title-bar-lg\]/,
249
+ component: SoamesTitleBarLg,
250
+ propsExtractor: (match) => ({
251
+ title: getContent(match),
252
+ attributes: getAttributes(match),
253
+ }),
254
+ },
255
+ {
256
+ regex: /\[soames-text-block([^\]]*)\]([\s\S]*?)\[\/soames-text-block\]/,
257
+ component: SoamesTextBlock,
258
+ propsExtractor: (match) => ({ content: getContent(match) }),
259
+ },
260
+ {
261
+ regex: /\[soames-text-list([^\]]*)\]([\s\S]*?)\[\/soames-text-list\]/,
262
+ component: SoamesTextList,
263
+ propsExtractor: (match) => ({ content: getContent(match) }),
264
+ },
265
+ {
266
+ regex: /\[soames-icon-list([^\]]*)\]/,
267
+ component: SoamesIconList,
268
+ propsExtractor: (match) => ({ attributes: getAttributes(match) }),
269
+ },
270
+ {
271
+ regex: /\[soames-feature([^\]]*)\]([\s\S]*?)\[\/soames-feature\]/,
272
+ component: SoamesFeature,
273
+ propsExtractor: (match) => ({
274
+ content: getContent(match),
275
+ attributes: getAttributes(match),
276
+ }),
277
+ },
278
+ {
279
+ regex: /\[soames-gallery-menu([^\]]*)\]/,
280
+ component: SoamesGalleryMenu,
281
+ propsExtractor: (match) => ({ attributes: getAttributes(match) }),
282
+ },
283
+ {
284
+ regex: /\[soames-video([^\]]*)\]/,
285
+ component: SoamesVideo,
286
+ propsExtractor: (match) => ({ attributes: getAttributes(match) }),
287
+ },
288
+ {
289
+ regex: /\[soames-soundcloud([^\]]*)\]/,
290
+ component: SoamesSoundCloud,
291
+ propsExtractor: (match) => ({ attributes: getAttributes(match) }),
292
+ },
293
+ ];
294
+
295
+ for (const { regex, component: Component, propsExtractor } of shortcodeMappings) {
296
+ const match = shortcode.match(regex);
297
+ if (match) {
298
+ const props = propsExtractor(match);
299
+ return <Component {...props} />;
300
+ }
301
+ }
302
+ }
303
+ }
304
+
305
+ return undefined;
306
+ };
307
+
308
+ const Shortcodes: React.FC<ShortcodesProps> = ({ html, prose = false }) => {
309
+ proseMode = !!prose;
310
+ const reactElements = parse(html || "", {
311
+ replace: handleShortcodes,
312
+ });
313
+ proseMode = false;
314
+
315
+ return <div>{reactElements}</div>;
316
+ };
317
+
318
+ export { Shortcodes };
319
+ export default Shortcodes;
@@ -0,0 +1,54 @@
1
+ import React from "react";
2
+
3
+ interface SoamesFeatureProps {
4
+ content: string;
5
+ attributes: {
6
+ image?: string;
7
+ title?: string;
8
+ css?: string;
9
+ };
10
+ }
11
+
12
+ const SoamesFeature: React.FC<SoamesFeatureProps> = ({ content, attributes }) => {
13
+ const { image, title, css } = attributes;
14
+ const paragraphs = content.split('__SOAMES_P__');
15
+
16
+ return (
17
+ <section className="soames-features">
18
+ <div className="container">
19
+ <div className="col-md-12">
20
+ <div className="media-container-row">
21
+ <div className="align-left aside-content">
22
+ {title && (
23
+ <h2 className="mbr-title pt-2 mbr-fonts-style display-2">
24
+ <div>{title}</div>
25
+ </h2>
26
+ )}
27
+ <div className="block-content">
28
+ <div className={`card ${css ?? ""}`}>
29
+ <div className="media">
30
+ <div className="media-body"></div>
31
+ </div>
32
+ <div className="card-box">
33
+ {paragraphs.map((paragraph, key) => (
34
+ <p key={key} className="block-text mbr-fonts-style display-7">
35
+ {paragraph}
36
+ </p>
37
+ ))}
38
+ </div>
39
+ </div>
40
+ </div>
41
+ </div>
42
+ {image && (
43
+ <div className="soames-figure" style={{ width: "50%" }}>
44
+ <img src={image} alt={title ?? "Feature"} title={title ?? "Feature"} />
45
+ </div>
46
+ )}
47
+ </div>
48
+ </div>
49
+ </div>
50
+ </section>
51
+ );
52
+ };
53
+
54
+ export default SoamesFeature;
@@ -0,0 +1,90 @@
1
+ import React from "react";
2
+
3
+ // New (ORBI-20) grouped format: one object per item.
4
+ interface GalleryMenuItemInput {
5
+ image: string;
6
+ label?: string | null;
7
+ link?: string | null;
8
+ css?: string | null;
9
+ }
10
+
11
+ // Legacy format: parallel, index-aligned comma arrays.
12
+ interface GalleryMenuAttributes {
13
+ images: string[];
14
+ links?: string[];
15
+ labels?: string[];
16
+ css?: string[];
17
+ }
18
+
19
+ interface GalleryMenuItem {
20
+ id: string;
21
+ imageUrl: string;
22
+ label?: string | null;
23
+ link?: string | null;
24
+ css?: string | null;
25
+ }
26
+
27
+ interface SoamesGalleryMenuProps {
28
+ items?: GalleryMenuItemInput[];
29
+ attributes?: GalleryMenuAttributes;
30
+ }
31
+
32
+ const SoamesGalleryMenu: React.FC<SoamesGalleryMenuProps> = ({ items, attributes }) => {
33
+ const normalizeUrl = (url: string) => (url ?? "").replace(/['""]+/g, '"');
34
+
35
+ let menuItems: GalleryMenuItem[];
36
+ if (items && items.length) {
37
+ menuItems = items.map((it, i) => ({
38
+ id: `icon_${i}`,
39
+ imageUrl: normalizeUrl(it.image),
40
+ label: it.label ?? null,
41
+ link: it.link ?? null,
42
+ css: it.css ?? null,
43
+ }));
44
+ } else if (attributes) {
45
+ const { images, links, labels, css } = attributes;
46
+ menuItems = (images ?? []).map((image, i) => ({
47
+ id: `icon_${i}`,
48
+ imageUrl: normalizeUrl(image),
49
+ label: labels?.[i] ?? null,
50
+ link: links?.[i] ?? null,
51
+ css: css?.[i] ?? null,
52
+ }));
53
+ } else {
54
+ menuItems = [];
55
+ }
56
+
57
+ // Drop rows with no image (e.g. a trailing comma in the legacy format).
58
+ menuItems = menuItems.filter((item) => item.imageUrl.trim().length > 0);
59
+
60
+ return (
61
+ <section className="features1 soames-gallery-menu">
62
+ <div className="container-fluid">
63
+ <div className="media-container-row">
64
+ {menuItems.map(menuItem => (
65
+ <div key={menuItem.id} className="card p-3 col-md-12 col-lg-3">
66
+ <div className="card-wrapper">
67
+ <div className="card-img">
68
+ <a href={menuItem.link ?? "#"}>
69
+ <img
70
+ src={menuItem.imageUrl}
71
+ alt={menuItem.label ?? ""}
72
+ title={menuItem.label ?? ""}
73
+ />
74
+ </a>
75
+ </div>
76
+ <div className="card-box">
77
+ <h4 className="card-title pb-3 mbr-fonts-style display-7">
78
+ {menuItem.label}
79
+ </h4>
80
+ </div>
81
+ </div>
82
+ </div>
83
+ ))}
84
+ </div>
85
+ </div>
86
+ </section>
87
+ );
88
+ };
89
+
90
+ export default SoamesGalleryMenu;
@@ -0,0 +1,90 @@
1
+ import React from "react";
2
+
3
+ // New (ORBI-20) grouped format: one object per icon.
4
+ interface IconItem {
5
+ image: string;
6
+ label?: string | null;
7
+ link?: string | null;
8
+ css?: string | null;
9
+ }
10
+
11
+ // Legacy format: parallel, index-aligned comma arrays.
12
+ interface IconListAttributes {
13
+ images: string[];
14
+ links?: string[];
15
+ labels?: string[];
16
+ css?: string[];
17
+ }
18
+
19
+ interface Icon {
20
+ id: string;
21
+ imageUrl: string;
22
+ label?: string | null;
23
+ link?: string | null;
24
+ css?: string | null;
25
+ }
26
+
27
+ interface SoamesIconListProps {
28
+ items?: IconItem[];
29
+ attributes?: IconListAttributes;
30
+ }
31
+
32
+ const SoamesIconList: React.FC<SoamesIconListProps> = ({ items, attributes }) => {
33
+ const normalizeUrl = (url: string) => (url ?? "").replace(/['""]+/g, '"');
34
+
35
+ let icons: Icon[];
36
+ if (items && items.length) {
37
+ // New grouped format.
38
+ icons = items.map((it, i) => ({
39
+ id: `icon_${i}`,
40
+ imageUrl: normalizeUrl(it.image),
41
+ label: it.label ?? null,
42
+ link: it.link ?? null,
43
+ css: it.css ?? null,
44
+ }));
45
+ } else if (attributes) {
46
+ // Legacy positional arrays.
47
+ const { images, links, labels, css } = attributes;
48
+ icons = (images ?? []).map((image, i) => ({
49
+ id: `icon_${i}`,
50
+ imageUrl: normalizeUrl(image),
51
+ label: labels?.[i] ?? null,
52
+ link: links?.[i] ?? null,
53
+ css: css?.[i] ?? null,
54
+ }));
55
+ } else {
56
+ icons = [];
57
+ }
58
+
59
+ // Drop rows with no image (e.g. a trailing comma in the legacy format).
60
+ icons = icons.filter((icon) => icon.imageUrl.trim().length > 0);
61
+
62
+ return (
63
+ <section className="soames-section">
64
+ <div className="container">
65
+ <div className="d-flex flex-wrap align-items-center justify-content-center">
66
+ {icons.map((icon) => (
67
+ <a
68
+ key={icon.id}
69
+ className={`d-flex flex-column align-items-center text-decoration-none p-2 ${icon.css ?? ''}`}
70
+ href={icon.link ?? "#"}
71
+ target="_blank"
72
+ rel="noreferrer"
73
+ >
74
+ <img
75
+ className="d-block mb-2"
76
+ src={icon.imageUrl}
77
+ alt={icon.label ?? ""}
78
+ height="116"
79
+ loading="lazy"
80
+ />
81
+ <span className="text-muted">{icon.label}</span>
82
+ </a>
83
+ ))}
84
+ </div>
85
+ </div>
86
+ </section>
87
+ );
88
+ };
89
+
90
+ export default SoamesIconList;
@@ -0,0 +1,71 @@
1
+ import React from "react";
2
+
3
+ interface SoamesSoundCloudProps {
4
+ attributes: {
5
+ bandName: string;
6
+ siteLink: string;
7
+ playlistId: string;
8
+ albumLink: string;
9
+ albumName: string;
10
+ };
11
+ }
12
+
13
+ const SoamesSoundCloud: React.FC<SoamesSoundCloudProps> = ({ attributes }) => {
14
+ const { bandName, siteLink, playlistId, albumLink, albumName } = attributes;
15
+
16
+ return (
17
+ <section className="soames-section article soames-list">
18
+ <div className="container">
19
+ <div className="media-container-row">
20
+ <div className="soames-text counter-container col-12 col-md-10 pt-2 mbr-fonts-style display-7">
21
+ <iframe
22
+ title={albumName}
23
+ width="100%"
24
+ height="300"
25
+ scrolling="no"
26
+ frameBorder="no"
27
+ allow="autoplay"
28
+ src={`https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/playlists/${playlistId}&color=%23ff5500&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false&show_teaser=true&visual=true`}
29
+ ></iframe>
30
+ <div
31
+ style={{
32
+ fontSize: '10px',
33
+ color: '#cccccc',
34
+ lineBreak: 'anywhere',
35
+ wordBreak: 'normal',
36
+ overflow: 'hidden',
37
+ whiteSpace: 'nowrap',
38
+ textOverflow: 'ellipsis',
39
+ fontFamily:
40
+ 'Interstate,Lucida Grande,Lucida Sans Unicode,Lucida Sans,Garuda,Verdana,Tahoma,sans-serif',
41
+ fontWeight: 100,
42
+ }}
43
+ >
44
+ <a
45
+ href={siteLink}
46
+ title={bandName}
47
+ target="_blank"
48
+ style={{ color: '#cccccc', textDecoration: 'none' }}
49
+ rel="noreferrer"
50
+ >
51
+ {bandName}
52
+ </a>{' '}
53
+ ·{' '}
54
+ <a
55
+ href={albumLink}
56
+ title={albumName}
57
+ target="_blank"
58
+ style={{ color: '#cccccc', textDecoration: 'none' }}
59
+ rel="noreferrer"
60
+ >
61
+ {albumName}
62
+ </a>
63
+ </div>
64
+ </div>
65
+ </div>
66
+ </div>
67
+ </section>
68
+ );
69
+ };
70
+
71
+ export default SoamesSoundCloud;
@@ -0,0 +1,27 @@
1
+ import React from "react";
2
+
3
+ interface SoamesTextBlockProps {
4
+ content: string;
5
+ }
6
+
7
+ const SoamesTextBlock: React.FC<SoamesTextBlockProps> = ({ content }) => {
8
+ const paragraphs = content?.split('__SOAMES_P__') ?? [];
9
+
10
+ return (
11
+ <section className="soames-section article soames-article">
12
+ <div className="container col-md-10">
13
+ <div className="inner-container" style={{ width: "100%" }}>
14
+ <div className="section-text align-center mbr-fonts-style display-7 pb-2">
15
+ {paragraphs.map((paragraph, key) => (
16
+ <p key={key} className="block-text mbr-fonts-style display-7">
17
+ {paragraph}
18
+ </p>
19
+ ))}
20
+ </div>
21
+ </div>
22
+ </div>
23
+ </section>
24
+ );
25
+ };
26
+
27
+ export default SoamesTextBlock;
@@ -0,0 +1,27 @@
1
+ import React from "react";
2
+
3
+ interface SoamesTextListProps {
4
+ content: string;
5
+ }
6
+
7
+ const SoamesTextList: React.FC<SoamesTextListProps> = ({ content }) => {
8
+ const lineItems = content?.split('__SOAMES_LI__') ?? [];
9
+
10
+ return (
11
+ <section className="soames-section article soames-list pb-0">
12
+ <div className="container">
13
+ <div className="media-container-row">
14
+ <div className="soames-text counter-container col-12 col-md-10 mbr-fonts-style pt-0 display-7">
15
+ <ul>
16
+ {lineItems.map((lineItem, key) => (
17
+ <li key={key}>{lineItem}</li>
18
+ ))}
19
+ </ul>
20
+ </div>
21
+ </div>
22
+ </div>
23
+ </section>
24
+ );
25
+ };
26
+
27
+ export default SoamesTextList;