From edd62a3d2f8cd294f1a3457591e6110003862771 Mon Sep 17 00:00:00 2001 From: Levi Zitting Date: Sun, 4 Oct 2026 02:35:17 -0500 Subject: [PATCH 1/5] feat: add shared Svelte UI components --- package-lock.json | 10 -- package.json | 1 - src/app.css | 76 ++++++++- src/lib/components/Button.svelte | 92 ++++++++++ src/lib/components/DefaultNav.svelte | 51 ++++++ src/lib/components/FontAwesomeIcon.svelte | 19 +++ src/lib/components/Footer.svelte | 66 ++++++++ src/lib/components/HomeNav.svelte | 73 ++++++++ src/lib/components/LocationBlock.svelte | 73 ++++++++ src/lib/components/Navigation.svelte | 26 +++ src/lib/components/RichText.svelte | 21 +++ src/lib/components/RichTextBlock.svelte | 20 +++ src/lib/components/SectionTitleBar.svelte | 18 ++ src/lib/components/SponsorCard.svelte | 82 +++++++++ src/lib/components/SponsorTierItem.svelte | 50 ++++++ src/lib/components/SponsorsBlock.svelte | 59 +++++++ src/lib/components/TextInput.svelte | 42 +++++ .../components/TextWithButtonsBlock.svelte | 27 +++ src/lib/components/richText.ts | 32 ++++ src/lib/components/sponsorCard.ts | 12 ++ src/routes/+error.svelte | 66 ++++++++ src/routes/+layout.svelte | 35 +++- .../47df9ba1c7236d3b-s.p.137759vg1sbmi.woff2 | Bin 0 -> 28792 bytes .../94b4a5dc1fe84361-s.3wm_n5jv1to7s.woff2 | Bin 0 -> 18400 bytes .../99596c30eb072d3c-s.2fp9tilo6sy14.woff2 | Bin 0 -> 14580 bytes static/fonts/source-sans-3/OFL.txt | 93 +++++++++++ .../da8a9dd0d68e512b-s.0n8i_inn0i2i9.woff2 | Bin 0 -> 10684 bytes .../e285ad1a914469f0-s.15241mr5-1s6i.woff2 | Bin 0 -> 60048 bytes .../e5864b32625b6bc3-s.0m7i6b77o5yxd.woff2 | Bin 0 -> 10336 bytes .../ff5be760267bb4bc-s.3dkqd4mro2h3_.woff2 | Bin 0 -> 9796 bytes static/icon.png | Bin 0 -> 6462 bytes static/opengraph-image.jpg | Bin 0 -> 129919 bytes tests/unit/shared-ui-assets.test.ts | 66 ++++++++ tests/unit/shared-ui-components.test.ts | 157 ++++++++++++++++++ 34 files changed, 1254 insertions(+), 13 deletions(-) create mode 100644 src/lib/components/Button.svelte create mode 100644 src/lib/components/DefaultNav.svelte create mode 100644 src/lib/components/FontAwesomeIcon.svelte create mode 100644 src/lib/components/Footer.svelte create mode 100644 src/lib/components/HomeNav.svelte create mode 100644 src/lib/components/LocationBlock.svelte create mode 100644 src/lib/components/Navigation.svelte create mode 100644 src/lib/components/RichText.svelte create mode 100644 src/lib/components/RichTextBlock.svelte create mode 100644 src/lib/components/SectionTitleBar.svelte create mode 100644 src/lib/components/SponsorCard.svelte create mode 100644 src/lib/components/SponsorTierItem.svelte create mode 100644 src/lib/components/SponsorsBlock.svelte create mode 100644 src/lib/components/TextInput.svelte create mode 100644 src/lib/components/TextWithButtonsBlock.svelte create mode 100644 src/lib/components/richText.ts create mode 100644 src/lib/components/sponsorCard.ts create mode 100644 src/routes/+error.svelte create mode 100644 static/fonts/source-sans-3/47df9ba1c7236d3b-s.p.137759vg1sbmi.woff2 create mode 100644 static/fonts/source-sans-3/94b4a5dc1fe84361-s.3wm_n5jv1to7s.woff2 create mode 100644 static/fonts/source-sans-3/99596c30eb072d3c-s.2fp9tilo6sy14.woff2 create mode 100644 static/fonts/source-sans-3/OFL.txt create mode 100644 static/fonts/source-sans-3/da8a9dd0d68e512b-s.0n8i_inn0i2i9.woff2 create mode 100644 static/fonts/source-sans-3/e285ad1a914469f0-s.15241mr5-1s6i.woff2 create mode 100644 static/fonts/source-sans-3/e5864b32625b6bc3-s.0m7i6b77o5yxd.woff2 create mode 100644 static/fonts/source-sans-3/ff5be760267bb4bc-s.3dkqd4mro2h3_.woff2 create mode 100644 static/icon.png create mode 100644 static/opengraph-image.jpg create mode 100644 tests/unit/shared-ui-assets.test.ts create mode 100644 tests/unit/shared-ui-components.test.ts diff --git a/package-lock.json b/package-lock.json index cf02bea..9e9e816 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "0.1.0", "dependencies": { "@date-fns/tz": "^1.5.0", - "@fontsource-variable/source-sans-3": "^5.3.0", "@fortawesome/fontawesome-svg-core": "^7.3.1", "@fortawesome/free-brands-svg-icons": "^7.3.1", "@fortawesome/free-regular-svg-icons": "^7.3.1", @@ -693,15 +692,6 @@ "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@fontsource-variable/source-sans-3": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@fontsource-variable/source-sans-3/-/source-sans-3-5.3.0.tgz", - "integrity": "sha512-dpi0GZk7EQe2tYpg6Q0Fx0OmUgnuGu+0rHPgtXqtKhglYmJuP7jZ12Mu1uN+NfRaJRY1Emn8AQJEWzmoZ4wa9g==", - "license": "OFL-1.1", - "funding": { - "url": "https://github.com/sponsors/ayuhito" - } - }, "node_modules/@fortawesome/fontawesome-common-types": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-7.3.1.tgz", diff --git a/package.json b/package.json index 3b06b6d..39cf37c 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,6 @@ }, "dependencies": { "@date-fns/tz": "^1.5.0", - "@fontsource-variable/source-sans-3": "^5.3.0", "@fortawesome/fontawesome-svg-core": "^7.3.1", "@fortawesome/free-brands-svg-icons": "^7.3.1", "@fortawesome/free-regular-svg-icons": "^7.3.1", diff --git a/src/app.css b/src/app.css index 1f5307d..a8649fa 100644 --- a/src/app.css +++ b/src/app.css @@ -1,13 +1,87 @@ @import "tailwindcss"; +@import "@fortawesome/fontawesome-svg-core/styles.css"; @plugin "@tailwindcss/typography"; +@font-face { + font-family: "Source Sans 3"; + font-style: normal; + font-weight: 200 900; + font-display: swap; + src: url("/fonts/source-sans-3/da8a9dd0d68e512b-s.0n8i_inn0i2i9.woff2") format("woff2"); + unicode-range: U+460-52F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; +} + +@font-face { + font-family: "Source Sans 3"; + font-style: normal; + font-weight: 200 900; + font-display: swap; + src: url("/fonts/source-sans-3/94b4a5dc1fe84361-s.3wm_n5jv1to7s.woff2") format("woff2"); + unicode-range: U+301, U+400-45F, U+490-491, U+4B0-4B1, U+2116; +} + +@font-face { + font-family: "Source Sans 3"; + font-style: normal; + font-weight: 200 900; + font-display: swap; + src: url("/fonts/source-sans-3/ff5be760267bb4bc-s.3dkqd4mro2h3_.woff2") format("woff2"); + unicode-range: U+1F??; +} + +@font-face { + font-family: "Source Sans 3"; + font-style: normal; + font-weight: 200 900; + font-display: swap; + src: url("/fonts/source-sans-3/99596c30eb072d3c-s.2fp9tilo6sy14.woff2") format("woff2"); + unicode-range: U+370-377, U+37A-37F, U+384-38A, U+38C, U+38E-3A1, U+3A3-3FF; +} + +@font-face { + font-family: "Source Sans 3"; + font-style: normal; + font-weight: 200 900; + font-display: swap; + src: url("/fonts/source-sans-3/e5864b32625b6bc3-s.0m7i6b77o5yxd.woff2") format("woff2"); + unicode-range: U+102-103, U+110-111, U+128-129, U+168-169, U+1A0-1A1, U+1AF-1B0, U+300-301, U+303-304, U+308-309, U+323, U+329, U+1EA0-1EF9, U+20AB; +} + +@font-face { + font-family: "Source Sans 3"; + font-style: normal; + font-weight: 200 900; + font-display: swap; + src: url("/fonts/source-sans-3/e285ad1a914469f0-s.15241mr5-1s6i.woff2") format("woff2"); + unicode-range: U+100-2BA, U+2BD-2C5, U+2C7-2CC, U+2CE-2D7, U+2DD-2FF, U+304, U+308, U+329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +@font-face { + font-family: "Source Sans 3"; + font-style: normal; + font-weight: 200 900; + font-display: swap; + src: url("/fonts/source-sans-3/47df9ba1c7236d3b-s.p.137759vg1sbmi.woff2") format("woff2"); + unicode-range: U+??, U+131, U+152-153, U+2BB-2BC, U+2C6, U+2DA, U+2DC, U+304, U+308, U+329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +@font-face { + font-family: "Source Sans 3 Fallback"; + src: local(Arial); + ascent-override: 109.21%; + descent-override: 42.66%; + line-gap-override: 0%; + size-adjust: 93.76%; +} + @theme { --color-primary: #D72F32; --color-secondary: #1E1E1E; - --font-sans: "Source Sans 3 Variable", sans-serif; + --font-sans: "Source Sans 3", "Source Sans 3 Fallback"; } @layer base { + html, body { font-family: var(--font-sans); } diff --git a/src/lib/components/Button.svelte b/src/lib/components/Button.svelte new file mode 100644 index 0000000..ad443a9 --- /dev/null +++ b/src/lib/components/Button.svelte @@ -0,0 +1,92 @@ + + +{#if resolved.kind === 'anchor'} + + {#if children} + {@render children()} + {:else} + {label} + {/if} + +{:else} + +{/if} diff --git a/src/lib/components/DefaultNav.svelte b/src/lib/components/DefaultNav.svelte new file mode 100644 index 0000000..bcd0579 --- /dev/null +++ b/src/lib/components/DefaultNav.svelte @@ -0,0 +1,51 @@ + + +
+
+
+ Method Logo +

+ Method Conference + {date + ? `${formatDate(date, 'EEEE, MMMM do, yyyy')} in ` + : ''}Springfield, MO +

+

+ An immersive day of code, content, and design +

+
+
+
+ + +
diff --git a/src/lib/components/FontAwesomeIcon.svelte b/src/lib/components/FontAwesomeIcon.svelte new file mode 100644 index 0000000..67fdf7c --- /dev/null +++ b/src/lib/components/FontAwesomeIcon.svelte @@ -0,0 +1,19 @@ + + + + +{@html markup} diff --git a/src/lib/components/Footer.svelte b/src/lib/components/Footer.svelte new file mode 100644 index 0000000..c44b93e --- /dev/null +++ b/src/lib/components/Footer.svelte @@ -0,0 +1,66 @@ + + + diff --git a/src/lib/components/HomeNav.svelte b/src/lib/components/HomeNav.svelte new file mode 100644 index 0000000..1efd220 --- /dev/null +++ b/src/lib/components/HomeNav.svelte @@ -0,0 +1,73 @@ + + +
+
+
+ Method Logo +

+ Method Conference + {date + ? `${formatDate(date, 'EEEE, MMMM do, yyyy')} in ` + : ''}Springfield, MO +

+

+ An immersive day of code, + content, and + more +

+
+
+ + Register Now + +
+ +
+ + + + +
+
+ + +
diff --git a/src/lib/components/LocationBlock.svelte b/src/lib/components/LocationBlock.svelte new file mode 100644 index 0000000..b589535 --- /dev/null +++ b/src/lib/components/LocationBlock.svelte @@ -0,0 +1,73 @@ + + +
+ + +
+
+
+ +
+
+
+ {#each details as { heading, content } (heading)} +
{heading}
+ + {/each} +
+
+
+
+
diff --git a/src/lib/components/Navigation.svelte b/src/lib/components/Navigation.svelte new file mode 100644 index 0000000..42c9d5b --- /dev/null +++ b/src/lib/components/Navigation.svelte @@ -0,0 +1,26 @@ + + + diff --git a/src/lib/components/RichText.svelte b/src/lib/components/RichText.svelte new file mode 100644 index 0000000..8b7fb71 --- /dev/null +++ b/src/lib/components/RichText.svelte @@ -0,0 +1,21 @@ + + +
+ + + {@html renderedMarkup} +
diff --git a/src/lib/components/RichTextBlock.svelte b/src/lib/components/RichTextBlock.svelte new file mode 100644 index 0000000..6e2a9be --- /dev/null +++ b/src/lib/components/RichTextBlock.svelte @@ -0,0 +1,20 @@ + + +
+
+ {#if markup} + + {/if} +
+
diff --git a/src/lib/components/SectionTitleBar.svelte b/src/lib/components/SectionTitleBar.svelte new file mode 100644 index 0000000..0212e43 --- /dev/null +++ b/src/lib/components/SectionTitleBar.svelte @@ -0,0 +1,18 @@ + + +
+
+ +

+ {title} +

+
+
diff --git a/src/lib/components/SponsorCard.svelte b/src/lib/components/SponsorCard.svelte new file mode 100644 index 0000000..4b3e7be --- /dev/null +++ b/src/lib/components/SponsorCard.svelte @@ -0,0 +1,82 @@ + + +
+ {#if properties.url} + +
+ {#if image && logoUrl} + + {:else} +

{title}

+ {/if} +
+
+ {:else} +
+
+ {#if image && logoUrl} + + {:else} +

{title}

+ {/if} +
+
+ {/if} +
diff --git a/src/lib/components/SponsorTierItem.svelte b/src/lib/components/SponsorTierItem.svelte new file mode 100644 index 0000000..5a31602 --- /dev/null +++ b/src/lib/components/SponsorTierItem.svelte @@ -0,0 +1,50 @@ + + +
+ {#if title} +
+
+
+

{title}

+
+
+
+ {/if} + + {#if sponsors.length > 0} +
+
+ {#each sponsors as sponsor (sponsor.id)} + + {/each} +
+
+ {/if} +
diff --git a/src/lib/components/SponsorsBlock.svelte b/src/lib/components/SponsorsBlock.svelte new file mode 100644 index 0000000..1f615b6 --- /dev/null +++ b/src/lib/components/SponsorsBlock.svelte @@ -0,0 +1,59 @@ + + + diff --git a/src/lib/components/TextInput.svelte b/src/lib/components/TextInput.svelte new file mode 100644 index 0000000..74f1bc7 --- /dev/null +++ b/src/lib/components/TextInput.svelte @@ -0,0 +1,42 @@ + + +
+
+
+ + +
+
diff --git a/src/lib/components/TextWithButtonsBlock.svelte b/src/lib/components/TextWithButtonsBlock.svelte new file mode 100644 index 0000000..0c6bfcd --- /dev/null +++ b/src/lib/components/TextWithButtonsBlock.svelte @@ -0,0 +1,27 @@ + + +
+
+ {#if text} + + {/if} +
+ {#each buttons as button, index (button.destinationId ?? button.url ?? index)} + + {/each} +
+
+
diff --git a/src/lib/components/richText.ts b/src/lib/components/richText.ts new file mode 100644 index 0000000..0d92be0 --- /dev/null +++ b/src/lib/components/richText.ts @@ -0,0 +1,32 @@ +const CMS_MEDIA_URL_PATTERN = + /(?\b(?:href|src)=["'])(?(?:https?:\/\/[^"']+)?\/media\/[^"']+)(?["'])/gi; + +function rewriteCmsMediaUrl(url: string): string { + if (url.startsWith('/cms-media/')) { + return url; + } + + if (url.startsWith('/media/')) { + return `/cms-media${url}`; + } + + try { + const parsed = new URL(url); + + if (!parsed.hostname.endsWith('methodconf.com')) { + return url; + } + + return `/cms-media${parsed.pathname}${parsed.search}${parsed.hash}`; + } catch { + return url; + } +} + +export function rewriteRichTextMediaUrls(markup: string): string { + return markup.replace( + CMS_MEDIA_URL_PATTERN, + (_match, attribute: string, url: string, quote: string) => + `${attribute}${rewriteCmsMediaUrl(url)}${quote}`, + ); +} diff --git a/src/lib/components/sponsorCard.ts b/src/lib/components/sponsorCard.ts new file mode 100644 index 0000000..ca983fb --- /dev/null +++ b/src/lib/components/sponsorCard.ts @@ -0,0 +1,12 @@ +import type { CardSize } from './SponsorCard.svelte'; + +export function parseCardSize(str: string | null = ''): CardSize | undefined { + str = str?.toLowerCase() ?? ''; + + switch (str) { + case 'large': + case 'medium': + case 'small': + return str; + } +} diff --git a/src/routes/+error.svelte b/src/routes/+error.svelte new file mode 100644 index 0000000..414eed9 --- /dev/null +++ b/src/routes/+error.svelte @@ -0,0 +1,66 @@ + + + + {#if page.status === 404} + 404: This page could not be found. + {:else} + {page.status}: {page.error?.message ?? 'Application error'} + {/if} + + + + +
+
+

{page.status}

+
+ {#if page.status === 404} +

This page could not be found.

+ {:else} +

{page.error?.message ?? 'Application error'}

+ {/if} +
+
+
+ + diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index aed7e77..17a40ac 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,18 +1,51 @@ + Method Conference - October 12th 2024 - Springfield, MO + + + {#if !data.searchIndexingEnabled} {/if} + {#if !dev} + + + {@html plausibleHeadHtml} + {/if}
{@render children()}
+ +{#if !isErrorPage} +