From 7e13f1adfe4f6b794324ac634c9a9e132bd4ec4e Mon Sep 17 00:00:00 2001 From: Tanner Heffner Date: Sat, 20 Jan 2024 02:05:25 -0800 Subject: [PATCH 1/3] actually run prettier --- .github/ISSUE_TEMPLATE/bug_report.md | 18 +- .github/ISSUE_TEMPLATE/feature_request.md | 1 - .github/ISSUE_TEMPLATE/new-blog-post.md | 6 +- .github/ISSUE_TEMPLATE/new-gallery-post.md | 6 +- .github/dependabot.yml | 6 +- package.json | 2 +- src/lib/components/Card.svelte | 38 +-- src/lib/components/Comments.svelte | 16 +- src/lib/components/GetInTouch.svelte | 22 +- src/lib/components/Header.svelte | 6 +- src/lib/components/MobileMenu.svelte | 26 +- src/lib/components/Og.svelte | 44 +-- src/lib/components/PostItem.svelte | 14 +- src/lib/components/Slice.svelte | 2 +- src/lib/content.js | 27 +- src/lib/localContent.js | 62 ++-- src/lib/types.d.ts | 8 +- src/lib/utils.js | 30 +- src/mdsvexlayout.svelte | 4 +- src/routes/(main)/+error.svelte | 4 +- src/routes/(main)/+layout.svelte | 100 +++--- src/routes/(main)/+page.svelte | 33 +- src/routes/(main)/[slug]/+page.js | 10 +- src/routes/(main)/[slug]/+page.svelte | 43 +-- src/routes/(main)/blog/+page.js | 6 +- src/routes/(main)/blog/+page.svelte | 25 +- src/routes/(main)/gallery/+page.js | 6 +- src/routes/(main)/gallery/+page.svelte | 30 +- src/routes/(main)/gallery/[slug]/+page.svelte | 43 +-- src/routes/(main)/resume/+page.svelte | 49 +-- src/routes/(main)/work/+page.js | 4 +- src/routes/(main)/work/+page.svelte | 25 +- src/routes/(main)/work/[slug]/+page.svelte | 16 +- src/routes/(nowrapper)/+layout.svelte | 6 +- .../(nowrapper)/christmas/+page.server.js | 2 +- src/routes/(nowrapper)/christmas/+page.svelte | 295 +++++++++--------- .../(nowrapper)/christmas/christmas.css | 44 +-- src/routes/(nowrapper)/christmas/christmas.js | 82 ++--- src/routes/+layout.js | 10 +- .../api/listLocalContent.json/+server.js | 16 +- src/routes/api/work/[slug].json/+server.js | 2 +- src/routes/rss.xml/+server.js | 27 +- src/routes/sitemap.xml/+server.js | 45 ++- src/tailwind.css | 4 +- vite.config.js | 16 +- 45 files changed, 666 insertions(+), 615 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 3edc7ed..6a6de19 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -4,7 +4,6 @@ about: Create a report to help us improve title: '' labels: bug, Published assignees: '' - --- **Describe the bug** @@ -12,6 +11,7 @@ A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: + 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' @@ -24,15 +24,17 @@ A clear and concise description of what you expected to happen. If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - - OS: [e.g. iOS] - - Browser [e.g. chrome, safari] - - Version [e.g. 22] + +- OS: [e.g. iOS] +- Browser [e.g. chrome, safari] +- Version [e.g. 22] **Smartphone (please complete the following information):** - - Device: [e.g. iPhone6] - - OS: [e.g. iOS8.1] - - Browser [e.g. stock browser, safari] - - Version [e.g. 22] + +- Device: [e.g. iPhone6] +- OS: [e.g. iOS8.1] +- Browser [e.g. stock browser, safari] +- Version [e.g. 22] **Additional context** Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 11fc491..5f0a04c 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -4,7 +4,6 @@ about: Suggest an idea for this project title: '' labels: enhancement assignees: '' - --- **Is your feature request related to a problem? Please describe.** diff --git a/.github/ISSUE_TEMPLATE/new-blog-post.md b/.github/ISSUE_TEMPLATE/new-blog-post.md index c963c00..2c7daab 100644 --- a/.github/ISSUE_TEMPLATE/new-blog-post.md +++ b/.github/ISSUE_TEMPLATE/new-blog-post.md @@ -4,15 +4,15 @@ about: Template with front matter for blog posts title: '' labels: Published assignees: '' - --- -*** swap these for dashes for front matter +\*\*\* swap these for dashes for front matter title: "post title" date: 2022-01-16 00:00:00 category: note recipe snippet technical tags: extra context after category description: "will show underneath title on list page" -*** + +--- post content goes here, underneath diff --git a/.github/ISSUE_TEMPLATE/new-gallery-post.md b/.github/ISSUE_TEMPLATE/new-gallery-post.md index b0650ef..a584fcb 100644 --- a/.github/ISSUE_TEMPLATE/new-gallery-post.md +++ b/.github/ISSUE_TEMPLATE/new-gallery-post.md @@ -4,15 +4,15 @@ about: Template with front matter for gallery posts title: '' labels: Gallery assignees: '' - --- -*** swap these for dashes for front matter +\*\*\* swap these for dashes for front matter title: "post title" date: 2022-01-16 00:00:00 description: "will show underneath title on list page" image: upload cover image here alt: image alt text -*** + +--- post content goes here, underneath diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 3a3cce5..13fbd7f 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,7 +5,7 @@ version: 2 updates: - - package-ecosystem: "npm" # See documentation for possible values - directory: "/" # Location of package manifests + - package-ecosystem: 'npm' # See documentation for possible values + directory: '/' # Location of package manifests schedule: - interval: "weekly" + interval: 'weekly' diff --git a/package.json b/package.json index 7887634..0d5e48d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "heffnerdotdev", "private": true, - "version": "0.8.0", + "version": "0.9.0", "scripts": { "start": "vite dev", "build": "vite build", diff --git a/src/lib/components/Card.svelte b/src/lib/components/Card.svelte index 73dfc66..d4e123a 100644 --- a/src/lib/components/Card.svelte +++ b/src/lib/components/Card.svelte @@ -1,27 +1,29 @@ - -
-

- {title} -

-
- +
+

+ {title} +

+
+
diff --git a/src/lib/components/Comments.svelte b/src/lib/components/Comments.svelte index 024796f..bf61a6d 100644 --- a/src/lib/components/Comments.svelte +++ b/src/lib/components/Comments.svelte @@ -1,14 +1,14 @@ -
-
diff --git a/src/lib/components/GetInTouch.svelte b/src/lib/components/GetInTouch.svelte index b076aba..3b57264 100644 --- a/src/lib/components/GetInTouch.svelte +++ b/src/lib/components/GetInTouch.svelte @@ -1,18 +1,18 @@ -

If any of the above resonates with you, let's talk.

+

If any of the above resonates with you, let's talk.

-

- Feel free to send me an email or connect on - linkedin with a message. -

+

+ Feel free to send me an email or connect on + linkedin with a message. +

-

- Please no recruiters. I'm happy where I'm at. 🤠 -

+

+ Please no recruiters. I'm happy where I'm at. 🤠 +

diff --git a/src/lib/components/Header.svelte b/src/lib/components/Header.svelte index 226b21d..648978e 100644 --- a/src/lib/components/Header.svelte +++ b/src/lib/components/Header.svelte @@ -24,7 +24,9 @@ diff --git a/src/lib/components/Icon.svelte b/src/lib/components/Icon.svelte index 7bc2719..9e5a4f4 100644 --- a/src/lib/components/Icon.svelte +++ b/src/lib/components/Icon.svelte @@ -1,15 +1,15 @@ - + diff --git a/src/lib/components/MobileMenu.svelte b/src/lib/components/MobileMenu.svelte index 5211cdf..c3d10f6 100644 --- a/src/lib/components/MobileMenu.svelte +++ b/src/lib/components/MobileMenu.svelte @@ -1,178 +1,179 @@
- - {#if isOpen} - - {/if} + + {#if isOpen} + + {/if}
diff --git a/src/lib/components/NavLink.svelte b/src/lib/components/NavLink.svelte index a49c339..5cd9eb3 100644 --- a/src/lib/components/NavLink.svelte +++ b/src/lib/components/NavLink.svelte @@ -1,14 +1,14 @@ - + diff --git a/src/lib/components/Og.svelte b/src/lib/components/Og.svelte index 89f70cc..45ae275 100644 --- a/src/lib/components/Og.svelte +++ b/src/lib/components/Og.svelte @@ -1,32 +1,32 @@
-
- {message} -
+
+ {message} +
diff --git a/src/lib/components/PostItem.svelte b/src/lib/components/PostItem.svelte index 243494e..84f4c28 100644 --- a/src/lib/components/PostItem.svelte +++ b/src/lib/components/PostItem.svelte @@ -1,41 +1,45 @@ -
-

{item.title}

+
+

+ {item.title} +

-

+

+ +

- {#if item.category} - - {item.category} - - {/if} + > + {item.category} + + {/if} -

- {new Date(item.date).toISOString().slice(0, 10)} -

-
+

+ {new Date(item.date).toISOString().slice(0, 10)} +

+
diff --git a/src/lib/components/ProjectItem.svelte b/src/lib/components/ProjectItem.svelte index f2e24fc..74f3bad 100644 --- a/src/lib/components/ProjectItem.svelte +++ b/src/lib/components/ProjectItem.svelte @@ -1,24 +1,26 @@ -
-

{item.name}

+
+

+ {item.name} +

-

{item.description}

-
+

{item.description}

+
diff --git a/src/lib/components/Slice.svelte b/src/lib/components/Slice.svelte index a80c150..deaef54 100644 --- a/src/lib/components/Slice.svelte +++ b/src/lib/components/Slice.svelte @@ -1,50 +1,50 @@
- {#if title} -
-

+

- - {title} - -

-
- {/if} -
-

- - -

-
+ > + + {title} + + +
+ {/if} +
+

+ + +

+
diff --git a/src/lib/content.js b/src/lib/content.js index 65f0df7..4c24f94 100644 --- a/src/lib/content.js +++ b/src/lib/content.js @@ -1,17 +1,17 @@ -import { dev } from '$app/environment'; -import fetch from 'node-fetch'; +import { dev } from '$app/environment' +import fetch from 'node-fetch' import { - GH_USER_REPO, - APPROVED_POSTERS_GH_USERNAME, - GH_PUBLISHED_TAGS, - REPO_OWNER -} from './siteConfig'; -import { slugify, readingTime, baseIssueContent, formatContent } from './utils'; -import parse from 'parse-link-header'; - -let allBlogposts = []; -let allGalleries = []; -let allPosts = []; + GH_USER_REPO, + APPROVED_POSTERS_GH_USERNAME, + GH_PUBLISHED_TAGS, + REPO_OWNER, +} from './siteConfig' +import { slugify, readingTime, baseIssueContent, formatContent } from './utils' +import parse from 'parse-link-header' + +let allBlogposts = [] +let allGalleries = [] +let allPosts = [] /* * Gets all github issues with a provided label. @@ -21,110 +21,115 @@ let allPosts = []; * Gallery pages: 'Gallery' */ export async function listContentFromIssues(label) { - let allContentWithLabel = []; - let next = null; - - const authheader = process.env.GH_TOKEN && { - Authorization: `token ${process.env.GH_TOKEN}` - }; - - let url = - `https://api.github.com/repos/${GH_USER_REPO}/issues?` + - new URLSearchParams({ - state: 'all', - labels: label, - per_page: '100' - }); - - // pull issues created by owner only if allowed author = repo owner - if (APPROVED_POSTERS_GH_USERNAME.length === 1 && APPROVED_POSTERS_GH_USERNAME[0] === REPO_OWNER) { - url += '&' + new URLSearchParams({ creator: REPO_OWNER }); - } - - do { - const res = await fetch(next?.url ?? url, { - headers: authheader - }); - - const issues = await res.json(); - if ('message' in issues && res.status > 400) - throw new Error(res.status + ' ' + res.statusText + '\n' + (issues && issues.message)); - - issues.forEach((issue) => { - if (APPROVED_POSTERS_GH_USERNAME.includes(issue.user.login)) { - allContentWithLabel.push(parseIssue(issue, label)); - } - }); - const headers = parse(res.headers.get('Link')); - next = headers && headers.next; - } while (next); - - allContentWithLabel.sort((a, b) => b.date.valueOf() - a.date.valueOf()); // use valueOf to make TS happy https://stackoverflow.com/a/60688789/1106414 - return allContentWithLabel; + let allContentWithLabel = [] + let next = null + + const authheader = process.env.GH_TOKEN && { + Authorization: `token ${process.env.GH_TOKEN}`, + } + + let url = + `https://api.github.com/repos/${GH_USER_REPO}/issues?` + + new URLSearchParams({ + state: 'all', + labels: label, + per_page: '100', + }) + + // pull issues created by owner only if allowed author = repo owner + if ( + APPROVED_POSTERS_GH_USERNAME.length === 1 && + APPROVED_POSTERS_GH_USERNAME[0] === REPO_OWNER + ) { + url += '&' + new URLSearchParams({ creator: REPO_OWNER }) + } + + do { + const res = await fetch(next?.url ?? url, { + headers: authheader, + }) + + const issues = await res.json() + if ('message' in issues && res.status > 400) + throw new Error( + res.status + ' ' + res.statusText + '\n' + (issues && issues.message) + ) + + issues.forEach((issue) => { + if (APPROVED_POSTERS_GH_USERNAME.includes(issue.user.login)) { + allContentWithLabel.push(parseIssue(issue, label)) + } + }) + const headers = parse(res.headers.get('Link')) + next = headers && headers.next + } while (next) + + allContentWithLabel.sort((a, b) => b.date.valueOf() - a.date.valueOf()) // use valueOf to make TS happy https://stackoverflow.com/a/60688789/1106414 + return allContentWithLabel } // searches the list of content returned and matches based on slug export async function getContent(slug) { - // get all posts if not already done - or in development - if (dev || allPosts.length === 0) { - console.log('loading allPosts'); - allBlogposts = await listContentFromIssues('Published'); - allGalleries = await listContentFromIssues('Gallery'); - allPosts = [...allBlogposts, ...allGalleries]; - console.log('loaded ' + allBlogposts.length + ' blogposts'); - console.log('loaded ' + allGalleries.length + ' galleries'); - console.log('loaded ' + allPosts.length + ' posts from issues'); - - if (!allPosts.length) - throw new Error( - 'failed to load posts from github issues for some reason. check token' + - process.env.GH_TOKEN - ); - } - if (!allPosts.length) throw new Error('no posts'); - // find the issue that matches this slug - const post = allPosts.find((p) => p.slug === slug); - if (post) { - const content = await formatContent(post.content); - - return { ...post, content }; - } else { - throw new Error('Issue not found for slug: ' + slug); - } + // get all posts if not already done - or in development + if (dev || allPosts.length === 0) { + console.log('loading allPosts') + allBlogposts = await listContentFromIssues('Published') + allGalleries = await listContentFromIssues('Gallery') + allPosts = [...allBlogposts, ...allGalleries] + console.log('loaded ' + allBlogposts.length + ' blogposts') + console.log('loaded ' + allGalleries.length + ' galleries') + console.log('loaded ' + allPosts.length + ' posts from issues') + + if (!allPosts.length) + throw new Error( + 'failed to load posts from github issues for some reason. check token' + + process.env.GH_TOKEN + ) + } + if (!allPosts.length) throw new Error('no posts') + // find the issue that matches this slug + const post = allPosts.find((p) => p.slug === slug) + if (post) { + const content = await formatContent(post.content) + + return { ...post, content } + } else { + throw new Error('Issue not found for slug: ' + slug) + } } // format github issue into object that page type expects. // work pages are loaded using localContent.js for .svx files, not github issues function parseIssue(issue, label) { - const base = baseIssueContent(issue); - const data = base.frontmatter; - - let post; - - switch (label) { - case 'Gallery': - post = { - type: 'gallery', - ...base, - alt: data.alt - }; - break; - case 'Published': - default: - let tags = []; - if (data.tags) tags = Array.isArray(data.tags) ? data.tags : [data.tags]; - tags = tags.map((tag) => tag.toLowerCase()); - - post = { - type: 'blog', - ...base, - category: data.category?.toLowerCase() || 'note', - tags, - readingTime: readingTime(base.content) - }; - - break; - } - - return post; + const base = baseIssueContent(issue) + const data = base.frontmatter + + let post + + switch (label) { + case 'Gallery': + post = { + type: 'gallery', + ...base, + alt: data.alt, + } + break + case 'Published': + default: + let tags = [] + if (data.tags) tags = Array.isArray(data.tags) ? data.tags : [data.tags] + tags = tags.map((tag) => tag.toLowerCase()) + + post = { + type: 'blog', + ...base, + category: data.category?.toLowerCase() || 'note', + tags, + readingTime: readingTime(base.content), + } + + break + } + + return post } diff --git a/src/lib/localContent.js b/src/lib/localContent.js index 9ab64c1..984661a 100644 --- a/src/lib/localContent.js +++ b/src/lib/localContent.js @@ -1,50 +1,51 @@ -import { dev } from '$app/environment'; +import { dev } from '$app/environment' -let localContent = []; +let localContent = [] // fetch all markdown posts and shape into item with metadata export const fetchMarkdownPosts = async () => { - const allPostFiles = import.meta.glob('$lib/content/work/*.svx'); - const iterablePostFiles = Object.entries(allPostFiles); - - const allPosts = await Promise.all( - iterablePostFiles.map(async ([path, resolver]) => { - const post = await resolver(); - const { name, url, slug, description, type, date, image } = post.metadata; - - const project = { - content: post.default.render().html, - name, - url, - slug, - description, - type, - date, - image, - path - }; - - return { ...project }; - }) - ); - - localContent = allPosts; - - return allPosts; -}; + const allPostFiles = import.meta.glob('$lib/content/work/*.svx') + const iterablePostFiles = Object.entries(allPostFiles) + + const allPosts = await Promise.all( + iterablePostFiles.map(async ([path, resolver]) => { + const post = await resolver() + const { name, url, slug, description, type, date, image } = post.metadata + + const project = { + content: post.default.render().html, + name, + url, + slug, + description, + type, + date, + image, + path, + } + + return { ...project } + }) + ) + + localContent = allPosts + + return allPosts +} // fetch single markdown post and format it for display export async function fetchMarkdownPost(slug) { - if (dev || localContent.length === 0) { - console.log('loading allProjects'); - localContent = await fetchMarkdownPosts(); - console.log('loaded ' + localContent.length + ' projects'); - if (!localContent.length) throw new Error('failed to load projects for some reason.'); - } - - const project = localContent.find((p) => p.slug === slug); - - return { - ...project - }; + if (dev || localContent.length === 0) { + console.log('loading allProjects') + localContent = await fetchMarkdownPosts() + console.log('loaded ' + localContent.length + ' projects') + if (!localContent.length) + throw new Error('failed to load projects for some reason.') + } + + const project = localContent.find((p) => p.slug === slug) + + return { + ...project, + } } diff --git a/src/lib/siteConfig.js b/src/lib/siteConfig.js index 7f561be..20f55c9 100644 --- a/src/lib/siteConfig.js +++ b/src/lib/siteConfig.js @@ -1,18 +1,18 @@ // export const SITE_URL = 'https://heffner.netlify.app'; -export const SITE_URL = 'https://heffner.dev'; -export const GH_USER = 'tjheffner'; -export const GH_USER_REPO = 'tjheffner/heffdotdev'; // used for pulling github issues and offering comments -export const SITE_TITLE = 'heffner.dev'; -export const SITE_DESCRIPTION = 'personal site of tanner heffner'; -export const DEFAULT_OG_IMAGE = 'https://heffner.dev/og?message=heffner.dev'; -export const MY_TWITTER_HANDLE = 'foodpyramids'; +export const SITE_URL = 'https://heffner.dev' +export const GH_USER = 'tjheffner' +export const GH_USER_REPO = 'tjheffner/heffdotdev' // used for pulling github issues and offering comments +export const SITE_TITLE = 'heffner.dev' +export const SITE_DESCRIPTION = 'personal site of tanner heffner' +export const DEFAULT_OG_IMAGE = 'https://heffner.dev/og?message=heffner.dev' +export const MY_TWITTER_HANDLE = 'foodpyramids' -export const APPROVED_POSTERS_GH_USERNAME = ['tjheffner']; -export const GH_PUBLISHED_TAGS = ['Published']; +export const APPROVED_POSTERS_GH_USERNAME = ['tjheffner'] +export const GH_PUBLISHED_TAGS = ['Published'] // auto generated variables -export const REPO_URL = 'https://github.com/' + GH_USER_REPO; -export const REPO_OWNER = GH_USER_REPO.split('/')[0]; +export const REPO_URL = 'https://github.com/' + GH_USER_REPO +export const REPO_OWNER = GH_USER_REPO.split('/')[0] // dont forget process.env.GH_TOKEN // if supplied, raises rate limit from 60 to 5000 diff --git a/src/lib/types.d.ts b/src/lib/types.d.ts index 1630298..f3a9afd 100644 --- a/src/lib/types.d.ts +++ b/src/lib/types.d.ts @@ -1,108 +1,108 @@ export type BaseContentItem = { - content: string; - frontmatter: { - [key: string]: string; - }; - title: string; - subtitle: string; - description: string; - canonical: string; - slug: string; - date: Date; - ghMetadata: GHMetadata; - image: string; -}; + content: string + frontmatter: { + [key: string]: string + } + title: string + subtitle: string + description: string + canonical: string + slug: string + date: Date + ghMetadata: GHMetadata + image: string +} export type BlogItem = BaseContentItem & { - type: 'blog'; - category: string; - tags: string[]; - readingTime: string; -}; + type: 'blog' + category: string + tags: string[] + readingTime: string +} export type GalleryItem = BaseContentItem & { - type: 'gallery'; - images: GalleryImage[]; -}; + type: 'gallery' + images: GalleryImage[] +} export type GalleryImage = { - src: string; - alt: string; - size: string; -}; + src: string + alt: string + size: string +} export type GHMetadata = { - issueUrl: string; - commentsUrl: string; - title: string; - created_at: Date; - updated_at: Date; - reactions: GHReactions; -}; + issueUrl: string + commentsUrl: string + title: string + created_at: Date + updated_at: Date + reactions: GHReactions +} export type GHReactions = { - total_count: number; - '+1': number; - '-1': number; - laugh: number; - hooray: number; - confused: number; - heart: number; - rocket: number; - eyes: number; -}; + total_count: number + '+1': number + '-1': number + laugh: number + hooray: number + confused: number + heart: number + rocket: number + eyes: number +} export type GHComment = { - body: string; - user: GHUser; - created_at: Date; - updated_at: Date; - html_url: string; - issue_url: string; - author_association: string; - reactions: GHReactions; -}; + body: string + user: GHUser + created_at: Date + updated_at: Date + html_url: string + issue_url: string + author_association: string + reactions: GHReactions +} export type GHUser = { - login: string; - avatar_url: string; - id: number; - node_id: string; - avatar_url: string; - gravatar_id: string; - url: string; - html_url: string; - followers_url: string; - following_url: string; - gists_url: string; - starred_url: string; - subscriptions_url: string; - organizations_url: string; - repos_url: string; - events_url: string; - received_events_url: string; - type: 'User'; - site_admin: boolean; -}; + login: string + avatar_url: string + id: number + node_id: string + avatar_url: string + gravatar_id: string + url: string + html_url: string + followers_url: string + following_url: string + gists_url: string + starred_url: string + subscriptions_url: string + organizations_url: string + repos_url: string + events_url: string + received_events_url: string + type: 'User' + site_admin: boolean +} export type GithubIssue = { - user: GHUser; - labels: { - name: string; - }[]; - title: string; - body: string; - created_at: Date; - updated_at: Date; - html_url: string; - comments_url: string; - reactions: GHReactions; -}; + user: GHUser + labels: { + name: string + }[] + title: string + body: string + created_at: Date + updated_at: Date + html_url: string + comments_url: string + reactions: GHReactions +} export type Project = { - type: 'project'; - frontmatter: { - [key: string]: string; - }; - content: string; -}; + type: 'project' + frontmatter: { + [key: string]: string + } + content: string +} diff --git a/src/lib/utils.js b/src/lib/utils.js index 00e26db..0dc025f 100644 --- a/src/lib/utils.js +++ b/src/lib/utils.js @@ -1,32 +1,32 @@ -import grayMatter from 'gray-matter'; -import { compile } from 'mdsvex'; -import { remark } from 'remark'; -import remarkParse from 'remark-parse'; -import remarkStringify from 'remark-stringify'; -import rehypeStringify from 'rehype-stringify'; -import rehypeSlug from 'rehype-slug'; -import rehypeAutoLink from 'rehype-autolink-headings'; +import grayMatter from 'gray-matter' +import { compile } from 'mdsvex' +import { remark } from 'remark' +import remarkParse from 'remark-parse' +import remarkStringify from 'remark-stringify' +import rehypeStringify from 'rehype-stringify' +import rehypeSlug from 'rehype-slug' +import rehypeAutoLink from 'rehype-autolink-headings' -const remarkPlugins = undefined; +const remarkPlugins = undefined const rehypePlugins = [ - rehypeStringify, - rehypeSlug, - [ - rehypeAutoLink, - { - behavior: 'wrap', - properties: { class: 'hover:text-yellow-100 no-underline' } - } - ] -]; + rehypeStringify, + rehypeSlug, + [ + rehypeAutoLink, + { + behavior: 'wrap', + properties: { class: 'hover:text-yellow-100 no-underline' }, + }, + ], +] /** * @param {string} text * @returns {string} */ export function readingTime(text) { - let minutes = Math.ceil(text.trim().split(' ').length / 225); - return minutes > 1 ? `${minutes} minutes` : `${minutes} minute`; + let minutes = Math.ceil(text.trim().split(' ').length / 225) + return minutes > 1 ? `${minutes} minutes` : `${minutes} minute` } /** @@ -34,15 +34,15 @@ export function readingTime(text) { * @returns {string} */ export function slugify(text) { - return text - .toString() // Cast to string (optional) - .normalize('NFKD') // The normalize() using NFKD method returns the Unicode Normalization Form of a given string. - .toLowerCase() // Convert the string to lowercase letters - .trim() // Remove whitespace from both sides of a string (optional) - .replace(/\s+/g, '-') // Replace spaces with hyphen - .replace(/[^\w-]+/g, '') // Remove all non-word chars - .replace(/--+/g, '-') // Replace multiple hyphen with single hyphen - .replace(/(^-|-$)/g, ''); // Remove leading or trailing hyphen + return text + .toString() // Cast to string (optional) + .normalize('NFKD') // The normalize() using NFKD method returns the Unicode Normalization Form of a given string. + .toLowerCase() // Convert the string to lowercase letters + .trim() // Remove whitespace from both sides of a string (optional) + .replace(/\s+/g, '-') // Replace spaces with hyphen + .replace(/[^\w-]+/g, '') // Remove all non-word chars + .replace(/--+/g, '-') // Replace multiple hyphen with single hyphen + .replace(/(^-|-$)/g, '') // Remove leading or trailing hyphen } /** @@ -52,57 +52,61 @@ export function slugify(text) { * @returns {import('./types').BaseContentItem} */ export function baseIssueContent(issue) { - const src = issue.body; - const { content, data } = grayMatter(src); - let title = data.title ?? issue.title; - let slug; - if (data.slug) { - slug = data.slug; - } else { - slug = slugify(title); - } + const src = issue.body + const { content, data } = grayMatter(src) + let title = data.title ?? issue.title + let slug + if (data.slug) { + slug = data.slug + } else { + slug = slugify(title) + } - let description = data.description ?? content.trim().split('\n')[0]; - // extract plain text from markdown - description = remark().use(remarkParse).use(remarkStringify).processSync(description).toString(); - description = description.replace(/\n/g, ' '); - // strip html - description = description.replace(/<[^>]*>?/gm, ''); - // strip markdown - description = description.replace(/[[\]]/gm, ''); - // strip markdown - description = description.replace(/[[\]]/gm, ''); + let description = data.description ?? content.trim().split('\n')[0] + // extract plain text from markdown + description = remark() + .use(remarkParse) + .use(remarkStringify) + .processSync(description) + .toString() + description = description.replace(/\n/g, ' ') + // strip html + description = description.replace(/<[^>]*>?/gm, '') + // strip markdown + description = description.replace(/[[\]]/gm, '') + // strip markdown + description = description.replace(/[[\]]/gm, '') - return { - frontmatter: data, - issueNumber: issue.number, - slug: slug, - title, - description, - content, - image: data.image ?? data.cover_image, - date: new Date(data.date ?? issue.created_at), - ghMetadata: { - issueUrl: issue.html_url, - commentsUrl: issue.comments_url, - title: issue.title, - created_at: issue.created_at, - updated_at: issue.updated_at, - reactions: issue.reactions - } - }; + return { + frontmatter: data, + issueNumber: issue.number, + slug: slug, + title, + description, + content, + image: data.image ?? data.cover_image, + date: new Date(data.date ?? issue.created_at), + ghMetadata: { + issueUrl: issue.html_url, + commentsUrl: issue.comments_url, + title: issue.title, + created_at: issue.created_at, + updated_at: issue.updated_at, + reactions: issue.reactions, + }, + } } export async function formatContent(content) { - const formatted = content - .replace(/\n{% youtube (.*?) %}/g, (_, x) => { - // https://stackoverflow.com/a/27728417/1106414 - function youtube_parser(url) { - var rx = /^.*(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|&v(?:i)?=))([^#&?]*).*/; - return url.match(rx)[1]; - } - const videoId = x.startsWith('https://') ? youtube_parser(x) : x; - return ``; - }) - .replace(/\n{% (tweet|twitter) (.*?) %}/g, (_, _2, x) => { - const url = x.startsWith('https://twitter.com/') ? x : `https://twitter.com/x/status/${x}`; - return ` + aria-hidden="true">` + }) + .replace(/\n{% (tweet|twitter) (.*?) %}/g, (_, _2, x) => { + const url = x.startsWith('https://twitter.com/') + ? x + : `https://twitter.com/x/status/${x}` + return ` - `; - }); + ` + }) - // compile it with mdsvex - const output = ( - await compile(formatted, { - remarkPlugins, - // @ts-ignore - rehypePlugins - }) - ).code - // https://github.com/pngwn/MDsveX/issues/392 - .replace(/>{@html ``}<\/pre>/g, ''); + // compile it with mdsvex + const output = ( + await compile(formatted, { + remarkPlugins, + // @ts-ignore + rehypePlugins, + }) + ).code + // https://github.com/pngwn/MDsveX/issues/392 + .replace(/>{@html ``}<\/pre>/g, '') - return output; + return output } diff --git a/src/mdsvexlayout.svelte b/src/mdsvexlayout.svelte index 937ec97..6093b4c 100644 --- a/src/mdsvexlayout.svelte +++ b/src/mdsvexlayout.svelte @@ -1,9 +1,9 @@
- +
diff --git a/src/routes/(main)/+error.svelte b/src/routes/(main)/+error.svelte index 5a18d70..326ddca 100644 --- a/src/routes/(main)/+error.svelte +++ b/src/routes/(main)/+error.svelte @@ -1,61 +1,70 @@ - {title} + {title}
-

{$page.status}: {title}

- - {#if $page.status === 404} -

There is no post at the slug {$page.url.pathname}.

-

Try searching for it here!

-

- If you believe this was a bug, please let me know! Open an issue here. -

- {:else} -

{message}

- {/if} - {#if dev && $page.error.stack} -
{$page.error.stack}
- {/if} +

{$page.status}: {title}

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

+ There is no post at the slug {$page.url.pathname}. +

+

+ Try searching for it here! +

+

+ If you believe this was a bug, please let me know! Open an issue here. +

+ {:else} +

{message}

+ {/if} + {#if dev && $page.error.stack} +
{$page.error.stack}
+ {/if}
diff --git a/src/routes/(main)/+layout.svelte b/src/routes/(main)/+layout.svelte index 83ab59a..39c0c8e 100644 --- a/src/routes/(main)/+layout.svelte +++ b/src/routes/(main)/+layout.svelte @@ -1,76 +1,89 @@ - + -
-
+
+
{#key data.currentRoute} -
- +
+ - {#if showBackToTop} - Back to top - {/if} -
+ {#if showBackToTop} + Back to top + {/if} +
-
-
-
-
- Posts - Work - Gallery - About -
+
+
+
+
+ Posts + Work + Gallery + About +
-
- - - - - +
+ + + + + - - + clip-rule="evenodd" + /> + + - - - -
-
-
+ + + +
+
+ {/key} diff --git a/src/routes/(main)/+page.svelte b/src/routes/(main)/+page.svelte index e091a48..58c0bb2 100644 --- a/src/routes/(main)/+page.svelte +++ b/src/routes/(main)/+page.svelte @@ -1,70 +1,87 @@ - {SITE_TITLE} - - - - - - - - - - - - - + {SITE_TITLE} + + + + + + + + + + + + +
- -

🌊 welcome to my page 🌊

-
+ +

+ 🌊 welcome to my page 🌊 +

+
- -

This site is perpetually under construction but coming along. 😎

+ +

This site is perpetually under construction but coming along. 😎

-

- It's mostly a place for me to post recipes I like and - travel photos, with the occasional technical post or personal blog - mixed in. Some work-related stuff can be found here - too, but that's not what I want to talk about. -

-

-

Thanks for stopping by, check out the links that work. ✌️

-
+

+ It's mostly a place for me to post recipes I like + and + travel photos, with the occasional technical post + or personal blog mixed in. Some work-related + stuff + can be found here too, but that's not what I want to talk + about. +

+

+

Thanks for stopping by, check out the links that work. ✌️

+
- - - -

🎶 - Stove God Cooks, Nujabes, Fleetwood Mac

-

🍿 - The Righteous Gemstones, Saltburn

-

📚 - Thinking, Fast And Slow by Daniel Kahneman

-

🎮 - Baldur's Gate 3, Monster Train, RDR2

+ + + +

+ 🎶 - Stove God Cooks, Nujabes, Fleetwood Mac +

+

🍿 - The Righteous Gemstones, Saltburn

+

+ 📚 - Thinking, Fast And Slow by Daniel Kahneman +

+

+ 🎮 - Baldur's Gate 3, Monster Train, RDR2 +

-

Last updated: Jan 12, 2024

-
+

Last updated: Jan 12, 2024

+
- -

In no particular order...

-
- - - - - - -
-
+ +

In no particular order...

+
+ + + + + + +
+
diff --git a/src/routes/(main)/[slug]/+page.js b/src/routes/(main)/[slug]/+page.js index 4857a4f..876e552 100644 --- a/src/routes/(main)/[slug]/+page.js +++ b/src/routes/(main)/[slug]/+page.js @@ -1,37 +1,37 @@ -import { error, redirect } from '@sveltejs/kit'; -import { REPO_URL } from '$lib/siteConfig'; +import { error, redirect } from '@sveltejs/kit' +import { REPO_URL } from '$lib/siteConfig' -export const csr = true; // https://github.com/sveltejs/kit/pull/6446 +export const csr = true // https://github.com/sveltejs/kit/pull/6446 export async function load({ params, fetch, setHeaders }) { - const slug = params.slug; + const slug = params.slug - // redirect these slugs to appropriate routes - if (slug === 'feed' || slug === 'rss' || slug === 'rss.xml') { - throw redirect(308, '/rss.xml'); - } - if (slug === 'sitemap' || slug === 'sitemap.xml') { - throw redirect(308, '/sitemap.xml'); - } + // redirect these slugs to appropriate routes + if (slug === 'feed' || slug === 'rss' || slug === 'rss.xml') { + throw redirect(308, '/rss.xml') + } + if (slug === 'sitemap' || slug === 'sitemap.xml') { + throw redirect(308, '/sitemap.xml') + } - let res = null; - res = await fetch(`/api/getContent/${slug}.json`); - if (res.status > 400) { - throw error(res.status, await res.text()); - } - const json = await res.json(); + let res = null + res = await fetch(`/api/getContent/${slug}.json`) + if (res.status > 400) { + throw error(res.status, await res.text()) + } + const json = await res.json() - // because [slug] is a catchall, it gets gallery slugs too. redirect them. - // e.g. /japan -> /gallery/japan - if (json.type === 'gallery') { - throw redirect(308, `/gallery/${json.slug}`); - } + // because [slug] is a catchall, it gets gallery slugs too. redirect them. + // e.g. /japan -> /gallery/japan + if (json.type === 'gallery') { + throw redirect(308, `/gallery/${json.slug}`) + } - setHeaders({ - 'Cache-Control': 'public, max-age=60' - }); - return { - json, - slug, - REPO_URL - }; + setHeaders({ + 'Cache-Control': 'public, max-age=60', + }) + return { + json, + slug, + REPO_URL, + } } diff --git a/src/routes/(main)/[slug]/+page.svelte b/src/routes/(main)/[slug]/+page.svelte index 528083a..368dded 100644 --- a/src/routes/(main)/[slug]/+page.svelte +++ b/src/routes/(main)/[slug]/+page.svelte @@ -1,91 +1,109 @@ - {json.title} - + {json.title} + - - - - - - - - - - {#if json.image} - - - - {:else} - - - - {/if} + + + + + + + + + + {#if json.image} + + + + {:else} + + + + {/if} Back
-

- {json.title} -

-
-

tjheffner

-

- {#if json.ghMetadata.reactions.total_count > 0} - - {json.ghMetadata.reactions.total_count} - {#if json.ghMetadata.reactions.total_count === 1}reaction{:else}reactions{/if} - - {/if} - {new Date(json.date).toISOString().slice(0, 10)} -

-
+

+ {json.title} +

+
+

+ tjheffner +

+

+ {#if json.ghMetadata.reactions.total_count > 0} + + {json.ghMetadata.reactions.total_count} + {#if json.ghMetadata.reactions.total_count === 1}reaction{:else}reactions{/if} + + {/if} + {new Date(json.date).toISOString().slice(0, 10)} +

+
-
+
- -
- {@html json.content} -
+
+ {@html json.content} +
-
-
+
+
-
- -
+
+ +
diff --git a/src/routes/(main)/blog/+page.js b/src/routes/(main)/blog/+page.js index e19a9bb..ad319d6 100644 --- a/src/routes/(main)/blog/+page.js +++ b/src/routes/(main)/blog/+page.js @@ -1,16 +1,16 @@ -import { error } from '@sveltejs/kit'; +import { error } from '@sveltejs/kit' // export const prerender = true; // turned off so it refreshes quickly export async function load({ setHeaders, fetch }) { - const res = await fetch(`/api/listContent.json`); + const res = await fetch(`/api/listContent.json`) - if (res.status > 400) { - throw error(res.status, await res.text()); - } + if (res.status > 400) { + throw error(res.status, await res.text()) + } - /** @type {import('$lib/types').ContentItem[]} */ - const items = await res.json(); - setHeaders({ - 'Cache-Control': 'public, max-age=60' // 1 minute - }); - return { items }; + /** @type {import('$lib/types').ContentItem[]} */ + const items = await res.json() + setHeaders({ + 'Cache-Control': 'public, max-age=60', // 1 minute + }) + return { items } } diff --git a/src/routes/(main)/blog/+page.svelte b/src/routes/(main)/blog/+page.svelte index 38f6004..7a97e35 100644 --- a/src/routes/(main)/blog/+page.svelte +++ b/src/routes/(main)/blog/+page.svelte @@ -1,240 +1,251 @@ - heffner.dev | posts - - - - + heffner.dev | posts + + + + -
-

Posts

-

- In total, I've written {items.length} posts on my blog. Use the search below to filter. -

- -
- +

+ Posts +

+

+ In total, I've written {items.length} posts on my blog. Use the search below + to filter. +

+ +
+ - - - -
- - -
- Filter: - - - - - -
- -
- - - {#if list.length} -
    - {#each list as item} -
  • - - {item.description} - -
  • - {/each} -
- {#if !showAll} -
-
+ + +
+ Filter: + + + + + +
+ +
+ + + {#if list.length} +
    + {#each list as item} +
  • + + {item.description} + +
  • + {/each} +
+ {#if !showAll} +
+ -
- {/if} - {:else if filterStr} -
- No posts found for - {filterStr}. -
- +
+ {/if} + {:else if filterStr} +
+ No posts found for + {filterStr}. +
+ - {:else} -
Search something else!
- {/if} - - + > + Clear your search + + {:else} +
Search something else!
+ {/if} + +
diff --git a/src/routes/(main)/gallery/+page.js b/src/routes/(main)/gallery/+page.js index d1c67e2..76e4f58 100644 --- a/src/routes/(main)/gallery/+page.js +++ b/src/routes/(main)/gallery/+page.js @@ -1,16 +1,16 @@ -import { error } from '@sveltejs/kit'; +import { error } from '@sveltejs/kit' // export const prerender = true; // turned off so it refreshes quickly export async function load({ setHeaders, fetch }) { - const res = await fetch(`/api/listGallery.json`); + const res = await fetch(`/api/listGallery.json`) - if (res.status > 400) { - throw error(res.status, await res.text()); - } + if (res.status > 400) { + throw error(res.status, await res.text()) + } - /** @type {import('$lib/types').GalleryItem[]} */ - const items = await res.json(); - setHeaders({ - 'Cache-Control': 'public, max-age=60' // 1 minute - }); - return { items }; + /** @type {import('$lib/types').GalleryItem[]} */ + const items = await res.json() + setHeaders({ + 'Cache-Control': 'public, max-age=60', // 1 minute + }) + return { items } } diff --git a/src/routes/(main)/gallery/+page.svelte b/src/routes/(main)/gallery/+page.svelte index af75c6d..cb2b817 100644 --- a/src/routes/(main)/gallery/+page.svelte +++ b/src/routes/(main)/gallery/+page.svelte @@ -1,32 +1,44 @@ - heffner.dev | gallery - - - - + heffner.dev | gallery + + + +
- -

Gallery

-

details, photos, ephemera from past adventures

-
+ +

+ Gallery +

+

+ details, photos, ephemera from past adventures +

+
- {#each items as trip} - - {trip.alt} - {trip.description} - - - {/each} + {#each items as trip} + + {trip.alt} + {trip.description} + + + {/each}
diff --git a/src/routes/(main)/gallery/[slug]/+page.js b/src/routes/(main)/gallery/[slug]/+page.js index e68b476..853bc5a 100644 --- a/src/routes/(main)/gallery/[slug]/+page.js +++ b/src/routes/(main)/gallery/[slug]/+page.js @@ -1,21 +1,21 @@ -import { error, redirect } from '@sveltejs/kit'; -import { REPO_URL } from '$lib/siteConfig'; +import { error, redirect } from '@sveltejs/kit' +import { REPO_URL } from '$lib/siteConfig' -export const csr = true; // https://github.com/sveltejs/kit/pull/6446 +export const csr = true // https://github.com/sveltejs/kit/pull/6446 export async function load({ params, url, fetch, setHeaders }) { - const slug = params.slug; + const slug = params.slug - let res = null; - res = await fetch(`/api/gallery/${slug}.json`); - if (res.status > 400) { - throw error(res.status, await res.text()); - } - setHeaders({ - 'Cache-Control': 'public, max-age=60' - }); - return { - json: await res.json(), - slug, - REPO_URL - }; + let res = null + res = await fetch(`/api/gallery/${slug}.json`) + if (res.status > 400) { + throw error(res.status, await res.text()) + } + setHeaders({ + 'Cache-Control': 'public, max-age=60', + }) + return { + json: await res.json(), + slug, + REPO_URL, + } } diff --git a/src/routes/(main)/gallery/[slug]/+page.svelte b/src/routes/(main)/gallery/[slug]/+page.svelte index f4ffa8a..bc69b2d 100644 --- a/src/routes/(main)/gallery/[slug]/+page.svelte +++ b/src/routes/(main)/gallery/[slug]/+page.svelte @@ -1,91 +1,109 @@ - {json.title} - + {json.title} + - - - - - - - - - - {#if json.image} - - - - {:else} - - - - {/if} + + + + + + + + + + {#if json.image} + + + + {:else} + + + + {/if} Back
-

- {json.title} -

-
-

tjheffner

-

- {#if json.ghMetadata.reactions.total_count > 0} - - {json.ghMetadata.reactions.total_count} - {#if json.ghMetadata.reactions.total_count === 1}reaction{:else}reactions{/if} - - {/if} - {new Date(json.date).toISOString().slice(0, 10)} -

-
+

+ {json.title} +

+
+

+ tjheffner +

+

+ {#if json.ghMetadata.reactions.total_count > 0} + + {json.ghMetadata.reactions.total_count} + {#if json.ghMetadata.reactions.total_count === 1}reaction{:else}reactions{/if} + + {/if} + {new Date(json.date).toISOString().slice(0, 10)} +

+
-
+
- -
- {@html json.content} -
+
+ {@html json.content} +
-
-
+
+
-
- -
+
+ +
diff --git a/src/routes/(main)/og/+server.js b/src/routes/(main)/og/+server.js index f741791..b8a4e27 100644 --- a/src/routes/(main)/og/+server.js +++ b/src/routes/(main)/og/+server.js @@ -1,43 +1,43 @@ // see https://geoffrich.net/posts/svelte-social-image/ -import satori from 'satori'; -import { Resvg } from '@resvg/resvg-js'; -import PTSerif from '$lib/font/PTSerif-Regular.ttf'; -import { html as toReactNode } from 'satori-html'; -import Og from '$lib/components/Og.svelte'; +import satori from 'satori' +import { Resvg } from '@resvg/resvg-js' +import PTSerif from '$lib/font/PTSerif-Regular.ttf' +import { html as toReactNode } from 'satori-html' +import Og from '$lib/components/Og.svelte' -const height = 400; -const width = 800; +const height = 400 +const width = 800 /** @type {import('./$types').RequestHandler} */ export const GET = async ({ url }) => { - const message = url.searchParams.get('message') ?? undefined; - const result = Og.render({ message }); - const element = toReactNode(`${result.html}`); + const message = url.searchParams.get('message') ?? undefined + const result = Og.render({ message }) + const element = toReactNode(`${result.html}`) - const svg = await satori(element, { - fonts: [ - { - name: 'PT Serif', - data: Buffer.from(PTSerif), - style: 'normal' - } - ], - height, - width - }); + const svg = await satori(element, { + fonts: [ + { + name: 'PT Serif', + data: Buffer.from(PTSerif), + style: 'normal', + }, + ], + height, + width, + }) - const resvg = new Resvg(svg, { - fitTo: { - mode: 'width', - value: width - } - }); + const resvg = new Resvg(svg, { + fitTo: { + mode: 'width', + value: width, + }, + }) - const image = resvg.render(); + const image = resvg.render() - return new Response(image.asPng(), { - headers: { - 'content-type': 'image/png' - } - }); -}; + return new Response(image.asPng(), { + headers: { + 'content-type': 'image/png', + }, + }) +} diff --git a/src/routes/(main)/resume/+page.svelte b/src/routes/(main)/resume/+page.svelte index ffe73d2..55a55ae 100644 --- a/src/routes/(main)/resume/+page.svelte +++ b/src/routes/(main)/resume/+page.svelte @@ -1,106 +1,126 @@ - Tanner Heffner's Resume - - - - + Tanner Heffner's Resume + + + +
- -

- Creative technologist passionate about design systems, responsive web design and - accessibility. -

-
+ +

+ Creative technologist passionate about design systems, responsive web + design and accessibility. +

+
- -

I believe the web should be enjoyable for all.

-

To me, that means that websites should:

+ +

+ I believe the web should be enjoyable for all. +

+

To me, that means that websites should:

-
    -
  • - Load quickly with minimal page bloat and disruption (no unnecessary tracking, excessive and - obtrusive advertisements, autoplaying videos etc.) -
  • -
  • - Be functional for all users regardless of their manner of access (sighted or screenreaders, - navigable by keyboard, mouse or touch, etc.) -
  • -
  • At the bare minimum, pass WCAG-AA accessibility guidelines
  • -
+
    +
  • + Load quickly with minimal page bloat and disruption (no unnecessary + tracking, excessive and obtrusive advertisements, autoplaying videos + etc.) +
  • +
  • + Be functional for all users regardless of their manner of access + (sighted or screenreaders, navigable by keyboard, mouse or touch, etc.) +
  • +
  • At the bare minimum, pass WCAG-AA accessibility guidelines
  • +
-

The web can be so much more than it is. Let's make it better.

-
+

+ The web can be so much more than it is. Let's make it better. +

+
- -

- Working across the web tech stack, I've consulted, architected, and delivered numerous projects for clients big and small with all sorts of use cases. -

+ +

+ Working across the web tech stack, I've consulted, architected, and + delivered numerous projects for clients big and small with + all sorts of use cases. +

-

- I love to design and architect design systems and responsive component libraries in close - collaboration with designers. In the past, I've helped build and maintain agency-wide - front-end tooling for consistently delivering best practices to clients and stakeholders. - Rapid prototyping is my jam. -

+

+ I love to design and architect design systems and responsive component + libraries in close collaboration with designers. In the past, I've helped + build and maintain agency-wide front-end tooling for consistently + delivering best practices to clients and stakeholders. Rapid prototyping + is my jam. +

-

- I'm comfortable working in and with a number of languages and technologies, but have a strong - preference towards modern Javascript and CSS. I've been a mostly off-and-on contributor to the Drupal ecosystem - for eight+ years. A long time ago (read: 2015), I dove into node and an early edition of - Pattern Lab - to build a command line-based training module around atomic design concepts for - nodeschool.io. It is not being maintained. -

+

+ I'm comfortable working in and with a number of languages and + technologies, but have a strong preference towards modern Javascript and + CSS. I've been a mostly off-and-on contributor to the Drupal ecosystem + for eight+ years. A long time ago (read: 2015), I dove into node and an early + edition of + Pattern Lab + to build a command line-based training module around atomic design concepts + for + nodeschool.io. It is not being + maintained. +

-

- I mentor new and junior developers and perform duties as a front-end tech lead. -

-
+

+ I mentor new and junior developers and perform duties as a front-end tech + lead. +

+
- -

Designing for the web since '05.

+ +

Designing for the web since '05.

-

- I hacked together my first CSS file at age 12 customizing a phpBB forum theme. Years later in - a college art class, I was exposed to the Processing language - and rediscovered how magical that feeling of creation through code was. -

+

+ I hacked together my first CSS file at age 12 customizing a phpBB forum + theme. Years later in a college art class, I was exposed to the Processing language and rediscovered how magical that feeling of creation through code + was. +

-

- After graduating from the University of Oregon with a journalism degree (minor in multimedia - art), I continued my education with a four-month programming bootcamp. I've been a - professional web developer since 2015, across all parts of the web stack. That means I know - just enough SQL to be dangerous, but I'd rather work on the front end. 😉 -

+

+ After graduating from the University of Oregon with a journalism degree + (minor in multimedia art), I continued my education with a four-month + programming bootcamp. I've been a professional web developer since 2015, + across all parts of the web stack. That means I know just enough SQL to be + dangerous, but I'd rather work on the front end. 😉 +

-

- Currently, I'm a senior software developer at Agile6, - working on the modernization effort for VA.gov serving millions of - veterans nationwide. -

+

+ Currently, I'm a senior software developer at Agile6, working on the modernization effort for + VA.gov serving millions of veterans nationwide. +

-

- Previously: Instrument, - Phase2 -

-
+

+ Previously: Instrument, + Phase2 +

+
- +
diff --git a/src/routes/(main)/work/+page.js b/src/routes/(main)/work/+page.js index 0637c96..af1a7da 100644 --- a/src/routes/(main)/work/+page.js +++ b/src/routes/(main)/work/+page.js @@ -1,16 +1,16 @@ -import { error } from '@sveltejs/kit'; +import { error } from '@sveltejs/kit' export async function load({ setHeaders, fetch }) { - const res = await fetch(`/api/listLocalContent.json`); + const res = await fetch(`/api/listLocalContent.json`) - if (res.status > 400) { - throw error(res.status, await res.text()); - } + if (res.status > 400) { + throw error(res.status, await res.text()) + } - /** @type {import('$lib/types').Project[]} */ - const items = await res.json(); - setHeaders({ - 'Cache-Control': 'public, max-age=60' // 1 minute - }); - return { items }; + /** @type {import('$lib/types').Project[]} */ + const items = await res.json() + setHeaders({ + 'Cache-Control': 'public, max-age=60', // 1 minute + }) + return { items } } diff --git a/src/routes/(main)/work/+page.svelte b/src/routes/(main)/work/+page.svelte index 2579fe6..b250545 100644 --- a/src/routes/(main)/work/+page.svelte +++ b/src/routes/(main)/work/+page.svelte @@ -1,66 +1,76 @@ - {SITE_TITLE} | Work - - - + {SITE_TITLE} | Work + + +
- -

Past work

-

- A selection of previously completed projects, personal and professional. -

-
+ +

+ Past work +

+

+ A selection of previously completed projects, personal and professional. +

+
- -
    - {#each data.items as project} - {#if project.type === 'professional'} -
  • - -
  • - {/if} - {/each} -
-

Other clients

-

- Department of Veterans Affairs, Epic Games, Crossfit, Norwegian Cruise Line, PGA Tour, Weight - Watchers, Urban Institute, Wilson Center -

+ +
    + {#each data.items as project} + {#if project.type === 'professional'} +
  • + +
  • + {/if} + {/each} +
+

+ Other clients +

+

+ Department of Veterans Affairs, Epic Games, Crossfit, Norwegian Cruise + Line, PGA Tour, Weight Watchers, Urban Institute, Wilson Center +

- Resume -
+ Resume +
- -
    - {#each data.items as project} - {#if project.type === 'personal'} -
  • - -
  • - {/if} - {/each} -
-
+ +
    + {#each data.items as project} + {#if project.type === 'personal'} +
  • + +
  • + {/if} + {/each} +
+
- +
diff --git a/src/routes/(main)/work/[slug]/+page.js b/src/routes/(main)/work/[slug]/+page.js index a456805..5d01ffb 100644 --- a/src/routes/(main)/work/[slug]/+page.js +++ b/src/routes/(main)/work/[slug]/+page.js @@ -1,24 +1,24 @@ -import { error } from '@sveltejs/kit'; -import { REPO_URL } from '$lib/siteConfig'; -export const csr = true; // https://github.com/sveltejs/kit/pull/6446 +import { error } from '@sveltejs/kit' +import { REPO_URL } from '$lib/siteConfig' +export const csr = true // https://github.com/sveltejs/kit/pull/6446 export async function load({ params, fetch, setHeaders }) { - const slug = params.slug; - let res = null; - res = await fetch(`/api/work/${slug}.json`); + const slug = params.slug + let res = null + res = await fetch(`/api/work/${slug}.json`) - if (res.status > 400) { - throw error(res.status, await res.text()); - } + if (res.status > 400) { + throw error(res.status, await res.text()) + } - const items = await res.json(); + const items = await res.json() - setHeaders({ - 'Cache-Control': 'public, max-age=60' - }); - return { - ...items, - slug, - REPO_URL - }; + setHeaders({ + 'Cache-Control': 'public, max-age=60', + }) + return { + ...items, + slug, + REPO_URL, + } } diff --git a/src/routes/(main)/work/[slug]/+page.svelte b/src/routes/(main)/work/[slug]/+page.svelte index 5d8596f..c97d6bb 100644 --- a/src/routes/(main)/work/[slug]/+page.svelte +++ b/src/routes/(main)/work/[slug]/+page.svelte @@ -1,63 +1,72 @@ - {name} - - - - - - - - - - - {#if image} - - - {:else} - - - {/if} + {name} + + + + + + + + + + + {#if image} + + + {:else} + + + {/if} Back
- {#if name} -
-

- {name} - ({date.slice(0, 4)}) -

-
- {/if} - - {#if image} - {name} - {/if} - -
- {@html content} -
- - {#if url} - View this project live - {/if} + {#if name} +
+

+ {name} + ({date.slice(0, 4)}) +

+
+ {/if} + + {#if image} + {name} + {/if} + +
+ {@html content} +
+ + {#if url} + View this project live + {/if}
diff --git a/src/routes/(nowrapper)/+layout.svelte b/src/routes/(nowrapper)/+layout.svelte index ce77090..67683c6 100644 --- a/src/routes/(nowrapper)/+layout.svelte +++ b/src/routes/(nowrapper)/+layout.svelte @@ -1,7 +1,7 @@ diff --git a/src/routes/(nowrapper)/christmas/+page.server.js b/src/routes/(nowrapper)/christmas/+page.server.js index ad4328e..9e7ac4b 100644 --- a/src/routes/(nowrapper)/christmas/+page.server.js +++ b/src/routes/(nowrapper)/christmas/+page.server.js @@ -1,8 +1,8 @@ -import { json } from './christmas'; +import { json } from './christmas' /** @type {import('./$types').PageLoad} */ export function load() { - return { - ...json - }; + return { + ...json, + } } diff --git a/src/routes/(nowrapper)/christmas/+page.svelte b/src/routes/(nowrapper)/christmas/+page.svelte index 7c0f222..111a071 100644 --- a/src/routes/(nowrapper)/christmas/+page.svelte +++ b/src/routes/(nowrapper)/christmas/+page.svelte @@ -1,162 +1,182 @@ - - - - - + + + + +
- - + +
-

{data.title}

-

at

-

- {data.where} | {data.when} -

-

- -

-

Schedule

-
    - {#each data.agenda as { event, time }} -
  • -

    {event} - {time}

    -
  • - {/each} -
- -

Menu

- -
    - {#each data.menu as { name, ingredients }} -
  • -

    {name}

    -

    {ingredients}

    -
  • - {/each} -
- -

{data.disclaimer}

+

+

Schedule

+
    + {#each data.agenda as { event, time }} +
  • +

    + {event} - {time} +

    +
  • + {/each} +
+ +

Menu

+ +
    + {#each data.menu as { name, ingredients }} +
  • +

    {name}

    +

    {ingredients}

    +
  • + {/each} +
+ +

{data.disclaimer}

- - + +
diff --git a/src/routes/(nowrapper)/christmas/christmas.css b/src/routes/(nowrapper)/christmas/christmas.css index 6045ad4..d2584dd 100644 --- a/src/routes/(nowrapper)/christmas/christmas.css +++ b/src/routes/(nowrapper)/christmas/christmas.css @@ -1,50 +1,54 @@ body { - background-color: rgb(17 24 39 / var(--tw-bg-opacity)) !important; + background-color: rgb(17 24 39 / var(--tw-bg-opacity)) !important; } body::-webkit-scrollbar-thumb { - background: repeating-linear-gradient(60deg, red 0 9%, white 10% 18%) !important; - border-radius: 100vw !important; - margin-block: 10px !important; + background: repeating-linear-gradient( + 60deg, + red 0 9%, + white 10% 18% + ) !important; + border-radius: 100vw !important; + margin-block: 10px !important; } body::-webkit-scrollbar-track { - background: powderblue !important; + background: powderblue !important; } .serif { - font-family: 'Gloock', serif; + font-family: 'Gloock', serif; } .sans { - font-family: 'Pragati Narrow', sans-serif; + font-family: 'Pragati Narrow', sans-serif; } #snow { - position: fixed; - height: 100%; - left: 0; - top: 0; - width: 100%; - z-index: 1; + position: fixed; + height: 100%; + left: 0; + top: 0; + width: 100%; + z-index: 1; } .holly-container { - position: relative; + position: relative; } .holly { - position: absolute; - top: 0; - background: none; - width: 20%; + position: absolute; + top: 0; + background: none; + width: 20%; } .left { - left: 0; + left: 0; } .right { - right: 0; - transform: scaleX(-1); + right: 0; + transform: scaleX(-1); } .bottom { - transform: scaleY(-1); + transform: scaleY(-1); } .bottom.right { - transform: scaleX(-1) scaleY(-1); + transform: scaleX(-1) scaleY(-1); } diff --git a/src/routes/(nowrapper)/christmas/christmas.js b/src/routes/(nowrapper)/christmas/christmas.js index 6a349b9..c25706c 100644 --- a/src/routes/(nowrapper)/christmas/christmas.js +++ b/src/routes/(nowrapper)/christmas/christmas.js @@ -1,42 +1,45 @@ export const json = { - title: 'Christmas Dinner', - where: 'the purr palace', - when: 'december 23, 2023', - agenda: [ - { - event: 'cocktails', - time: '1pm onwards' - }, - { - event: 'dinner', - time: 'around 3' - }, - { - event: 'presents', - time: '4' - }, - { - event: 'fun & games', - time: 'throughout' - } - ], - menu: [ - { - name: 'baked potato soup', - ingredients: 'yukon gold potatoes, cream, bacon, chives, cheese' - }, - { - name: 'winter salad', - ingredients: 'mixed greens, apple, cranberry, walnut, cranberry vinaigrette' - }, - { - name: "duck a l'orange", - ingredients: 'roast duck, orange pan sauce, porcini mushroom risotto, baked vegetables' - }, - { - name: 'dessert', - ingredients: 'creme brulee, assorted treats' - } - ], - disclaimer: 'all items are gluten-free, however some may contain dairy products' -}; + title: 'Christmas Dinner', + where: 'the purr palace', + when: 'december 23, 2023', + agenda: [ + { + event: 'cocktails', + time: '1pm onwards', + }, + { + event: 'dinner', + time: 'around 3', + }, + { + event: 'presents', + time: '4', + }, + { + event: 'fun & games', + time: 'throughout', + }, + ], + menu: [ + { + name: 'baked potato soup', + ingredients: 'yukon gold potatoes, cream, bacon, chives, cheese', + }, + { + name: 'winter salad', + ingredients: + 'mixed greens, apple, cranberry, walnut, cranberry vinaigrette', + }, + { + name: "duck a l'orange", + ingredients: + 'roast duck, orange pan sauce, porcini mushroom risotto, baked vegetables', + }, + { + name: 'dessert', + ingredients: 'creme brulee, assorted treats', + }, + ], + disclaimer: + 'all items are gluten-free, however some may contain dairy products', +} diff --git a/src/routes/+layout.js b/src/routes/+layout.js index 0beb67e..d3eb8c5 100644 --- a/src/routes/+layout.js +++ b/src/routes/+layout.js @@ -1,7 +1,7 @@ export const load = ({ url }) => { - const currentRoute = url.pathname; + const currentRoute = url.pathname - return { - currentRoute - }; -}; + return { + currentRoute, + } +} diff --git a/src/routes/api/gallery/[slug].json/+server.js b/src/routes/api/gallery/[slug].json/+server.js index b071857..247ee28 100644 --- a/src/routes/api/gallery/[slug].json/+server.js +++ b/src/routes/api/gallery/[slug].json/+server.js @@ -1,20 +1,20 @@ -import { getContent } from '$lib/content'; -import { error } from '@sveltejs/kit'; +import { getContent } from '$lib/content' +import { error } from '@sveltejs/kit' /** * @type {import('@sveltejs/kit').RequestHandler} */ export async function GET({ params }) { - const { slug } = params; - let data; - try { - data = await getContent(slug); - return new Response(JSON.stringify(data), { - headers: { - 'Cache-Control': `max-age=0, s-maxage=${60}` // 1 minute.. for now - } - }); - } catch (err) { - throw error(404, err.message); - } + const { slug } = params + let data + try { + data = await getContent(slug) + return new Response(JSON.stringify(data), { + headers: { + 'Cache-Control': `max-age=0, s-maxage=${60}`, // 1 minute.. for now + }, + }) + } catch (err) { + throw error(404, err.message) + } } diff --git a/src/routes/api/getContent/[slug].json/+server.js b/src/routes/api/getContent/[slug].json/+server.js index b071857..247ee28 100644 --- a/src/routes/api/getContent/[slug].json/+server.js +++ b/src/routes/api/getContent/[slug].json/+server.js @@ -1,20 +1,20 @@ -import { getContent } from '$lib/content'; -import { error } from '@sveltejs/kit'; +import { getContent } from '$lib/content' +import { error } from '@sveltejs/kit' /** * @type {import('@sveltejs/kit').RequestHandler} */ export async function GET({ params }) { - const { slug } = params; - let data; - try { - data = await getContent(slug); - return new Response(JSON.stringify(data), { - headers: { - 'Cache-Control': `max-age=0, s-maxage=${60}` // 1 minute.. for now - } - }); - } catch (err) { - throw error(404, err.message); - } + const { slug } = params + let data + try { + data = await getContent(slug) + return new Response(JSON.stringify(data), { + headers: { + 'Cache-Control': `max-age=0, s-maxage=${60}`, // 1 minute.. for now + }, + }) + } catch (err) { + throw error(404, err.message) + } } diff --git a/src/routes/api/listContent.json/+server.js b/src/routes/api/listContent.json/+server.js index f855d3f..3e44509 100644 --- a/src/routes/api/listContent.json/+server.js +++ b/src/routes/api/listContent.json/+server.js @@ -1,16 +1,16 @@ -import { listContentFromIssues } from '$lib/content'; +import { listContentFromIssues } from '$lib/content' /** * @type {import('./$types').RequestHandler} */ export async function GET({ setHeaders }) { - const list = await listContentFromIssues('Published'); - setHeaders({ - 'Cache-Control': `max-age=0, s-maxage=${60}` // 1 minute.. for now - }); - return new Response(JSON.stringify(list), { - headers: { - 'content-type': 'application/json; charset=utf-8' - } - }); + const list = await listContentFromIssues('Published') + setHeaders({ + 'Cache-Control': `max-age=0, s-maxage=${60}`, // 1 minute.. for now + }) + return new Response(JSON.stringify(list), { + headers: { + 'content-type': 'application/json; charset=utf-8', + }, + }) } diff --git a/src/routes/api/listGallery.json/+server.js b/src/routes/api/listGallery.json/+server.js index 6e8ed29..d027d0e 100644 --- a/src/routes/api/listGallery.json/+server.js +++ b/src/routes/api/listGallery.json/+server.js @@ -1,17 +1,17 @@ -import { listContentFromIssues } from '$lib/content'; +import { listContentFromIssues } from '$lib/content' /** * @type {import('./$types').RequestHandler} */ export async function GET({ setHeaders }) { - const list = await listContentFromIssues('Gallery'); + const list = await listContentFromIssues('Gallery') - setHeaders({ - 'Cache-Control': `max-age=0, s-maxage=${60}` // 1 minute.. for now - }); - return new Response(JSON.stringify(list), { - headers: { - 'content-type': 'application/json; charset=utf-8' - } - }); + setHeaders({ + 'Cache-Control': `max-age=0, s-maxage=${60}`, // 1 minute.. for now + }) + return new Response(JSON.stringify(list), { + headers: { + 'content-type': 'application/json; charset=utf-8', + }, + }) } diff --git a/src/routes/api/listLocalContent.json/+server.js b/src/routes/api/listLocalContent.json/+server.js index 7058516..d37e1aa 100644 --- a/src/routes/api/listLocalContent.json/+server.js +++ b/src/routes/api/listLocalContent.json/+server.js @@ -1,13 +1,13 @@ // src/routes/api/posts/+server.js -import { fetchMarkdownPosts } from '$lib/localContent'; -import { json } from '@sveltejs/kit'; +import { fetchMarkdownPosts } from '$lib/localContent' +import { json } from '@sveltejs/kit' export const GET = async () => { - const allPosts = await fetchMarkdownPosts(); + const allPosts = await fetchMarkdownPosts() - const sortedPosts = allPosts.sort((a, b) => { - return new Date(b.date) - new Date(a.date); - }); + const sortedPosts = allPosts.sort((a, b) => { + return new Date(b.date) - new Date(a.date) + }) - return json(sortedPosts); -}; + return json(sortedPosts) +} diff --git a/src/routes/api/work/[slug].json/+server.js b/src/routes/api/work/[slug].json/+server.js index 19f48d2..439c9b4 100644 --- a/src/routes/api/work/[slug].json/+server.js +++ b/src/routes/api/work/[slug].json/+server.js @@ -1,17 +1,19 @@ -import { error } from '@sveltejs/kit'; -import { fetchMarkdownPost } from '$lib/localContent'; +import { error } from '@sveltejs/kit' +import { fetchMarkdownPost } from '$lib/localContent' /** * @type {import('@sveltejs/kit').RequestHandler} */ export async function GET({ params }) { - const { slug } = params; - let data; - data = await fetchMarkdownPost(slug).catch((err) => new Response(err.message, { status: 404 })); + const { slug } = params + let data + data = await fetchMarkdownPost(slug).catch( + (err) => new Response(err.message, { status: 404 }) + ) - return new Response(JSON.stringify(data), { - headers: { - 'Cache-Control': `max-age=0, s-maxage=${60}` // 1 minute.. for now - } - }); + return new Response(JSON.stringify(data), { + headers: { + 'Cache-Control': `max-age=0, s-maxage=${60}`, // 1 minute.. for now + }, + }) } diff --git a/src/routes/rss.xml/+server.js b/src/routes/rss.xml/+server.js index 4692ffc..abc8a6f 100644 --- a/src/routes/rss.xml/+server.js +++ b/src/routes/rss.xml/+server.js @@ -1,47 +1,49 @@ -import RSS from 'rss'; -import { SITE_TITLE, SITE_URL } from '$lib/siteConfig'; -import { remark } from 'remark'; -import remarkHTML from 'remark-html'; -import { listContentFromIssues } from '$lib/content'; +import RSS from 'rss' +import { SITE_TITLE, SITE_URL } from '$lib/siteConfig' +import { remark } from 'remark' +import remarkHTML from 'remark-html' +import { listContentFromIssues } from '$lib/content' // Reference: https://github.com/sveltejs/kit/blob/master/examples/hn.svelte.dev/src/routes/%5Blist%5D/rss.js /** @type {import('@sveltejs/kit').RequestHandler} */ export async function GET({ fetch }) { - const feed = new RSS({ - title: SITE_TITLE + ' RSS Feed', - site_url: SITE_URL, - feed_url: SITE_URL + '/rss.xml' - }); + const feed = new RSS({ + title: SITE_TITLE + ' RSS Feed', + site_url: SITE_URL, + feed_url: SITE_URL + '/rss.xml', + }) - const allBlogs = await listContentFromIssues('Published'); - allBlogs.forEach((post) => { - // extract HTML from markdown - const htmlDescription = remark().use(remarkHTML).processSync(post.description); + const allBlogs = await listContentFromIssues('Published') + allBlogs.forEach((post) => { + // extract HTML from markdown + const htmlDescription = remark() + .use(remarkHTML) + .processSync(post.description) - feed.item({ - title: post.title, - url: SITE_URL + `/${post.slug}`, - date: post.date, - description: htmlDescription.toString() - }); - }); + feed.item({ + title: post.title, + url: SITE_URL + `/${post.slug}`, + date: post.date, + description: htmlDescription.toString(), + }) + }) - // inject our custom rss stylesheet - return new Response( - feed - .xml({ indent: true }) - .replace( - ``, - `` - ), - { - headers: { - 'Cache-Control': `public, max-age=${86400}`, // 24 hours - 'Content-Type': 'application/xml; charset=utf-8', // not application/rss+xml - 'x-content-type-options': 'nosniff' - } - } - ); + // inject our custom rss stylesheet + return new Response( + feed + .xml({ indent: true }) + .replace( + ``, + `` + ), + { + headers: { + 'Cache-Control': `public, max-age=${86400}`, // 24 hours + 'Content-Type': 'application/xml; charset=utf-8', // not application/rss+xml + 'x-content-type-options': 'nosniff', + }, + } + ) } // misc notes for future users diff --git a/src/routes/sitemap.xml/+server.js b/src/routes/sitemap.xml/+server.js index 4f9ce44..66500a7 100644 --- a/src/routes/sitemap.xml/+server.js +++ b/src/routes/sitemap.xml/+server.js @@ -1,24 +1,29 @@ -import { SITE_URL } from '$lib/siteConfig'; -import { listContentFromIssues } from '$lib/content'; -import { fetchMarkdownPosts } from '$lib/localContent'; +import { SITE_URL } from '$lib/siteConfig' +import { listContentFromIssues } from '$lib/content' +import { fetchMarkdownPosts } from '$lib/localContent' /** @type {import('@sveltejs/kit').RequestHandler} */ export async function GET({ fetch }) { - const posts = await listContentFromIssues('Published'); - const galleries = await listContentFromIssues('Gallery'); - const projects = await fetchMarkdownPosts(); - const pages = ['about', 'resume', 'blogroll', 'christmas', 'blog', 'work']; - const body = sitemap(posts, projects, pages, galleries); + const posts = await listContentFromIssues('Published') + const galleries = await listContentFromIssues('Gallery') + const projects = await fetchMarkdownPosts() + const pages = ['about', 'resume', 'blogroll', 'christmas', 'blog', 'work'] + const body = sitemap(posts, projects, pages, galleries) - return new Response(body, { - headers: { - 'Cache-Control': `public, max-age=${86400}`, // 24 hours - 'Content-Type': 'application/xml' - } - }); + return new Response(body, { + headers: { + 'Cache-Control': `public, max-age=${86400}`, // 24 hours + 'Content-Type': 'application/xml', + }, + }) } -const sitemap = (posts, projects, pages, galleries) => ` +const sitemap = ( + posts, + projects, + pages, + galleries +) => ` `${SITE_URL} ${pages - .map( - (page) => ` + .map( + (page) => ` ${SITE_URL}/${page} ` - ) - .join('')} + ) + .join('')} ${posts - .map((post) => - post.isPrivate - ? null - : ` + .map((post) => + post.isPrivate + ? null + : ` ${SITE_URL}/${post.slug} ${ - post.ghMetadata.updated_at - ? post.ghMetadata.updated_at.substring(0, 10) - : post.ghMetadata.created_at.substring(0, 10) - } + post.ghMetadata.updated_at + ? post.ghMetadata.updated_at.substring(0, 10) + : post.ghMetadata.created_at.substring(0, 10) + } ` - ) - .join('')} + ) + .join('')} ${galleries - .map((gallery) => - gallery.isPrivate - ? null - : ` + .map((gallery) => + gallery.isPrivate + ? null + : ` ${SITE_URL}/gallery/${gallery.slug} ${ - gallery.ghMetadata.updated_at - ? gallery.ghMetadata.updated_at.substring(0, 10) - : gallery.ghMetadata.created_at.substring(0, 10) - } + gallery.ghMetadata.updated_at + ? gallery.ghMetadata.updated_at.substring(0, 10) + : gallery.ghMetadata.created_at.substring(0, 10) + } ` - ) - .join('')} + ) + .join('')} ${projects - .map((project) => - project.isPrivate - ? null - : ` + .map((project) => + project.isPrivate + ? null + : ` ${SITE_URL}/work/${project.slug} ` - ) - .join('')} - `; + ) + .join('')} + ` diff --git a/src/tailwind.css b/src/tailwind.css index 29eee04..71f4e93 100644 --- a/src/tailwind.css +++ b/src/tailwind.css @@ -2,149 +2,153 @@ @tailwind components; @layer components { - body { - --brand-accent: theme('colors.sky.600'); - --brand-alternate: theme('colors.orange.300'); - - /* https://ishadeed.com/article/defensive-css */ - overflow-wrap: break-word; - overflow-y: auto; - scrollbar-gutter: stable; - } - .dark body { - --brand-accent: theme('colors.blue.300'); - --brand-alternate: theme('colors.indigo.700'); - } - :root { - accent-color: var(--brand-accent); - } - :focus-visible { - outline-color: var(--brand-accent); - } - ::selection { - color: theme('colors.sky.200'); - background-color: var(--brand-accent); - } - .dark ::selection { - color: theme('colors.slate.900'); - } - ::marker { - color: var(--brand-accent); - } - .text-accent { - color: var(--brand-accent); - } - - :is(::-webkit-calendar-picker-indicator, ::-webkit-clear-button, ::-webkit-inner-spin-button, ::-webkit-outer-spin-button) { - color: var(--brand-accent); - } - - /* https://css-tricks.com/two-issues-styling-the-details-element-and-how-to-solve-them/ */ - details { - border: 2px solid var(--brand-accent); - padding: 0.5rem 1rem; - } - details > summary { - cursor: pointer; - } - - details > summary > *:first-child { - display: inline; - } - - img { - /* https://ishadeed.com/article/defensive-css */ - max-width: 100%; - object-fit: cover; - background-color: grey; - } - - hr { - @apply w-full border-t-2 border-sky-600 dark:border-blue-300; - } - - /* header styles, works best larger font sizes */ - .text-shadow { - @apply text-red-600; - text-shadow: 2px 3px 1px theme('colors.sky.600'); - } - .dark .text-shadow { - @apply text-yellow-400; - text-shadow: 2px 3px 1px theme('colors.yellow.700'); - } - .text-unshadow { - text-shadow: none !important; - } - - /* Links that highlight from bottom */ - .gh-link, - .footer-link, - .prose a { - @apply from-red-600 to-red-600 dark:from-yellow-400 dark:to-yellow-400; - @apply text-red-600 hover:text-sky-200 dark:text-yellow-400 dark:hover:text-slate-900; - - cursor: pointer; - text-decoration: none; - padding: 1px 4px; - background: linear-gradient(to bottom, var(--tw-gradient-from) 0%, var(--tw-gradient-to) 100%); - background-position: 0 100%; - background-repeat: repeat-x; - background-size: 4px 2px; - transition: all 0.3s linear; - - &:hover { - background-size: 4px 50px; - } - } - - /* to undo above */ - .bg-unset { - background: unset; - color: currentColor; - } + body { + --brand-accent: theme('colors.sky.600'); + --brand-alternate: theme('colors.orange.300'); + + /* https://ishadeed.com/article/defensive-css */ + overflow-wrap: break-word; + overflow-y: auto; + scrollbar-gutter: stable; + } + .dark body { + --brand-accent: theme('colors.blue.300'); + --brand-alternate: theme('colors.indigo.700'); + } + :root { + accent-color: var(--brand-accent); + } + :focus-visible { + outline-color: var(--brand-accent); + } + ::selection { + color: theme('colors.sky.200'); + background-color: var(--brand-accent); + } + .dark ::selection { + color: theme('colors.slate.900'); + } + ::marker { + color: var(--brand-accent); + } + .text-accent { + color: var(--brand-accent); + } + + :is(::-webkit-calendar-picker-indicator, ::-webkit-clear-button, ::-webkit-inner-spin-button, ::-webkit-outer-spin-button) { + color: var(--brand-accent); + } + + /* https://css-tricks.com/two-issues-styling-the-details-element-and-how-to-solve-them/ */ + details { + border: 2px solid var(--brand-accent); + padding: 0.5rem 1rem; + } + details > summary { + cursor: pointer; + } + + details > summary > *:first-child { + display: inline; + } + + img { + /* https://ishadeed.com/article/defensive-css */ + max-width: 100%; + object-fit: cover; + background-color: grey; + } + + hr { + @apply w-full border-t-2 border-sky-600 dark:border-blue-300; + } + + /* header styles, works best larger font sizes */ + .text-shadow { + @apply text-red-600; + text-shadow: 2px 3px 1px theme('colors.sky.600'); + } + .dark .text-shadow { + @apply text-yellow-400; + text-shadow: 2px 3px 1px theme('colors.yellow.700'); + } + .text-unshadow { + text-shadow: none !important; + } + + /* Links that highlight from bottom */ + .gh-link, + .footer-link, + .prose a { + @apply from-red-600 to-red-600 dark:from-yellow-400 dark:to-yellow-400; + @apply text-red-600 hover:text-sky-200 dark:text-yellow-400 dark:hover:text-slate-900; + + cursor: pointer; + text-decoration: none; + padding: 1px 4px; + background: linear-gradient( + to bottom, + var(--tw-gradient-from) 0%, + var(--tw-gradient-to) 100% + ); + background-position: 0 100%; + background-repeat: repeat-x; + background-size: 4px 2px; + transition: all 0.3s linear; + + &:hover { + background-size: 4px 50px; + } + } + + /* to undo above */ + .bg-unset { + background: unset; + color: currentColor; + } } /* filters on /blog page */ .filter { - @apply m-1 inline-block whitespace-nowrap rounded rounded-md px-4 py-2; - @apply border-red-600 bg-sky-600 font-bold text-orange-100; - @apply dark:border-slate-600 dark:bg-gray-800 dark:text-gray-100; - @apply ring-sky-600 transition-all duration-200 ease-in-out hover:ring-2 dark:ring-lime-700; + @apply m-1 inline-block whitespace-nowrap rounded rounded-md px-4 py-2; + @apply border-red-600 bg-sky-600 font-bold text-orange-100; + @apply dark:border-slate-600 dark:bg-gray-800 dark:text-gray-100; + @apply ring-sky-600 transition-all duration-200 ease-in-out hover:ring-2 dark:ring-lime-700; } .activefilter { - @apply border-red-600 bg-orange-300 text-red-600; - @apply dark:border-slate-600 dark:bg-blue-300 dark:text-slate-800; - @apply ring-yellow-400 dark:ring-yellow-800; + @apply border-red-600 bg-orange-300 text-red-600; + @apply dark:border-slate-600 dark:bg-blue-300 dark:text-slate-800; + @apply ring-yellow-400 dark:ring-yellow-800; } /* Cleanup styles for markdown headers */ .prose h1 a { - @apply bg-unset text-lime-800 hover:text-lime-800 dark:text-yellow-400 dark:hover:text-yellow-400; + @apply bg-unset text-lime-800 hover:text-lime-800 dark:text-yellow-400 dark:hover:text-yellow-400; } .prose h2, .prose h3, .prose h4 { - @apply relative; - a { - @apply bg-unset p-0 font-bold !text-accent; - } - &::before { - @apply absolute -left-5; - @apply text-accent; - content: '#'; - } + @apply relative; + a { + @apply bg-unset p-0 font-bold !text-accent; + } + &::before { + @apply absolute -left-5; + @apply text-accent; + content: '#'; + } } .prose-img-dense { - @apply !m-0; + @apply !m-0; } .back-link { - @apply relative mx-4 mb-4 font-bold text-accent lg:mx-12; - &::before { - @apply absolute -left-5; - content: '<'; - color: currentColor; - } + @apply relative mx-4 mb-4 font-bold text-accent lg:mx-12; + &::before { + @apply absolute -left-5; + content: '<'; + color: currentColor; + } } /* @@ -152,37 +156,42 @@ - https://css-tricks.com/strut-your-stuff-with-a-custom-scrollbar/ */ body { - --sb-primary-color: theme('colors.amber.400'); - --sb-accent-color: theme('colors.red.500'); - --sb-track-1: theme('colors.sky.600'); - --sb-track-2: theme('colors.blue.300'); + --sb-primary-color: theme('colors.amber.400'); + --sb-accent-color: theme('colors.red.500'); + --sb-track-1: theme('colors.sky.600'); + --sb-track-2: theme('colors.blue.300'); } .dark body { - --sb-primary-color: theme('colors.yellow.600'); - --sb-accent-color: theme('colors.amber.900'); - --sb-track-1: theme('colors.sky.900'); - --sb-track-2: theme('colors.slate.800'); + --sb-primary-color: theme('colors.yellow.600'); + --sb-accent-color: theme('colors.amber.900'); + --sb-track-1: theme('colors.sky.900'); + --sb-track-2: theme('colors.slate.800'); } body::-webkit-scrollbar { - width: 1rem; - height: 1rem; + width: 1rem; + height: 1rem; } body::-webkit-scrollbar-thumb { - background: linear-gradient(270deg, var(--sb-accent-color), var(--sb-primary-color)); - border-radius: 2px; - box-shadow: inset 2px 2px 2px var(--sb-primary-color), inset -1px -1px 1px var(--sb-track-1); + background: linear-gradient( + 270deg, + var(--sb-accent-color), + var(--sb-primary-color) + ); + border-radius: 2px; + box-shadow: inset 2px 2px 2px var(--sb-primary-color), + inset -1px -1px 1px var(--sb-track-1); } body::-webkit-scrollbar-track { - background: linear-gradient( - 90deg, - var(--sb-track-2), - var(--sb-track-2) 1px, - var(--sb-track-1) 0, - var(--sb-track-1) - ); + background: linear-gradient( + 90deg, + var(--sb-track-2), + var(--sb-track-2) 1px, + var(--sb-track-1) 0, + var(--sb-track-1) + ); } @tailwind utilities; diff --git a/svelte.config.js b/svelte.config.js index 6c1aa3a..90435d8 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -1,54 +1,54 @@ -import preprocess from 'svelte-preprocess'; -import adapter from '@sveltejs/adapter-auto'; -import { mdsvex } from 'mdsvex'; -import remarkGithub from 'remark-github'; -import remarkAbbr from 'remark-abbr'; -import rehypeSlug from 'rehype-slug'; -import rehypeAutolinkHeadings from 'rehype-autolink-headings'; +import preprocess from 'svelte-preprocess' +import adapter from '@sveltejs/adapter-auto' +import { mdsvex } from 'mdsvex' +import remarkGithub from 'remark-github' +import remarkAbbr from 'remark-abbr' +import rehypeSlug from 'rehype-slug' +import rehypeAutolinkHeadings from 'rehype-autolink-headings' // mdsvex config const mdsvexConfig = { - extensions: ['.svelte.md', '.md', '.svx'], - layout: { - _: './src/mdsvexlayout.svelte' // default mdsvex layout - }, - remarkPlugins: [ - [ - remarkGithub, - { - // Use your own repository - repository: 'https://github.com/tjheffner/heffdotdev.git' - } - ], - remarkAbbr - ], - rehypePlugins: [ - rehypeSlug, - [ - rehypeAutolinkHeadings, - { - behavior: 'wrap' - } - ] - ] -}; + extensions: ['.svelte.md', '.md', '.svx'], + layout: { + _: './src/mdsvexlayout.svelte', // default mdsvex layout + }, + remarkPlugins: [ + [ + remarkGithub, + { + // Use your own repository + repository: 'https://github.com/tjheffner/heffdotdev.git', + }, + ], + remarkAbbr, + ], + rehypePlugins: [ + rehypeSlug, + [ + rehypeAutolinkHeadings, + { + behavior: 'wrap', + }, + ], + ], +} /** @type {import('@sveltejs/kit').Config} */ const config = { - extensions: ['.svelte', '.html', '.svx', ...mdsvexConfig.extensions], - preprocess: [ - mdsvex(mdsvexConfig), - preprocess({ - postcss: true - }) - ], - outDir: 'public', + extensions: ['.svelte', '.html', '.svx', ...mdsvexConfig.extensions], + preprocess: [ + mdsvex(mdsvexConfig), + preprocess({ + postcss: true, + }), + ], + outDir: 'public', - kit: { - adapter: adapter({ - split: true - }) - } -}; + kit: { + adapter: adapter({ + split: true, + }), + }, +} -export default config; +export default config diff --git a/tailwind.config.cjs b/tailwind.config.cjs index f90d1ab..6da8c99 100644 --- a/tailwind.config.cjs +++ b/tailwind.config.cjs @@ -1,90 +1,90 @@ module.exports = { - content: [ - './src/**/*.svelte', - // may also want to include HTML files - './src/**/*.html' - ], - darkMode: 'class', - theme: { - extend: { - colors: { - accent: 'var(--brand-accent)', - alternate: 'var(--brand-alternate)' - }, - typography: (theme) => ({ - DEFAULT: { - css: { - '--tw-prose-bullets': theme('colors.black'), - '--tw-prose-th-borders': theme('colors.accent'), - '--tw-prose-td-borders': theme('colors.accent'), - // these customizations are explained here https://youtu.be/-FzemNMcOGs - blockquote: { - borderLeft: '3px solid theme("colors.accent")', - fontSize: 'inherit', - fontStyle: 'inherit', - fontWeight: 'medium' - }, - 'blockquote p:first-of-type::before': { - content: '' - }, - 'blockquote p:last-of-type::after': { - content: '' - }, - 'code::before': false, - 'code::after': false, - code: { - 'border-radius': '0.25rem', - padding: '0.15rem 0.3rem', - borderWidth: '2px', - borderColor: 'rgba(0,0,0,0.1)' - }, - 'a code': { - color: 'unset' - }, - 'a strong': { - color: 'currentColor' - }, - 'li, ul, ol': { - margin: 0 - }, - 'li > img': { - margin: 0, - display: 'inline' - }, - 'ol > li::marker': { - color: 'var(--tw-prose-body)' - }, - 'ul > li::marker': { - color: 'var(--tw-prose-body)' - }, - tfoot: { - borderTopWidth: '1px', - borderColor: 'var(--tw-prose-th-borders)' - }, - mark: { - padding: '0.25rem', - background: theme('colors.indigo.300'), - color: 'var(--tw-prose-body)' - } - } - }, - // for dark:prose-invert - invert: { - css: { - '--tw-prose-th-borders': theme('colors.blue.300'), - '--tw-prose-td-borders': theme('colors.gray.600'), - mark: { - background: theme('colors.blue.300'), - color: theme('colors.slate.900') - }, - hr: { - borderColor: theme('colors.accent') - } - } - } - }) - } - }, - variants: {}, - plugins: [require('@tailwindcss/typography')] -}; + content: [ + './src/**/*.svelte', + // may also want to include HTML files + './src/**/*.html', + ], + darkMode: 'class', + theme: { + extend: { + colors: { + accent: 'var(--brand-accent)', + alternate: 'var(--brand-alternate)', + }, + typography: (theme) => ({ + DEFAULT: { + css: { + '--tw-prose-bullets': theme('colors.black'), + '--tw-prose-th-borders': theme('colors.accent'), + '--tw-prose-td-borders': theme('colors.accent'), + // these customizations are explained here https://youtu.be/-FzemNMcOGs + blockquote: { + borderLeft: '3px solid theme("colors.accent")', + fontSize: 'inherit', + fontStyle: 'inherit', + fontWeight: 'medium', + }, + 'blockquote p:first-of-type::before': { + content: '', + }, + 'blockquote p:last-of-type::after': { + content: '', + }, + 'code::before': false, + 'code::after': false, + code: { + 'border-radius': '0.25rem', + padding: '0.15rem 0.3rem', + borderWidth: '2px', + borderColor: 'rgba(0,0,0,0.1)', + }, + 'a code': { + color: 'unset', + }, + 'a strong': { + color: 'currentColor', + }, + 'li, ul, ol': { + margin: 0, + }, + 'li > img': { + margin: 0, + display: 'inline', + }, + 'ol > li::marker': { + color: 'var(--tw-prose-body)', + }, + 'ul > li::marker': { + color: 'var(--tw-prose-body)', + }, + tfoot: { + borderTopWidth: '1px', + borderColor: 'var(--tw-prose-th-borders)', + }, + mark: { + padding: '0.25rem', + background: theme('colors.indigo.300'), + color: 'var(--tw-prose-body)', + }, + }, + }, + // for dark:prose-invert + invert: { + css: { + '--tw-prose-th-borders': theme('colors.blue.300'), + '--tw-prose-td-borders': theme('colors.gray.600'), + mark: { + background: theme('colors.blue.300'), + color: theme('colors.slate.900'), + }, + hr: { + borderColor: theme('colors.accent'), + }, + }, + }, + }), + }, + }, + variants: {}, + plugins: [require('@tailwindcss/typography')], +} diff --git a/tsconfig.json b/tsconfig.json index b6fcc24..2abb47e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,39 +1,39 @@ { - "extends": "./.svelte-kit/tsconfig.json", - "compilerOptions": { - "moduleResolution": "node", - "module": "es2020", - "lib": ["es2021"], - "target": "es2021", - /** + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "moduleResolution": "node", + "module": "es2020", + "lib": ["es2021"], + "target": "es2021", + /** svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript to enforce using \`import type\` instead of \`import\` for Types. */ - "importsNotUsedAsValues": "error", - "isolatedModules": true, - "resolveJsonModule": true, - /** + "importsNotUsedAsValues": "error", + "isolatedModules": true, + "resolveJsonModule": true, + /** To have warnings/errors of the Svelte compiler at the correct position, enable source maps by default. */ - "sourceMap": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "baseUrl": ".", - "allowJs": true, - "checkJs": true, - "paths": { - "$lib/*": ["src/lib/*"], - "$lib": ["src/lib"] - } - }, - "include": [ - "src/**/*.d.ts", - "src/**/*.js", - "src/**/*.ts", - "src/**/*.svelte", - "src/lib/content.js", - "src/lib/types.d.ts" - ] + "sourceMap": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "baseUrl": ".", + "allowJs": true, + "checkJs": true, + "paths": { + "$lib/*": ["src/lib/*"], + "$lib": ["src/lib"] + } + }, + "include": [ + "src/**/*.d.ts", + "src/**/*.js", + "src/**/*.ts", + "src/**/*.svelte", + "src/lib/content.js", + "src/lib/types.d.ts" + ] } diff --git a/vite.config.js b/vite.config.js index 608c5ea..64d31cd 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,22 +1,22 @@ // vite.config.js -import { sveltekit } from '@sveltejs/kit/vite'; -import fs from 'fs'; +import { sveltekit } from '@sveltejs/kit/vite' +import fs from 'fs' /** @type {import('vite').UserConfig} */ const config = { - plugins: [sveltekit(), rawFonts(['.ttf'])] -}; + plugins: [sveltekit(), rawFonts(['.ttf'])], +} function rawFonts(ext) { - return { - name: 'vite-plugin-raw-fonts', - transform(code, id) { - if (ext.some((e) => id.endsWith(e))) { - const buffer = fs.readFileSync(id); - return { code: `export default ${JSON.stringify(buffer)}`, map: null }; - } - } - }; + return { + name: 'vite-plugin-raw-fonts', + transform(code, id) { + if (ext.some((e) => id.endsWith(e))) { + const buffer = fs.readFileSync(id) + return { code: `export default ${JSON.stringify(buffer)}`, map: null } + } + }, + } } -export default config; +export default config