Updating external link and about page, subsequent components, to use it.

This commit is contained in:
Bradley Shellnut 2024-12-12 16:55:46 -08:00
parent 15135369f4
commit 553ce0f783
12 changed files with 966 additions and 1021 deletions

View file

@ -18,15 +18,15 @@
"packageManager": "pnpm@9.11.0",
"devDependencies": {
"@biomejs/biome": "^1.9.4",
"@playwright/test": "^1.49.0",
"@sveltejs/adapter-node": "^5.2.9",
"@sveltejs/enhanced-img": "^0.4.1",
"@sveltejs/kit": "^2.9.0",
"@sveltejs/vite-plugin-svelte": "^5.0.1",
"@playwright/test": "^1.49.1",
"@sveltejs/adapter-node": "^5.2.10",
"@sveltejs/enhanced-img": "^0.4.3",
"@sveltejs/kit": "^2.11.0",
"@sveltejs/vite-plugin-svelte": "^5.0.2",
"@zerodevx/svelte-img": "^2.1.2",
"autoprefixer": "^10.4.20",
"just-intersect": "^4.3.0",
"mdsvex": "^0.11.2",
"mdsvex": "^0.12.3",
"mdsvex-relative-images": "^1.0.3",
"postcss": "^8.4.49",
"postcss-import": "^16.1.0",
@ -36,7 +36,7 @@
"satori-html": "^0.3.2",
"scrape-it": "^6.1.3",
"sharp": "^0.33.5",
"svelte": "^5.9.0",
"svelte": "^5.11.3",
"svelte-check": "^4.1.0",
"svelte-meta-tags": "^4.0.4",
"svelte-preprocess": "^6.0.3",
@ -46,19 +46,19 @@
"vanilla-lazyload": "^19.1.3",
"vite": "^6.0.2",
"vite-imagetools": "^7.0.5",
"vitest": "^1.6.0"
"vitest": "^2.1.8"
},
"type": "module",
"dependencies": {
"@resvg/resvg-js": "^2.6.2",
"@sveltejs/adapter-vercel": "^5.5.0",
"@sveltejs/adapter-vercel": "^5.5.2",
"@types/nprogress": "^0.2.3",
"@unpic/svelte": "^0.0.56",
"@unpic/svelte": "^0.0.57",
"@vercel/og": "^0.6.4",
"bits-ui": "1.0.0-next.66",
"flexsearch": "^0.7.43",
"ioredis": "^5.4.1",
"lucide-svelte": "^0.462.0",
"lucide-svelte": "^0.468.0",
"nprogress": "^0.2.0",
"svelte-local-storage-store": "^0.6.4"
}

File diff suppressed because it is too large Load diff

View file

@ -1,28 +1,34 @@
<script lang="ts">
import { Mail } from 'lucide-svelte';
import { blueSkyIcon, gitHubIcon, linkedInIcon, xIcon } from '../util/logoIcons.svelte';
import { Mail } from "lucide-svelte";
import {
blueSkyIcon,
gitHubIcon,
linkedInIcon,
xIcon,
} from "../util/logoIcons.svelte";
import ExternalLink from '$lib/components/ExternalLink.svelte';
interface Props {
showBlueSky?: boolean;
showEmail?: boolean;
showGithub?: boolean;
showLinkedIn?: boolean;
showX?: boolean;
userNames: Record<string, string>;
showText?: boolean;
justify?: boolean;
}
showBlueSky?: boolean;
showEmail?: boolean;
showGithub?: boolean;
showLinkedIn?: boolean;
showX?: boolean;
userNames: Record<string, string>;
showText?: boolean;
justify?: boolean;
}
let {
showBlueSky = false,
showEmail = false,
showGithub = false,
showLinkedIn = false,
showX = false,
userNames,
showText = false,
justify = false,
}: Props = $props();
showBlueSky = false,
showEmail = false,
showGithub = false,
showLinkedIn = false,
showX = false,
userNames,
showText = false,
justify = false,
}: Props = $props();
</script>
{#if showText}
@ -30,129 +36,94 @@
{/if}
<div class:justifyCenter={justify}>
{#if showX && userNames?.x}
<a
href={`https://www.x.com/${userNames.x}`}
target="_blank"
title="Contact through X"
aria-label="Contact through X"
rel="noreferrer"
class="hub-icon"
>
{@render xIcon('x-contact')}
</a>
<ExternalLink
linkData={{ href: `https://www.x.com/${userNames.x}`, ariaLabel: 'Contact through X', title: 'Contact through X', target: '_blank', clazz: "hub-icon x-contact" }}
iconData={{ type: 'svg', icon: xIcon, iconClass: 'center' }}
textData={{ showIcon: true }}
/>
{/if}
{#if showBlueSky && userNames?.blueSky}
<a
href={`https://bsky.app/profile/${userNames.blueSky}`}
target="_blank"
title="Contact through Bluesky"
aria-label="Contact through Bluesky"
rel="noreferrer"
class="hub-icon"
>
{@render blueSkyIcon('bluesky-contact')}
</a>
<ExternalLink
linkData={{ href: `https://bsky.app/profile/${userNames.blueSky}`, ariaLabel: 'Contact through Bluesky', title: 'Contact through Bluesky', target: '_blank', clazz: "hub-icon bluesky-contact" }}
iconData={{ type: 'svg', icon: blueSkyIcon, iconClass: 'center' }}
textData={{ showIcon: true }}
/>
{/if}
{#if showLinkedIn && userNames?.linkedIn}
<a
href={`https://www.linkedin.com/in/${userNames.linkedIn}`}
target="_blank"
title="Contact through LinkedIn"
aria-label="Contact through LinkedIn"
rel="noreferrer"
class="hub-icon"
>
{@render linkedInIcon('linkedIn-contact')}
</a>
{/if}
<ExternalLink
linkData={{ href: `https://www.linkedin.com/in/${userNames.linkedIn}`, ariaLabel: 'Contact through LinkedIn', title: 'Contact through LinkedIn', target: '_blank', clazz: "hub-icon linkedIn-contact" }}
iconData={{ type: 'svg', icon: linkedInIcon, iconClass: 'center' }}
textData={{ showIcon: true }}
/>
{/if}
{#if showGithub && userNames?.github}
<a
href={`https://www.github.com/${userNames.github}`}
target="_blank"
title="View Github"
aria-label="View Github"
rel="noreferrer"
class="hub-icon github-contact"
>
{@render gitHubIcon('github-contact')}
</a>
{/if}
<ExternalLink
linkData={{ href: `https://www.github.com/${userNames.github}`, ariaLabel: 'Contact through Github', title: 'Contact through Github', target: '_blank', clazz: "hub-icon github-contact" }}
iconData={{ type: 'svg', icon: gitHubIcon, iconClass: 'center' }}
textData={{ showIcon: true }}
/>
{/if}
{#if showEmail && userNames?.email}
<a
href={`mailto:${userNames.email}`}
target="_blank"
title="Contact by email"
aria-label="Contact by email"
rel="noreferrer"
class="email-contact hub-icon"
>
<Mail size="25" />
</a>
{/if}
<ExternalLink
linkData={{ href: `mailto:${userNames.email}`, ariaLabel: 'Contact by email', title: 'Contact by email', target: '_blank', clazz: "hub-icon email-contact" }}
iconData={{ type: 'icon', icon: Mail, iconClass: 'center' }}
textData={{ showIcon: true }}
/>
{/if}
</div>
<style lang="postcss">
div {
display: flex;
flex-wrap: wrap;
gap: 2rem;
align-items: center;
&.justifyCenter {
justify-content: center;
}
--xColor: #000000;
--linkedInColor: #0077B5;
--githubColor: #000000;
--blueskyColor: #0085FF;
--emailColor: var(--linkHover);
}
.hub-icon {
transition: transform 0.2s cubic-bezier(0.65, 0, 0.35, 1);
&:hover {
transition: transform 0.4s cubic-bezier(0.65, 0, 0.35, 1);
transform: translateY(-4px);
}
}
a {
font-size: 4rem;
margin: 1.5rem;
@media (max-width: 550px) {
font-size: 3.55rem;
}
}
.x-contact {
color: var(--textColor);
&:hover {
color: var(--twitterColor);
}
}
.linkedIn-contact {
color: var(--textColor);
&:hover {
color: var(--linkedInColor);
}
}
.github-contact {
color: var(--textColor);
&:hover {
color: var(--githubColor);
}
:global(.hub-icon) {
transition: transform 0.2s cubic-bezier(0.65, 0, 0.35, 1);
&:hover {
transition: transform 0.4s cubic-bezier(0.65, 0, 0.35, 1);
transform: translateY(-4px);
}
}
.bluesky-contact {
:global(.x-contact) {
color: var(--textColor);
&:hover {
color: var(--xColor);
}
}
:global(.linkedIn-contact) {
color: var(--textColor);
&:hover {
color: var(--linkedInColor);
}
}
:global(.github-contact) {
color: var(--textColor);
&:hover {
color: var(--githubColor);
}
}
:global(.bluesky-contact) {
color: var(--textColor);
&:hover {
color: var(--blueskyColor);
}
}
.email-contact {
color: var(--textColor);
&:hover {
color: var(--linkHover);
}
}
</style>
}
:global(.email-contact) {
color: var(--textColor);
&:hover {
color: var(--linkHover);
}
}
</style>

View file

@ -1,47 +1,112 @@
<script lang="ts">
import { ExternalLink,type Icon as IconType } from 'lucide-svelte';
import { ExternalLink } from "lucide-svelte";
import type {
ExternalLinkType,
LinkIconType,
} from "$lib/types/externalLinkTypes";
interface Props {
rel?: string;
target?: string;
href: string;
ariaLabel: string;
showIcon?: boolean;
clazz?: string;
icon?: typeof IconType | string;
children?: import('svelte').Snippet;
const { iconData, linkData, textData }: ExternalLinkType = $props();
let textLocationClass = "";
if (textData?.location === "top") {
textLocationClass = "text-top";
} else if (textData?.location === "bottom") {
textLocationClass = "text-bottom";
} else if (textData?.location === "left") {
textLocationClass = "text-left";
} else if (textData?.location === "right") {
textLocationClass = "text-right";
}
let { rel = 'noreferrer', target = '_blank', href, ariaLabel, showIcon = false, clazz = '', icon = ExternalLink, children }: Props = $props();
const linkDecoration =
linkData?.textDecoration && linkData?.textDecoration === "none"
? `text-decoration-${linkData?.textDecoration}`
: "text-decoration-underline";
const linkClass =
`${linkData?.clazz} ${textLocationClass} ${linkDecoration}`.trim();
</script>
<a class:show-icon={showIcon} class={clazz} aria-label={`Open ${ariaLabel} externally`} title={`Open ${ariaLabel} externally`} {href} {rel} {target}>
{@render children?.()}
{#if showIcon}
{#if typeof icon === 'string'}
{@html icon}
{:else}
{@const Icon = icon}
<Icon />
{#snippet externalLink({ iconData, linkData, textData }: ExternalLinkType)}
<a
class={linkClass}
aria-label={`Open ${linkData?.ariaLabel ?? linkData?.title ?? linkData?.href} externally`}
title={linkData?.title ?? `Open ${linkData?.ariaLabel} externally`}
href={linkData.href}
rel={linkData?.rel ?? "noreferrer"}
target={linkData?.target ?? "_blank"}
>
{#if textData?.location === "top" || (textData?.location === "left" && textData?.text)}
<span class="link-text">{textData?.text}</span>
{/if}
{#if textData?.showIcon}
{@render linkIcon?.(iconData ?? {})}
{/if}
{#if textData?.location === "bottom" || (textData?.location === "right" && textData?.text)}
<span class="link-text">{textData?.text}</span>
{/if}
</a>
{/snippet}
{#snippet linkIcon({ type, icon, iconClass }: LinkIconType)}
{#if type === "svg" && icon}
<svg
style="width: 2.5rem; height: 2.5rem;"
class={iconClass ?? ""}
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
{@render icon?.()}
</svg>
{:else if type === "icon" && icon}
{@const Icon = icon}
<Icon />
{:else}
{@const Icon = ExternalLink}
<Icon />
{/if}
</a>
{/snippet}
{@render externalLink({ iconData, linkData, textData })}
<style lang="postcss">
a {
margin: 1rem 0;
display: grid;
place-items: center;
padding: 0;
font-size: var(--bodyTextSize);
}
.show-icon {
display: inline-flex;
align-items: center;
.text-top {
padding-bottom: 0.3rem;
}
.text-bottom {
padding-top: 0.3rem;
}
.text-left,
.text-right {
display: flex;
flex-direction: row;
gap: 0.5rem;
}
.text-decoration-none {
text-decoration: none;
}
.text-decoration-underline {
text-decoration: underline;
}
.show-icon {
/* display: inline-flex;
align-items: center;
gap: 0.5rem; */
&:hover {
color: var(--shellYellow);
}
}
</style>
</style>

View file

@ -27,6 +27,7 @@
<style lang="postcss">
footer {
display: grid;
gap: 2rem;
grid-template-rows: repeat(3, 1fr);
background: var(--footerBackground);
place-content: center;

View file

@ -0,0 +1,30 @@
import type { Snippet } from 'svelte';
import type { Icon as IconType } from 'lucide-svelte';
export interface LinkTextType {
text?: string;
showIcon: boolean;
location?: 'top' | 'bottom' | 'left' | 'right';
}
export interface LinkDataType {
rel?: string;
target?: string;
href: string;
title?: string;
ariaLabel: string;
clazz?: string | undefined;
textDecoration?: 'none' | 'underline' | 'line-through';
}
export interface ExternalLinkType {
iconData?: LinkIconType;
linkData: LinkDataType;
textData?: LinkTextType;
}
export interface LinkIconType {
type?: 'icon' | 'svg';
icon?: Snippet | typeof IconType;
iconClass?: string | undefined;
}

View file

@ -1,54 +1,53 @@
<script module>
export { blueSkyIcon, dockerIcon, drizzleIcon, honoIcon, gitHubIcon, linkedInIcon, nextDotJsIcon, reactIcon, svelteIcon, typescriptIcon, xIcon };
import { ExternalLink } from 'lucide-svelte';
export { blueSkyIcon, dockerIcon, drizzleIcon, honoIcon, gitHubIcon, linkedInIcon, lucideIcon, nextDotJsIcon, reactIcon, svelteIcon, typescriptIcon, xIcon };
</script>
{#snippet gitHubIcon(clazz)}
<svg class={clazz} role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>GitHub</title><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>
{#snippet gitHubIcon()}
<path fill="currentColor" d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/>
{/snippet}
{#snippet drizzleIcon(clazz, style)}
<svg class={clazz} {style} role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Drizzle</title><path d="M5.353 11.823a1.036 1.036 0 0 0-.395-1.422 1.063 1.063 0 0 0-1.437.399L.138 16.702a1.035 1.035 0 0 0 .395 1.422 1.063 1.063 0 0 0 1.437-.398l3.383-5.903Zm11.216 0a1.036 1.036 0 0 0-.394-1.422 1.064 1.064 0 0 0-1.438.399l-3.382 5.902a1.036 1.036 0 0 0 .394 1.422c.506.283 1.15.104 1.438-.398l3.382-5.903Zm7.293-4.525a1.036 1.036 0 0 0-.395-1.422 1.062 1.062 0 0 0-1.437.399l-3.383 5.902a1.036 1.036 0 0 0 .395 1.422 1.063 1.063 0 0 0 1.437-.399l3.383-5.902Zm-11.219 0a1.035 1.035 0 0 0-.394-1.422 1.064 1.064 0 0 0-1.438.398l-3.382 5.903a1.036 1.036 0 0 0 .394 1.422c.506.282 1.15.104 1.438-.399l3.382-5.902Z"/></svg>
{#snippet drizzleIcon()}
<path fill="currentColor" d="M5.353 11.823a1.036 1.036 0 0 0-.395-1.422 1.063 1.063 0 0 0-1.437.399L.138 16.702a1.035 1.035 0 0 0 .395 1.422 1.063 1.063 0 0 0 1.437-.398l3.383-5.903Zm11.216 0a1.036 1.036 0 0 0-.394-1.422 1.064 1.064 0 0 0-1.438.399l-3.382 5.902a1.036 1.036 0 0 0 .394 1.422c.506.283 1.15.104 1.438-.398l3.382-5.903Zm7.293-4.525a1.036 1.036 0 0 0-.395-1.422 1.062 1.062 0 0 0-1.437.399l-3.383 5.902a1.036 1.036 0 0 0 .395 1.422 1.063 1.063 0 0 0 1.437-.399l3.383-5.902Zm-11.219 0a1.035 1.035 0 0 0-.394-1.422 1.064 1.064 0 0 0-1.438.398l-3.382 5.903a1.036 1.036 0 0 0 .394 1.422c.506.282 1.15.104 1.438-.399l3.382-5.902Z"/>
{/snippet}
{#snippet svelteIcon(clazz)}
<svg class={clazz} role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Svelte</title><path d="M10.354 21.125a4.44 4.44 0 0 1-4.765-1.767 4.109 4.109 0 0 1-.703-3.107 3.898 3.898 0 0 1 .134-.522l.105-.321.287.21a7.21 7.21 0 0 0 2.186 1.092l.208.063-.02.208a1.253 1.253 0 0 0 .226.83 1.337 1.337 0 0 0 1.435.533 1.231 1.231 0 0 0 .343-.15l5.59-3.562a1.164 1.164 0 0 0 .524-.778 1.242 1.242 0 0 0-.211-.937 1.338 1.338 0 0 0-1.435-.533 1.23 1.23 0 0 0-.343.15l-2.133 1.36a4.078 4.078 0 0 1-1.135.499 4.44 4.44 0 0 1-4.765-1.766 4.108 4.108 0 0 1-.702-3.108 3.855 3.855 0 0 1 1.742-2.582l5.589-3.563a4.072 4.072 0 0 1 1.135-.499 4.44 4.44 0 0 1 4.765 1.767 4.109 4.109 0 0 1 .703 3.107 3.943 3.943 0 0 1-.134.522l-.105.321-.286-.21a7.204 7.204 0 0 0-2.187-1.093l-.208-.063.02-.207a1.255 1.255 0 0 0-.226-.831 1.337 1.337 0 0 0-1.435-.532 1.231 1.231 0 0 0-.343.15L8.62 9.368a1.162 1.162 0 0 0-.524.778 1.24 1.24 0 0 0 .211.937 1.338 1.338 0 0 0 1.435.533 1.235 1.235 0 0 0 .344-.151l2.132-1.36a4.067 4.067 0 0 1 1.135-.498 4.44 4.44 0 0 1 4.765 1.766 4.108 4.108 0 0 1 .702 3.108 3.857 3.857 0 0 1-1.742 2.583l-5.589 3.562a4.072 4.072 0 0 1-1.135.499m10.358-17.95C18.484-.015 14.082-.96 10.9 1.068L5.31 4.63a6.412 6.412 0 0 0-2.896 4.295 6.753 6.753 0 0 0 .666 4.336 6.43 6.43 0 0 0-.96 2.396 6.833 6.833 0 0 0 1.168 5.167c2.229 3.19 6.63 4.135 9.812 2.108l5.59-3.562a6.41 6.41 0 0 0 2.896-4.295 6.756 6.756 0 0 0-.665-4.336 6.429 6.429 0 0 0 .958-2.396 6.831 6.831 0 0 0-1.167-5.168Z"/></svg>
{#snippet svelteIcon()}
<path fill="currentColor" d="M10.354 21.125a4.44 4.44 0 0 1-4.765-1.767 4.109 4.109 0 0 1-.703-3.107 3.898 3.898 0 0 1 .134-.522l.105-.321.287.21a7.21 7.21 0 0 0 2.186 1.092l.208.063-.02.208a1.253 1.253 0 0 0 .226.83 1.337 1.337 0 0 0 1.435.533 1.231 1.231 0 0 0 .343-.15l5.59-3.562a1.164 1.164 0 0 0 .524-.778 1.242 1.242 0 0 0-.211-.937 1.338 1.338 0 0 0-1.435-.533 1.23 1.23 0 0 0-.343.15l-2.133 1.36a4.078 4.078 0 0 1-1.135.499 4.44 4.44 0 0 1-4.765-1.766 4.108 4.108 0 0 1-.702-3.108 3.855 3.855 0 0 1 1.742-2.582l5.589-3.563a4.072 4.072 0 0 1 1.135-.499 4.44 4.44 0 0 1 4.765 1.767 4.109 4.109 0 0 1 .703 3.107 3.943 3.943 0 0 1-.134.522l-.105.321-.286-.21a7.204 7.204 0 0 0-2.187-1.093l-.208-.063.02-.207a1.255 1.255 0 0 0-.226-.831 1.337 1.337 0 0 0-1.435-.532 1.231 1.231 0 0 0-.343.15L8.62 9.368a1.162 1.162 0 0 0-.524.778 1.24 1.24 0 0 0 .211.937 1.338 1.338 0 0 0 1.435.533 1.235 1.235 0 0 0 .344-.151l2.132-1.36a4.067 4.067 0 0 1 1.135-.498 4.44 4.44 0 0 1 4.765 1.766 4.108 4.108 0 0 1 .702 3.108 3.857 3.857 0 0 1-1.742 2.583l-5.589 3.562a4.072 4.072 0 0 1-1.135.499m10.358-17.95C18.484-.015 14.082-.96 10.9 1.068L5.31 4.63a6.412 6.412 0 0 0-2.896 4.295 6.753 6.753 0 0 0 .666 4.336 6.43 6.43 0 0 0-.96 2.396 6.833 6.833 0 0 0 1.168 5.167c2.229 3.19 6.63 4.135 9.812 2.108l5.59-3.562a6.41 6.41 0 0 0 2.896-4.295 6.756 6.756 0 0 0-.665-4.336 6.429 6.429 0 0 0 .958-2.396 6.831 6.831 0 0 0-1.167-5.168Z"/>
{/snippet}
{#snippet typescriptIcon(clazz)}
<svg class={clazz} role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>TypeScript</title><path d="M1.125 0C.502 0 0 .502 0 1.125v21.75C0 23.498.502 24 1.125 24h21.75c.623 0 1.125-.502 1.125-1.125V1.125C24 .502 23.498 0 22.875 0zm17.363 9.75c.612 0 1.154.037 1.627.111a6.38 6.38 0 0 1 1.306.34v2.458a3.95 3.95 0 0 0-.643-.361 5.093 5.093 0 0 0-.717-.26 5.453 5.453 0 0 0-1.426-.2c-.3 0-.573.028-.819.086a2.1 2.1 0 0 0-.623.242c-.17.104-.3.229-.393.374a.888.888 0 0 0-.14.49c0 .196.053.373.156.529.104.156.252.304.443.444s.423.276.696.41c.273.135.582.274.926.416.47.197.892.407 1.266.628.374.222.695.473.963.753.268.279.472.598.614.957.142.359.214.776.214 1.253 0 .657-.125 1.21-.373 1.656a3.033 3.033 0 0 1-1.012 1.085 4.38 4.38 0 0 1-1.487.596c-.566.12-1.163.18-1.79.18a9.916 9.916 0 0 1-1.84-.164 5.544 5.544 0 0 1-1.512-.493v-2.63a5.033 5.033 0 0 0 3.237 1.2c.333 0 .624-.03.872-.09.249-.06.456-.144.623-.25.166-.108.29-.234.373-.38a1.023 1.023 0 0 0-.074-1.089 2.12 2.12 0 0 0-.537-.5 5.597 5.597 0 0 0-.807-.444 27.72 27.72 0 0 0-1.007-.436c-.918-.383-1.602-.852-2.053-1.405-.45-.553-.676-1.222-.676-2.005 0-.614.123-1.141.369-1.582.246-.441.58-.804 1.004-1.089a4.494 4.494 0 0 1 1.47-.629 7.536 7.536 0 0 1 1.77-.201zm-15.113.188h9.563v2.166H9.506v9.646H6.789v-9.646H3.375z"/></svg>
{#snippet typescriptIcon()}
<path fill="currentColor" d="M1.125 0C.502 0 0 .502 0 1.125v21.75C0 23.498.502 24 1.125 24h21.75c.623 0 1.125-.502 1.125-1.125V1.125C24 .502 23.498 0 22.875 0zm17.363 9.75c.612 0 1.154.037 1.627.111a6.38 6.38 0 0 1 1.306.34v2.458a3.95 3.95 0 0 0-.643-.361 5.093 5.093 0 0 0-.717-.26 5.453 5.453 0 0 0-1.426-.2c-.3 0-.573.028-.819.086a2.1 2.1 0 0 0-.623.242c-.17.104-.3.229-.393.374a.888.888 0 0 0-.14.49c0 .196.053.373.156.529.104.156.252.304.443.444s.423.276.696.41c.273.135.582.274.926.416.47.197.892.407 1.266.628.374.222.695.473.963.753.268.279.472.598.614.957.142.359.214.776.214 1.253 0 .657-.125 1.21-.373 1.656a3.033 3.033 0 0 1-1.012 1.085 4.38 4.38 0 0 1-1.487.596c-.566.12-1.163.18-1.79.18a9.916 9.916 0 0 1-1.84-.164 5.544 5.544 0 0 1-1.512-.493v-2.63a5.033 5.033 0 0 0 3.237 1.2c.333 0 .624-.03.872-.09.249-.06.456-.144.623-.25.166-.108.29-.234.373-.38a1.023 1.023 0 0 0-.074-1.089 2.12 2.12 0 0 0-.537-.5 5.597 5.597 0 0 0-.807-.444 27.72 27.72 0 0 0-1.007-.436c-.918-.383-1.602-.852-2.053-1.405-.45-.553-.676-1.222-.676-2.005 0-.614.123-1.141.369-1.582.246-.441.58-.804 1.004-1.089a4.494 4.494 0 0 1 1.47-.629 7.536 7.536 0 0 1 1.77-.201zm-15.113.188h9.563v2.166H9.506v9.646H6.789v-9.646H3.375z"/>
{/snippet}
{#snippet reactIcon(clazz)}
<svg class={clazz} role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>React</title><path d="M14.23 12.004a2.236 2.236 0 0 1-2.235 2.236 2.236 2.236 0 0 1-2.236-2.236 2.236 2.236 0 0 1 2.235-2.236 2.236 2.236 0 0 1 2.236 2.236zm2.648-10.69c-1.346 0-3.107.96-4.888 2.622-1.78-1.653-3.542-2.602-4.887-2.602-.41 0-.783.093-1.106.278-1.375.793-1.683 3.264-.973 6.365C1.98 8.917 0 10.42 0 12.004c0 1.59 1.99 3.097 5.043 4.03-.704 3.113-.39 5.588.988 6.38.32.187.69.275 1.102.275 1.345 0 3.107-.96 4.888-2.624 1.78 1.654 3.542 2.603 4.887 2.603.41 0 .783-.09 1.106-.275 1.374-.792 1.683-3.263.973-6.365C22.02 15.096 24 13.59 24 12.004c0-1.59-1.99-3.097-5.043-4.032.704-3.11.39-5.587-.988-6.38-.318-.184-.688-.277-1.092-.278zm-.005 1.09v.006c.225 0 .406.044.558.127.666.382.955 1.835.73 3.704-.054.46-.142.945-.25 1.44-.96-.236-2.006-.417-3.107-.534-.66-.905-1.345-1.727-2.035-2.447 1.592-1.48 3.087-2.292 4.105-2.295zm-9.77.02c1.012 0 2.514.808 4.11 2.28-.686.72-1.37 1.537-2.02 2.442-1.107.117-2.154.298-3.113.538-.112-.49-.195-.964-.254-1.42-.23-1.868.054-3.32.714-3.707.19-.09.4-.127.563-.132zm4.882 3.05c.455.468.91.992 1.36 1.564-.44-.02-.89-.034-1.345-.034-.46 0-.915.01-1.36.034.44-.572.895-1.096 1.345-1.565zM12 8.1c.74 0 1.477.034 2.202.093.406.582.802 1.203 1.183 1.86.372.64.71 1.29 1.018 1.946-.308.655-.646 1.31-1.013 1.95-.38.66-.773 1.288-1.18 1.87-.728.063-1.466.098-2.21.098-.74 0-1.477-.035-2.202-.093-.406-.582-.802-1.204-1.183-1.86-.372-.64-.71-1.29-1.018-1.946.303-.657.646-1.313 1.013-1.954.38-.66.773-1.286 1.18-1.868.728-.064 1.466-.098 2.21-.098zm-3.635.254c-.24.377-.48.763-.704 1.16-.225.39-.435.782-.635 1.174-.265-.656-.49-1.31-.676-1.947.64-.15 1.315-.283 2.015-.386zm7.26 0c.695.103 1.365.23 2.006.387-.18.632-.405 1.282-.66 1.933-.2-.39-.41-.783-.64-1.174-.225-.392-.465-.774-.705-1.146zm3.063.675c.484.15.944.317 1.375.498 1.732.74 2.852 1.708 2.852 2.476-.005.768-1.125 1.74-2.857 2.475-.42.18-.88.342-1.355.493-.28-.958-.646-1.956-1.1-2.98.45-1.017.81-2.01 1.085-2.964zm-13.395.004c.278.96.645 1.957 1.1 2.98-.45 1.017-.812 2.01-1.086 2.964-.484-.15-.944-.318-1.37-.5-1.732-.737-2.852-1.706-2.852-2.474 0-.768 1.12-1.742 2.852-2.476.42-.18.88-.342 1.356-.494zm11.678 4.28c.265.657.49 1.312.676 1.948-.64.157-1.316.29-2.016.39.24-.375.48-.762.705-1.158.225-.39.435-.788.636-1.18zm-9.945.02c.2.392.41.783.64 1.175.23.39.465.772.705 1.143-.695-.102-1.365-.23-2.006-.386.18-.63.406-1.282.66-1.933zM17.92 16.32c.112.493.2.968.254 1.423.23 1.868-.054 3.32-.714 3.708-.147.09-.338.128-.563.128-1.012 0-2.514-.807-4.11-2.28.686-.72 1.37-1.536 2.02-2.44 1.107-.118 2.154-.3 3.113-.54zm-11.83.01c.96.234 2.006.415 3.107.532.66.905 1.345 1.727 2.035 2.446-1.595 1.483-3.092 2.295-4.11 2.295-.22-.005-.406-.05-.553-.132-.666-.38-.955-1.834-.73-3.703.054-.46.142-.944.25-1.438zm4.56.64c.44.02.89.034 1.345.034.46 0 .915-.01 1.36-.034-.44.572-.895 1.095-1.345 1.565-.455-.47-.91-.993-1.36-1.565z"/></svg>
{#snippet reactIcon()}
<path fill="currentColor" d="M14.23 12.004a2.236 2.236 0 0 1-2.235 2.236 2.236 2.236 0 0 1-2.236-2.236 2.236 2.236 0 0 1 2.235-2.236 2.236 2.236 0 0 1 2.236 2.236zm2.648-10.69c-1.346 0-3.107.96-4.888 2.622-1.78-1.653-3.542-2.602-4.887-2.602-.41 0-.783.093-1.106.278-1.375.793-1.683 3.264-.973 6.365C1.98 8.917 0 10.42 0 12.004c0 1.59 1.99 3.097 5.043 4.03-.704 3.113-.39 5.588.988 6.38.32.187.69.275 1.102.275 1.345 0 3.107-.96 4.888-2.624 1.78 1.654 3.542 2.603 4.887 2.603.41 0 .783-.09 1.106-.275 1.374-.792 1.683-3.263.973-6.365C22.02 15.096 24 13.59 24 12.004c0-1.59-1.99-3.097-5.043-4.032.704-3.11.39-5.587-.988-6.38-.318-.184-.688-.277-1.092-.278zm-.005 1.09v.006c.225 0 .406.044.558.127.666.382.955 1.835.73 3.704-.054.46-.142.945-.25 1.44-.96-.236-2.006-.417-3.107-.534-.66-.905-1.345-1.727-2.035-2.447 1.592-1.48 3.087-2.292 4.105-2.295zm-9.77.02c1.012 0 2.514.808 4.11 2.28-.686.72-1.37 1.537-2.02 2.442-1.107.117-2.154.298-3.113.538-.112-.49-.195-.964-.254-1.42-.23-1.868.054-3.32.714-3.707.19-.09.4-.127.563-.132zm4.882 3.05c.455.468.91.992 1.36 1.564-.44-.02-.89-.034-1.345-.034-.46 0-.915.01-1.36.034.44-.572.895-1.096 1.345-1.565zM12 8.1c.74 0 1.477.034 2.202.093.406.582.802 1.203 1.183 1.86.372.64.71 1.29 1.018 1.946-.308.655-.646 1.31-1.013 1.95-.38.66-.773 1.288-1.18 1.87-.728.063-1.466.098-2.21.098-.74 0-1.477-.035-2.202-.093-.406-.582-.802-1.204-1.183-1.86-.372-.64-.71-1.29-1.018-1.946.303-.657.646-1.313 1.013-1.954.38-.66.773-1.286 1.18-1.868.728-.064 1.466-.098 2.21-.098zm-3.635.254c-.24.377-.48.763-.704 1.16-.225.39-.435.782-.635 1.174-.265-.656-.49-1.31-.676-1.947.64-.15 1.315-.283 2.015-.386zm7.26 0c.695.103 1.365.23 2.006.387-.18.632-.405 1.282-.66 1.933-.2-.39-.41-.783-.64-1.174-.225-.392-.465-.774-.705-1.146zm3.063.675c.484.15.944.317 1.375.498 1.732.74 2.852 1.708 2.852 2.476-.005.768-1.125 1.74-2.857 2.475-.42.18-.88.342-1.355.493-.28-.958-.646-1.956-1.1-2.98.45-1.017.81-2.01 1.085-2.964zm-13.395.004c.278.96.645 1.957 1.1 2.98-.45 1.017-.812 2.01-1.086 2.964-.484-.15-.944-.318-1.37-.5-1.732-.737-2.852-1.706-2.852-2.474 0-.768 1.12-1.742 2.852-2.476.42-.18.88-.342 1.356-.494zm11.678 4.28c.265.657.49 1.312.676 1.948-.64.157-1.316.29-2.016.39.24-.375.48-.762.705-1.158.225-.39.435-.788.636-1.18zm-9.945.02c.2.392.41.783.64 1.175.23.39.465.772.705 1.143-.695-.102-1.365-.23-2.006-.386.18-.63.406-1.282.66-1.933zM17.92 16.32c.112.493.2.968.254 1.423.23 1.868-.054 3.32-.714 3.708-.147.09-.338.128-.563.128-1.012 0-2.514-.807-4.11-2.28.686-.72 1.37-1.536 2.02-2.44 1.107-.118 2.154-.3 3.113-.54zm-11.83.01c.96.234 2.006.415 3.107.532.66.905 1.345 1.727 2.035 2.446-1.595 1.483-3.092 2.295-4.11 2.295-.22-.005-.406-.05-.553-.132-.666-.38-.955-1.834-.73-3.703.054-.46.142-.944.25-1.438zm4.56.64c.44.02.89.034 1.345.034.46 0 .915-.01 1.36-.034-.44.572-.895 1.095-1.345 1.565-.455-.47-.91-.993-1.36-1.565z"/>
{/snippet}
{#snippet dockerIcon(clazz)}
<svg class={clazz} role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Docker</title><path d="M13.983 11.078h2.119a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.119a.185.185 0 00-.185.185v1.888c0 .102.083.185.185.185m-2.954-5.43h2.118a.186.186 0 00.186-.186V3.574a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m0 2.716h2.118a.187.187 0 00.186-.186V6.29a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.887c0 .102.082.185.185.186m-2.93 0h2.12a.186.186 0 00.184-.186V6.29a.185.185 0 00-.185-.185H8.1a.185.185 0 00-.185.185v1.887c0 .102.083.185.185.186m-2.964 0h2.119a.186.186 0 00.185-.186V6.29a.185.185 0 00-.185-.185H5.136a.186.186 0 00-.186.185v1.887c0 .102.084.185.186.186m5.893 2.715h2.118a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m-2.93 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.083.185.185.185m-2.964 0h2.119a.185.185 0 00.185-.185V9.006a.185.185 0 00-.184-.186h-2.12a.186.186 0 00-.186.186v1.887c0 .102.084.185.186.185m-2.92 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.082.185.185.185M23.763 9.89c-.065-.051-.672-.51-1.954-.51-.338.001-.676.03-1.01.087-.248-1.7-1.653-2.53-1.716-2.566l-.344-.199-.226.327c-.284.438-.49.922-.612 1.43-.23.97-.09 1.882.403 2.661-.595.332-1.55.413-1.744.42H.751a.751.751 0 00-.75.748 11.376 11.376 0 00.692 4.062c.545 1.428 1.355 2.48 2.41 3.124 1.18.723 3.1 1.137 5.275 1.137.983.003 1.963-.086 2.93-.266a12.248 12.248 0 003.823-1.389c.98-.567 1.86-1.288 2.61-2.136 1.252-1.418 1.998-2.997 2.553-4.4h.221c1.372 0 2.215-.549 2.68-1.009.309-.293.55-.65.707-1.046l.098-.288Z"/></svg>
{#snippet dockerIcon()}
<path fill="currentColor" d="M13.983 11.078h2.119a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.119a.185.185 0 00-.185.185v1.888c0 .102.083.185.185.185m-2.954-5.43h2.118a.186.186 0 00.186-.186V3.574a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m0 2.716h2.118a.187.187 0 00.186-.186V6.29a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.887c0 .102.082.185.185.186m-2.93 0h2.12a.186.186 0 00.184-.186V6.29a.185.185 0 00-.185-.185H8.1a.185.185 0 00-.185.185v1.887c0 .102.083.185.185.186m-2.964 0h2.119a.186.186 0 00.185-.186V6.29a.185.185 0 00-.185-.185H5.136a.186.186 0 00-.186.185v1.887c0 .102.084.185.186.186m5.893 2.715h2.118a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m-2.93 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.083.185.185.185m-2.964 0h2.119a.185.185 0 00.185-.185V9.006a.185.185 0 00-.184-.186h-2.12a.186.186 0 00-.186.186v1.887c0 .102.084.185.186.185m-2.92 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.082.185.185.185M23.763 9.89c-.065-.051-.672-.51-1.954-.51-.338.001-.676.03-1.01.087-.248-1.7-1.653-2.53-1.716-2.566l-.344-.199-.226.327c-.284.438-.49.922-.612 1.43-.23.97-.09 1.882.403 2.661-.595.332-1.55.413-1.744.42H.751a.751.751 0 00-.75.748 11.376 11.376 0 00.692 4.062c.545 1.428 1.355 2.48 2.41 3.124 1.18.723 3.1 1.137 5.275 1.137.983.003 1.963-.086 2.93-.266a12.248 12.248 0 003.823-1.389c.98-.567 1.86-1.288 2.61-2.136 1.252-1.418 1.998-2.997 2.553-4.4h.221c1.372 0 2.215-.549 2.68-1.009.309-.293.55-.65.707-1.046l.098-.288Z"/>
{/snippet}
{#snippet honoIcon(clazz)}
<svg class={clazz} role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Hono</title><path d="M12.445.002a45.529 45.529 0 0 0-5.252 8.146 8.595 8.595 0 0 1-.555-.53 27.796 27.796 0 0 0-1.205-1.542 8.762 8.762 0 0 0-1.251 2.12 20.743 20.743 0 0 0-1.448 5.88 8.867 8.867 0 0 0 .338 3.468c1.312 3.48 3.794 5.593 7.445 6.337 3.055.438 5.755-.333 8.097-2.312 2.677-2.59 3.359-5.634 2.047-9.132a33.287 33.287 0 0 0-2.988-5.59A91.34 91.34 0 0 0 12.615.053a.216.216 0 0 0-.17-.051Zm-.336 3.906a50.93 50.93 0 0 1 4.794 6.552c.448.767.817 1.57 1.108 2.41.606 2.386-.044 4.354-1.951 5.904-1.845 1.298-3.87 1.683-6.072 1.156-2.376-.737-3.75-2.335-4.121-4.794a5.107 5.107 0 0 1 .242-2.266c.358-.908.79-1.774 1.3-2.601l1.446-2.121a397.33 397.33 0 0 0 3.254-4.24Z"/></svg>
{#snippet honoIcon()}
<path fill="currentColor" d="M12.445.002a45.529 45.529 0 0 0-5.252 8.146 8.595 8.595 0 0 1-.555-.53 27.796 27.796 0 0 0-1.205-1.542 8.762 8.762 0 0 0-1.251 2.12 20.743 20.743 0 0 0-1.448 5.88 8.867 8.867 0 0 0 .338 3.468c1.312 3.48 3.794 5.593 7.445 6.337 3.055.438 5.755-.333 8.097-2.312 2.677-2.59 3.359-5.634 2.047-9.132a33.287 33.287 0 0 0-2.988-5.59A91.34 91.34 0 0 0 12.615.053a.216.216 0 0 0-.17-.051Zm-.336 3.906a50.93 50.93 0 0 1 4.794 6.552c.448.767.817 1.57 1.108 2.41.606 2.386-.044 4.354-1.951 5.904-1.845 1.298-3.87 1.683-6.072 1.156-2.376-.737-3.75-2.335-4.121-4.794a5.107 5.107 0 0 1 .242-2.266c.358-.908.79-1.774 1.3-2.601l1.446-2.121a397.33 397.33 0 0 0 3.254-4.24Z"/>
{/snippet}
{#snippet linkedInIcon(clazz)}
<svg class={clazz} role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>LinkedIn</title><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>
{#snippet linkedInIcon()}
<path fill="currentColor" d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/>
{/snippet}
{#snippet xIcon(clazz)}
<svg class={clazz} role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>X</title><path d="M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z"/></svg>
{#snippet xIcon()}
<path fill="currentColor" d="M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z"/>
{/snippet}
{#snippet blueSkyIcon(clazz)}
<svg class={clazz} role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Bluesky</title><path d="M12 10.8c-1.087-2.114-4.046-6.053-6.798-7.995C2.566.944 1.561 1.266.902 1.565.139 1.908 0 3.08 0 3.768c0 .69.378 5.65.624 6.479.815 2.736 3.713 3.66 6.383 3.364.136-.02.275-.039.415-.056-.138.022-.276.04-.415.056-3.912.58-7.387 2.005-2.83 7.078 5.013 5.19 6.87-1.113 7.823-4.308.953 3.195 2.05 9.271 7.733 4.308 4.267-4.308 1.172-6.498-2.74-7.078a8.741 8.741 0 0 1-.415-.056c.14.017.279.036.415.056 2.67.297 5.568-.628 6.383-3.364.246-.828.624-5.79.624-6.478 0-.69-.139-1.861-.902-2.206-.659-.298-1.664-.62-4.3 1.24C16.046 4.748 13.087 8.687 12 10.8Z"/></svg>
{#snippet blueSkyIcon()}
<path fill="currentColor" d="M12 10.8c-1.087-2.114-4.046-6.053-6.798-7.995C2.566.944 1.561 1.266.902 1.565.139 1.908 0 3.08 0 3.768c0 .69.378 5.65.624 6.479.815 2.736 3.713 3.66 6.383 3.364.136-.02.275-.039.415-.056-.138.022-.276.04-.415.056-3.912.58-7.387 2.005-2.83 7.078 5.013 5.19 6.87-1.113 7.823-4.308.953 3.195 2.05 9.271 7.733 4.308 4.267-4.308 1.172-6.498-2.74-7.078a8.741 8.741 0 0 1-.415-.056c.14.017.279.036.415.056 2.67.297 5.568-.628 6.383-3.364.246-.828.624-5.79.624-6.478 0-.69-.139-1.861-.902-2.206-.659-.298-1.664-.62-4.3 1.24C16.046 4.748 13.087 8.687 12 10.8Z"/>
{/snippet}
{#snippet nextDotJsIcon(clazz)}
<svg class={clazz} role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Next.js</title><path d="M18.665 21.978C16.758 23.255 14.465 24 12 24 5.377 24 0 18.623 0 12S5.377 0 12 0s12 5.377 12 12c0 3.583-1.574 6.801-4.067 9.001L9.219 7.2H7.2v9.596h1.615V9.251l9.85 12.727Zm-3.332-8.533 1.6 2.061V7.2h-1.6v6.245Z"/></svg>
{#snippet nextDotJsIcon()}
<path fill="currentColor" d="M18.665 21.978C16.758 23.255 14.465 24 12 24 5.377 24 0 18.623 0 12S5.377 0 12 0s12 5.377 12 12c0 3.583-1.574 6.801-4.067 9.001L9.219 7.2H7.2v9.596h1.615V9.251l9.85 12.727Zm-3.332-8.533 1.6 2.061V7.2h-1.6v6.245Z"/>
{/snippet}
<style lang="postcss">
svg {
width: 2.5rem;
height: 2.5rem;
}
</style>
{#snippet lucideIcon(icon)}
{@const Icon = icon}
<Icon />
{/snippet}

View file

@ -8,6 +8,7 @@ import { dockerIcon, drizzleIcon, nextDotJsIcon, reactIcon, svelteIcon, typescri
import CourseCard from './CourseCard.svelte';
import TechListItem from './TechListItem.svelte';
import courseData from './course.json';
import ExternalLink from '$lib/components/ExternalLink.svelte';
const courses: Course[] = courseData.courses;
</script>
@ -40,47 +41,35 @@ const courses: Course[] = courseData.courses;
as:
</p>
<div class="tech-list">
<TechListItem
itemText="Svelte"
ariaLabel="Svelte"
href="https://svelte.dev"
clazz="center"
icon={svelteIcon}
<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' }}
/>
<TechListItem
itemText="TypeScript"
ariaLabel="TypeScript"
href="https://www.typescriptlang.org/"
clazz="center"
icon={typescriptIcon}
<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' }}
/>
<TechListItem
itemText="Drizzle ORM"
ariaLabel="Prisma ORM"
href="https://orm.drizzle.team/"
clazz="center"
icon={drizzleIcon}
<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' }}
/>
<TechListItem
itemText="React"
ariaLabel="React"
href="https://reactjs.org/"
clazz="center"
icon={reactIcon}
<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' }}
/>
<TechListItem
itemText="NextJS"
ariaLabel="NextJS"
href="https://nextjs.org/"
clazz="center"
icon={nextDotJsIcon}
<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' }}
/>
<TechListItem
itemText="Docker"
ariaLabel="Docker"
href="https://docker.com/"
clazz="center"
icon={dockerIcon}
<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>
@ -155,7 +144,7 @@ const courses: Course[] = courseData.courses;
flex-wrap: wrap;
flex-direction: row;
place-content: center;
gap: 1rem;
gap: 2rem;
margin-top: 1rem;
font-size: 2rem;
}

View file

@ -15,12 +15,9 @@
<h3>
{#each externalLinks as link}
<ExternalLink
ariaLabel={link.ariaLabel}
href={link.href}
showIcon={link.showIcon}
>
{link.text}
</ExternalLink>
linkData={{ href: link.href, ariaLabel: link.ariaLabel, title: link.ariaLabel, target: '_blank', clazz: "tech-list-item" }}
textData={{ text: link.text, showIcon: link.showIcon, location: 'bottom' }}
/>
{/each}
</h3>
<div class="tags">

View file

@ -1,31 +1,28 @@
<script lang="ts">
import ExternalLink from '$lib/components/ExternalLink.svelte';
import { lucideIcon } from '$lib/util/logoIcons.svelte';
import type { Snippet } from "svelte";
import type { LinkTextType } from '$lib/types/externalLinkTypes';
interface Props {
linkData: LinkTextType;
ariaLabel: string;
href: string;
clazz?: string;
itemText: string;
icon?: Snippet;
children?: any;
textData?: LinkTextType;
icon: Snippet;
}
let { ariaLabel, href, clazz = '', itemText, icon, children }: Props = $props();
let { ariaLabel, href, clazz = '', textData, icon }: Props = $props();
</script>
<a
target="_blank"
rel="noreferrer"
aria-label={ariaLabel}
{href}
class={clazz}
>
{#if icon}
{@render icon?.()}
{/if}
{@render children?.()}
<p>{itemText}</p>
</a>
<ExternalLink
ariaLabel={ariaLabel}
href={href}
linkClass={clazz}
icon={lucideIcon}
textData={textData}
/>
<style lang="postcss">
a {

View file

@ -10,7 +10,6 @@
// @ts-expect-error: Cannot find module '$lib/content/uses/development.md' or its corresponding type declarations.ts(2307)
import MarkShellnutArchitect from "$lib/content/portfolio/professional/mark-shellnut-architect.md";
import type { ExternalLinkType } from "$lib/types/externalLinkType";
import GitHub from "@iconify-icons/simple-icons/github";
import { Tabs } from "bits-ui";
import personalSite from "../../lib/assets/images/portfolio/Bradley_Shellnut_New_Site.png?enhanced";
import shellnutArchitectWebsite from "../../lib/assets/images/portfolio/Mark_Shellnut_Architect.png?enhanced";

View file

@ -29,6 +29,12 @@
--linkHover: var(--shellYellow);
--lightHairLine: #c5c5c5;
--xColor: #000000;
--linkedInColor: #0077b5;
--githubColor: #000000;
--blueskyColor: #0085ff;
--emailColor: var(--linkHover);
/* Styles */
--line: solid 1px var(--lineColor);