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..f9aeb6e 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); } @@ -45,6 +119,26 @@ } } + .text-input-root { + position: relative; + isolation: isolate; + } + + .text-input-backdrop { + position: absolute; + right: 0; + bottom: 0; + left: 0; + z-index: 0; + height: 50%; + background-color: #000; + } + + .text-input-field { + position: relative; + z-index: 1; + } + .links a { @apply text-primary underline; } diff --git a/src/app.d.ts b/src/app.d.ts new file mode 100644 index 0000000..7067dd7 --- /dev/null +++ b/src/app.d.ts @@ -0,0 +1,11 @@ +import type { SharedHead } from '#lib/head.ts'; + +declare global { + namespace App { + interface PageData { + sharedHead?: SharedHead; + } + } +} + +export {}; 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..2881053 --- /dev/null +++ b/src/lib/components/SponsorsBlock.svelte @@ -0,0 +1,64 @@ + + + diff --git a/src/lib/components/TextInput.svelte b/src/lib/components/TextInput.svelte new file mode 100644 index 0000000..26b07dc --- /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/lib/head.ts b/src/lib/head.ts new file mode 100644 index 0000000..41eecee --- /dev/null +++ b/src/lib/head.ts @@ -0,0 +1,110 @@ +export const DEFAULT_TITLE = + 'Method Conference - October 12th 2024 - Springfield, MO'; +export const NOT_FOUND_BROWSER_TITLE = '404: This page could not be found.'; +const DEFAULT_OG_IMAGE_PATH = + '/opengraph-image.jpg?opengraph-image.44rvcdk19e2xk.jpg'; +const DEFAULT_ICON_PATH = '/icon.png?icon.232taq741yhvg.png'; + +export type SharedHeadImage = { + url?: string; + type?: string; + width?: number; + height?: number; +}; + +export type SharedHeadMetadata = { + title?: string; + description?: string; + openGraph?: { + title?: string; + image?: SharedHeadImage; + }; +}; + +export type SharedHead = { + title: string; + description?: string; + openGraph: { + title: string; + image: Required; + }; + twitter: { + card: 'summary_large_image'; + title: string; + image: Required; + }; + icon: { + href: string; + sizes: string; + type: string; + }; +}; + +type BuildSharedHeadOptions = { + siteUrl: string; + metadata?: SharedHeadMetadata; +}; + +type ResolveDocumentTitleOptions = { + sharedTitle: string; + status: number; + errorMessage?: string; + clientMounted: boolean; +}; + +export function resolveDocumentTitle({ + sharedTitle, + status, + errorMessage, + clientMounted, +}: ResolveDocumentTitleOptions): string { + if (status === 404) { + return clientMounted ? NOT_FOUND_BROWSER_TITLE : sharedTitle; + } + + if (status >= 400) { + return `${status}: ${errorMessage ?? 'Application error'}`; + } + + return sharedTitle; +} + +function resolveFromSiteUrl(siteUrl: string, pathOrUrl: string): string { + return new URL(pathOrUrl, siteUrl).toString(); +} + +export function buildSharedHead({ + siteUrl, + metadata, +}: BuildSharedHeadOptions): SharedHead { + const title = metadata?.title ?? DEFAULT_TITLE; + const image = { + url: resolveFromSiteUrl( + siteUrl, + metadata?.openGraph?.image?.url ?? DEFAULT_OG_IMAGE_PATH, + ), + type: metadata?.openGraph?.image?.type ?? 'image/jpeg', + width: metadata?.openGraph?.image?.width ?? 1200, + height: metadata?.openGraph?.image?.height ?? 630, + }; + const openGraphTitle = metadata?.openGraph?.title ?? title; + + return { + title, + description: metadata?.description, + openGraph: { + title: openGraphTitle, + image, + }, + twitter: { + card: 'summary_large_image', + title: openGraphTitle, + image, + }, + icon: { + href: DEFAULT_ICON_PATH, + sizes: '180x180', + type: 'image/png', + }, + }; +} diff --git a/src/routes/+error.svelte b/src/routes/+error.svelte new file mode 100644 index 0000000..952bd8c --- /dev/null +++ b/src/routes/+error.svelte @@ -0,0 +1,61 @@ + + + + + + +
+
+

{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.server.ts b/src/routes/+layout.server.ts index c514362..3e6ebea 100644 --- a/src/routes/+layout.server.ts +++ b/src/routes/+layout.server.ts @@ -1,5 +1,11 @@ -import { isSearchIndexingEnabled } from '#lib/server/config.ts'; +import { buildSharedHead } from '#lib/head.ts'; +import { getSiteUrl, isSearchIndexingEnabled } from '#lib/server/config.ts'; -export const load = () => ({ - searchIndexingEnabled: isSearchIndexingEnabled(), -}); +export const load = () => { + const siteUrl = getSiteUrl().toString(); + + return { + searchIndexingEnabled: isSearchIndexingEnabled(), + sharedHead: buildSharedHead({ siteUrl }), + }; +}; diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index aed7e77..a057e06 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,18 +1,102 @@ - {#if !data.searchIndexingEnabled} + {documentTitle} + {#if head.description} + + {/if} + + {#if head.description} + + {/if} + + + + + + + {#if head.description} + + {/if} + + + + + + + {#if shouldEmitLayoutRobots} {/if} + {#if !dev} + + + {@html plausibleHeadHtml} + {/if} -
+
{@render children()}
+ +