Merge pull request #33 from BradNut/development

Development
This commit is contained in:
Bradley Shellnut 2025-04-29 18:47:09 -07:00 committed by GitHub
commit 97551e7b5c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 1748 additions and 1088 deletions

View file

@ -3,6 +3,8 @@ name: Run_Svelte_Check_on_PRs
on:
pull_request:
workflow_dispatch:
env:
WALLABAG_MAX_ARTICLES: ${{ secrets.WALLABAG_MAX_ARTICLES }}
WALLABAG_MAX_PAGES: ${{ secrets.WALLABAG_MAX_PAGES }}
@ -27,17 +29,17 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: pnpm-setup
uses: pnpm/action-setup@v2
uses: pnpm/action-setup@v4
with:
version: 8
version: 10
- name: Set up Node.js
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: 18.18.2
node-version: 22.x
cache: 'pnpm'
- name: Install dependencies

View file

@ -18,44 +18,43 @@
},
"devDependencies": {
"@biomejs/biome": "^1.9.4",
"@playwright/test": "^1.49.1",
"@playwright/test": "^1.52.0",
"@sveltejs/enhanced-img": "^0.4.4",
"@sveltejs/kit": "^2.15.0",
"@sveltejs/kit": "^2.20.7",
"@sveltejs/vite-plugin-svelte": "^5.0.3",
"@types/nprogress": "^0.2.3",
"@unpic/svelte": "^1.0.0",
"@zerodevx/svelte-img": "^2.1.2",
"autoprefixer": "^10.4.20",
"autoprefixer": "^10.4.21",
"just-intersect": "^4.3.0",
"postcss": "^8.4.49",
"postcss": "^8.5.3",
"postcss-custom-media": "^11.0.5",
"postcss-import": "^16.1.0",
"postcss-load-config": "^6.0.1",
"postcss-preset-env": "^10.1.4",
"satori": "^0.12.0",
"postcss-preset-env": "^10.1.6",
"satori": "^0.12.2",
"satori-html": "^0.3.2",
"svelte": "^5.15.0",
"svelte-check": "^4.1.0",
"svelte-meta-tags": "^4.0.4",
"svelte": "^5.28.2",
"svelte-check": "^4.1.6",
"svelte-meta-tags": "^4.2.0",
"svelte-preprocess": "^6.0.3",
"svelte-sequential-preprocessor": "^2.0.2",
"tslib": "^2.8.1",
"typescript": "^5.7.2",
"typescript": "^5.8.3",
"vanilla-lazyload": "^19.1.3",
"vite": "^6.0.5",
"vite": "^6.3.3",
"vite-imagetools": "^7.0.5",
"vitest": "^3.0.6"
"vitest": "^3.1.2"
},
"dependencies": {
"@resvg/resvg-js": "^2.6.2",
"@sveltejs/adapter-node": "^5.2.11",
"@vercel/og": "^0.6.4",
"bits-ui": "1.3.0",
"flexsearch": "^0.7.43",
"ioredis": "^5.4.2",
"lucide-svelte": "^0.475.0",
"nprogress": "^0.2.0",
"scrape-it": "^6.1.3",
"sharp": "^0.33.5",
"@vercel/og": "^0.6.8",
"bits-ui": "1.4.1",
"flexsearch": "^0.8.158",
"ioredis": "^5.6.1",
"lucide-svelte": "^0.503.0",
"scrape-it": "^6.1.5",
"sharp": "^0.34.1",
"svelte-local-storage-store": "^0.6.4"
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,6 @@
const postcssPresetEnv = require('postcss-preset-env');
const atImport = require('postcss-import');
const postCssCustomMedia = require('postcss-custom-media');
const config = {
plugins: [
@ -11,6 +12,18 @@ const config = {
'custom-media-queries': true,
'media-query-ranges': true
}
}),
postCssCustomMedia({
customMedia: {
'--below_small': '(width < 400px)',
'--below_med': '(width < 700px)',
'--below_large': '(width < 900px)',
'--below_xlarge': '(width < 1200px)',
'--above_small': '(width > 400px)',
'--above_med': '(width > 700px)',
'--above_large': '(width > 900px)',
'--above_xlarge': '(width > 1200px)',
}
})
]
};

19
src/app.d.ts vendored
View file

@ -1,12 +1,19 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface Platform {}
}
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface Platform {}
interface Document {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
startViewTransition: (callback: never) => void; // Add your custom property/method here
}
}
}
// biome-ignore lint/complexity/noUselessEmptyExport: <explanation>
// biome-ignore lint/style/useExportType: <explanation>
export {};

View file

@ -14,7 +14,7 @@ import type {
WallabagArticle,
} from "$lib/types/article";
import { ArticleTag } from "$lib/types/articleTag";
import type { PageQuery } from "$lib/types/pageQuery";
import type { PageQuery } from "./types/pageQuery";
import { URLSearchParams } from "node:url";
import { redis } from "$lib/server/redis";

View file

@ -4,7 +4,7 @@
const userNames = {
github: 'BradNut',
linkedIn: 'bradley-shellnut',
email: 'bradleyshellnut[at]pm.me',
email: 'website[at]bradleyshellnut.com',
};
</script>

View file

@ -1,5 +1,4 @@
<script module>
import { ExternalLink } from 'lucide-svelte';
export { blueSkyIcon, dockerIcon, drizzleIcon, honoIcon, gitHubIcon, linkedInIcon, lucideIcon, nextDotJsIcon, reactIcon, svelteIcon, typescriptIcon, xIcon };
</script>

View file

@ -0,0 +1,55 @@
<script lang="ts">
import { onNavigate } from "$app/navigation";
let visible = $state(false);
let progress = $state(0);
let load_durations = $state<number[]>([]);
let average_load = $derived(
load_durations.reduce((a, b) => a + b, 0) / load_durations.length,
);
const increment = 1;
onNavigate((navigation) => {
const typical_load_time = average_load || 200; //ms
const frequency = typical_load_time / 100;
let start = performance.now();
// Start the progress bar
visible = true;
progress = 0;
const interval = setInterval(() => {
// Increment the progress bar
progress += increment;
}, frequency);
// Resolve the promise when the page is done loading
navigation?.complete.then(() => {
progress = 100; // Fill out the progress bar
clearInterval(interval);
// after 100 ms hide the progress bar
setTimeout(() => {
visible = false;
}, 500);
// Log how long that one took
const end = performance.now();
const duration = end - start;
load_durations = [...load_durations, duration];
});
});
</script>
{#if visible}
<div class="progress" style="width: {progress}%;"></div>
{/if}
<style lang="postcss">
.progress {
background: var(--lightGrey);
position: fixed;
top: 0;
left: 0;
right: 0;
height: 0.25rem;
z-index: 50;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
</style>

View file

@ -1,51 +1,50 @@
<script lang="ts">
import { MetaTags } from 'svelte-meta-tags';
import NProgress from "nprogress";
import { browser } from "$app/environment";
import { navigating, page } from "$app/stores";
import { PUBLIC_SITE_URL } from '$env/static/public';
import "nprogress/nprogress.css";
import '$root/styles/styles.pcss';
import Header from '$lib/components/header/index.svelte';
import Footer from '$lib/components/footer/index.svelte';
import Analytics from '$lib/components/analytics/index.svelte';
import "../styles/styles.pcss";
import { MetaTags } from "svelte-meta-tags";
import { dev } from "$app/environment";
import { page } from "$app/state";
import Header from "../lib/components/header/index.svelte";
import Footer from "../lib/components/footer/index.svelte";
import Analytics from "../lib/components/analytics/index.svelte";
import { onNavigate } from "$app/navigation";
import PageLoadingIndicator from "../lib/util/page_loading_indicator.svelte";
interface Props {
children?: import('svelte').Snippet;
children?: import("svelte").Snippet;
}
let { children }: Props = $props();
NProgress.configure({
// Full list: https://github.com/rstacruz/nprogress#configuration
minimum: 0.16,
});
const production = !dev || import.meta.env.NODE_ENV !== "production";
const dev = process.env.NODE_ENV !== 'production';
onNavigate(async (navigation) => {
if (!document.startViewTransition) return;
$effect(() => {
if (browser && $navigating) {
NProgress.start();
} else {
NProgress.done();
}
return new Promise((oldStateCaptureResolve) => {
document.startViewTransition(async () => {
oldStateCaptureResolve();
await navigation.complete;
});
});
});
let metaTags = $derived({
titleTemplate: '%s | Bradley Shellnut',
titleTemplate: "%s | Bradley Shellnut",
additionalMetaTags: [
{
property: 'theme-color',
content: '#272727'
}
property: "theme-color",
content: "#272727",
},
],
...$page.data.metaTagsChild
})
...page.data.metaTagsChild,
});
</script>
{#if !dev}
{#if production}
<Analytics />
{/if}
<PageLoadingIndicator />
<MetaTags {...metaTags} />
<div class="wrapper">
@ -77,24 +76,15 @@
box-sizing: border-box;
}
:global(#nprogress .bar) {
background: var(--lightGrey);
:global(p) {
word-wrap: normal;
font-size: var(--bodyTextSize);
color: var(--lightShade);
}
:global(#nprogress .spinner-icon) {
border-top-color: var(--lightGrey);
border-left-color: var(--lightGrey);
}
:global(p) {
word-wrap: normal;
font-size: var(--bodyTextSize);
color: var(--lightShade);
}
:global(li) {
word-wrap: normal;
font-size: var(--bodyTextSize);
color: var(--lightShade);
word-wrap: normal;
font-size: var(--bodyTextSize);
color: var(--lightShade);
}
</style>
</style>

View file

@ -14,7 +14,7 @@ let totalArticles: number = $derived(articlesData.totalArticles);
const userNames = {
github: 'BradNut',
linkedIn: 'bradley-shellnut',
email: 'bradleyshellnut@pm.me',
email: 'website@bradleyshellnut.com',
};
</script>

View file

@ -1,14 +1,20 @@
<script lang="ts">
import cruise from '$lib/assets/images/cruise.png?enhanced';
import orange_derp from '$lib/assets/images/orange_derp.jpg?enhanced';
import tortie_derp from '$lib/assets/images/tortie_derp.jpg?enhanced';
import turnip from '$lib/assets/images/turnip.svg';
import type { Course } from '$lib/types/courses';
import { dockerIcon, drizzleIcon, nextDotJsIcon, reactIcon, svelteIcon, typescriptIcon } from '$lib/util/logoIcons.svelte';
import CourseCard from './CourseCard.svelte';
import TechListItem from './TechListItem.svelte';
import courseData from './course.json';
import ExternalLink from '$lib/components/ExternalLink.svelte';
import orange_derp from "../../lib/assets/images/orange_derp.jpg?enhanced";
import tortie_derp from "../../lib/assets/images/tortie_derp.jpg?enhanced";
import turnip from "../../lib/assets/images/turnip.svg?enhanced";
import type { Course } from "../../lib/types/courses";
import {
dockerIcon,
drizzleIcon,
nextDotJsIcon,
reactIcon,
svelteIcon,
typescriptIcon,
honoIcon,
} from "../../lib/util/logoIcons.svelte";
import CourseCard from "./CourseCard.svelte";
import courseData from "./course.json";
import ExternalLink from "../../lib/components/ExternalLink.svelte";
const courses: Course[] = courseData.courses;
</script>
@ -18,66 +24,116 @@
<h1>About</h1>
<p>Hey! My name is Bradley Shellnut.</p>
<p>
I'm {new Date().getFullYear() - 1991} years old and I am a full stack
software engineer who's interested in new tech and not afraid to
discover new interests.
I'm {new Date().getFullYear() - 1991} years old and I am a full stack software
engineer who's interested in new tech and not afraid to discover new interests.
</p>
</div>
<div>
<h2>More deets</h2>
<p>
I graduated from Cal Poly San Luis Obispo in 2013 with a Bachelor's
degree in Computer Engineering.{' '}
<span class="emoji" title="Software + Hardware">
💻
</span>
I graduated from Cal Poly San Luis Obispo in 2013 with a Bachelor's degree
in Computer Engineering.{" "}
<span class="emoji" title="Software + Hardware"> 💻 </span>
</p>
<p>
At work I develop in Java Spring, Spring Boot, PostgreSQL, and React /
Angular.
</p>
<p>
At home I delve into other frameworks, languages, and platforms such
as:
At home I delve into other frameworks, languages, and platforms such as:
</p>
<div class="tech-list">
<ExternalLink
iconData={{ type: 'svg', icon: svelteIcon, iconClass: 'center' }}
linkData={{ href: 'https://svelte.dev', ariaLabel: 'Svelte', title: 'Svelte', target: '_blank', clazz: "tech-list-item", textDecoration: 'none' }}
textData={{ text: "Svelte", showIcon: true, location: 'bottom' }}
iconData={{ type: "svg", icon: svelteIcon, iconClass: "center" }}
linkData={{
href: "https://svelte.dev",
ariaLabel: "Svelte",
title: "Svelte",
target: "_blank",
clazz: "tech-list-item",
textDecoration: "none",
}}
textData={{ text: "Svelte", showIcon: true, location: "bottom" }}
/>
<ExternalLink
iconData={{ type: 'svg', icon: typescriptIcon, iconClass: 'center' }}
linkData={{ href: 'https://www.typescriptlang.org/', ariaLabel: 'TypeScript', title: 'TypeScript', target: '_blank', clazz: "tech-list-item", textDecoration: 'none' }}
textData={{ text: "TypeScript", showIcon: true, location: 'bottom' }}
iconData={{ type: "svg", icon: honoIcon, iconClass: "center" }}
linkData={{
href: "https://hono.dev",
ariaLabel: "Hono",
title: "Hono",
target: "_blank",
clazz: "tech-list-item",
textDecoration: "none",
}}
textData={{ text: "Hono", showIcon: true, location: "bottom" }}
/>
<ExternalLink
iconData={{ type: 'svg', icon: drizzleIcon, iconClass: 'center' }}
linkData={{ href: 'https://orm.drizzle.team/', ariaLabel: 'Drizzle ORM', title: 'Drizzle ORM', target: '_blank', clazz: "tech-list-item", textDecoration: 'none' }}
textData={{ text: "Drizzle ORM", showIcon: true, location: 'bottom' }}
iconData={{ type: "svg", icon: typescriptIcon, iconClass: "center" }}
linkData={{
href: "https://www.typescriptlang.org/",
ariaLabel: "TypeScript",
title: "TypeScript",
target: "_blank",
clazz: "tech-list-item",
textDecoration: "none",
}}
textData={{ text: "TypeScript", showIcon: true, location: "bottom" }}
/>
<ExternalLink
iconData={{ type: 'svg', icon: reactIcon, iconClass: 'center' }}
linkData={{ href: 'https://reactjs.org/', ariaLabel: 'React', title: 'React', target: '_blank', clazz: "tech-list-item", textDecoration: 'none' }}
textData={{ text: "React", showIcon: true, location: 'bottom' }}
iconData={{ type: "svg", icon: drizzleIcon, iconClass: "center" }}
linkData={{
href: "https://orm.drizzle.team/",
ariaLabel: "Drizzle ORM",
title: "Drizzle ORM",
target: "_blank",
clazz: "tech-list-item",
textDecoration: "none",
}}
textData={{ text: "Drizzle ORM", showIcon: true, location: "bottom" }}
/>
<ExternalLink
iconData={{ type: 'svg', icon: nextDotJsIcon, iconClass: 'center' }}
linkData={{ href: 'https://nextjs.org/', ariaLabel: 'Next.js', title: 'Next.js', target: '_blank', clazz: "tech-list-item", textDecoration: 'none' }}
textData={{ text: "Next.js", showIcon: true, location: 'bottom' }}
iconData={{ type: "svg", icon: reactIcon, iconClass: "center" }}
linkData={{
href: "https://reactjs.org/",
ariaLabel: "React",
title: "React",
target: "_blank",
clazz: "tech-list-item",
textDecoration: "none",
}}
textData={{ text: "React", showIcon: true, location: "bottom" }}
/>
<ExternalLink
iconData={{ type: 'svg', icon: dockerIcon, iconClass: 'center' }}
linkData={{ href: 'https://www.docker.com/', ariaLabel: 'Docker', title: 'Docker', target: '_blank', clazz: "tech-list-item", textDecoration: 'none' }}
textData={{ text: "Docker", showIcon: true, location: 'bottom' }}
iconData={{ type: "svg", icon: nextDotJsIcon, iconClass: "center" }}
linkData={{
href: "https://nextjs.org/",
ariaLabel: "Next.js",
title: "Next.js",
target: "_blank",
clazz: "tech-list-item",
textDecoration: "none",
}}
textData={{ text: "Next.js", showIcon: true, location: "bottom" }}
/>
<ExternalLink
iconData={{ type: "svg", icon: dockerIcon, iconClass: "center" }}
linkData={{
href: "https://www.docker.com/",
ariaLabel: "Docker",
title: "Docker",
target: "_blank",
clazz: "tech-list-item",
textDecoration: "none",
}}
textData={{ text: "Docker", showIcon: true, location: "bottom" }}
/>
</div>
</div>
<div>
<h2>Extracurricular</h2>
<p>
Outside of work I like to take tutorials from many instructors like
those below:
Outside of work I like to take tutorials from many instructors like those
below:
</p>
<div class="extracurricular">
{#each courses as course}
@ -88,19 +144,17 @@
<div>
<h2>Other fun things about me&hellip;</h2>
<div style="display: grid;">
<p>
Recently cruised the Mediterranean.
</p>
<p>Recently visited Taiwan and Japan.</p>
<div
style="
display: grid;
grid-template-columns: minmax(200px, 400px);
align-items: center;
justify-content: center;
font-size: 5rem;
"
>
<enhanced:img src={cruise} alt="Clip art of a cruise ship. Cruise icons created by C-mo Box - Flaticon" />
<p class="center">Crusin'</p>
🇹🇼 🇯🇵 🌸
</div>
</div>
<div>
@ -108,7 +162,15 @@
<div class="cat-pics">
<figure>
<enhanced:img src={tortie_derp} alt="Tortie Cat" />
<p class="center">Turnip <img class="icon" src={String(turnip)} width="25px" height="25px" alt="Turnip" /></p>
<p class="center">
Turnip <img
class="icon"
src={String(turnip)}
width="25px"
height="25px"
alt="Turnip"
/>
</p>
</figure>
<figure>
<enhanced:img src={orange_derp} alt="Tortie Cat" />
@ -173,4 +235,4 @@
justify-content: center;
gap: 2rem;
}
</style>
</style>

View file

@ -1,7 +1,7 @@
import { json, error } from '@sveltejs/kit';
import { BANDCAMP_USERNAME, PAGE_SIZE, USE_REDIS_CACHE } from '$env/static/private';
import { fetchArticlesApi } from '$lib/api';
import { redis } from '$root/lib/server/redis';
import { redis } from '$lib/server/redis';
import type { Album, BandCampResults } from '$lib/types/album';
import scrapeIt, { type ScrapeResult } from 'scrape-it';

View file

@ -1,34 +1,34 @@
import SocialImageCard from "$lib/components/socialImageCard.svelte";
import { componentToPng } from "$lib/renderImage";
import SocialImageCard from '$lib/components/socialImageCard.svelte';
import { componentToPng } from '$lib/renderImage';
const height = 630;
const width = 1200;
/** @type {import('./$types').RequestHandler} */
export async function GET({ url }) {
try {
const faviconImageName = "b_shell_nut_favicon.png";
const image = `${new URL(url.origin).href}${faviconImageName}`;
const header = url.searchParams.get("header") ?? undefined;
const page = url.searchParams.get("page") ?? undefined;
const content = url.searchParams.get("content") ?? "";
try {
const faviconImageName = 'b_shell_nut_favicon.png';
const image = `${new URL(url.origin).href}${faviconImageName}`;
const header = url.searchParams.get('header') ?? undefined;
const page = url.searchParams.get('page') ?? undefined;
const content = url.searchParams.get('content') ?? '';
// @ts-expect-error: Argument of type 'typeof SocialImageCard__SvelteComponent_' is not assignable to parameter of type 'SvelteComponent<any, any, any>'
return componentToPng(
SocialImageCard,
{
header,
page,
content,
image,
width: `${width}`,
height: `${height}`,
url: new URL(url.origin).href,
},
height,
width
);
} catch (e) {
console.error(e);
}
// @ts-expect-error: Argument of type 'typeof SocialImageCard__SvelteComponent_' is not assignable to parameter of type 'SvelteComponent<any, any, any>'
return componentToPng(
SocialImageCard,
{
header,
page,
content,
image,
width: `${width}`,
height: `${height}`,
url: new URL(url.origin).href,
},
height,
width,
);
} catch (e) {
console.error(e);
}
}

View file

@ -5,39 +5,39 @@ import { PUBLIC_SITE_URL } from '$env/static/public';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ url }) => {
const baseUrl = new URL(url.origin).href || PUBLIC_SITE_URL || 'https://bradleyshellnut.com';
const currentPageUrl = new URL(url.pathname, url.origin).href;
const baseUrl = new URL(url.origin).href || PUBLIC_SITE_URL || 'https://bradleyshellnut.com';
const currentPageUrl = new URL(url.pathname, url.origin).href;
const metaTags: MetaTagsProps = Object.freeze({
title: 'Portfolio',
description: "Bradley Shellnut's Portfolio",
openGraph: {
title: 'Portfolio',
description: "Bradley Shellnut's Portfolio",
url: currentPageUrl,
siteName: 'Bradley Shellnut Personal Website',
type: 'website',
locale: 'en_US',
images: [
{
url: `${baseUrl}og?header=Portfolio | bradleyshellnut.com&page=My portfolio of sites I have created.`,
alt: 'Bradley Shellnut Portfolio Page',
width: 1200,
height: 630
}
]
},
twitter: {
title: 'Portfolio',
description: "Bradley Shellnut's Portfolio",
card: 'summary_large_image',
image: `${baseUrl}og?header=Portfolio | bradleyshellnut.com&page=My portfolio of sites I have created.`,
imageAlt: 'Bradley Shellnut Website Logo'
},
url: currentPageUrl
});
const metaTags: MetaTagsProps = Object.freeze({
title: 'Portfolio',
description: "Bradley Shellnut's Portfolio",
openGraph: {
title: 'Portfolio',
description: "Bradley Shellnut's Portfolio",
url: currentPageUrl,
siteName: 'Bradley Shellnut Personal Website',
type: 'website',
locale: 'en_US',
images: [
{
url: `${baseUrl}og?header=Portfolio | bradleyshellnut.com&page=My portfolio of sites I have created.`,
alt: 'Bradley Shellnut Portfolio Page',
width: 1200,
height: 630,
},
],
},
twitter: {
title: 'Portfolio',
description: "Bradley Shellnut's Portfolio",
card: 'summary_large_image',
image: `${baseUrl}og?header=Portfolio | bradleyshellnut.com&page=My portfolio of sites I have created.`,
imageAlt: 'Bradley Shellnut Website Logo',
},
url: currentPageUrl,
});
return {
metaTagsChild: metaTags
};
return {
metaTagsChild: metaTags,
};
};

View file

@ -15,7 +15,7 @@
import shellnutArchitectWebsite from "../../lib/assets/images/portfolio/Mark_Shellnut_Architect.png?enhanced";
import oldSite from "../../lib/assets/images/portfolio/Old_Website_Bradley_Shellnut.png?enhanced";
import weddingWebsite from "../../lib/assets/images/portfolio/Wedding_Website.png?enhanced";
import { gitHubIcon } from "$root/lib/util/logoIcons.svelte";
import { gitHubIcon } from "$lib/util/logoIcons.svelte";
</script>
{#snippet links(externalLinks: ExternalLinkType[])}
@ -124,34 +124,43 @@
/>
</li>
</ul>
<p>The previous version of my website was written using React and Gatsby which
you can view <ExternalLink
linkData={{
href: "https://wonderful-austin-9f17d2.netlify.app/",
ariaLabel: "React and Gatsby Personal Site version",
}}
textData={{ text: "here.", showIcon: true, location: "left" }}
/></p>
<p>
The previous version of my website was written using React and Gatsby
which you can view <ExternalLink
linkData={{
href: "https://wonderful-austin-9f17d2.netlify.app/",
ariaLabel: "React and Gatsby Personal Site version",
}}
textData={{ text: "here.", showIcon: true, location: "left" }}
/>
</p>
<p>
Each iteration brings better code and my previous React version was
improved after the suggestions on <ExternalLink
linkData={{
href: "https://syntax.fm/show/444/syntax-highlight#t=33:19",
ariaLabel: "Syntax.fm Podcast Number 444",
}}
textData={{ text: "Show 444", showIcon: true, location: "left" }}
/> of the <ExternalLink
linkData={{ href: "https://syntax.fm/", ariaLabel: "Syntax.fm" }}
textData={{ text: "Syntax Pocast.", showIcon: true, location: "left" }}
/></p>
<p>You can view the previous archived version of the site before those
changes <ExternalLink
textData={{ text: "here.", showIcon: true, location: "left" }}
linkData={{
href: "https://web.archive.org/web/20210224002046/https://bradleyshellnut.com/",
ariaLabel: "Archive before Syntax Podcast",
}}
/></p>
improved after the suggestions on <ExternalLink
linkData={{
href: "https://syntax.fm/show/444/syntax-highlight#t=33:19",
ariaLabel: "Syntax.fm Podcast Number 444",
}}
textData={{ text: "Show 444", showIcon: true, location: "left" }}
/> of the <ExternalLink
linkData={{ href: "https://syntax.fm/", ariaLabel: "Syntax.fm" }}
textData={{
text: "Syntax Pocast.",
showIcon: true,
location: "left",
}}
/>
</p>
<p>
You can view the previous archived version of the site before those
changes <ExternalLink
textData={{ text: "here.", showIcon: true, location: "left" }}
linkData={{
href: "https://web.archive.org/web/20210224002046/https://bradleyshellnut.com/",
ariaLabel: "Archive before Syntax Podcast",
}}
/>
</p>
</Portfolio>
<Portfolio
name="Wedding Website"
@ -169,16 +178,24 @@
},
]}
>
<p>The app was initially created for my wedding but what is linked here is a public demo of the application.</p>
<p>An application that allows viewing of wedding details and provides the ability to RSVP to the wedding.</p>
<p>
The app was initially created for my wedding but what is linked here is
a public demo of the application.
</p>
<p>
An application that allows viewing of wedding details and provides the
ability to RSVP to the wedding.
</p>
<p>Tech stack:</p>
<ul>
<li>Next.js 13</li>
<li>React 18</li>
<li><ExternalLink
linkData={{ href: "https://radix-ui.com/", ariaLabel: "Radix UI" }}
textData={{ text: "Radix UI", showIcon: true, location: "left" }}
/></li>
<li>
<ExternalLink
linkData={{ href: "https://radix-ui.com/", ariaLabel: "Radix UI" }}
textData={{ text: "Radix UI", showIcon: true, location: "left" }}
/>
</li>
<li>MongoDB</li>
<li>Styled Components</li>
<li>Next Iron Session</li>

View file

@ -1,7 +1,7 @@
<script lang="ts">
import type { Picture } from 'vite-imagetools';
import type { Snippet } from 'svelte';
import type { ExternalLinkType } from '$lib/types/externalLinkType';
import type { Picture } from "vite-imagetools";
import type { Snippet } from "svelte";
import type { ExternalLinkType } from "$lib/types/externalLinkType";
const {
links,
@ -10,11 +10,20 @@
src,
alt,
style,
fetchPriority = 'auto',
loading = 'lazy',
children
}: { links: Snippet<ExternalLinkType[]>, externalLinks: ExternalLinkType[], name: string; src: string | Picture; alt: string;
style: string; fetchPriority?: 'high' | 'low' | 'auto'; loading?: 'lazy' | 'eager', children?: Snippet } = $props();
fetchPriority = "auto",
loading = "lazy",
children,
}: {
links: Snippet<ExternalLinkType[]>;
externalLinks: ExternalLinkType[];
name: string;
src: string | Picture;
alt: string;
style: string;
fetchPriority?: "high" | "low" | "auto";
loading?: "lazy" | "eager";
children?: Snippet;
} = $props();
</script>
<div class="portfolio">

View file

@ -1,70 +1,147 @@
<script lang="ts">
import ExternalLink from '$lib/components/ExternalLink.svelte';
import ExternalLink from "$lib/components/ExternalLink.svelte";
</script>
<div>
<h1>Privacy</h1>
<p>
Long story short, I believe everyone should know who has your personal
data, how it is being collected/stored, and what it is being used for.
Long story short, I believe everyone should know who has your personal data,
how it is being collected/stored, and what it is being used for.
</p>
<p>
However, it is ultimately up to each person to determine how much data
they are willing to give to any business/entity.
However, it is ultimately up to each person to determine how much data they
are willing to give to any business/entity.
</p>
<p>
For the sake of transparency I am using <ExternalLink textData={{ text: "Umami Analytics", showIcon: true, location: "left" }} linkData={{ href: "https://umami.is", ariaLabel: "Umami Analytics" }} /> to anonymously track visits to my site. You can completely block this if you want by either using an AdBlocker like <ExternalLink textData={{ text: "uBlock Origin", showIcon: true, location: "left" }} linkData={{ href: "https://ublockorigin.com/", ariaLabel: "uBlock Origin" }} />. Sending "Do Not Track" requests in your browser is supported but not overall it is not recommended to turn this on since it can be used to fingerprint you on the web.</p>
For the sake of transparency I am using <ExternalLink
textData={{ text: "Umami Analytics", showIcon: true, location: "left" }}
linkData={{ href: "https://umami.is", ariaLabel: "Umami Analytics" }}
/> to anonymously track visits to my site. You can completely block this if you
want by either using an AdBlocker like <ExternalLink
textData={{ text: "uBlock Origin", showIcon: true, location: "left" }}
linkData={{
href: "https://ublockorigin.com/",
ariaLabel: "uBlock Origin",
}}
/>. Sending "Do Not Track" requests in your browser is supported but not
overall it is not recommended to turn this on since it can be used to
fingerprint you on the web.
</p>
</div>
<div>
<h2>Useful Resources</h2>
<p>
Here are a few sites/lists of privacy oriented software for you to
check out:
Here are a few sites/lists of privacy oriented software for you to check
out:
</p>
<ul>
<li>
<ExternalLink linkData={{ href: "https://www.eff.org/privacybadger", ariaLabel: "Privacy Badger" }} textData={{ text: "Privacy Badger", showIcon: true, location: "left" }} />
<ExternalLink textData={{ text: "Awesome Privacy", showIcon: true, location: "left" }} linkData={{ href: "https://github.com/Lissy93/awesome-privacy", ariaLabel: "Awesome Privacy" }} />
<ExternalLink
textData={{ text: "Awesome Privacy", showIcon: true, location: "left" }}
linkData={{
href: "https://github.com/Lissy93/awesome-privacy",
ariaLabel: "Awesome Privacy",
}}
/>
</li>
<li>
<ExternalLink linkData={{ href: "https://privacyguides.org/", ariaLabel: "Privacy Guides" }} textData={{ text: "Privacy Guides", showIcon: true, location: "left" }} />
<ExternalLink
linkData={{
href: "https://privacyguides.org/",
ariaLabel: "Privacy Guides",
}}
textData={{ text: "Privacy Guides", showIcon: true, location: "left" }}
/>
</li>
<li>
<ExternalLink linkData={{ href: "https://ethical.net/resources/", ariaLabel: "Ethical Alternatives" }} textData={{ text: "Ethical Alternatives", showIcon: true, location: "left" }} />
<ExternalLink
linkData={{
href: "https://ethical.net/resources/",
ariaLabel: "Ethical Alternatives",
}}
textData={{
text: "Ethical Alternatives",
showIcon: true,
location: "left",
}}
/>
</li>
</ul>
</div>
<div>
<h3>Privacy Centric Paid Services I use:</h3>
<ul>
<li>
Article Saving: <ExternalLink textData={{ text: "Wallabag Article Saver", showIcon: true, location: "left" }} linkData={{ href: "https://wallabag.com/", ariaLabel: "Wallabag Article Saver" }} />
</li>
<li>
Anonymous Email Forwarding: <ExternalLink textData={{ text: "SimpleLogin (Now owned by Proton AG)", showIcon: true, location: "left" }} linkData={{ href: "https://simplelogin.io", ariaLabel: "SimpleLogin" }} />
</li>
<li>
Email: <ExternalLink textData={{ text: "ProtonMail", showIcon: true, location: "left" }} linkData={{ href: "https://protonmail.com/", ariaLabel: "ProtonMail" }} />
</li>
<li>
Notes: <ExternalLink textData={{ text: "Joplin Notes", showIcon: true, location: "left" }} linkData={{ href: "https://joplinapp.org/", ariaLabel: "Joplin Notes" }} />
</li>
<li>
VPN: <ExternalLink textData={{ text: "ProtonVPN", showIcon: true, location: "left" }} linkData={{ href: "https://protonvpn.com", ariaLabel: "ProtonVPN" }} />
</li>
</ul>
<h3>Privacy Centric Paid Services I use:</h3>
<ul>
<li>
Article Saving: <ExternalLink
textData={{
text: "Wallabag Article Saver",
showIcon: true,
location: "left",
}}
linkData={{
href: "https://wallabag.com/",
ariaLabel: "Wallabag Article Saver",
}}
/>
</li>
<li>
Anonymous Email Forwarding: <ExternalLink
textData={{
text: "SimpleLogin (Now owned by Proton AG)",
showIcon: true,
location: "left",
}}
linkData={{ href: "https://simplelogin.io", ariaLabel: "SimpleLogin" }}
/>
</li>
<li>
Email: <ExternalLink
textData={{ text: "ProtonMail", showIcon: true, location: "left" }}
linkData={{ href: "https://protonmail.com/", ariaLabel: "ProtonMail" }}
/>
</li>
<li>
Notes: <ExternalLink
textData={{ text: "Joplin Notes", showIcon: true, location: "left" }}
linkData={{ href: "https://joplinapp.org/", ariaLabel: "Joplin Notes" }}
/>
</li>
<li>
VPN: <ExternalLink
textData={{ text: "ProtonVPN", showIcon: true, location: "left" }}
linkData={{ href: "https://protonvpn.com", ariaLabel: "ProtonVPN" }}
/>
</li>
</ul>
</div>
<div>
<h3>NAS Servers for Self Hosting:</h3>
<ul>
<li>
<ExternalLink textData={{ text: "Synology NAS", showIcon: true, location: "left" }} linkData={{ href: "https://synology.com/", ariaLabel: "Synology NAS" }} />
: An easy, not cheap, local solution for Google Services like Drive, Photos, Calendar, other services using Docker, etc. (Yes I should use <ExternalLink textData={{ text: "NextCloud Local Hosting Service", showIcon: true, location: "left" }} linkData={{ href: "https://nextcloud.com/", ariaLabel: "NextCloud Local Hosting Service" }} />...maybe eventually)
</li>
<li>
Custom NAS Server used as a <ExternalLink textData={{ text: "Plex Machine", showIcon: true, location: "left" }} linkData={{ href: "https://www.plex.tv/", ariaLabel: "Plex" }} />
</li>
</ul>
<h3>NAS Servers for Self Hosting:</h3>
<ul>
<li>
<ExternalLink
textData={{ text: "Synology NAS", showIcon: true, location: "left" }}
linkData={{ href: "https://synology.com/", ariaLabel: "Synology NAS" }}
/>
: An easy, not cheap, local solution for Google Services like Drive, Photos,
Calendar, other services using Docker, etc. (Yes I should use <ExternalLink
textData={{
text: "NextCloud Local Hosting Service",
showIcon: true,
location: "left",
}}
linkData={{
href: "https://nextcloud.com/",
ariaLabel: "NextCloud Local Hosting Service",
}}
/>...maybe eventually)
</li>
<li>
Custom NAS Server used as a <ExternalLink
textData={{ text: "Plex Machine", showIcon: true, location: "left" }}
linkData={{ href: "https://www.plex.tv/", ariaLabel: "Plex" }}
/>
</li>
</ul>
</div>
<style lang="postcss">
@ -78,4 +155,4 @@ import ExternalLink from '$lib/components/ExternalLink.svelte';
list-style-type: square;
padding-inline-start: 4rem;
}
</style>
</style>

View file

@ -5,39 +5,39 @@ import { PUBLIC_SITE_URL } from '$env/static/public';
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ url }) => {
const baseUrl = new URL(url.origin).href || PUBLIC_SITE_URL || 'https://bradleyshellnut.com';
const currentPageUrl = new URL(url.pathname, url.origin).href;
const baseUrl = new URL(url.origin).href || PUBLIC_SITE_URL || 'https://bradleyshellnut.com';
const currentPageUrl = new URL(url.pathname, url.origin).href;
const metaTags: MetaTagsProps = Object.freeze({
title: 'Privacy Blog',
description: 'My thoughts on personal internet privacy.',
openGraph: {
title: 'Privacy Blog',
description: 'My thoughts on personal internet privacy.',
url: new URL(url.pathname, url.origin).href,
siteName: 'Bradley Shellnut Personal Website',
type: 'website',
locale: 'en_US',
images: [
{
url: `${baseUrl}og?header=Privacy Blog | bradleyshellnut.com&page=My thoughts on personal internet privacy.`,
alt: 'Bradley Shellnut Privacy Blog',
width: 1200,
height: 630
}
]
},
twitter: {
title: 'Privacy Blog',
description: 'My thoughts on personal internet privacy.',
card: 'summary_large_image',
image: `${baseUrl}og?header=Privacy Blog | bradleyshellnut.com&page=My thoughts on personal internet privacy.`,
imageAlt: 'Bradley Shellnut Website Logo'
},
url: currentPageUrl
});
const metaTags: MetaTagsProps = Object.freeze({
title: 'Privacy Blog',
description: 'My thoughts on personal internet privacy.',
openGraph: {
title: 'Privacy Blog',
description: 'My thoughts on personal internet privacy.',
url: new URL(url.pathname, url.origin).href,
siteName: 'Bradley Shellnut Personal Website',
type: 'website',
locale: 'en_US',
images: [
{
url: `${baseUrl}og?header=Privacy Blog | bradleyshellnut.com&page=My thoughts on personal internet privacy.`,
alt: 'Bradley Shellnut Privacy Blog',
width: 1200,
height: 630,
},
],
},
twitter: {
title: 'Privacy Blog',
description: 'My thoughts on personal internet privacy.',
card: 'summary_large_image',
image: `${baseUrl}og?header=Privacy Blog | bradleyshellnut.com&page=My thoughts on personal internet privacy.`,
imageAlt: 'Bradley Shellnut Website Logo',
},
url: currentPageUrl,
});
return {
metaTagsChild: metaTags
};
return {
metaTagsChild: metaTags,
};
};

View file

@ -1,4 +1,3 @@
@import 'reset.pcss';
@import 'global.pcss';
/* @import '$root/styles/theme.pcss'; */
@import 'typeography.pcss';

View file

@ -14,7 +14,7 @@ const config = {
kit: {
adapter: adapter(),
alias: {
$root: './src'
$: './src',
}
},
compilerOptions: {

View file

@ -1,24 +1,24 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"useUnknownInCatchVariables": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"paths": {
"@/*": ["./src/*"]
}
}
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"useUnknownInCatchVariables": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"paths": {
"@/*": ["./src/*"]
}
}
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}

View file

@ -1,45 +1,28 @@
import { sveltekit } from "@sveltejs/kit/vite";
import { defineConfig } from "vite";
import { enhancedImages } from "@sveltejs/enhanced-img";
import { imagetools } from "@zerodevx/svelte-img/vite";
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
import { enhancedImages } from '@sveltejs/enhanced-img';
import { imagetools } from '@zerodevx/svelte-img/vite';
export default defineConfig({
plugins: [
enhancedImages(),
sveltekit(),
imagetools({
// By default, directives are `?width=480;1024;1920&format=avif;webp;jpg`
// Now we change it to generate 5 variants instead - `avif/jpg` formats at `640/1280` + LQIP (Now as:run)
profiles: {
run: new URLSearchParams(
"?w=300;480;640;1024;1920&format=avif;webp;jpg&as=run:64"
),
},
}),
],
esbuild: {
target: "es2022",
},
test: {
include: ["src/**/*.{test,spec}.{js,ts}"],
mockReset: true,
},
css: {
devSourcemap: true,
preprocessorOptions: {
postcss: {
additionalData: `
@custom-media --below_small (width < 400px);
@custom-media --below_med (width < 700px);
@custom-media --below_large (width < 900px);
@custom-media --below_xlarge (width < 1200px);
@custom-media --above_small (width > 400px);
@custom-media --above_med (width > 700px);
@custom-media --above_large (width > 900px);
@custom-media --above_xlarge (width > 1200px);
`,
},
},
},
plugins: [
enhancedImages(),
sveltekit(),
imagetools({
// By default, directives are `?width=480;1024;1920&format=avif;webp;jpg`
// Now we change it to generate 5 variants instead - `avif/jpg` formats at `640/1280` + LQIP (Now as:run)
profiles: {
run: new URLSearchParams('?w=300;480;640;1024;1920&format=avif;webp;jpg&as=run:64'),
},
}),
],
esbuild: {
target: 'es2022',
},
test: {
include: ['src/**/*.{test,spec}.{js,ts}'],
mockReset: true,
},
css: {
devSourcemap: true,
},
});