diff --git a/assets/styles.css b/assets/styles.css index 7c3fca3..397b62f 100644 --- a/assets/styles.css +++ b/assets/styles.css @@ -1,552 +1,549 @@ @font-face { - font-family: "Jersey 15"; - src: url("/fonts/jersey15.ttf") format("truetype"); -} - -@font-face { - font-family: "Share Tech"; - src: url("/fonts/str.ttf") format("truetype"); + font-family: "Jersey 15"; + src: url("/fonts/jersey15.ttf") format("truetype"); } :root { - --theme-bg: #16191c; - --theme-bg-secondary: #252a2e; - --theme-bg-tertiary: #1b1f22; - --theme-fg: #a3aaaf; - --theme-fg-alt-rgb: 106, 111, 121; - --theme-fg-alt: rgb(var(--theme-fg-alt-rgb)); - --theme-accent-rgb: 163, 170, 175; /* 147, 184, 217; */ - --theme-accent: rgb(var(--theme-accent-rgb)); - --theme-accent-alt: #2c3237; /* #2a353e; */ - --theme-accent-third: rgb(140, 155, 189); /* #2a353e; */ - --theme-accent-title-rgb: var(--theme-accent-rgb); - --theme-accent-title: rgb(var(--theme-accent-title-rgb)); - --theme-border-rgb: 56, 60, 66; - --theme-border: rgb(var(--theme-border-rgb)); + --theme-bg: #16191c; + --theme-bg-secondary: #252a2e; + --theme-bg-tertiary: #1b1f22; + --theme-fg: #a3aaaf; + --theme-fg-alt-rgb: 106, 111, 121; + --theme-fg-alt: rgb(var(--theme-fg-alt-rgb)); + --theme-accent-rgb: 163, 170, 175; /* 147, 184, 217; */ + --theme-accent: rgb(var(--theme-accent-rgb)); + --theme-accent-alt: #2c3237; /* #2a353e; */ + --theme-accent-third: rgb(140, 155, 189); /* #2a353e; */ + --theme-accent-title-rgb: var(--theme-accent-rgb); + --theme-accent-title: rgb(var(--theme-accent-title-rgb)); + --theme-border-rgb: 56, 60, 66; + --theme-border: rgb(var(--theme-border-rgb)); } html { - line-height: 1.5; - font-family: "Jersey 15", sans-serif, "Share Tech", monospace; - font-size: 22px; - -webkit-font-smoothing: none; - font-smooth: never; + line-height: 1.5; + font-family: "Jersey 15", sans-serif, monospace; + font-size: 22px; + -webkit-font-smoothing: none; + font-smooth: never; + scroll-behavior: smooth; } body { - padding: 16px; - margin: auto; - background-color: var(--theme-bg); - color: var(--theme-fg); + padding: 16px; + margin: auto; + background-color: var(--theme-bg); + color: var(--theme-fg); } .section { - display: flex; - flex-direction: column; - padding: 2.4rem; - overflow-wrap: break-word; - clip-path: polygon( - 0px calc(100% - 8px), - 8px calc(100% - 8px), - 8px 100%, - calc(100% - 8px) 100%, - calc(100% - 8px) calc(100% - 8px), - 100% calc(100% - 8px), - 100% 8px, - calc(100% - 8px) 8px, - calc(100% - 8px) 0px, - 8px 0px, - 8px 8px, - 0px 8px - ); - box-sizing: border-box; - background-color: var(--theme-bg-secondary); - gap: 1rem; + display: flex; + flex-direction: column; + padding: 2.4rem; + overflow-wrap: break-word; + clip-path: polygon( + 0px calc(100% - 8px), + 8px calc(100% - 8px), + 8px 100%, + calc(100% - 8px) 100%, + calc(100% - 8px) calc(100% - 8px), + 100% calc(100% - 8px), + 100% 8px, + calc(100% - 8px) 8px, + calc(100% - 8px) 0px, + 8px 0px, + 8px 8px, + 0px 8px + ); + box-sizing: border-box; + background-color: var(--theme-bg-secondary); + gap: 1rem; + animation: fadeIn 0.4s ease-out; +} + +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } } .section, #header { - max-width: 36rem; - width: 100%; + max-width: 36rem; + width: 100%; } .alt { - color: var(--theme-fg-alt); + color: var(--theme-fg-alt); } -.alt-font { - font-size: 16px; - font-family: "Share Tech", monospace; - font-weight: 700; - letter-spacing: 0.05em; -} +/* .alt-font { */ +/* font-size: 16px; */ +/* font-family: "Gamja Flower", monospace; */ +/* font-weight: 700; */ +/* letter-spacing: 0.05em; */ +/* } */ .section p { - margin: 0; + margin: 0; } p { - hyphens: auto; -} - -.intro > .logo { - height: 1em; - margin-right: 0.5ch; - vertical-align: -4px; + -webkit-hyphens: auto; + hyphens: auto; } .centered { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - width: 100%; - height: 100%; - gap: 1em; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; + gap: 1em; } .title { - color: var(--theme-accent-title); - padding-bottom: 0.25em; - border-bottom: 2px solid var(--theme-border); - text-align: center; - justify-content: center; - margin: 0px; - padding-bottom: 0.5rem; - display: flex; - align-items: center; - gap: 0.25em; + color: var(--theme-accent-title); + padding-bottom: 0.25em; + border-bottom: 2px solid var(--theme-border); + text-align: center; + justify-content: center; + margin: 0px; + padding-bottom: 0.5rem; + display: flex; + align-items: center; + gap: 0.25em; } .title-1 { - font-size: 1.25rem; + font-size: 1.25rem; } -.title-3 { - color: var(--theme-fg-alt); - padding-bottom: 0.16rem; - border-bottom: none; - font-size: 0.9rem; +.title-2 { + color: var(--theme-fg-alt); + padding-bottom: 0.16rem; + border-bottom: none; + font-size: 0.9rem; } -.title.title-3::after { - background-image: linear-gradient( - 90deg, - rgba(var(--theme-border-rgb), 0) 0, - rgba(var(--theme-fg-alt-rgb), 255) - ); +.title.title-2::after { + background-image: linear-gradient( + 90deg, + rgba(var(--theme-border-rgb), 0) 0, + rgba(var(--theme-fg-alt-rgb), 255) + ); } .title::after { - background-image: linear-gradient( - 90deg, - rgba(var(--theme-accent-title-rgb), 0) 0, - rgba(var(--theme-accent-title-rgb), 255) - ); - content: ""; - display: block; - flex: 1; - margin-left: 0.5ch; - height: 0.3rem; - border-radius: 3px; + background-image: linear-gradient( + 90deg, + rgba(var(--theme-accent-title-rgb), 0) 0, + rgba(var(--theme-accent-title-rgb), 255) + ); + content: ""; + display: block; + flex: 1; + margin-left: 0.5ch; + height: 0.3rem; + border-radius: 3px; } .title .icon { - height: 24px; - width: 24px; - shape-rendering: crispEdges; + height: 24px; + width: 24px; + shape-rendering: crispEdges; } .box { - background-color: var(--theme-accent-alt); - color: var(--theme-accent); - padding: 0.5rem 1rem; - line-height: 1; - box-sizing: border-box; - width: fit-content; - overflow: hidden; - text-overflow: ellipsis; - word-break: break-all; - clip-path: polygon( - 0px calc(100% - 6px), - 6px calc(100% - 6px), - 6px 100%, - calc(100% - 6px) 100%, - calc(100% - 6px) calc(100% - 6px), - 100% calc(100% - 6px), - 100% 6px, - calc(100% - 6px) 6px, - calc(100% - 6px) 0px, - 6px 0px, - 6px 6px, - 0px 6px - ); - display: flex; - align-items: center; - gap: 0.35rem; + background-color: var(--theme-accent-alt); + color: var(--theme-accent); + padding: 0.5rem 1rem; + line-height: 1; + box-sizing: border-box; + width: fit-content; + overflow: hidden; + text-overflow: ellipsis; + word-break: break-all; + clip-path: polygon( + 0px calc(100% - 6px), + 6px calc(100% - 6px), + 6px 100%, + calc(100% - 6px) 100%, + calc(100% - 6px) calc(100% - 6px), + 100% calc(100% - 6px), + 100% 6px, + calc(100% - 6px) 6px, + calc(100% - 6px) 0px, + 6px 0px, + 6px 6px, + 0px 6px + ); + display: flex; + align-items: center; + gap: 0.35rem; + transition: filter 0.2s ease; +} + +.box:hover { + filter: brightness(120%); +} + +.box svg { + flex-shrink: 0; } a, a:visited, a:active { - color: var(--theme-accent-third); - text-decoration: none; + color: var(--theme-accent-third); + text-decoration: none; } a:hover { - text-decoration: 2px underline; - text-underline-offset: 3px; + text-decoration: 2px underline; + text-underline-offset: 3px; } #header { - position: relative; - display: flex; - flex-direction: row; - justify-content: space-between; - z-index: 1; + position: relative; + display: flex; + flex-direction: row; + justify-content: space-between; + z-index: 1; } #header, #header #left, #header #links { - flex-wrap: wrap; + flex-wrap: wrap; } #header #left, #header #links { - display: flex; - flex-direction: row; - align-items: center; - gap: 0.5rem; - margin: 0; - padding: 0; + display: flex; + flex-direction: row; + align-items: center; + gap: 0.5rem; + margin: 0; + padding: 0; } #header img { - width: auto; - height: 1.5em; + width: auto; + height: 1.5em; } nav .module { - clip-path: polygon( - 0px calc(100% - 6px), - 6px calc(100% - 6px), - 6px 100%, - calc(100% - 6px) 100%, - calc(100% - 6px) calc(100% - 6px), - 100% calc(100% - 6px), - 100% 6px, - calc(100% - 6px) 6px, - calc(100% - 6px) 0px, - 6px 0px, - 6px 6px, - 0px 6px - ); - padding: 0.5em 1.25em; - box-sizing: border-box; - background-color: var(--theme-bg-secondary); + clip-path: polygon( + 0px calc(100% - 6px), + 6px calc(100% - 6px), + 6px 100%, + calc(100% - 6px) 100%, + calc(100% - 6px) calc(100% - 6px), + 100% calc(100% - 6px), + 100% 6px, + calc(100% - 6px) 6px, + calc(100% - 6px) 0px, + 6px 0px, + 6px 6px, + 0px 6px + ); + padding: 0.5em 1.25em; + box-sizing: border-box; + background-color: var(--theme-bg-secondary); } #logo { - display: flex; - align-items: center; - padding: 0.5em; + display: flex; + align-items: center; + padding: 0.5em; } .labeled-icons { - margin: 0; - padding: 0; - list-style: none; - display: flex; - flex-direction: row; - flex-wrap: wrap; - gap: 0.35rem; - margin-top: 0.25rem; + margin: 0; + padding: 0; + list-style: none; + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 0.35rem; + margin-top: 0.25rem; } .labeled-icons.full { - flex-direction: column; - & > * { - flex-grow: 1; - } - & .box { - width: 100%; - } + flex-direction: column; + & > * { + flex-grow: 1; + } + & .box { + width: 100%; + } } .labeled-icons.b88x31 { - gap: 0.35rem; + gap: 0.35rem; } .labeled-icons.b88x31 a { - display: flex; - width: 88px; - height: 31px; - background: var(--theme-bg-tertiary); - color: var(--theme-fg-alt); - justify-content: center; + display: flex; + width: 88px; + height: 31px; + background: var(--theme-bg-tertiary); + color: var(--theme-fg-alt); + justify-content: center; } .labeled-icons.b88x31 a, #muxiepuff { - clip-path: polygon( - 0px calc(100% - 3px), - 3px calc(100% - 3px), - 3px 100%, - calc(100% - 3px) 100%, - calc(100% - 3px) calc(100% - 3px), - 100% calc(100% - 3px), - 100% 3px, - calc(100% - 3px) 3px, - calc(100% - 3px) 0px, - 3px 0px, - 3px 3px, - 0px 3px - ); + clip-path: polygon( + 0px calc(100% - 3px), + 3px calc(100% - 3px), + 3px 100%, + calc(100% - 3px) 100%, + calc(100% - 3px) calc(100% - 3px), + 100% calc(100% - 3px), + 100% 3px, + calc(100% - 3px) 3px, + calc(100% - 3px) 0px, + 3px 0px, + 3px 3px, + 0px 3px + ); } .labeled-icons.b88x31 li { - transition: filter 0.2s ease; + transition: filter 0.2s ease; } .labeled-icons.b88x31 li:hover { - filter: brightness(0.5); + filter: brightness(0.5); } .labeled-icons.b88x31 img { - image-rendering: pixelated; + image-rendering: pixelated; } .labeled-icons.b88x31 img { - image-rendering: pixelated; + image-rendering: pixelated; } #extra { - display: flex; - list-style: none; - flex-wrap: wrap; - align-items: center; - justify-content: space-between; - padding: 0; + display: flex; + list-style: none; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + padding: 0; } #extra svg { - width: 15rem; - max-width: 100%; - height: 15rem; + width: 15rem; + max-width: 100%; + height: 15rem; } @media (max-width: 768px) { - html { - font-size: 18px; - } + html { + font-size: 18px; + } + .section { + padding: 1.5rem; + } + #extra { + justify-content: center; + gap: 1rem; + } + + #extra svg { + width: 8rem; + height: 8rem; + } } @media (max-width: 480px) { - html { - font-size: 16px; - } + html { + font-size: 16px; + } - body { - padding: 8px; - } -} + body { + padding: 8px; + } -@media (max-width: 768px) { - .section { - padding: 1.5rem; - } -} + .section { + padding: 1rem; + } -@media (max-width: 480px) { - .section { - padding: 1rem; - } -} + .box { + padding: 0.4rem 0.8rem; + font-size: 0.9em; + } -@media (max-width: 480px) { - .box { - padding: 0.4rem 0.8rem; - font-size: 0.9em; - } + .box svg { + width: 20px; + height: 20px; + } - .box svg { - width: 20px; - height: 20px; - } -} + .title { + font-size: 1.1rem; + } -@media (max-width: 480px) { - .title { - font-size: 1.1rem; - } -} + #extra svg { + width: 6rem; + height: 6rem; + } -@media (max-width: 768px) { - #extra { - justify-content: center; - gap: 1rem; - } - - #extra svg { - width: 8rem; - height: 8rem; - } -} - -@media (max-width: 480px) { - #extra svg { - width: 6rem; - height: 6rem; - } -} - -@media (max-width: 480px) { - .alt-font { - font-size: 14px; - } + .alt-font { + font-size: 14px; + } } #muxiepuff { - display: flex; - padding: 0; - background: none; - outline: none; - border: none; - cursor: pointer; + display: flex; + padding: 0; + background: none; + outline: none; + border: none; + cursor: pointer; } .name-scroller { - display: inline-block; - position: relative; - vertical-align: middle; - margin-top: -3px; - overflow: hidden; + display: inline-block; + position: relative; + vertical-align: middle; + margin-top: -3px; + overflow: hidden; } .name-wrapper { - transition: - transform 0.3s ease-in-out, - opacity 0.3s ease-in-out; - transform: translateY(0); - opacity: 1; + transition: transform 0.3s ease-in-out, opacity 0.3s ease-in-out; + transform: translateY(0); + opacity: 1; } .name-wrapper.animating { - transform: translateY(-100%); - opacity: 0; + transform: translateY(-100%); + opacity: 0; } .name-wrapper:not(.animating) { - transform: translateY(0); - opacity: 1; + transform: translateY(0); + opacity: 1; } .name-text { - position: relative; - display: inline-block; + position: relative; + display: inline-block; } .name-underline { - position: absolute; - bottom: 2px; - left: 0; - width: 100%; - height: 2px; - background: currentColor; - box-shadow: 0 0 8px currentColor; - animation: pulse 2s ease-in-out infinite; + position: absolute; + bottom: 2px; + left: 0; + width: 100%; + height: 2px; + background: currentColor; + box-shadow: 0 0 8px currentColor; + animation: pulse 2s ease-in-out infinite; } @keyframes pulse { - 0%, - 100% { - opacity: 1; - } - 50% { - opacity: 0.5; - } + 0%, + 100% { + opacity: 1; + } + 50% { + opacity: 0.5; + } } .inline-code { - font-family: "Share Tech Mono", "Share Tech", monospace; - font-size: 0.75em; - background-color: var(--theme-border); - padding: 1px; - display: inline-block; - clip-path: polygon( - 0px calc(100% - 2px), - 2px calc(100% - 2px), - 2px 100%, - calc(100% - 2px) 100%, - calc(100% - 2px) calc(100% - 2px), - 100% calc(100% - 2px), - 100% 2px, - calc(100% - 2px) 2px, - calc(100% - 2px) 0px, - 2px 0px, - 2px 2px, - 0px 2px - ); - line-height: 1.6; - vertical-align: middle; + font-family: monospace, "Jersey 15", sans-serif; + font-size: 0.75em; + background-color: var(--theme-border); + padding: 1px; + display: inline-block; + clip-path: polygon( + 0px calc(100% - 2px), + 2px calc(100% - 2px), + 2px 100%, + calc(100% - 2px) 100%, + calc(100% - 2px) calc(100% - 2px), + 100% calc(100% - 2px), + 100% 2px, + calc(100% - 2px) 2px, + calc(100% - 2px) 0px, + 2px 0px, + 2px 2px, + 0px 2px + ); + line-height: 1.6; + vertical-align: middle; + cursor: pointer; + outline: none; + border: none; } .inline-code > span { - display: block; - background-color: var(--theme-bg-tertiary); - color: var(--theme-fg); - padding: 0.15em 0.4em; - clip-path: polygon( - 0px calc(100% - 2px), - 2px calc(100% - 2px), - 2px 100%, - calc(100% - 2px) 100%, - calc(100% - 2px) calc(100% - 2px), - 100% calc(100% - 2px), - 100% 2px, - calc(100% - 2px) 2px, - calc(100% - 2px) 0px, - 2px 0px, - 2px 2px, - 0px 2px - ); - font-weight: 500; + display: block; + background-color: var(--theme-bg-tertiary); + color: var(--theme-fg); + padding: 0.15em 0.4em; + clip-path: polygon( + 0px calc(100% - 2px), + 2px calc(100% - 2px), + 2px 100%, + calc(100% - 2px) 100%, + calc(100% - 2px) calc(100% - 2px), + 100% calc(100% - 2px), + 100% 2px, + calc(100% - 2px) 2px, + calc(100% - 2px) 0px, + 2px 0px, + 2px 2px, + 0px 2px + ); + font-weight: 500; } p, .title { - transform: translateZ(0px); - -webkit-transform: translateZ(0px); - will-change: transform; + transform: translateZ(0px); + -webkit-transform: translateZ(0px); + will-change: transform; } hr { - width: 100%; - border: none; - border-top: 2px solid var(--theme-border); + width: 100%; + border: none; + border-top: 2px solid var(--theme-border); } footer.section { - display: flex; - align-items: center; - justify-content: center; - gap: 1ch; + display: flex; + align-items: center; + justify-content: center; + gap: 1ch; } footer p { - text-align: center; - gap: 1ch; - margin: 0; - padding: 0; + text-align: center; + gap: 1ch; + margin: 0; + padding: 0; } footer a { - overflow-wrap: anywhere; - word-break: break-word; + overflow-wrap: anywhere; + word-break: break-word; } footer #f-heart { - color: var(--theme-accent-third); - vertical-align: middle; + color: var(--theme-accent-third); + vertical-align: middle; } diff --git a/components/Box.tsx b/components/Box.tsx index 11c2d53..66b0e2d 100644 --- a/components/Box.tsx +++ b/components/Box.tsx @@ -7,16 +7,16 @@ import { ComponentChildren, h } from "preact"; import { LinkIcon } from "./Icon.tsx"; interface PolygonBoxProps { - as?: keyof HTMLElementTagNameMap; - children?: ComponentChildren; - [key: string]: any; + as?: keyof HTMLElementTagNameMap; + children?: ComponentChildren; + [key: string]: any; } export default function Box( - { as: Tag = "span", class: className, children, ...props }: PolygonBoxProps, + { as: Tag = "span", class: className, children, ...props }: PolygonBoxProps, ) { - const content = Tag === "a" - ? [children, h(LinkIcon, { size: 16, class: "link-icon" })] - : children; - return h(Tag, { class: `box ${className || ""}`, ...props }, content); + const content = Tag === "a" + ? [children, h(LinkIcon, { size: 16, class: "link-icon" })] + : children; + return h(Tag, { class: `box ${className || ""}`, ...props }, content); } diff --git a/components/Footer.tsx b/components/Footer.tsx index 4d81208..5855baa 100644 --- a/components/Footer.tsx +++ b/components/Footer.tsx @@ -7,23 +7,25 @@ import { HeartFilledIcon } from "./Icon.tsx"; import Link from "./Link.tsx"; export default function Footer() { - return ( - - ); + return ( + + ); } diff --git a/components/Header.tsx b/components/Header.tsx index 3c71fb1..c6fbc66 100644 --- a/components/Header.tsx +++ b/components/Header.tsx @@ -4,32 +4,38 @@ */ export default function Header() { - return ( - - ); + return ( + + ); } diff --git a/components/Icon.tsx b/components/Icon.tsx index cff513b..8cd4823 100644 --- a/components/Icon.tsx +++ b/components/Icon.tsx @@ -4,439 +4,455 @@ */ interface IconProps { - size?: number; - [key: string]: any; + size?: number; + [key: string]: any; } export function LinkIcon({ size = 24, ...props }: IconProps) { - return ( - - - - ); + return ( + + + + ); } export function PersonIcon({ size = 24, ...props }: IconProps) { - return ( - - - - ); + return ( + + + + ); } export function HeartIcon({ size = 24, ...props }: IconProps) { - return ( - - - - ); + return ( + + + + ); } export function CommentIcon({ size = 24, ...props }: IconProps) { - return ( - - - - ); + return ( + + + + ); } export function AttachmentIcon({ size = 24, ...props }: IconProps) { - return ( - - - - ); + return ( + + + + ); } export function LabelIcon({ size = 24, ...props }: IconProps) { - return ( - - - - ); + return ( + + + + ); } export function DiscordIcon({ size = 24, ...props }: IconProps) { - return ( - - Discord - - - ); + return ( + + Discord + + + ); } export function SignalIcon({ size = 24, ...props }: IconProps) { - return ( - - Signal - - - ); + return ( + + Signal + + + ); } export function LastfmIcon({ size = 24, ...props }: IconProps) { - return ( - - Last.fm - - - ); + return ( + + Last.fm + + + ); } export function BlueskyIcon({ size = 24, ...props }: IconProps) { - return ( - - Bluesky - - - ); + return ( + + Bluesky + + + ); } export function MastodonIcon({ size = 24, ...props }: IconProps) { - return ( - - Mastodon / Fediverse - - - ); + return ( + + Mastodon / Fediverse + + + ); } export function ForgejoIcon({ size = 24, ...props }: IconProps) { - return ( - - Forgejo - - - ); + return ( + + Forgejo + + + ); } export function CodebergIcon({ size = 24, ...props }: IconProps) { - return ( - - Codeberg - - - ); + return ( + + Codeberg + + + ); } export function GitHubIcon({ size = 24, ...props }: IconProps) { - return ( - - GitHub - - - ); + return ( + + GitHub + + + ); } export function MailIcon({ size = 24, ...props }: IconProps) { - return ( - - - - - ); + return ( + + + + + ); } export function TwitterIcon({ size = 24, ...props }: IconProps) { - return ( - - - - ); + return ( + + + + ); } export function KofiIcon({ size = 24, ...props }: IconProps) { - return ( - - Ko-fi - - - ); + return ( + + Ko-fi + + + ); } export function BitcoinIcon({ size = 24, ...props }: IconProps) { - return ( - - Bitcoin - - - ); + return ( + + Bitcoin + + + ); } export function BitcoinCashIcon({ size = 24, ...props }: IconProps) { - return ( - - Bitcoin Cash - - - ); + return ( + + Bitcoin Cash + + + ); } export function MoneroIcon({ size = 24, ...props }: IconProps) { - return ( - - Monero - - - ); + return ( + + Monero + + + ); } export function NanoIcon({ size = 24, ...props }: IconProps) { - return ( - - Nano - - - ); + return ( + + Nano + + + ); } export function LitecoinIcon({ size = 24, ...props }: IconProps) { - return ( - - Litecoin - - - ); + return ( + + Litecoin + + + ); } export function EthereumIcon({ size = 24, ...props }: IconProps) { - return ( - - Ethereum - - - ); + return ( + + Ethereum + + + ); } export function HeartFilledIcon({ size = 24, ...props }: IconProps) { - return ( - - - - ); + return ( + + + + ); +} + +export function GitHubSponsorsIcon({ size = 24, ...props }: IconProps) { + return ( + + GitHub Sponsors + + + ); } diff --git a/components/Layout.tsx b/components/Layout.tsx index 1d1e586..e169fc4 100644 --- a/components/Layout.tsx +++ b/components/Layout.tsx @@ -6,27 +6,27 @@ import { ComponentChildren } from "preact"; interface LayoutProps { - children: ComponentChildren; - [key: string]: any; + children: ComponentChildren; + [key: string]: any; } export function Section({ children, class: className, ...props }: LayoutProps) { - return ( -
- {children} -
- ); + return ( +
+ {children} +
+ ); } export function Centered( - { children, class: className, ...props }: LayoutProps, + { children, class: className, ...props }: LayoutProps, ) { - return ( -
- {children} -
- ); + return ( +
+ {children} +
+ ); } diff --git a/components/Link.tsx b/components/Link.tsx index 636d545..3e60f4a 100644 --- a/components/Link.tsx +++ b/components/Link.tsx @@ -7,18 +7,18 @@ import { HTMLAttributes, JSX } from "preact"; import { LinkIcon } from "./Icon.tsx"; interface LinkProps extends HTMLAttributes { - href: string; - children: JSX.Element | JSX.Element[] | string; - noIcon?: boolean; + href: string; + children: JSX.Element | JSX.Element[] | string; + noIcon?: boolean; } export default function Link( - { children, noIcon = false, ...props }: LinkProps, + { children, noIcon = false, ...props }: LinkProps, ) { - return ( - - {children} - {!noIcon && } - - ); + return ( + + {children} + {!noIcon && } + + ); } diff --git a/components/Tinted.tsx b/components/Tinted.tsx index 85096d7..c7a11b7 100644 --- a/components/Tinted.tsx +++ b/components/Tinted.tsx @@ -4,57 +4,57 @@ */ interface TintedImageProps { - src: string; - colour: string; - [key: string]: any; + src: string; + colour: string; + [key: string]: any; } export default function TintedImage({ - colour, - src, - ...props + colour, + src, + ...props }: TintedImageProps) { - return ( - - - - - - - - - - - ); + return ( + + + + + + + + + + + ); } diff --git a/components/Title.tsx b/components/Title.tsx index 5246dfc..7e55ccc 100644 --- a/components/Title.tsx +++ b/components/Title.tsx @@ -6,24 +6,24 @@ import { ComponentChildren, h } from "preact"; interface TitleProps { - level?: 1 | 2 | 3 | 4 | 5 | 6; - children: ComponentChildren; - [key: string]: any; + level?: 1 | 2 | 3 | 4 | 5 | 6; + children: ComponentChildren; + [key: string]: any; } export default function Title({ - level = 1, - children, - class: className, - ...props + level = 1, + children, + class: className, + ...props }: TitleProps) { - const Heading = `h${Math.min(Math.max(level, 1), 6)}`; - return h( - Heading, - { - class: `title title-${level} ${className || ""}`, - ...props, - }, - children, - ); + const Heading = `h${Math.min(Math.max(level, 1), 6)}`; + return h( + Heading, + { + class: `title title-${level} ${className || ""}`, + ...props, + }, + children, + ); } diff --git a/deno.json b/deno.json index e9072ef..80270b7 100644 --- a/deno.json +++ b/deno.json @@ -1,58 +1,66 @@ { - "nodeModulesDir": "auto", - "tasks": { - "check": "deno fmt --check . && deno lint . && deno check", - "dev": "vite", - "build": "vite build", - "start": "deno serve -A _fresh/server.js", - "update": "deno run -A -r jsr:@fresh/update ." - }, - "lint": { - "rules": { - "tags": [ - "fresh", - "recommended" - ], - "exclude": ["no-explicit-any", "no-window", "no-window-prefix"] - } - }, - "exclude": [ - "**/_fresh/*" - ], - "imports": { - "fresh": "jsr:@fresh/core@^2.1.2", - "preact": "npm:preact@^10.27.2", - "@preact/signals": "npm:@preact/signals@^2.3.1", - "@fresh/plugin-vite": "jsr:@fresh/plugin-vite@^1.0.5", - "vite": "npm:vite@^7.1.3" - }, - "compilerOptions": { - "lib": [ - "dom", - "dom.asynciterable", - "dom.iterable", - "deno.ns" - ], - "jsx": "precompile", - "jsxImportSource": "preact", - "jsxPrecompileSkipElements": [ - "a", - "img", - "source", - "body", - "html", - "head", - "title", - "meta", - "script", - "link", - "style", - "base", - "noscript", - "template" - ], - "types": [ - "vite/client" - ] - } + "nodeModulesDir": "auto", + "tasks": { + "check": "deno fmt --check . && deno lint . && deno check", + "dev": "vite", + "build": "vite build", + "start": "deno serve -A _fresh/server.js", + "update": "deno run -A -r jsr:@fresh/update ." + }, + "fmt": { + "useTabs": true + }, + "lint": { + "rules": { + "tags": [ + "fresh", + "recommended" + ], + "exclude": [ + "no-explicit-any", + "no-window", + "no-window-prefix", + "no-unused-vars" + ] + } + }, + "exclude": [ + "**/_fresh/*" + ], + "imports": { + "fresh": "jsr:@fresh/core@^2.1.2", + "preact": "npm:preact@^10.27.2", + "@preact/signals": "npm:@preact/signals@^2.3.1", + "@fresh/plugin-vite": "jsr:@fresh/plugin-vite@^1.0.5", + "vite": "npm:vite@^7.1.3" + }, + "compilerOptions": { + "lib": [ + "dom", + "dom.asynciterable", + "dom.iterable", + "deno.ns" + ], + "jsx": "precompile", + "jsxImportSource": "preact", + "jsxPrecompileSkipElements": [ + "a", + "img", + "source", + "body", + "html", + "head", + "title", + "meta", + "script", + "link", + "style", + "base", + "noscript", + "template" + ], + "types": [ + "vite/client" + ] + } } diff --git a/islands/Code.tsx b/islands/Code.tsx index fae6439..bc97b62 100644 --- a/islands/Code.tsx +++ b/islands/Code.tsx @@ -6,25 +6,24 @@ import { ComponentChildren } from "preact"; interface CodeProps { - children: ComponentChildren; - [key: string]: any; + children: ComponentChildren; + [key: string]: any; } export default function Code({ children, ...props }: CodeProps) { - const handleClick = () => { - if (typeof children === "string") { - navigator.clipboard.writeText(children); - } - }; - return ( - - {children} - - ); + const handleClick = () => { + if (typeof children === "string") { + navigator.clipboard.writeText(children); + } + }; + return ( + + ); } diff --git a/islands/Meow.tsx b/islands/Meow.tsx index 101786e..e34ef8b 100644 --- a/islands/Meow.tsx +++ b/islands/Meow.tsx @@ -4,16 +4,20 @@ */ export default function Meow() { - return ( - - ); + return ( + + ); } diff --git a/islands/Name.tsx b/islands/Name.tsx index 9e9ee88..3682f26 100644 --- a/islands/Name.tsx +++ b/islands/Name.tsx @@ -6,32 +6,31 @@ import { useEffect, useState } from "preact/hooks"; export default function Name() { - const names = ["muxiepuff", "lívia", "aury"]; - const ipas = ["/ˈmuˌksipʌf/", "/li.vjɐ/", "/ˈaʊ̯ˌɾi/"] - ; - const [currentIndex, setCurrentIndex] = useState(0); - const [isAnimating, setIsAnimating] = useState(false); + const names = ["muxiepuff", "lívia", "aury"]; + const ipas = ["/ˈmuˌksipʌf/", "/li.vjɐ/", "/ˈaʊ̯ˌɾi/"]; + const [currentIndex, setCurrentIndex] = useState(0); + const [isAnimating, setIsAnimating] = useState(false); - useEffect(() => { - const interval = setInterval(() => { - setIsAnimating(true); - setTimeout(() => { - setCurrentIndex((prev) => (prev + 1) % names.length); - setIsAnimating(false); - }, 200); - }, 2000); - return () => clearInterval(interval); - }, []); + useEffect(() => { + const interval = setInterval(() => { + setIsAnimating(true); + setTimeout(() => { + setCurrentIndex((prev) => (prev + 1) % names.length); + setIsAnimating(false); + }, 200); + }, 2000); + return () => clearInterval(interval); + }, []); - return ( - - - - {names[currentIndex]} - - {" "} - ({ipas[currentIndex]}, she/her) - - - ); + return ( + + + + {names[currentIndex]} + + {" "} + ({ipas[currentIndex]}, she/her) + + + ); } diff --git a/islands/Rain.tsx b/islands/Rain.tsx index e7a26de..4bc8f86 100644 --- a/islands/Rain.tsx +++ b/islands/Rain.tsx @@ -6,41 +6,41 @@ import { useEffect, useRef } from "preact/hooks"; interface Particle { - offsetX: number; - offsetY: number; - velocityX: number; - velocityY: number; + offsetX: number; + offsetY: number; + velocityX: number; + velocityY: number; } interface Raindrop { - x: number; - y: number; - length: number; - speed: number; - opacity: number; - floorHeight: number; + x: number; + y: number; + length: number; + speed: number; + opacity: number; + floorHeight: number; } interface Splash { - x: number; - y: number; - age: number; - maxAge: number; - particles: Particle[]; + x: number; + y: number; + age: number; + maxAge: number; + particles: Particle[]; } interface Star { - x: number; - y: number; - size: number; - brightness: number; - twinkleSpeed: number; - twinkleOffset: number; - age: number; - maxAge: number; - canVanish: boolean; - color: { r: number; g: number; b: number }; - colorIndex: number; + x: number; + y: number; + size: number; + brightness: number; + twinkleSpeed: number; + twinkleOffset: number; + age: number; + maxAge: number; + canVanish: boolean; + color: { r: number; g: number; b: number }; + colorIndex: number; } const PIXEL_SIZE = 4; @@ -82,293 +82,299 @@ const ALPHA_STAR_COLORS: string[] = []; const NUM_COLOR_VARIATIONS = 20; for (let i = 0; i < NUM_COLOR_VARIATIONS; i++) { - const brightness = 1 - (i / NUM_COLOR_VARIATIONS) * 0.5; - RAIN_COLORS.push( - `rgb(${Math.floor(RAIN_COLOR.r * brightness)},${ - Math.floor(RAIN_COLOR.g * brightness) - },${Math.floor(RAIN_COLOR.b * brightness)})`, - ); + const brightness = 1 - (i / NUM_COLOR_VARIATIONS) * 0.5; + RAIN_COLORS.push( + `rgb(${Math.floor(RAIN_COLOR.r * brightness)},${ + Math.floor( + RAIN_COLOR.g * brightness, + ) + },${Math.floor(RAIN_COLOR.b * brightness)})`, + ); } for (let i = 0; i < NUM_COLOR_VARIATIONS; i++) { - const brightness = STAR_BRIGHTNESS_MIN + - (i / NUM_COLOR_VARIATIONS) * STAR_BRIGHTNESS_RANGE; - STAR_COLORS.push( - `rgb(${Math.floor(RAIN_COLOR.r * brightness)},${ - Math.floor(RAIN_COLOR.g * brightness) - },${Math.floor(RAIN_COLOR.b * brightness)})`, - ); - ALPHA_STAR_COLORS.push( - `rgba(${Math.floor(RAIN_COLOR.r * brightness)},${ - Math.floor(RAIN_COLOR.g * brightness) - },${Math.floor(RAIN_COLOR.b * brightness)},`, - ); + const brightness = STAR_BRIGHTNESS_MIN + + (i / NUM_COLOR_VARIATIONS) * STAR_BRIGHTNESS_RANGE; + STAR_COLORS.push( + `rgb(${Math.floor(RAIN_COLOR.r * brightness)},${ + Math.floor( + RAIN_COLOR.g * brightness, + ) + },${Math.floor(RAIN_COLOR.b * brightness)})`, + ); + ALPHA_STAR_COLORS.push( + `rgba(${Math.floor(RAIN_COLOR.r * brightness)},${ + Math.floor( + RAIN_COLOR.g * brightness, + ) + },${Math.floor(RAIN_COLOR.b * brightness)},`, + ); } const SPLASH_PARTICLE_COLOR = `rgb(${RAIN_COLOR.r * 0.9},${ - RAIN_COLOR.g * 0.95 + RAIN_COLOR.g * 0.95 },${RAIN_COLOR.b * 1.1})`; const SPLASH_RIPPLE_COLOR = `rgb(${RAIN_COLOR.r * 0.7},${RAIN_COLOR.g * 0.8},${ - RAIN_COLOR.b * 0.95 + RAIN_COLOR.b * 0.95 })`; function createParticle(): Particle { - return { - offsetX: (Math.random() - 0.5) * PARTICLE_OFFSET_RANGE, - offsetY: -(Math.random() * PARTICLE_Y_RANGE + PARTICLE_Y_MIN), - velocityX: (Math.random() - 0.5) * PARTICLE_VX_RANGE, - velocityY: -(Math.random() * PARTICLE_VY_RANGE + PARTICLE_VY_MIN), - }; + return { + offsetX: (Math.random() - 0.5) * PARTICLE_OFFSET_RANGE, + offsetY: -(Math.random() * PARTICLE_Y_RANGE + PARTICLE_Y_MIN), + velocityX: (Math.random() - 0.5) * PARTICLE_VX_RANGE, + velocityY: -(Math.random() * PARTICLE_VY_RANGE + PARTICLE_VY_MIN), + }; } function createSplash(x: number, y: number): Splash { - return { - x, - y, - age: 0, - maxAge: 12, - particles: Array.from( - { length: Math.floor(Math.random() * 3) + 3 }, - createParticle, - ), - }; + return { + x, + y, + age: 0, + maxAge: 12, + particles: Array.from( + { length: Math.floor(Math.random() * 3) + 3 }, + createParticle, + ), + }; } function createRaindrop(gridWidth: number, gridHeight: number): Raindrop { - const bias = Math.random() * Math.random(); - return { - x: Math.floor(Math.random() * gridWidth), - y: Math.floor(Math.random() * gridHeight) - gridHeight, - length: Math.floor(Math.random() * 4) + 2, - speed: RAIN_SPEED * (Math.random() * 2 + 1), - opacity: Math.random() * 0.3 + 0.6, - floorHeight: window.innerHeight * - (FLOOR_HEIGHT_MIN + bias * FLOOR_HEIGHT_RANGE), - }; + const bias = Math.random() * Math.random(); + return { + x: Math.floor(Math.random() * gridWidth), + y: Math.floor(Math.random() * gridHeight) - gridHeight, + length: Math.floor(Math.random() * 4) + 2, + speed: RAIN_SPEED * (Math.random() * 2 + 1), + opacity: Math.random() * 0.3 + 0.6, + floorHeight: window.innerHeight * + (FLOOR_HEIGHT_MIN + bias * FLOOR_HEIGHT_RANGE), + }; } function createStar(): Star { - const brightness = Math.random() * STAR_BRIGHTNESS_RANGE + - STAR_BRIGHTNESS_MIN; - const colorIndex = Math.floor( - (brightness - STAR_BRIGHTNESS_MIN) / STAR_BRIGHTNESS_RANGE * - (NUM_COLOR_VARIATIONS - 1), - ); + const brightness = Math.random() * STAR_BRIGHTNESS_RANGE + + STAR_BRIGHTNESS_MIN; + const colorIndex = Math.floor( + ((brightness - STAR_BRIGHTNESS_MIN) / STAR_BRIGHTNESS_RANGE) * + (NUM_COLOR_VARIATIONS - 1), + ); - return { - x: Math.random() * window.innerWidth, - y: Math.random() * window.innerHeight, - size: Math.random() > STAR_LARGE_THRESHOLD - ? STAR_SIZE_LARGE - : STAR_SIZE_SMALL, - twinkleSpeed: Math.random() * STAR_TWINKLE_RANGE + STAR_TWINKLE_MIN, - twinkleOffset: Math.random() * Math.PI * 2, - brightness, - age: 0, - maxAge: STAR_AGE_MIN + Math.random() * STAR_AGE_RANGE, - canVanish: false, - colorIndex, - }; + return { + x: Math.random() * window.innerWidth, + y: Math.random() * window.innerHeight, + size: Math.random() > STAR_LARGE_THRESHOLD + ? STAR_SIZE_LARGE + : STAR_SIZE_SMALL, + twinkleSpeed: Math.random() * STAR_TWINKLE_RANGE + STAR_TWINKLE_MIN, + twinkleOffset: Math.random() * Math.PI * 2, + brightness, + age: 0, + maxAge: STAR_AGE_MIN + Math.random() * STAR_AGE_RANGE, + canVanish: false, + colorIndex, + }; } function updateParticle(p: Particle) { - p.offsetX += p.velocityX; - p.offsetY += p.velocityY; - p.velocityY += PARTICLE_GRAVITY; + p.offsetX += p.velocityX; + p.offsetY += p.velocityY; + p.velocityY += PARTICLE_GRAVITY; } function updateSplash(splash: Splash) { - splash.age++; - splash.particles.forEach(updateParticle); + splash.age++; + splash.particles.forEach(updateParticle); } function updateRaindrop( - drop: Raindrop, - splashes: Splash[], - gridWidth: number, - gridHeight: number, + drop: Raindrop, + splashes: Splash[], + gridWidth: number, + gridHeight: number, ) { - drop.y += drop.speed; - const dropY = drop.y * PIXEL_SIZE; + drop.y += drop.speed; + const dropY = drop.y * PIXEL_SIZE; - if (dropY >= drop.floorHeight && Math.random() < SPLASH_CHANCE) { - splashes.push(createSplash(drop.x, Math.floor(dropY / PIXEL_SIZE))); - Object.assign(drop, createRaindrop(gridWidth, gridHeight)); - } else if (dropY > window.innerHeight) { - Object.assign(drop, createRaindrop(gridWidth, gridHeight)); - } + if (dropY >= drop.floorHeight && Math.random() < SPLASH_CHANCE) { + splashes.push(createSplash(drop.x, Math.floor(dropY / PIXEL_SIZE))); + Object.assign(drop, createRaindrop(gridWidth, gridHeight)); + } else if (dropY > window.innerHeight) { + Object.assign(drop, createRaindrop(gridWidth, gridHeight)); + } } function updateStar(star: Star): void { - star.age++; - const phase = (star.age * star.twinkleSpeed + star.twinkleOffset) % - (2 * Math.PI); + star.age++; + const phase = (star.age * star.twinkleSpeed + star.twinkleOffset) % + (2 * Math.PI); - if (!star.canVanish && phase < star.twinkleSpeed) { - star.canVanish = true; - } + if (!star.canVanish && phase < star.twinkleSpeed) { + star.canVanish = true; + } - if (star.canVanish && star.age > star.maxAge) { - Object.assign(star, createStar()); - } else if (star.x > window.innerWidth || star.y > window.innerHeight) { - Object.assign(star, createStar()); - } + if (star.canVanish && star.age > star.maxAge) { + Object.assign(star, createStar()); + } else if (star.x > window.innerWidth || star.y > window.innerHeight) { + Object.assign(star, createStar()); + } } const isSplashDead = (splash: Splash): boolean => splash.age >= splash.maxAge; const drawRaindrop = (ctx: CanvasRenderingContext2D, drop: Raindrop): void => { - ctx.globalAlpha = drop.opacity; - const xPos = drop.x * PIXEL_SIZE; + ctx.globalAlpha = drop.opacity; + const xPos = drop.x * PIXEL_SIZE; - for (let i = 0; i < drop.length; i++) { - const colorIndex = Math.floor( - (i / drop.length) * NUM_COLOR_VARIATIONS * 0.5, - ); - ctx.fillStyle = RAIN_COLORS[colorIndex]; - ctx.fillRect(xPos, (drop.y - i) * PIXEL_SIZE, PIXEL_SIZE, PIXEL_SIZE); - } - ctx.globalAlpha = 1; + for (let i = 0; i < drop.length; i++) { + const colorIndex = Math.floor( + (i / drop.length) * NUM_COLOR_VARIATIONS * 0.5, + ); + ctx.fillStyle = RAIN_COLORS[colorIndex]; + ctx.fillRect(xPos, (drop.y - i) * PIXEL_SIZE, PIXEL_SIZE, PIXEL_SIZE); + } + ctx.globalAlpha = 1; }; function drawSplash(ctx: CanvasRenderingContext2D, splash: Splash) { - if (splash.age >= splash.maxAge) return; + if (splash.age >= splash.maxAge) return; - const progress = splash.age / splash.maxAge; - ctx.globalAlpha = 1 - progress; + const progress = splash.age / splash.maxAge; + ctx.globalAlpha = 1 - progress; - ctx.fillStyle = SPLASH_PARTICLE_COLOR; - splash.particles.forEach((p) => { - const px = Math.floor(splash.x + p.offsetX) * PIXEL_SIZE; - const py = Math.floor(splash.y + p.offsetY) * PIXEL_SIZE; - ctx.fillRect(px, py, PIXEL_SIZE, PIXEL_SIZE); - }); + ctx.fillStyle = SPLASH_PARTICLE_COLOR; + splash.particles.forEach((p) => { + const px = Math.floor(splash.x + p.offsetX) * PIXEL_SIZE; + const py = Math.floor(splash.y + p.offsetY) * PIXEL_SIZE; + ctx.fillRect(px, py, PIXEL_SIZE, PIXEL_SIZE); + }); - if (splash.age < 6) { - const rippleSize = Math.floor(splash.age / 2) + 1; - const splashY = splash.y * PIXEL_SIZE; - ctx.fillStyle = SPLASH_RIPPLE_COLOR; - ctx.fillRect( - (splash.x - rippleSize) * PIXEL_SIZE, - splashY, - PIXEL_SIZE, - PIXEL_SIZE, - ); - ctx.fillRect( - (splash.x + rippleSize) * PIXEL_SIZE, - splashY, - PIXEL_SIZE, - PIXEL_SIZE, - ); - } + if (splash.age < 6) { + const rippleSize = Math.floor(splash.age / 2) + 1; + const splashY = splash.y * PIXEL_SIZE; + ctx.fillStyle = SPLASH_RIPPLE_COLOR; + ctx.fillRect( + (splash.x - rippleSize) * PIXEL_SIZE, + splashY, + PIXEL_SIZE, + PIXEL_SIZE, + ); + ctx.fillRect( + (splash.x + rippleSize) * PIXEL_SIZE, + splashY, + PIXEL_SIZE, + PIXEL_SIZE, + ); + } - ctx.globalAlpha = 1; + ctx.globalAlpha = 1; } -function drawStar( - ctx: CanvasRenderingContext2D, - star: Star, - time: number, -) { - const twinkle = Math.sin(time * star.twinkleSpeed + star.twinkleOffset) * - STAR_TWINKLE_AMPLITUDE + STAR_TWINKLE_BASE; - const alpha = star.brightness * twinkle; +function drawStar(ctx: CanvasRenderingContext2D, star: Star, time: number) { + const twinkle = Math.sin(time * star.twinkleSpeed + star.twinkleOffset) * + STAR_TWINKLE_AMPLITUDE + + STAR_TWINKLE_BASE; + const alpha = star.brightness * twinkle; - const x = Math.floor(star.x); - const y = Math.floor(star.y); - const color = ALPHA_STAR_COLORS[star.colorIndex]; + const x = Math.floor(star.x); + const y = Math.floor(star.y); + const color = ALPHA_STAR_COLORS[star.colorIndex]; - ctx.fillStyle = color + alpha; - ctx.fillRect(x, y, star.size, star.size); + ctx.fillStyle = color + alpha; + ctx.fillRect(x, y, star.size, star.size); - if (star.size === STAR_SIZE_LARGE && alpha > STAR_GLOW_THRESHOLD) { - ctx.fillStyle = color + (alpha * STAR_GLOW_ALPHA); - ctx.fillRect(x - 4, y + 2, 4, 4); - ctx.fillRect(x + 8, y + 2, 4, 4); - ctx.fillRect(x + 2, y - 4, 4, 4); - ctx.fillRect(x + 2, y + 8, 4, 4); - } + if (star.size === STAR_SIZE_LARGE && alpha > STAR_GLOW_THRESHOLD) { + ctx.fillStyle = color + alpha * STAR_GLOW_ALPHA; + ctx.fillRect(x - 4, y + 2, 4, 4); + ctx.fillRect(x + 8, y + 2, 4, 4); + ctx.fillRect(x + 2, y - 4, 4, 4); + ctx.fillRect(x + 2, y + 8, 4, 4); + } } export default function Rain() { - const canvasRef = useRef(null); + const canvasRef = useRef(null); - useEffect(() => { - const prefersReducedMotion = - window.matchMedia("(prefers-reduced-motion: reduce)").matches; - if (prefersReducedMotion) return; + useEffect(() => { + const prefersReducedMotion = window.matchMedia( + "(prefers-reduced-motion: reduce)", + ).matches; + if (prefersReducedMotion) return; - const canvas = canvasRef.current; - if (!canvas) return; + const canvas = canvasRef.current; + if (!canvas) return; - const ctx = canvas.getContext("2d"); - if (!ctx) return; + const ctx = canvas.getContext("2d"); + if (!ctx) return; - let gridWidth = 0; - let gridHeight = 0; + let gridWidth = 0; + let gridHeight = 0; - const resizeCanvas = () => { - canvas.width = window.innerWidth; - canvas.height = window.innerHeight; - gridWidth = Math.floor(window.innerWidth / PIXEL_SIZE); - gridHeight = Math.floor(window.innerHeight / PIXEL_SIZE); - }; + const resizeCanvas = () => { + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + gridWidth = Math.floor(window.innerWidth / PIXEL_SIZE); + gridHeight = Math.floor(window.innerHeight / PIXEL_SIZE); + }; - resizeCanvas(); - window.addEventListener("resize", resizeCanvas); + resizeCanvas(); + window.addEventListener("resize", resizeCanvas); - const stars = Array.from({ length: STAR_COUNT }, createStar); - const raindrops = Array.from( - { length: DROP_COUNT }, - () => createRaindrop(gridWidth, gridHeight), - ); - const splashes: Splash[] = []; + const stars = Array.from({ length: STAR_COUNT }, createStar); + const raindrops = Array.from( + { length: DROP_COUNT }, + () => createRaindrop(gridWidth, gridHeight), + ); + const splashes: Splash[] = []; - const startTime = performance.now(); + const startTime = performance.now(); - const animate = (time: number) => { - ctx.fillStyle = BACKGROUND_COLOUR; - ctx.fillRect(0, 0, canvas.width, canvas.height); + const animate = (time: number) => { + ctx.fillStyle = BACKGROUND_COLOUR; + ctx.fillRect(0, 0, canvas.width, canvas.height); - for (let i = 0; i < stars.length; i++) { - updateStar(stars[i]); - drawStar(ctx, stars[i], time - startTime); - } - raindrops.forEach((drop) => - updateRaindrop(drop, splashes, gridWidth, gridHeight) - ); - for (let i = splashes.length - 1; i >= 0; i--) { - updateSplash(splashes[i]); - if (isSplashDead(splashes[i])) { - splashes.splice(i, 1); - } - } + for (let i = 0; i < stars.length; i++) { + updateStar(stars[i]); + drawStar(ctx, stars[i], time - startTime); + } + raindrops.forEach((drop) => + updateRaindrop(drop, splashes, gridWidth, gridHeight) + ); + for (let i = splashes.length - 1; i >= 0; i--) { + updateSplash(splashes[i]); + if (isSplashDead(splashes[i])) { + splashes.splice(i, 1); + } + } - for (let i = 0; i < stars.length; i++) { - updateStar(stars[i]); - drawStar(ctx, stars[i], performance.now() - startTime); - } - raindrops.forEach((drop) => drawRaindrop(ctx, drop)); - splashes.forEach((splash) => drawSplash(ctx, splash)); + for (let i = 0; i < stars.length; i++) { + updateStar(stars[i]); + drawStar(ctx, stars[i], performance.now() - startTime); + } + raindrops.forEach((drop) => drawRaindrop(ctx, drop)); + splashes.forEach((splash) => drawSplash(ctx, splash)); - requestAnimationFrame(animate); - }; + requestAnimationFrame(animate); + }; - animate(0); - }, []); + animate(0); + }, []); - return ( - - ); + return ( +