Add tech stack page and rearrange structure

This commit is contained in:
Clove 2026-05-29 23:23:33 +01:00
parent 5772a5a718
commit 5ce6ad3c91
24 changed files with 1007 additions and 0 deletions

429
css/style.css Normal file
View File

@ -0,0 +1,429 @@
/* Comic Code Import*/
@font-face {
font-family: 'Comic Code';
src: url('https://fonts.doughmination.co.uk/ComicCode-Regular_2022-05-24-151938_hsmz.woff2') format('woff2'),
url('https://fonts.doughmination.co.uk/ComicCode-Regular_2022-05-24-151938_hsmz.woff') format('woff');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'Comic Code';
src: url('https://fonts.doughmination.co.uk/ComicCode-Italic_2022-05-24-151939_rdtu.woff2') format('woff2'),
url('https://fonts.doughmination.co.uk/ComicCode-Italic_2022-05-24-151939_rdtu.woff') format('woff');
font-weight: 400;
font-style: italic;
}
@font-face {
font-family: 'Comic Code';
src: url('https://fonts.doughmination.co.uk/ComicCode-Medium_2022-05-24-151941_ugqm.woff2') format('woff2'),
url('https://fonts.doughmination.co.uk/ComicCode-Medium_2022-05-24-151941_ugqm.woff') format('woff');
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: 'Comic Code';
src: url('https://fonts.doughmination.co.uk/ComicCode-Bold_2022-05-24-152309_zqkm.woff2') format('woff2'),
url('https://fonts.doughmination.co.uk/ComicCode-Bold_2022-05-24-152309_zqkm.woff') format('woff');
font-weight: 700;
font-style: normal;
}
:root {
/* https://catppuccin.com/palette/ */
/* Catppuccin Mocha */
/* accents */
--rosewater: #f5e0dc;
--flamingo: #f2cdcd;
--pink: #f5c2e7;
--mauve: #cba6f7;
--red: #f38ba8;
--maroon: #eba0ac;
--peach: #fab387;
--yellow: #f9e2af;
--green: #a6e3a1;
--teal: #94e2d5;
--sky: #89dceb;
--saphire: #74c7ec;
--blue: #89b4fa;
/* a lovely new blue passport */
--lavender: #b4befe;
/* Text */
--text: #cdd6f4;
--subtext-0: #a6adc8;
--subtext-1: #bac2de;
--overlay-0: #6c7086;
--overlay-1: #7f849c;
--overlay-2: #9399b2;
--surface-0: #313244;
--surface-1: #45475a;
--surface-2: #585b70;
/* Backgrounds */
--base: #1e1e2e;
--mantle: #181825;
--crust: #11111b;
/* Is this the crusty crab? */
}
* {
box-sizing: border-box;
}
/* Smooth cross-fade between pages (Link Center <-> Tech Stack) */
@view-transition {
navigation: auto;
}
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 0.35s;
animation-timing-function: ease;
}
@media (prefers-reduced-motion: reduce) {
::view-transition-old(root),
::view-transition-new(root) {
animation: none;
}
}
html,
body {
height: 100%;
overflow: hidden;
}
body {
font-family: 'Comic Code', sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
height: 100dvh;
margin: 0;
padding: 1.5rem 1rem;
background: linear-gradient(135deg, var(--base) 0%, var(--mantle) 60%, var(--crust) 100%);
color: var(--text);
}
/* Estrogen watermark blended into the background */
body::before {
content: "";
position: fixed;
inset: 0;
background: url(/images/estrogen.svg) center / cover no-repeat;
filter: invert(86%) sepia(8%) saturate(900%) hue-rotate(190deg) brightness(105%);
opacity: 0.05;
pointer-events: none;
z-index: 0;
}
.hub {
position: relative;
z-index: 1;
width: 100%;
max-width: 460px;
}
.pfp {
width: 96px;
height: 96px;
border-radius: 50%;
object-fit: cover;
border: 3px solid var(--yellow);
box-shadow: 0 4px 18px rgba(245, 194, 231, 0.25);
margin-bottom: 0.75rem;
}
.hub-header {
text-align: center;
margin-bottom: 2.25rem;
}
.hub-header h1 {
margin: 0;
font-size: 2rem;
font-weight: 700;
color: var(--pink);
}
.tagline {
margin: 0.35rem 0 0;
color: var(--subtext-0);
font-size: 0.95rem;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.pronouns {
margin: 0.35rem 0 0;
color: var(--mauve);
font-size: 0.95rem;
letter-spacing: 0.04em;
text-transform: lowercase;
}
.links {
display: grid;
grid-template-columns: repeat(auto-fit, 72px);
gap: 0.9rem;
justify-content: center;
}
.link-card {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 72px;
height: 72px;
border-radius: 16px;
background: var(--surface-0);
border: 1px solid var(--surface-1);
color: var(--text);
text-decoration: none;
transition: transform 0.15s ease, border-color 0.15s ease,
background 0.15s ease, box-shadow 0.15s ease;
}
.link-card:hover {
transform: translateY(-3px);
background: var(--surface-1);
border-color: var(--pink);
box-shadow: 0 6px 20px rgba(245, 194, 231, 0.22);
}
.icon {
width: 30px;
height: 30px;
flex-shrink: 0;
filter: invert(86%) sepia(8%) saturate(900%) hue-rotate(190deg) brightness(105%);
transition: filter 0.15s ease;
}
.link-card:hover .icon {
filter: invert(78%) sepia(36%) saturate(640%) hue-rotate(280deg) brightness(105%);
}
/* git.gay: white by default, true pride colors on hover */
.icon.raw {
filter: brightness(0) invert(1);
}
.link-card:hover .icon.raw {
filter: none;
}
/* Details revealed on hover as a tooltip */
.link-text {
position: absolute;
bottom: calc(100% + 10px);
left: 50%;
transform: translateX(-50%) translateY(4px);
display: flex;
flex-direction: column;
align-items: center;
gap: 0.1rem;
padding: 0.5rem 0.75rem;
border-radius: 10px;
background: var(--crust);
border: 1px solid var(--pink);
box-shadow: 0 6px 18px rgba(17, 17, 27, 0.55);
white-space: nowrap;
line-height: 1.3;
opacity: 0;
pointer-events: none;
transition: opacity 0.15s ease, transform 0.15s ease;
z-index: 10;
}
/* Tooltip arrow */
.link-text::after {
content: "";
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
border: 6px solid transparent;
border-top-color: var(--pink);
}
.link-card:hover .link-text,
.link-card:focus-visible .link-text {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
.link-title {
font-weight: 500;
font-size: 0.95rem;
color: var(--text);
}
.link-sub {
font-size: 0.78rem;
color: var(--subtext-0);
}
.link-card.arch .link-title {
color: var(--pink);
}
/* Page nav, pinned bottom-left, selected item with a pointer triangle */
.nav {
position: fixed;
left: 1rem;
bottom: 1rem;
z-index: 6;
}
.nav-links {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 0.4rem;
}
.nav-link {
position: relative;
display: inline-flex;
align-items: center;
padding: 0.3rem 0.7rem;
border-radius: 999px;
background: var(--surface-0);
border: 1px solid var(--surface-1);
color: var(--subtext-1);
font-size: 0.8rem;
text-decoration: none;
transition: transform 0.15s ease, border-color 0.15s ease,
background 0.15s ease, color 0.15s ease;
}
.nav-link:hover {
border-color: var(--pink);
color: var(--text);
transform: translateX(2px);
}
.nav-link.selected {
background: var(--pink);
border-color: var(--pink);
color: var(--crust);
font-weight: 700;
margin-left: 14px;
}
/* Rotated triangle pointing at the selected item */
.nav-link.selected::before {
content: "";
position: absolute;
left: -14px;
top: 50%;
transform: translateY(-50%);
border: 6px solid transparent;
border-left-color: var(--pink);
}
/* Cosmetic system badges, pinned bottom-right */
.badges {
position: fixed;
right: 1rem;
bottom: 1rem;
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 0.4rem;
pointer-events: none;
z-index: 5;
}
.badge {
display: inline-flex;
align-items: center;
gap: 0.4rem;
padding: 0.3rem 0.6rem;
border-radius: 999px;
background: var(--surface-0);
border: 1px solid var(--surface-1);
color: var(--subtext-1);
font-size: 0.75rem;
white-space: nowrap;
}
.badge-icon {
width: 15px;
height: 15px;
filter: invert(78%) sepia(36%) saturate(640%) hue-rotate(280deg) brightness(105%);
}
/* Shrink to fit narrow / short screens so the hub never scrolls */
@media (max-width: 420px),
(max-height: 640px) {
.hub-header {
margin-bottom: 1.25rem;
}
.hub-header h1 {
font-size: 1.6rem;
}
.pfp {
width: 72px;
height: 72px;
margin-bottom: 0.5rem;
}
.links {
grid-template-columns: repeat(auto-fit, 60px);
gap: 0.6rem;
}
.link-card {
width: 60px;
height: 60px;
border-radius: 13px;
}
.icon {
width: 26px;
height: 26px;
}
}
/* ===== Tech stack page ===== */
/* Let only the tech-stack page scroll; link hub stays locked */
html:has(.tech-stack),
body:has(.tech-stack) {
height: auto;
min-height: 100dvh;
overflow-y: auto;
}
body:has(.tech-stack) {
align-items: flex-start;
}
body:has(.tech-stack) .hub {
max-width: 860px;
}
.tech-stack {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 0.5rem;
margin: 0 auto;
padding-bottom: 4.5rem;
}
.tech-badge {
height: 28px;
border-radius: 5px;
transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.tech-badge:hover {
transform: translateY(-2px) scale(1.04);
box-shadow: 0 4px 12px rgba(245, 194, 231, 0.25);
}

3
images/amd.svg Normal file
View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-amd" viewBox="0 0 16 16">
<path d="m.334 0 4.358 4.359h7.15v7.15l4.358 4.358V0zM.2 9.72l4.487-4.488v6.281h6.28L6.48 16H.2z"/>
</svg>

After

Width:  |  Height:  |  Size: 230 B

4
images/apple.svg Normal file
View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-apple" viewBox="0 0 16 16">
<path d="M11.182.008C11.148-.03 9.923.023 8.857 1.18c-1.066 1.156-.902 2.482-.878 2.516s1.52.087 2.475-1.258.762-2.391.728-2.43m3.314 11.733c-.048-.096-2.325-1.234-2.113-3.422s1.675-2.789 1.698-2.854-.597-.79-1.254-1.157a3.7 3.7 0 0 0-1.563-.434c-.108-.003-.483-.095-1.254.116-.508.139-1.653.589-1.968.607-.316.018-1.256-.522-2.267-.665-.647-.125-1.333.131-1.824.328-.49.196-1.422.754-2.074 2.237-.652 1.482-.311 3.83-.067 4.56s.625 1.924 1.273 2.796c.576.984 1.34 1.667 1.659 1.899s1.219.386 1.843.067c.502-.308 1.408-.485 1.766-.472.357.013 1.061.154 1.782.539.571.197 1.111.115 1.652-.105.541-.221 1.324-1.059 2.238-2.758q.52-1.185.473-1.282"/>
<path d="M11.182.008C11.148-.03 9.923.023 8.857 1.18c-1.066 1.156-.902 2.482-.878 2.516s1.52.087 2.475-1.258.762-2.391.728-2.43m3.314 11.733c-.048-.096-2.325-1.234-2.113-3.422s1.675-2.789 1.698-2.854-.597-.79-1.254-1.157a3.7 3.7 0 0 0-1.563-.434c-.108-.003-.483-.095-1.254.116-.508.139-1.653.589-1.968.607-.316.018-1.256-.522-2.267-.665-.647-.125-1.333.131-1.824.328-.49.196-1.422.754-2.074 2.237-.652 1.482-.311 3.83-.067 4.56s.625 1.924 1.273 2.796c.576.984 1.34 1.667 1.659 1.899s1.219.386 1.843.067c.502-.308 1.408-.485 1.766-.472.357.013 1.061.154 1.782.539.571.197 1.111.115 1.652-.105.541-.221 1.324-1.059 2.238-2.758q.52-1.185.473-1.282"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

3
images/arch.svg Normal file
View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640">
<path d="M320.7 56.2C296.6 115.2 282.1 153.9 255.3 211.2C271.7 228.6 291.9 248.8 324.7 271.7C289.5 257.2 265.5 242.8 247.5 227.7C213.2 299.3 159.4 401.2 50.3 597.1C136 547.6 202.5 517.1 264.4 505.5C261.7 494.1 260.3 481.7 260.4 468.8L260.5 466.1C261.9 411.2 290.4 368.9 324.3 371.7C358.2 374.5 384.5 421.5 383.1 476.4C382.8 486.7 381.7 496.6 379.7 505.8C440.9 517.8 506.7 548.2 591.3 597C574.6 566.3 559.7 538.7 545.5 512.3C523.1 494.9 499.8 472.4 452.2 447.9C484.9 456.4 508.5 466.2 526.7 477.2C382.2 208.1 370.5 172.4 320.9 56.1z"/>
</svg>

After

Width:  |  Height:  |  Size: 604 B

3
images/bluesky.svg Normal file
View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-bluesky" viewBox="0 0 16 16">
<path d="M3.468 1.948C5.303 3.325 7.276 6.118 8 7.616c.725-1.498 2.698-4.29 4.532-5.668C13.855.955 16 .186 16 2.632c0 .489-.28 4.105-.444 4.692-.572 2.04-2.653 2.561-4.504 2.246 3.236.551 4.06 2.375 2.281 4.2-3.376 3.464-4.852-.87-5.23-1.98-.07-.204-.103-.3-.103-.218 0-.081-.033.014-.102.218-.379 1.11-1.855 5.444-5.231 1.98-1.778-1.825-.955-3.65 2.28-4.2-1.85.315-3.932-.205-4.503-2.246C.28 6.737 0 3.12 0 2.632 0 .186 2.145.955 3.468 1.948"/>
</svg>

After

Width:  |  Height:  |  Size: 580 B

3
images/debian.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.6 KiB

3
images/discord.svg Normal file
View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-discord" viewBox="0 0 16 16">
<path d="M13.545 2.907a13.2 13.2 0 0 0-3.257-1.011.05.05 0 0 0-.052.025c-.141.25-.297.577-.406.833a12.2 12.2 0 0 0-3.658 0 8 8 0 0 0-.412-.833.05.05 0 0 0-.052-.025c-1.125.194-2.22.534-3.257 1.011a.04.04 0 0 0-.021.018C.356 6.024-.213 9.047.066 12.032q.003.022.021.037a13.3 13.3 0 0 0 3.995 2.02.05.05 0 0 0 .056-.019q.463-.63.818-1.329a.05.05 0 0 0-.01-.059l-.018-.011a9 9 0 0 1-1.248-.595.05.05 0 0 1-.02-.066l.015-.019q.127-.095.248-.195a.05.05 0 0 1 .051-.007c2.619 1.196 5.454 1.196 8.041 0a.05.05 0 0 1 .053.007q.121.1.248.195a.05.05 0 0 1-.004.085 8 8 0 0 1-1.249.594.05.05 0 0 0-.03.03.05.05 0 0 0 .003.041c.24.465.515.909.817 1.329a.05.05 0 0 0 .056.019 13.2 13.2 0 0 0 4.001-2.02.05.05 0 0 0 .021-.037c.334-3.451-.559-6.449-2.366-9.106a.03.03 0 0 0-.02-.019m-8.198 7.307c-.789 0-1.438-.724-1.438-1.612s.637-1.613 1.438-1.613c.807 0 1.45.73 1.438 1.613 0 .888-.637 1.612-1.438 1.612m5.316 0c-.788 0-1.438-.724-1.438-1.612s.637-1.613 1.438-1.613c.807 0 1.451.73 1.438 1.613 0 .888-.631 1.612-1.438 1.612"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

3
images/email.svg Normal file
View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-envelope-fill" viewBox="0 0 16 16">
<path d="M.05 3.555A2 2 0 0 1 2 2h12a2 2 0 0 1 1.95 1.555L8 8.414zM0 4.697v7.104l5.803-3.558zM6.761 8.83l-6.57 4.027A2 2 0 0 0 2 14h12a2 2 0 0 0 1.808-1.144l-6.57-4.027L8 9.586zm3.436-.586L16 11.801V4.697z"/>
</svg>

After

Width:  |  Height:  |  Size: 349 B

55
images/estrogen.svg Normal file
View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" id="Слой_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 556.37 303.36" style="enable-background:new 0 0 556.37 303.36;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;}
</style>
<g>
<polygon points="32.85,271.82 17.96,271.82 17.96,260.06 14.17,260.06 14.17,288.69 17.96,288.69 17.96,275.2 32.85,275.2
32.85,288.69 36.63,288.69 36.63,260.06 32.85,260.06 "/>
<path d="M62.65,261.45c-2.12-1.26-4.51-1.89-7.15-1.89c-4.04,0-7.33,1.34-9.88,4.04c-2.55,2.69-3.83,6.41-3.83,11.16
c0,2.52,0.55,4.9,1.64,7.15c1.09,2.25,2.7,4.03,4.82,5.33c2.12,1.3,4.53,1.95,7.23,1.95c2.49,0,4.8-0.59,6.95-1.77
c2.15-1.18,3.81-2.91,4.98-5.21c1.17-2.29,1.76-4.89,1.76-7.79c0-2.86-0.56-5.43-1.67-7.68C66.39,264.47,64.77,262.71,62.65,261.45
z M62.5,282.93c-1.84,2.01-4.19,3.01-7.04,3.01c-2.8,0-5.13-0.99-6.98-2.98c-1.86-1.99-2.78-4.7-2.78-8.16
c0-4.31,0.97-7.39,2.9-9.23c1.93-1.84,4.24-2.77,6.92-2.77c1.89,0,3.59,0.48,5.11,1.44s2.67,2.3,3.46,4.04
c0.79,1.74,1.18,3.77,1.18,6.1C65.27,278.08,64.35,280.93,62.5,282.93z"/>
<polygon points="262.41,214.22 247.52,214.22 247.52,202.46 243.73,202.46 243.73,231.1 247.52,231.1 247.52,217.6 262.41,217.6
262.41,231.1 266.2,231.1 266.2,202.46 262.41,202.46 "/>
<polygon points="312.29,127.82 297.4,127.82 297.4,116.06 293.62,116.06 293.62,144.7 297.4,144.7 297.4,131.2 312.29,131.2
312.29,144.7 316.08,144.7 316.08,116.06 312.29,116.06 "/>
<rect x="173.77" y="159.62" transform="matrix(0.4998 -0.8661 0.8661 0.4998 -70.4902 242.7992)" width="2.4" height="45.62"/>
<rect x="114.5" y="193.83" width="2.4" height="45.64"/>
<rect x="152.16" y="249.67" transform="matrix(0.8662 -0.4997 0.4997 0.8662 -101.9474 120.9965)" width="45.62" height="2.4"/>
<polygon points="260.54,193.57 249.4,193.57 248.97,195.97 260.97,195.97 "/>
<polygon points="258.36,181.33 251.59,181.33 251.16,183.73 258.79,183.73 "/>
<polygon points="256.17,169.09 253.77,169.09 253.35,171.49 256.6,171.49 "/>
<path d="M420.15,41.85c2.12,1.3,4.53,1.95,7.23,1.95c2.49,0,4.8-0.59,6.95-1.77s3.81-2.91,4.98-5.21s1.76-4.89,1.76-7.79
c0-2.87-0.56-5.43-1.67-7.69s-2.73-4.02-4.85-5.28c-2.12-1.26-4.51-1.89-7.15-1.89c-4.04,0-7.33,1.34-9.88,4.03
c-2.55,2.69-3.83,6.41-3.83,11.16c0,2.51,0.55,4.9,1.64,7.15S418.03,40.55,420.15,41.85z M420.5,20.2
c1.93-1.84,4.24-2.76,6.92-2.76c1.89,0,3.59,0.48,5.11,1.44s2.67,2.3,3.46,4.04s1.18,3.77,1.18,6.1c0,3.69-0.92,6.53-2.77,8.54
s-4.19,3.01-7.04,3.01c-2.8,0-5.13-0.99-6.98-2.98s-2.78-4.7-2.78-8.15C417.6,25.12,418.56,22.04,420.5,20.2z"/>
<polygon points="464.75,26.44 449.87,26.44 449.87,14.68 446.08,14.68 446.08,43.31 449.87,43.31 449.87,29.82 464.75,29.82
464.75,43.31 468.54,43.31 468.54,14.68 464.75,14.68 "/>
<path d="M426.42,51.04l-11.41-3.71l-6.53,35.4l-52.4,17.03l4.66-55.9h-12l4.62,55.42l-48.5-28l-51.08,29.49v57.59l-48.68,28.12
l-49.88-28.8l-51.08,29.48v57.6l-31.34,18.1l1.2,2.06l31.35-18.08l49.88,28.8l49.88-28.8l49.88,28.8l51.08-29.48v-57.6l48.82-28.2
l55.1,17.92l34.88-48.02L410.9,83.51L426.42,51.04z M155.21,272.87l-48.68-28.12v-56.2l48.68-28.12l48.68,28.12v56.2L155.21,272.87
z M254.97,272.87l-48.68-28.12v-56.2l48.68-28.12l48.68,28.12v56.2L254.97,272.87z M306.35,185.61l4.51-34.64h-12l4.51,34.64
l-47.19-27.26v-56.21l48.68-28.11l48.68,28.11v56.21L306.35,185.61z M441.89,130.25l-32.83,45.18l-53.12-17.26v-55.85l53.12-17.26
L441.89,130.25z"/>
<polygon points="362.17,214.22 347.28,214.22 347.28,202.46 343.49,202.46 343.49,231.1 347.28,231.1 347.28,217.6 362.17,217.6
362.17,231.1 365.96,231.1 365.96,202.46 362.17,202.46 "/>
<polygon points="348.73,195.97 360.73,195.97 360.31,193.57 349.16,193.57 "/>
<polygon points="350.92,183.73 358.55,183.73 358.12,181.33 351.35,181.33 "/>
<polygon points="353.53,169.09 353.11,171.49 356.36,171.49 355.93,169.09 "/>
<path d="M508.21,117.45c-2.12-1.26-4.51-1.89-7.15-1.89c-4.04,0-7.33,1.34-9.88,4.03c-2.55,2.69-3.83,6.41-3.83,11.16
c0,2.51,0.55,4.9,1.64,7.15s2.7,4.03,4.82,5.33c2.12,1.3,4.53,1.95,7.23,1.95c2.49,0,4.8-0.59,6.95-1.77s3.81-2.91,4.98-5.21
c1.17-2.29,1.76-4.89,1.76-7.79c0-2.87-0.56-5.43-1.67-7.69C511.95,120.47,510.33,118.71,508.21,117.45z M508.06,138.93
c-1.84,2.01-4.19,3.01-7.04,3.01c-2.8,0-5.13-0.99-6.98-2.98s-2.78-4.7-2.78-8.15c0-4.31,0.96-7.39,2.9-9.23s4.24-2.76,6.92-2.76
c1.89,0,3.59,0.48,5.11,1.44s2.67,2.3,3.46,4.04s1.18,3.77,1.18,6.1C510.83,134.08,509.9,136.93,508.06,138.93z"/>
<polygon points="538.4,116.06 538.4,127.82 523.52,127.82 523.52,116.06 519.73,116.06 519.73,144.7 523.52,144.7 523.52,131.2
538.4,131.2 538.4,144.7 542.19,144.7 542.19,116.06 "/>
<polygon points="478.41,135.83 480.81,136.25 480.81,124.25 478.41,124.67 "/>
<polygon points="465.9,133.63 468.3,134.05 468.3,126.45 465.9,126.85 "/>
<polygon points="453.38,131.45 455.78,131.87 455.78,128.63 453.38,129.05 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.4 KiB

20
images/favicon.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.7 KiB

1
images/git-gay.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 120 120" width="32" height="32"><defs><clipPath id="a" clipPathUnits="userSpaceOnUse"><path stroke-width="1.306" d="m117.728 54.656-52.39-52.39a7.727 7.727 0 0 0-10.931 0l-10.875 10.88 13.797 13.8a9.18 9.18 0 0 1 11.622 11.702l13.3 13.3a9.18 9.18 0 0 1 9.505 2.183 9.194 9.194 0 0 1 0 13.003 9.205 9.205 0 0 1-13.013 0 9.2 9.2 0 0 1-1.995-10.003L64.343 44.728v32.645c5.642 2.788 6.879 10.294 2.428 14.744a9.19 9.19 0 0 1-13.002 0 9.194 9.194 0 0 1 0-13.005 9.2 9.2 0 0 1 3.009-2.01V44.158a9.206 9.206 0 0 1-4.988-12.06L38.186 18.493 2.26 54.41a7.74 7.74 0 0 0 0 10.931l52.392 52.399a7.736 7.736 0 0 0 10.93 0l52.157-52.152a7.74 7.74 0 0 0 0-10.932"/></clipPath></defs><g clip-path="url(#a)" paint-order="markers stroke fill"><rect width="120" height="22.5" fill="#fe9494" ry="0"/><rect width="120" height="22.5" y="20" fill="#ffc783" ry="0"/><rect width="120" height="22.5" y="40" fill="#fff683" ry="0"/><rect width="120" height="22.5" y="60.001" fill="#69cb86" ry="0"/><rect width="120" height="22.5" y="80.001" fill="#83a8ff" ry="0"/><rect width="120" height="19.999" y="100.002" fill="#c56ad4" ry="0"/></g></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

3
images/github.svg Normal file
View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-github" viewBox="0 0 16 16">
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8"/>
</svg>

After

Width:  |  Height:  |  Size: 708 B

3
images/linkedin.svg Normal file
View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-linkedin" viewBox="0 0 16 16">
<path d="M0 1.146C0 .513.526 0 1.175 0h13.65C15.474 0 16 .513 16 1.146v13.708c0 .633-.526 1.146-1.175 1.146H1.175C.526 16 0 15.487 0 14.854zm4.943 12.248V6.169H2.542v7.225zm-1.2-8.212c.837 0 1.358-.554 1.358-1.248-.015-.709-.52-1.248-1.342-1.248S2.4 3.226 2.4 3.934c0 .694.521 1.248 1.327 1.248zm4.908 8.212V9.359c0-.216.016-.432.08-.586.173-.431.568-.878 1.232-.878.869 0 1.216.662 1.216 1.634v3.865h2.401V9.25c0-2.22-1.184-3.252-2.764-3.252-1.274 0-1.845.7-2.165 1.193v.025h-.016l.016-.025V6.169h-2.4c.03.678 0 7.225 0 7.225z"/>
</svg>

After

Width:  |  Height:  |  Size: 666 B

3
images/mastodon.svg Normal file
View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-mastodon" viewBox="0 0 16 16">
<path d="M11.19 12.195c2.016-.24 3.77-1.475 3.99-2.603.348-1.778.32-4.339.32-4.339 0-3.47-2.286-4.488-2.286-4.488C12.062.238 10.083.017 8.027 0h-.05C5.92.017 3.942.238 2.79.765c0 0-2.285 1.017-2.285 4.488l-.002.662c-.004.64-.007 1.35.011 2.091.083 3.394.626 6.74 3.78 7.57 1.454.383 2.703.463 3.709.408 1.823-.1 2.847-.647 2.847-.647l-.06-1.317s-1.303.41-2.767.36c-1.45-.05-2.98-.156-3.215-1.928a4 4 0 0 1-.033-.496s1.424.346 3.228.428c1.103.05 2.137-.064 3.188-.189zm1.613-2.47H11.13v-4.08c0-.859-.364-1.295-1.091-1.295-.804 0-1.207.517-1.207 1.541v2.233H7.168V5.89c0-1.024-.403-1.541-1.207-1.541-.727 0-1.091.436-1.091 1.296v4.079H3.197V5.522q0-1.288.66-2.046c.456-.505 1.052-.764 1.793-.764.856 0 1.504.328 1.933.983L8 4.39l.417-.695c.429-.655 1.077-.983 1.934-.983.74 0 1.336.259 1.791.764q.662.757.661 2.046z"/>
</svg>

After

Width:  |  Height:  |  Size: 952 B

BIN
images/oneko.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

4
images/reddit.svg Normal file
View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-reddit" viewBox="0 0 16 16">
<path d="M6.167 8a.83.83 0 0 0-.83.83c0 .459.372.84.83.831a.831.831 0 0 0 0-1.661m1.843 3.647c.315 0 1.403-.038 1.976-.611a.23.23 0 0 0 0-.306.213.213 0 0 0-.306 0c-.353.363-1.126.487-1.67.487-.545 0-1.308-.124-1.671-.487a.213.213 0 0 0-.306 0 .213.213 0 0 0 0 .306c.564.563 1.652.61 1.977.61zm.992-2.807c0 .458.373.83.831.83s.83-.381.83-.83a.831.831 0 0 0-1.66 0z"/>
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0m-3.828-1.165c-.315 0-.602.124-.812.325-.801-.573-1.9-.945-3.121-.993l.534-2.501 1.738.372a.83.83 0 1 0 .83-.869.83.83 0 0 0-.744.468l-1.938-.41a.2.2 0 0 0-.153.028.2.2 0 0 0-.086.134l-.592 2.788c-1.24.038-2.358.41-3.17.992-.21-.2-.496-.324-.81-.324a1.163 1.163 0 0 0-.478 2.224q-.03.17-.029.353c0 1.795 2.091 3.256 4.669 3.256s4.668-1.451 4.668-3.256c0-.114-.01-.238-.029-.353.401-.181.688-.592.688-1.069 0-.65-.525-1.165-1.165-1.165"/>
</svg>

After

Width:  |  Height:  |  Size: 984 B

3
images/site.svg Normal file
View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-globe2" viewBox="0 0 16 16">
<path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8m7.5-6.923c-.67.204-1.335.82-1.887 1.855q-.215.403-.395.872c.705.157 1.472.257 2.282.287zM4.249 3.539q.214-.577.481-1.078a7 7 0 0 1 .597-.933A7 7 0 0 0 3.051 3.05q.544.277 1.198.49zM3.509 7.5c.036-1.07.188-2.087.436-3.008a9 9 0 0 1-1.565-.667A6.96 6.96 0 0 0 1.018 7.5zm1.4-2.741a12.3 12.3 0 0 0-.4 2.741H7.5V5.091c-.91-.03-1.783-.145-2.591-.332M8.5 5.09V7.5h2.99a12.3 12.3 0 0 0-.399-2.741c-.808.187-1.681.301-2.591.332zM4.51 8.5c.035.987.176 1.914.399 2.741A13.6 13.6 0 0 1 7.5 10.91V8.5zm3.99 0v2.409c.91.03 1.783.145 2.591.332.223-.827.364-1.754.4-2.741zm-3.282 3.696q.18.469.395.872c.552 1.035 1.218 1.65 1.887 1.855V11.91c-.81.03-1.577.13-2.282.287zm.11 2.276a7 7 0 0 1-.598-.933 9 9 0 0 1-.481-1.079 8.4 8.4 0 0 0-1.198.49 7 7 0 0 0 2.276 1.522zm-1.383-2.964A13.4 13.4 0 0 1 3.508 8.5h-2.49a6.96 6.96 0 0 0 1.362 3.675c.47-.258.995-.482 1.565-.667m6.728 2.964a7 7 0 0 0 2.275-1.521 8.4 8.4 0 0 0-1.197-.49 9 9 0 0 1-.481 1.078 7 7 0 0 1-.597.933M8.5 11.909v3.014c.67-.204 1.335-.82 1.887-1.855q.216-.403.395-.872A12.6 12.6 0 0 0 8.5 11.91zm3.555-.401c.57.185 1.095.409 1.565.667A6.96 6.96 0 0 0 14.982 8.5h-2.49a13.4 13.4 0 0 1-.437 3.008M14.982 7.5a6.96 6.96 0 0 0-1.362-3.675c-.47.258-.995.482-1.565.667.248.92.4 1.938.437 3.008zM11.27 2.461q.266.502.482 1.078a8.4 8.4 0 0 0 1.196-.49 7 7 0 0 0-2.275-1.52c.218.283.418.597.597.932m-.488 1.343a8 8 0 0 0-.395-.872C9.835 1.897 9.17 1.282 8.5 1.077V4.09c.81-.03 1.577-.13 2.282-.287z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

3
images/spotify.svg Normal file
View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-spotify" viewBox="0 0 16 16">
<path d="M8 0a8 8 0 1 0 0 16A8 8 0 0 0 8 0m3.669 11.538a.5.5 0 0 1-.686.165c-1.879-1.147-4.243-1.407-7.028-.77a.499.499 0 0 1-.222-.973c3.048-.696 5.662-.397 7.77.892a.5.5 0 0 1 .166.686m.979-2.178a.624.624 0 0 1-.858.205c-2.15-1.321-5.428-1.704-7.972-.932a.625.625 0 0 1-.362-1.194c2.905-.881 6.517-.454 8.986 1.063a.624.624 0 0 1 .206.858m.084-2.268C10.154 5.56 5.9 5.419 3.438 6.166a.748.748 0 1 1-.434-1.432c2.825-.857 7.523-.692 10.492 1.07a.747.747 0 1 1-.764 1.288"/>
</svg>

After

Width:  |  Height:  |  Size: 609 B

4
images/twitch.svg Normal file
View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-twitch" viewBox="0 0 16 16">
<path d="M3.857 0 1 2.857v10.286h3.429V16l2.857-2.857H9.57L14.714 8V0zm9.714 7.429-2.285 2.285H9l-2 2v-2H4.429V1.143h9.142z"/>
<path d="M11.857 3.143h-1.143V6.57h1.143zm-3.143 0H7.571V6.57h1.143z"/>
</svg>

After

Width:  |  Height:  |  Size: 334 B

3
images/twitter.svg Normal file
View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-twitter" viewBox="0 0 16 16">
<path d="M5.026 15c6.038 0 9.341-5.003 9.341-9.334q.002-.211-.006-.422A6.7 6.7 0 0 0 16 3.542a6.7 6.7 0 0 1-1.889.518 3.3 3.3 0 0 0 1.447-1.817 6.5 6.5 0 0 1-2.087.793A3.286 3.286 0 0 0 7.875 6.03a9.32 9.32 0 0 1-6.767-3.429 3.29 3.29 0 0 0 1.018 4.382A3.3 3.3 0 0 1 .64 6.575v.045a3.29 3.29 0 0 0 2.632 3.218 3.2 3.2 0 0 1-.865.115 3 3 0 0 1-.614-.057 3.28 3.28 0 0 0 3.067 2.277A6.6 6.6 0 0 1 .78 13.58a6 6 0 0 1-.78-.045A9.34 9.34 0 0 0 5.026 15"/>
</svg>

After

Width:  |  Height:  |  Size: 586 B

3
images/youtube.svg Normal file
View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-youtube" viewBox="0 0 16 16">
<path d="M8.051 1.999h.089c.822.003 4.987.033 6.11.335a2.01 2.01 0 0 1 1.415 1.42c.101.38.172.883.22 1.402l.01.104.022.26.008.104c.065.914.073 1.77.074 1.957v.075c-.001.194-.01 1.108-.082 2.06l-.008.105-.009.104c-.05.572-.124 1.14-.235 1.558a2.01 2.01 0 0 1-1.415 1.42c-1.16.312-5.569.334-6.18.335h-.142c-.309 0-1.587-.006-2.927-.052l-.17-.006-.087-.004-.171-.007-.171-.007c-1.11-.049-2.167-.128-2.654-.26a2.01 2.01 0 0 1-1.415-1.419c-.111-.417-.185-.986-.235-1.558L.09 9.82l-.008-.104A31 31 0 0 1 0 7.68v-.123c.002-.215.01-.958.064-1.778l.007-.103.003-.052.008-.104.022-.26.01-.104c.048-.519.119-1.023.22-1.402a2.01 2.01 0 0 1 1.415-1.42c.487-.13 1.544-.21 2.654-.26l.17-.007.172-.006.086-.003.171-.007A100 100 0 0 1 7.858 2zM6.4 5.209v4.818l4.157-2.408z"/>
</svg>

After

Width:  |  Height:  |  Size: 893 B

279
js/cat.js Normal file
View File

@ -0,0 +1,279 @@
// oneko.js: https://github.com/adryd325/oneko.js
(function oneko() {
const isReducedMotion =
window.matchMedia(`(prefers-reduced-motion: reduce)`) === true ||
window.matchMedia(`(prefers-reduced-motion: reduce)`).matches === true;
if (isReducedMotion) return;
const nekoEl = document.createElement("div");
let persistPosition = true;
let nekoPosX = 32;
let nekoPosY = 32;
let mousePosX = 0;
let mousePosY = 0;
let frameCount = 0;
let idleTime = 0;
let idleAnimation = null;
let idleAnimationFrame = 0;
const nekoSpeed = 10;
const spriteSets = {
idle: [[-3, -3]],
alert: [[-7, -3]],
scratchSelf: [
[-5, 0],
[-6, 0],
[-7, 0],
],
scratchWallN: [
[0, 0],
[0, -1],
],
scratchWallS: [
[-7, -1],
[-6, -2],
],
scratchWallE: [
[-2, -2],
[-2, -3],
],
scratchWallW: [
[-4, 0],
[-4, -1],
],
tired: [[-3, -2]],
sleeping: [
[-2, 0],
[-2, -1],
],
N: [
[-1, -2],
[-1, -3],
],
NE: [
[0, -2],
[0, -3],
],
E: [
[-3, 0],
[-3, -1],
],
SE: [
[-5, -1],
[-5, -2],
],
S: [
[-6, -3],
[-7, -2],
],
SW: [
[-5, -3],
[-6, -1],
],
W: [
[-4, -2],
[-4, -3],
],
NW: [
[-1, 0],
[-1, -1],
],
};
function init() {
let nekoFile = "./oneko.gif"
const curScript = document.currentScript
if (curScript && curScript.dataset.cat) {
nekoFile = curScript.dataset.cat
}
if (curScript && curScript.dataset.persistPosition) {
if (curScript.dataset.persistPosition === "") {
persistPosition = true;
} else {
persistPosition = JSON.parse(curScript.dataset.persistPosition.toLowerCase());
}
}
if (persistPosition) {
let storedNeko = JSON.parse(window.localStorage.getItem("oneko"));
if (storedNeko !== null) {
nekoPosX = storedNeko.nekoPosX;
nekoPosY = storedNeko.nekoPosY;
mousePosX = storedNeko.mousePosX;
mousePosY = storedNeko.mousePosY;
frameCount = storedNeko.frameCount;
idleTime = storedNeko.idleTime;
idleAnimation = storedNeko.idleAnimation;
idleAnimationFrame = storedNeko.idleAnimationFrame;
nekoEl.style.backgroundPosition = storedNeko.bgPos;
}
}
nekoEl.id = "oneko";
nekoEl.ariaHidden = true;
nekoEl.style.width = "32px";
nekoEl.style.height = "32px";
nekoEl.style.position = "fixed";
nekoEl.style.pointerEvents = "none";
nekoEl.style.imageRendering = "pixelated";
nekoEl.style.left = `${nekoPosX - 16}px`;
nekoEl.style.top = `${nekoPosY - 16}px`;
nekoEl.style.zIndex = 2147483647;
nekoEl.style.backgroundImage = `url(${nekoFile})`;
document.body.appendChild(nekoEl);
document.addEventListener("mousemove", function (event) {
mousePosX = event.clientX;
mousePosY = event.clientY;
});
if (persistPosition) {
window.addEventListener("beforeunload", function (event) {
window.localStorage.setItem("oneko", JSON.stringify({
nekoPosX: nekoPosX,
nekoPosY: nekoPosY,
mousePosX: mousePosX,
mousePosY: mousePosY,
frameCount: frameCount,
idleTime: idleTime,
idleAnimation: idleAnimation,
idleAnimationFrame: idleAnimationFrame,
bgPos: nekoEl.style.backgroundPosition
}));
});
}
window.requestAnimationFrame(onAnimationFrame);
}
let lastFrameTimestamp;
function onAnimationFrame(timestamp) {
// Stops execution if the neko element is removed from DOM
if (!nekoEl.isConnected) {
return;
}
if (!lastFrameTimestamp) {
lastFrameTimestamp = timestamp;
}
if (timestamp - lastFrameTimestamp > 100) {
lastFrameTimestamp = timestamp;
frame();
}
window.requestAnimationFrame(onAnimationFrame);
}
function setSprite(name, frame) {
const sprite = spriteSets[name][frame % spriteSets[name].length];
nekoEl.style.backgroundPosition = `${sprite[0] * 32}px ${sprite[1] * 32}px`;
}
function resetIdleAnimation() {
idleAnimation = null;
idleAnimationFrame = 0;
}
function idle() {
idleTime += 1;
// every ~ 20 seconds
if (
idleTime > 10 &&
Math.floor(Math.random() * 200) == 0 &&
idleAnimation == null
) {
let avalibleIdleAnimations = ["sleeping", "scratchSelf"];
if (nekoPosX < 32) {
avalibleIdleAnimations.push("scratchWallW");
}
if (nekoPosY < 32) {
avalibleIdleAnimations.push("scratchWallN");
}
if (nekoPosX > window.innerWidth - 32) {
avalibleIdleAnimations.push("scratchWallE");
}
if (nekoPosY > window.innerHeight - 32) {
avalibleIdleAnimations.push("scratchWallS");
}
idleAnimation =
avalibleIdleAnimations[
Math.floor(Math.random() * avalibleIdleAnimations.length)
];
}
switch (idleAnimation) {
case "sleeping":
if (idleAnimationFrame < 8) {
setSprite("tired", 0);
break;
}
setSprite("sleeping", Math.floor(idleAnimationFrame / 4));
if (idleAnimationFrame > 192) {
resetIdleAnimation();
}
break;
case "scratchWallN":
case "scratchWallS":
case "scratchWallE":
case "scratchWallW":
case "scratchSelf":
setSprite(idleAnimation, idleAnimationFrame);
if (idleAnimationFrame > 9) {
resetIdleAnimation();
}
break;
default:
setSprite("idle", 0);
return;
}
idleAnimationFrame += 1;
}
function frame() {
frameCount += 1;
const diffX = nekoPosX - mousePosX;
const diffY = nekoPosY - mousePosY;
const distance = Math.sqrt(diffX ** 2 + diffY ** 2);
if (distance < nekoSpeed || distance < 48) {
idle();
return;
}
idleAnimation = null;
idleAnimationFrame = 0;
if (idleTime > 1) {
setSprite("alert", 0);
// count down after being alerted before moving
idleTime = Math.min(idleTime, 7);
idleTime -= 1;
return;
}
let direction;
direction = diffY / distance > 0.5 ? "N" : "";
direction += diffY / distance < -0.5 ? "S" : "";
direction += diffX / distance > 0.5 ? "W" : "";
direction += diffX / distance < -0.5 ? "E" : "";
setSprite(direction, frameCount);
nekoPosX -= (diffX / distance) * nekoSpeed;
nekoPosY -= (diffY / distance) * nekoSpeed;
nekoPosX = Math.min(Math.max(16, nekoPosX), window.innerWidth - 16);
nekoPosY = Math.min(Math.max(16, nekoPosY), window.innerHeight - 16);
nekoEl.style.left = `${nekoPosX - 16}px`;
nekoEl.style.top = `${nekoPosY - 16}px`;
}
init();
})();

22
js/nav.js Normal file
View File

@ -0,0 +1,22 @@
// Navigate via data-href so the destination URL never shows in the
// browser status bar on hover. Pairs with the CSS View Transitions
// (@view-transition) for a smooth cross-fade between pages.
document.querySelectorAll("[data-href]").forEach((el) => {
el.style.cursor = "pointer";
if (!el.hasAttribute("role")) el.setAttribute("role", "link");
if (!el.hasAttribute("tabindex")) el.setAttribute("tabindex", "0");
const go = () => {
const url = el.dataset.href;
if (!url) return;
location.href = url;
};
el.addEventListener("click", go);
el.addEventListener("keydown", (e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
go();
}
});
});

150
tech-stack/index.html Normal file
View File

@ -0,0 +1,150 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Clove Twilight - Link Center</title>
<link rel="stylesheet" href="/css/style.css">
<link rel="icon" type="image/svg+xml" href="/images/favicon.svg">
<!-- SEO Meta Tags -->
<meta name="description" content="Link Center for Clove Twilight" />
<meta name="keywords" content="Portfolio, Personal, Developer" />
<meta name="author" content="doughmination" />
<meta name="robots" content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1" />
<!-- Canonical URL -->
<link rel="canonical" href="https://clove.is-a.dev" />
<!-- Alternate for mobile -->
<link rel="alternate" media="only screen and (max-width: 640px)" href="https://clove.is-a.dev" />
<!-- Theme Color -->
<meta name="theme-color" content="#f5c2e7" />
<!-- Open Graph / Discord / Facebook -->
<meta property="og:site_name" content="clove.is-a.dev" />
<meta property="og:title" content="Clove Twilight" />
<meta property="og:description" content="Link Center for Clove Twilight" />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://clove.is-a.dev" />
<meta property="og:locale" content="en_GB" />
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Clove Twilight" />
<meta name="twitter:description" content="Link Center for Clove Twilight" />
</head>
<body>
<header class="nav">
<nav class="nav-links">
<a class="nav-link" data-href="/">Link Center</a>
<a class="nav-link selected" data-href="/tech-stack">Tech Stack</a>
</nav>
</header>
<a rel="me" href="https://mastodon.social/@doughmination" style="display: none;"></a>
<main class="hub">
<header class="hub-header">
<img class="pfp" src="https://cdn.discordapp.com/avatars/1464890289922641993/83c6f674b7bdf901ab7b1e65204795ca.png" alt="Clove Twilight avatar">
<h1>Clove Twilight</h1>
<h2 class="pronouns">(fae/faer)</h2>
<p class="tagline">Tech Stack</p>
</header>
<section class="tech-stack" aria-label="Tech stack">
<img class="tech-badge" src="https://img.shields.io/badge/JavaScript-313244?style=flat-square&labelColor=1e1e2e&logo=javascript&logoColor=f5c2e7" alt="JavaScript" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/Java-313244?style=flat-square&labelColor=1e1e2e&logo=openjdk&logoColor=cba6f7" alt="Java" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/HTML5-313244?style=flat-square&labelColor=1e1e2e&logo=html5&logoColor=b4befe" alt="HTML5" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/Markdown-313244?style=flat-square&labelColor=1e1e2e&logo=markdown&logoColor=89b4fa" alt="Markdown" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/LaTeX-313244?style=flat-square&labelColor=1e1e2e&logo=latex&logoColor=94e2d5" alt="LaTeX" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/Windows%20Terminal-313244?style=flat-square&labelColor=1e1e2e&logo=windows-terminal&logoColor=a6e3a1" alt="Windows Terminal" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/TypeScript-313244?style=flat-square&labelColor=1e1e2e&logo=typescript&logoColor=fab387" alt="TypeScript" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/Bash%20Script-313244?style=flat-square&labelColor=1e1e2e&logo=gnu-bash&logoColor=f9e2af" alt="Bash Script" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/Python-313244?style=flat-square&labelColor=1e1e2e&logo=python&logoColor=f38ba8" alt="Python" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/PowerShell-313244?style=flat-square&labelColor=1e1e2e&logo=powershell&logoColor=89dceb" alt="PowerShell" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/PHP-313244?style=flat-square&labelColor=1e1e2e&logo=php&logoColor=eba0ac" alt="PHP" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/Cloudflare-313244?style=flat-square&labelColor=1e1e2e&logo=Cloudflare&logoColor=74c7ec" alt="Cloudflare" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/Heroku-313244?style=flat-square&labelColor=1e1e2e&logo=heroku&logoColor=f5e0dc" alt="Heroku" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/Linode-313244?style=flat-square&labelColor=1e1e2e&logo=linode&logoColor=f5c2e7" alt="Linode" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/Vercel-313244?style=flat-square&labelColor=1e1e2e&logo=vercel&logoColor=cba6f7" alt="Vercel" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/Bootstrap-313244?style=flat-square&labelColor=1e1e2e&logo=bootstrap&logoColor=b4befe" alt="Bootstrap" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/EJS-313244?style=flat-square&labelColor=1e1e2e&logo=ejs&logoColor=89b4fa" alt="EJS" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/Electron.js-313244?style=flat-square&labelColor=1e1e2e&logo=Electron&logoColor=94e2d5" alt="Electron.js" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/Express.js-313244?style=flat-square&labelColor=1e1e2e&logo=express&logoColor=a6e3a1" alt="Express.js" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/FastAPI-313244?style=flat-square&labelColor=1e1e2e&logo=fastapi&logoColor=fab387" alt="FastAPI" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/Flask-313244?style=flat-square&labelColor=1e1e2e&logo=flask&logoColor=f9e2af" alt="Flask" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/Insomnia-313244?style=flat-square&labelColor=1e1e2e&logo=insomnia&logoColor=f38ba8" alt="Insomnia" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/jQuery-313244?style=flat-square&labelColor=1e1e2e&logo=jquery&logoColor=89dceb" alt="jQuery" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/JWT-313244?style=flat-square&labelColor=1e1e2e&logo=JSON%20web%20tokens&logoColor=eba0ac" alt="JWT" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/NPM-313244?style=flat-square&labelColor=1e1e2e&logo=npm&logoColor=74c7ec" alt="NPM" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/Next%20JS-313244?style=flat-square&labelColor=1e1e2e&logo=next.js&logoColor=f5e0dc" alt="Next JS" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/NodeJS-313244?style=flat-square&labelColor=1e1e2e&logo=node.js&logoColor=f5c2e7" alt="NodeJS" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/Nodemon-313244?style=flat-square&labelColor=1e1e2e&logo=nodemon&logoColor=cba6f7" alt="Nodemon" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/PNPM-313244?style=flat-square&labelColor=1e1e2e&logo=pnpm&logoColor=b4befe" alt="PNPM" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/React-313244?style=flat-square&labelColor=1e1e2e&logo=react&logoColor=89b4fa" alt="React" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/React%20Native-313244?style=flat-square&labelColor=1e1e2e&logo=react&logoColor=94e2d5" alt="React Native" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/React%20Query-313244?style=flat-square&labelColor=1e1e2e&logo=react%20query&logoColor=a6e3a1" alt="React Query" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/React%20Hook%20Form-313244?style=flat-square&labelColor=1e1e2e&logo=reacthookform&logoColor=fab387" alt="React Hook Form" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/React%20Router-313244?style=flat-square&labelColor=1e1e2e&logo=react-router&logoColor=f9e2af" alt="React Router" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/TailwindCSS-313244?style=flat-square&labelColor=1e1e2e&logo=tailwind-css&logoColor=f38ba8" alt="TailwindCSS" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/Three%20js-313244?style=flat-square&labelColor=1e1e2e&logo=three.js&logoColor=89dceb" alt="Three js" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/Vite-313244?style=flat-square&labelColor=1e1e2e&logo=vite&logoColor=eba0ac" alt="Vite" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/Vue.js-313244?style=flat-square&labelColor=1e1e2e&logo=vuedotjs&logoColor=74c7ec" alt="Vue.js" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/WordPress-313244?style=flat-square&labelColor=1e1e2e&logo=WordPress&logoColor=f5e0dc" alt="WordPress" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/Yarn-313244?style=flat-square&labelColor=1e1e2e&logo=yarn&logoColor=f5c2e7" alt="Yarn" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/Nginx-313244?style=flat-square&labelColor=1e1e2e&logo=nginx&logoColor=cba6f7" alt="Nginx" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/MySQL-313244?style=flat-square&labelColor=1e1e2e&logo=mysql&logoColor=b4befe" alt="MySQL" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/SQLite-313244?style=flat-square&labelColor=1e1e2e&logo=sqlite&logoColor=89b4fa" alt="SQLite" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/Postgres-313244?style=flat-square&labelColor=1e1e2e&logo=postgresql&logoColor=94e2d5" alt="Postgres" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/Redis-313244?style=flat-square&labelColor=1e1e2e&logo=redis&logoColor=a6e3a1" alt="Redis" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/Adobe%20Photoshop-313244?style=flat-square&labelColor=1e1e2e&logo=adobe%20photoshop&logoColor=fab387" alt="Adobe Photoshop" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/Canva-313244?style=flat-square&labelColor=1e1e2e&logo=Canva&logoColor=f9e2af" alt="Canva" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/Inkscape-313244?style=flat-square&labelColor=1e1e2e&logo=inkscape&logoColor=f38ba8" alt="Inkscape" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/GitHub-313244?style=flat-square&labelColor=1e1e2e&logo=github&logoColor=89dceb" alt="GitHub" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/GitHub%20Actions-313244?style=flat-square&labelColor=1e1e2e&logo=githubactions&logoColor=eba0ac" alt="GitHub Actions" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/Git-313244?style=flat-square&labelColor=1e1e2e&logo=git&logoColor=74c7ec" alt="Git" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/Gitea-313244?style=flat-square&labelColor=1e1e2e&logo=gitea&logoColor=f5e0dc" alt="Gitea" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/Arduino-313244?style=flat-square&labelColor=1e1e2e&logo=Arduino&logoColor=f5c2e7" alt="Arduino" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/Cisco-313244?style=flat-square&labelColor=1e1e2e&logo=cisco&logoColor=cba6f7" alt="Cisco" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/Docker-313244?style=flat-square&labelColor=1e1e2e&logo=docker&logoColor=b4befe" alt="Docker" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/ESLint-313244?style=flat-square&labelColor=1e1e2e&logo=eslint&logoColor=89b4fa" alt="ESLint" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/FFmpeg-313244?style=flat-square&labelColor=1e1e2e&logo=ffmpeg&logoColor=94e2d5" alt="FFmpeg" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/Gradle-313244?style=flat-square&labelColor=1e1e2e&logo=Gradle&logoColor=a6e3a1" alt="Gradle" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/Grafana-313244?style=flat-square&labelColor=1e1e2e&logo=grafana&logoColor=fab387" alt="Grafana" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/OpenAPI%20Specification-313244?style=flat-square&labelColor=1e1e2e&logo=openapiinitiative&logoColor=f9e2af" alt="OpenAPI Specification" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/Raspberry%20Pi-313244?style=flat-square&labelColor=1e1e2e&logo=Raspberry-Pi&logoColor=f38ba8" alt="Raspberry Pi" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/Postman-313244?style=flat-square&labelColor=1e1e2e&logo=postman&logoColor=89dceb" alt="Postman" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/Swagger-313244?style=flat-square&labelColor=1e1e2e&logo=swagger&logoColor=eba0ac" alt="Swagger" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/TOR-313244?style=flat-square&labelColor=1e1e2e&logo=tor-project&logoColor=74c7ec" alt="TOR" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/AMD-313244?style=flat-square&labelColor=1e1e2e&logo=amd&logoColor=f5e0dc" alt="AMD" loading="lazy">
<img class="tech-badge" src="https://img.shields.io/badge/Epic%20Games-313244?style=flat-square&labelColor=1e1e2e&logo=epicgames&logoColor=f5c2e7" alt="Epic Games" loading="lazy">
</section>
</main>
<aside class="badges" aria-label="System badges">
<span class="badge">
<img class="badge-icon" src="/images/amd.svg" alt="">
AMD Purist
</span>
<span class="badge">
<img class="badge-icon" src="/images/arch.svg" alt="">
I use arch btw
</span>
<span class="badge">
<img class="badge-icon" src="/images/debian.svg" alt="">
Debian Professional
</span>
<span class="badge">
<img class="badge-icon" src="/images/apple.svg" alt="">
Apple Ecosystem Enthusiast
</span>
</aside>
<script src="/js/cat.js" data-cat="/images/oneko.gif"></script>
<script src="/js/nav.js"></script>
</body>
</html>