unwieldy pull request
1074
package-lock.json
generated
|
@ -9,16 +9,19 @@
|
|||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "13.5.4",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"next": "13.5.4"
|
||||
"react-markdown": "^9.0.0",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"remark-breaks": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "13.5.4"
|
||||
"eslint-config-next": "13.5.4",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
|
|
BIN
public/88x31/disqordia-approved-border.png
Normal file
After Width: | Height: | Size: 8.3 KiB |
BIN
public/88x31/dotart.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
public/88x31/firefox4.gif
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
public/88x31/ipg.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
public/88x31/ivorybutton.gif
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
public/88x31/kaboom3.gif
Normal file
After Width: | Height: | Size: 5.1 KiB |
BIN
public/88x31/kkdiagt.png
Normal file
After Width: | Height: | Size: 491 B |
BIN
public/88x31/pjfrix2023.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
BIN
public/88x31/spacy_webbutton.png
Normal file
After Width: | Height: | Size: 588 B |
BIN
public/88x31/sun_88x31_dual_border.png
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
public/88x31/tla.png
Normal file
After Width: | Height: | Size: 1 KiB |
BIN
public/favicon.ico
Normal file
After Width: | Height: | Size: 7.5 KiB |
BIN
public/fonts/Renogare/Renogare.otf
Normal file
BIN
public/fonts/Renogare/Renogare.woff
Normal file
BIN
public/fonts/Renogare/Renogare.woff2
Normal file
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
Before Width: | Height: | Size: 1.3 KiB |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>
|
Before Width: | Height: | Size: 629 B |
0
src/app/api/status/index.tsx
Normal file
Before Width: | Height: | Size: 25 KiB |
|
@ -1,107 +0,0 @@
|
|||
:root {
|
||||
--max-width: 1100px;
|
||||
--border-radius: 12px;
|
||||
--font-mono: ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono',
|
||||
'Roboto Mono', 'Oxygen Mono', 'Ubuntu Monospace', 'Source Code Pro',
|
||||
'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace;
|
||||
|
||||
--foreground-rgb: 0, 0, 0;
|
||||
--background-start-rgb: 214, 219, 220;
|
||||
--background-end-rgb: 255, 255, 255;
|
||||
|
||||
--primary-glow: conic-gradient(
|
||||
from 180deg at 50% 50%,
|
||||
#16abff33 0deg,
|
||||
#0885ff33 55deg,
|
||||
#54d6ff33 120deg,
|
||||
#0071ff33 160deg,
|
||||
transparent 360deg
|
||||
);
|
||||
--secondary-glow: radial-gradient(
|
||||
rgba(255, 255, 255, 1),
|
||||
rgba(255, 255, 255, 0)
|
||||
);
|
||||
|
||||
--tile-start-rgb: 239, 245, 249;
|
||||
--tile-end-rgb: 228, 232, 233;
|
||||
--tile-border: conic-gradient(
|
||||
#00000080,
|
||||
#00000040,
|
||||
#00000030,
|
||||
#00000020,
|
||||
#00000010,
|
||||
#00000010,
|
||||
#00000080
|
||||
);
|
||||
|
||||
--callout-rgb: 238, 240, 241;
|
||||
--callout-border-rgb: 172, 175, 176;
|
||||
--card-rgb: 180, 185, 188;
|
||||
--card-border-rgb: 131, 134, 135;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--foreground-rgb: 255, 255, 255;
|
||||
--background-start-rgb: 0, 0, 0;
|
||||
--background-end-rgb: 0, 0, 0;
|
||||
|
||||
--primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0));
|
||||
--secondary-glow: linear-gradient(
|
||||
to bottom right,
|
||||
rgba(1, 65, 255, 0),
|
||||
rgba(1, 65, 255, 0),
|
||||
rgba(1, 65, 255, 0.3)
|
||||
);
|
||||
|
||||
--tile-start-rgb: 2, 13, 46;
|
||||
--tile-end-rgb: 2, 5, 19;
|
||||
--tile-border: conic-gradient(
|
||||
#ffffff80,
|
||||
#ffffff40,
|
||||
#ffffff30,
|
||||
#ffffff20,
|
||||
#ffffff10,
|
||||
#ffffff10,
|
||||
#ffffff80
|
||||
);
|
||||
|
||||
--callout-rgb: 20, 20, 20;
|
||||
--callout-border-rgb: 108, 108, 108;
|
||||
--card-rgb: 100, 100, 100;
|
||||
--card-border-rgb: 200, 200, 200;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
max-width: 100vw;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
color: rgb(var(--foreground-rgb));
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
transparent,
|
||||
rgb(var(--background-end-rgb))
|
||||
)
|
||||
rgb(var(--background-start-rgb));
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
color-scheme: dark;
|
||||
}
|
||||
}
|
|
@ -1,22 +1,26 @@
|
|||
import './globals.css'
|
||||
import type { Metadata } from 'next'
|
||||
import { Inter } from 'next/font/google'
|
||||
|
||||
const inter = Inter({ subsets: ['latin'] })
|
||||
import "@/styles/globals.css";
|
||||
import fonts from "@/utility/fonts";
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Create Next App',
|
||||
description: 'Generated by create next app',
|
||||
}
|
||||
title: "MeowcaTheoRange",
|
||||
description: "hehe :3c",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={inter.className}>{children}</body>
|
||||
<html lang="en" className={fonts}>
|
||||
<head>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200"
|
||||
/>
|
||||
</head>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,229 +0,0 @@
|
|||
.main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 6rem;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.description {
|
||||
display: inherit;
|
||||
justify-content: inherit;
|
||||
align-items: inherit;
|
||||
font-size: 0.85rem;
|
||||
max-width: var(--max-width);
|
||||
width: 100%;
|
||||
z-index: 2;
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.description a {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.description p {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
padding: 1rem;
|
||||
background-color: rgba(var(--callout-rgb), 0.5);
|
||||
border: 1px solid rgba(var(--callout-border-rgb), 0.3);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.code {
|
||||
font-weight: 700;
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(25%, auto));
|
||||
max-width: 100%;
|
||||
width: var(--max-width);
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 1rem 1.2rem;
|
||||
border-radius: var(--border-radius);
|
||||
background: rgba(var(--card-rgb), 0);
|
||||
border: 1px solid rgba(var(--card-border-rgb), 0);
|
||||
transition: background 200ms, border 200ms;
|
||||
}
|
||||
|
||||
.card span {
|
||||
display: inline-block;
|
||||
transition: transform 200ms;
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.7rem;
|
||||
}
|
||||
|
||||
.card p {
|
||||
margin: 0;
|
||||
opacity: 0.6;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.5;
|
||||
max-width: 30ch;
|
||||
}
|
||||
|
||||
.center {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
padding: 4rem 0;
|
||||
}
|
||||
|
||||
.center::before {
|
||||
background: var(--secondary-glow);
|
||||
border-radius: 50%;
|
||||
width: 480px;
|
||||
height: 360px;
|
||||
margin-left: -400px;
|
||||
}
|
||||
|
||||
.center::after {
|
||||
background: var(--primary-glow);
|
||||
width: 240px;
|
||||
height: 180px;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.center::before,
|
||||
.center::after {
|
||||
content: '';
|
||||
left: 50%;
|
||||
position: absolute;
|
||||
filter: blur(45px);
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
.logo {
|
||||
position: relative;
|
||||
}
|
||||
/* Enable hover only on non-touch devices */
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
.card:hover {
|
||||
background: rgba(var(--card-rgb), 0.1);
|
||||
border: 1px solid rgba(var(--card-border-rgb), 0.15);
|
||||
}
|
||||
|
||||
.card:hover span {
|
||||
transform: translateX(4px);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion) {
|
||||
.card:hover span {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile */
|
||||
@media (max-width: 700px) {
|
||||
.content {
|
||||
padding: 4rem;
|
||||
}
|
||||
|
||||
.grid {
|
||||
grid-template-columns: 1fr;
|
||||
margin-bottom: 120px;
|
||||
max-width: 320px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 1rem 2.5rem;
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.center {
|
||||
padding: 8rem 0 6rem;
|
||||
}
|
||||
|
||||
.center::before {
|
||||
transform: none;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.description a {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.description p,
|
||||
.description div {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.description p {
|
||||
align-items: center;
|
||||
inset: 0 0 auto;
|
||||
padding: 2rem 1rem 1.4rem;
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25);
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
rgba(var(--background-start-rgb), 1),
|
||||
rgba(var(--callout-rgb), 0.5)
|
||||
);
|
||||
background-clip: padding-box;
|
||||
backdrop-filter: blur(24px);
|
||||
}
|
||||
|
||||
.description div {
|
||||
align-items: flex-end;
|
||||
pointer-events: none;
|
||||
inset: auto 0 0;
|
||||
padding: 2rem;
|
||||
height: 200px;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
transparent 0%,
|
||||
rgb(var(--background-end-rgb)) 40%
|
||||
);
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Tablet and Smaller Desktop */
|
||||
@media (min-width: 701px) and (max-width: 1120px) {
|
||||
.grid {
|
||||
grid-template-columns: repeat(2, 50%);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.vercelLogo {
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
.logo {
|
||||
filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
from {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
}
|
861
src/app/page.tsx
|
@ -1,95 +1,776 @@
|
|||
import Image from 'next/image'
|
||||
import styles from './page.module.css'
|
||||
/* eslint-disable react/jsx-no-comment-textnodes */
|
||||
/* eslint-disable react/no-unescaped-entities */
|
||||
"use client";
|
||||
import ColourChip from "@/components/ColourChip/ColourChip";
|
||||
import Eighty from "@/components/Eighty/Eighty";
|
||||
import Floaty from "@/components/Floaty/Floaty";
|
||||
import Page from "@/components/Page/Page";
|
||||
import ProjectList, { Project } from "@/components/ProjectList/ProjectList";
|
||||
import ScrollBackInd from "@/components/ScrollBackInd/ScrollBackInd";
|
||||
import SpeedDial from "@/components/SpeedDial/SpeedDial";
|
||||
import Time from "@/components/Time/Time";
|
||||
import LastFM from "@/components/net/LastFM/LastFM";
|
||||
import { Color3 } from "@/utility/color";
|
||||
import { Space_Grotesk } from "next/font/google";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
const space_grotesk = Space_Grotesk({
|
||||
subsets: ["latin"],
|
||||
variable: "--font-Space-Grotesk",
|
||||
});
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<main className={styles.main}>
|
||||
<div className={styles.description}>
|
||||
<p>
|
||||
Get started by editing
|
||||
<code className={styles.code}>src/app/page.tsx</code>
|
||||
</p>
|
||||
<div>
|
||||
<a
|
||||
href="https://vercel.com?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
By{' '}
|
||||
<Image
|
||||
src="/vercel.svg"
|
||||
alt="Vercel Logo"
|
||||
className={styles.vercelLogo}
|
||||
width={100}
|
||||
height={24}
|
||||
priority
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
const [page, setPage] = useState(0);
|
||||
const body = useRef<HTMLDivElement>(null);
|
||||
|
||||
<div className={styles.center}>
|
||||
<Image
|
||||
className={styles.logo}
|
||||
src="/next.svg"
|
||||
alt="Next.js Logo"
|
||||
width={180}
|
||||
height={37}
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
// TIME
|
||||
const dateObj = new Date();
|
||||
const [time, setTime] = useState("00:00:00 PM");
|
||||
const [date, setDate] = useState("0/0/0000");
|
||||
let animFrame = useRef(0);
|
||||
|
||||
<div className={styles.grid}>
|
||||
<a
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
className={styles.card}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<h2>
|
||||
Docs <span>-></span>
|
||||
</h2>
|
||||
<p>Find in-depth information about Next.js features and API.</p>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
className={styles.card}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<h2>
|
||||
Learn <span>-></span>
|
||||
</h2>
|
||||
<p>Learn about Next.js in an interactive course with quizzes!</p>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
className={styles.card}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<h2>
|
||||
Templates <span>-></span>
|
||||
</h2>
|
||||
<p>Explore the Next.js 13 playground.</p>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
className={styles.card}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<h2>
|
||||
Deploy <span>-></span>
|
||||
</h2>
|
||||
<p>
|
||||
Instantly deploy your Next.js site to a shareable URL with Vercel.
|
||||
</p>
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
// hot reloading memory saver
|
||||
useEffect(() => {
|
||||
window.cancelAnimationFrame(animFrame.current);
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
function setTheTime() {
|
||||
dateObj.setTime(Date.now());
|
||||
setTime(
|
||||
dateObj.toLocaleTimeString("en-US", {
|
||||
minute: "2-digit",
|
||||
hour: "2-digit",
|
||||
timeZone: "America/Chicago",
|
||||
})
|
||||
);
|
||||
setDate(
|
||||
dateObj.toLocaleDateString("en-US", {
|
||||
weekday: "long",
|
||||
day: "2-digit",
|
||||
month: "long",
|
||||
timeZone: "America/Chicago",
|
||||
})
|
||||
);
|
||||
animFrame.current = window.requestAnimationFrame(setTheTime);
|
||||
}
|
||||
animFrame.current = window.requestAnimationFrame(setTheTime);
|
||||
}, []);
|
||||
|
||||
// LAST FM
|
||||
let [player, setPlayer] = useState<{ [key: string]: any } | null | undefined>(
|
||||
null
|
||||
);
|
||||
const FMGate = useRef(true);
|
||||
useEffect(() => {
|
||||
async function LastFMGet() {
|
||||
FMGate.current = false;
|
||||
const api_key = "8f9b0255cc55a19f82d37c22600aff1a";
|
||||
const LAST_FM_URL = `https://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=MeowcaTheoRange&api_key=${api_key}&format=json&extended=1&limit=1`;
|
||||
const res = await fetch(LAST_FM_URL);
|
||||
setPlayer(await res.json());
|
||||
setTimeout(() => LastFMGet(), 20000);
|
||||
}
|
||||
if (FMGate.current) LastFMGet();
|
||||
}, []);
|
||||
|
||||
// Gallery
|
||||
let [images, setImages] = useState<Project[]>([
|
||||
{
|
||||
name: "See More",
|
||||
description: "blog.abtmtr.link",
|
||||
url: "https://blog.abtmtr.link/",
|
||||
},
|
||||
]);
|
||||
const ImageGate = useRef(true);
|
||||
useEffect(() => {
|
||||
async function ImageGet() {
|
||||
ImageGate.current = false;
|
||||
const IMAGE_URL = `https://img.abtmtr.link/api/collections/mtr/posts`;
|
||||
const res = await fetch(IMAGE_URL);
|
||||
const gallery = (await res.json()).data;
|
||||
setImages([
|
||||
...gallery.posts?.map((imagePost: any) => ({
|
||||
name: imagePost.title,
|
||||
description: imagePost.body.replace(
|
||||
/^!\[.*?\]\((.*?)\).*(?:\n\nCharacters:.*?\n\n)(.*)\n\n.*$/gs,
|
||||
"$2"
|
||||
),
|
||||
image: imagePost.images[0] ?? null,
|
||||
url: "https://img.abtmtr.link/" + imagePost.slug,
|
||||
})),
|
||||
{
|
||||
name: "See More",
|
||||
description: "img.abtmtr.link",
|
||||
url: "https://img.abtmtr.link/",
|
||||
},
|
||||
]);
|
||||
}
|
||||
if (ImageGate.current) ImageGet();
|
||||
}, []);
|
||||
|
||||
// Gallery
|
||||
let [blog, setBlog] = useState<Project[]>([
|
||||
{
|
||||
name: "See More",
|
||||
description: "blog.abtmtr.link",
|
||||
url: "https://blog.abtmtr.link/mtr/",
|
||||
},
|
||||
]);
|
||||
const BlogGate = useRef(true);
|
||||
useEffect(() => {
|
||||
async function BlogGet() {
|
||||
BlogGate.current = false;
|
||||
const BLOG_URL = `https://blog.abtmtr.link/api/collections/mtr/posts`;
|
||||
const res = await fetch(BLOG_URL);
|
||||
const blogss = (await res.json()).data;
|
||||
setBlog([
|
||||
...blogss.posts?.map((blogPost: any) => ({
|
||||
name: blogPost.title,
|
||||
description: blogPost.body.replace(/#\w*/g, ""),
|
||||
url: "https://blog.abtmtr.link/mtr/" + blogPost.slug,
|
||||
})),
|
||||
{
|
||||
name: "See More",
|
||||
description: "blog.abtmtr.link",
|
||||
url: "https://blog.abtmtr.link/mtr/",
|
||||
},
|
||||
]);
|
||||
}
|
||||
if (BlogGate.current) BlogGet();
|
||||
}, []);
|
||||
|
||||
// Gallery
|
||||
let [repos, setRepos] = useState<Project[]>([
|
||||
{
|
||||
name: "See More",
|
||||
description: "github.com",
|
||||
url: "https://github.com/MeowcaTheoRange",
|
||||
},
|
||||
]);
|
||||
const GithubGate = useRef(true);
|
||||
useEffect(() => {
|
||||
async function GithubGet() {
|
||||
GithubGate.current = false;
|
||||
const GITHUB_URL = `https://api.github.com/users/meowcatheorange/starred`;
|
||||
const res = await fetch(GITHUB_URL);
|
||||
const reposs = (await res.json())?.filter(
|
||||
(x: any) => x.owner.login == "MeowcaTheoRange"
|
||||
);
|
||||
console.log(reposs);
|
||||
setRepos([
|
||||
...reposs?.map((repository: any) => ({
|
||||
name: repository.name,
|
||||
description: repository.description ?? "No description",
|
||||
url: repository.html_url,
|
||||
})),
|
||||
{
|
||||
name: "See More",
|
||||
description: "github.com",
|
||||
url: "https://github.com/MeowcaTheoRange",
|
||||
},
|
||||
]);
|
||||
}
|
||||
if (GithubGate.current) GithubGet();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="body"
|
||||
ref={body}
|
||||
onScroll={(e) => {
|
||||
const target = e.target as HTMLDivElement;
|
||||
const curPage = Math.round(target.scrollTop / target.clientHeight);
|
||||
if (curPage != page) setPage(curPage);
|
||||
}}
|
||||
>
|
||||
<ScrollBackInd
|
||||
hide={page <= 0}
|
||||
scroll={body.current}
|
||||
player={player}
|
||||
time={time}
|
||||
/>
|
||||
<Page
|
||||
scroll={body.current}
|
||||
color={Color3.fromHex("ff4000")}
|
||||
bg={`linear-gradient(0deg, #200800ee, #200800ee)${
|
||||
player?.recenttracks.track[0].image[2]["#text"] != null
|
||||
? `, no-repeat center/cover url("${player.recenttracks.track[0].image[2]["#text"]}")`
|
||||
: ""
|
||||
}`}
|
||||
id="top"
|
||||
>
|
||||
<h1>
|
||||
ABTMTR
|
||||
<wbr />
|
||||
.LINK
|
||||
</h1>
|
||||
<Floaty>
|
||||
<LastFM player={player} />
|
||||
</Floaty>
|
||||
<Time time={time} date={date} />
|
||||
<Floaty start>
|
||||
<p className="hv">Scroll down for more!</p>
|
||||
</Floaty>
|
||||
</Page>
|
||||
<Page scroll={body.current} color={Color3.fromHex("FFFFFF")} id="hi">
|
||||
<h1>Welcome 👋</h1>
|
||||
<p>
|
||||
I'm <b>MeowcaTheoRange</b> <small>(miau-kuh-thee~oh-ray~nj)</small>.
|
||||
<br />
|
||||
I'm a web developer, Fediverse enthusiast, and compulsory Minnesotan.
|
||||
</p>
|
||||
<p>
|
||||
I'm also known as <strong>Iszac</strong> or <strong>Theo</strong> as
|
||||
well.
|
||||
</p>
|
||||
<p className="chip">he/they/it</p>
|
||||
<p className="chip">Male</p>
|
||||
<p className="chip">Minor</p>
|
||||
<p className="chip">Autistic</p>
|
||||
<p>I run this domain and all of the services on it.</p>
|
||||
<p>
|
||||
My favourite hobbies are <strong>programming</strong>,{" "}
|
||||
<strong>drawing</strong>,{" "}
|
||||
<strong>occasionally making small bits of music</strong>,{" "}
|
||||
<strong>obsessing over fonts</strong>, and{" "}
|
||||
<strong>being pedantic</strong>.
|
||||
</p>
|
||||
<p>
|
||||
You may see parts of all of these aspects within this website. Please
|
||||
tread carefully.
|
||||
</p>
|
||||
<p>
|
||||
Some other important things I think you should know about me are...
|
||||
</p>
|
||||
<ul>
|
||||
<li>Please be patient with me.</li>
|
||||
<li>
|
||||
Please be understanding! Ask me for clarification if required.
|
||||
</li>
|
||||
<li>
|
||||
I don't really like small talk - keep if brief if you want to check
|
||||
up on me, please.
|
||||
</li>
|
||||
<li>
|
||||
I'm not one to pick sides at first, usually. Being an "all or
|
||||
nothing" kind of person isn't my thing, and if you don't like that,
|
||||
feel free to tell me why your side is good.
|
||||
</li>
|
||||
<li>
|
||||
I like getting tangled up in drama, but I'm not a spiteful person -
|
||||
I'm usually only in it for the correlations.
|
||||
</li>
|
||||
<li>
|
||||
You may see me hyperfixate on random stuff, like{" "}
|
||||
<a href="#sc_fonts" className="inline">
|
||||
certain fonts
|
||||
</a>{" "}
|
||||
or public transit.
|
||||
</li>
|
||||
</ul>
|
||||
<p>I believe in...</p>
|
||||
<ul>
|
||||
<li>Self-hosting important or personal infrastructure</li>
|
||||
<li>Free and open-source material</li>
|
||||
<li>Privacy as a basic human right</li>
|
||||
<li>Trans rights & gay rights</li>
|
||||
<li>
|
||||
Autistic superiority <span className="hv">/joke</span>
|
||||
</li>
|
||||
</ul>
|
||||
<p>Check me out on:</p>
|
||||
<SpeedDial
|
||||
services={[
|
||||
{
|
||||
name: "moth.zone",
|
||||
url: "https://moth.zone/meowcatheorange",
|
||||
purpose: "The Fediverse",
|
||||
},
|
||||
{
|
||||
name: "discord.com",
|
||||
url: "http://discord.trollcall.xyz/",
|
||||
},
|
||||
{
|
||||
name: "github.com",
|
||||
url: "http://github.com/MeowcaTheoRange",
|
||||
},
|
||||
{
|
||||
name: "ko-fi.com",
|
||||
url: "https://ko-fi.com/meowcatheorange",
|
||||
},
|
||||
{
|
||||
name: "mspfa.com",
|
||||
url: "https://mspfa.com/user/?u=109014333296332953331",
|
||||
},
|
||||
{
|
||||
name: "beta.trollcall.xyz",
|
||||
url: "https://beta.trollcall.xyz/clan/meowcatheorange",
|
||||
golden: true,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Page>
|
||||
<Page scroll={body.current} color={Color3.fromHex("00c0ff")}>
|
||||
<h1>What's on this domain?</h1>
|
||||
<p>
|
||||
Here's a quick list of all of the web services on this domain right
|
||||
now.
|
||||
</p>
|
||||
<ProjectList
|
||||
projects={[
|
||||
{
|
||||
name: "abtmtr.link",
|
||||
description: "You are here!",
|
||||
url: "https://abtmtr.link/",
|
||||
},
|
||||
{
|
||||
name: "abs.abtmtr.link",
|
||||
description:
|
||||
"An instance of ABS, a link normalizer for personal use.",
|
||||
url: "https://abs.abtmtr.link/",
|
||||
},
|
||||
{
|
||||
name: "blog.abtmtr.link",
|
||||
description: "An instance of WriteFreely, used for my blog(s).",
|
||||
url: "https://blog.abtmtr.link/",
|
||||
},
|
||||
{
|
||||
name: "cdn.abtmtr.link",
|
||||
description:
|
||||
"A CDN full of images I use, usually for my blog or gallery.",
|
||||
url: "https://cdn.abtmtr.link/",
|
||||
},
|
||||
{
|
||||
name: "img.abtmtr.link",
|
||||
description:
|
||||
"An instance of WriteFreely, used as an art gallery.",
|
||||
url: "https://img.abtmtr.link/",
|
||||
},
|
||||
{
|
||||
name: "local.abtmtr.link",
|
||||
description: "An instance of Akkoma, running on a home server.",
|
||||
url: "https://local.abtmtr.link/",
|
||||
},
|
||||
]}
|
||||
markdown
|
||||
/>
|
||||
<h2>Planned services</h2>
|
||||
<p>What I would like to put on this domain sometime in the future.</p>
|
||||
<ProjectList
|
||||
projects={[
|
||||
{
|
||||
name: "Drawpile",
|
||||
description:
|
||||
"Running Drawpile on my domain, for use by friends and such.",
|
||||
url: "https://pile.abtmtr.link/",
|
||||
},
|
||||
{
|
||||
name: "Minecraft",
|
||||
description:
|
||||
"Replace the craft.trollcall.xyz server with something a little nicer.",
|
||||
url: "https://craft.abtmtr.link/",
|
||||
},
|
||||
]}
|
||||
markdown
|
||||
/>
|
||||
<h2>
|
||||
L10n <small>(localization)</small>
|
||||
</h2>
|
||||
<p>What services I'd like to put on my server computer at home.</p>
|
||||
<ProjectList
|
||||
projects={[
|
||||
{
|
||||
name: "blog.abtmtr.link",
|
||||
description:
|
||||
"It's running on Oracle Cloud right now. That's not good.",
|
||||
url: "https://blog.abtmtr.link/",
|
||||
},
|
||||
{
|
||||
name: "img.abtmtr.link",
|
||||
description: "Ditto.",
|
||||
url: "https://img.abtmtr.link/",
|
||||
},
|
||||
]}
|
||||
markdown
|
||||
/>
|
||||
</Page>
|
||||
<Page
|
||||
scroll={body.current}
|
||||
color={Color3.fromHex("FF80C0")}
|
||||
id="currents"
|
||||
preview
|
||||
>
|
||||
<h1>Current Obsessions</h1>
|
||||
<p>
|
||||
I'm into a lot of stuff. As of this site's publication, you'll
|
||||
probably see me indulging in:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<a
|
||||
href="https://social.translunar.academy/@winter"
|
||||
className="inline"
|
||||
>
|
||||
@winter@translunar.academy
|
||||
</a>
|
||||
's fic{" "}
|
||||
<a href="https://translunar.academy/fic/Aetherglow" target="_blank">
|
||||
Ætherglow
|
||||
</a>
|
||||
</li>
|
||||
<li>Discussion of autism</li>
|
||||
<li>Discussion and usage of the Fediverse</li>
|
||||
<li>
|
||||
<a href="https://homestuck.com/" target="_blank">
|
||||
Homestuck
|
||||
</a>{" "}
|
||||
and related properties
|
||||
</li>
|
||||
<li>
|
||||
Adventures on{" "}
|
||||
<a href="https://mspfa.com/" target="_blank">
|
||||
MSPFA
|
||||
</a>
|
||||
, sometimes
|
||||
</li>
|
||||
<li>Using React or Next.js</li>
|
||||
<li>Creating alternatives to both because I'm just that cool</li>
|
||||
<li>Anti-JS practices</li>
|
||||
<li>Art and drawing</li>
|
||||
<li>Using Blender</li>
|
||||
<li>The MetroTransit bus system</li>
|
||||
</ul>
|
||||
<p>and possibly much more that I can't even remember.</p>
|
||||
<p>
|
||||
Want to know more? Below is my display of some of these things in
|
||||
sections. Enjoy!
|
||||
</p>
|
||||
</Page>
|
||||
<Page
|
||||
scroll={body.current}
|
||||
color={Color3.fromHex("80FF00")}
|
||||
preview
|
||||
id="sc_programming"
|
||||
>
|
||||
<h1>Programming</h1>
|
||||
<p>I like using what some call "programming languages".</p>
|
||||
<p>
|
||||
What do I call them? None of your business. All you have to know is
|
||||
that I use <strong>TypeScript</strong>, <strong>React</strong>, and{" "}
|
||||
<strong>Haxe</strong>.
|
||||
</p>
|
||||
<p>
|
||||
I also know <strong>JavaScript</strong> and some <strong>Bash</strong>{" "}
|
||||
in case of emergency.
|
||||
</p>
|
||||
<ProjectList projects={repos} />
|
||||
</Page>
|
||||
<Page
|
||||
scroll={body.current}
|
||||
color={Color3.fromHex("80ffff")}
|
||||
preview
|
||||
floaty
|
||||
id="sc_fonts"
|
||||
>
|
||||
<h1>Fonts</h1>
|
||||
<p>I also like UI and UI design. This includes fonts, quite a bit.</p>
|
||||
<p>
|
||||
Currently, I'm liking the look of <b>Renogare</b>, <b>Lexend Deca</b>,
|
||||
and <b>Space Grotesk</b>. This may change.
|
||||
</p>
|
||||
<p>
|
||||
I first used <strong>Space Grotesk</strong> for general-purpose
|
||||
applications. This was on my old portfolio site and it's on TrollCall
|
||||
right now. I think it just fits TrollCall; it's quirky, geometric, a
|
||||
bit alien - it's right at home with the intended vibe of the site.
|
||||
</p>
|
||||
<p>
|
||||
On this website, I'm using <strong>Renogare</strong> as a header/title
|
||||
font, and <strong>Lexend Deca</strong> for everything else.
|
||||
</p>
|
||||
<p>
|
||||
I found <strong>Renogare</strong> while playing{" "}
|
||||
<strong>Celeste</strong> - yaknow, the game where you climb a huge
|
||||
mountain for a few days?
|
||||
</p>
|
||||
<p>
|
||||
<strong>Renogare</strong> is a bold, geometric, display-optimized
|
||||
font. It's <i>super</i> cool and I am in love with how it looks.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Lexend Deca</strong> is a very strong second to{" "}
|
||||
<strong>Renogare</strong>, primarily used when I can't use{" "}
|
||||
<strong>Renogare</strong>, shouldn't use <strong>Renogare</strong>, or
|
||||
am using <strong>Renogare</strong> but need something that{" "}
|
||||
<i>isn't</i> <strong>Renogare</strong>.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Lexend Deca</strong> is similar-looking to{" "}
|
||||
<strong>Renogare</strong>, with a few major differences.{" "}
|
||||
<strong>Lexend Deca</strong> loses the geometry, instead opting for a
|
||||
beamed I, flat-top A, M, N, et al. t loses its curve, instead going
|
||||
straight down.
|
||||
</p>
|
||||
<p>
|
||||
It also has different weights and proper OTF support - plus a bonus
|
||||
series of different letter spacing choices under the{" "}
|
||||
<strong>Lexend</strong> name. <strong>Deca</strong> is the tightest,
|
||||
which is why I chose it.
|
||||
</p>
|
||||
<p>
|
||||
All in all, <strong>Lexend Deca</strong> is a good general-purpose
|
||||
font, while still leaving room for <strong>Renogare</strong> to be a
|
||||
great display font.
|
||||
</p>
|
||||
<Floaty>
|
||||
<div>
|
||||
<h1
|
||||
style={{
|
||||
fontFamily: "var(--font-Renogare)",
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
AaIiLlMm
|
||||
</h1>
|
||||
<p
|
||||
className="hv"
|
||||
style={{
|
||||
fontFamily: "var(--font-Renogare)",
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
Renogare
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h1
|
||||
style={{
|
||||
fontFamily: "var(--font-Lexend-Deca)",
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
AaIiLlMm
|
||||
</h1>
|
||||
<p
|
||||
className="hv"
|
||||
style={{
|
||||
fontFamily: "var(--font-Lexend-Deca)",
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
Lexend Deca
|
||||
</p>
|
||||
</div>
|
||||
<div className={space_grotesk.variable}>
|
||||
<h1
|
||||
style={{
|
||||
fontFamily: "var(--font-Space-Grotesk)",
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
AaIiLlMm
|
||||
</h1>
|
||||
<p
|
||||
className="hv"
|
||||
style={{
|
||||
fontFamily: "var(--font-Space-Grotesk)",
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
Space Grotesk
|
||||
</p>
|
||||
</div>
|
||||
</Floaty>
|
||||
</Page>
|
||||
<Page scroll={body.current} color={Color3.fromHex("00c0ff")} id="sc_art">
|
||||
<h1>Artistry</h1>
|
||||
<p>
|
||||
Artistry usually includes writing and drawing. These are the purposes
|
||||
for{" "}
|
||||
<a href="https://blog.abtmtr.link" className="inline">
|
||||
my blog
|
||||
</a>{" "}
|
||||
and{" "}
|
||||
<a href="https://img.abtmtr.link" className="inline">
|
||||
my public gallery
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
<ProjectList projects={images} markdown />
|
||||
<ProjectList projects={blog} markdown double />
|
||||
</Page>
|
||||
<Page
|
||||
scroll={body.current}
|
||||
color={Color3.fromHex("FFFFFF")}
|
||||
id="branding"
|
||||
>
|
||||
<h1>Branding</h1>
|
||||
<p>
|
||||
I don't really have strict branding guidelines, but I do have a few
|
||||
important rules if you would like to refer to me in a professional or
|
||||
formal context.
|
||||
</p>
|
||||
<h2>Name</h2>
|
||||
<p>
|
||||
If you would like to refer to me online, you'll usually be able to use
|
||||
my username <b>MeowcaTheoRange</b>. If that's too long, you can always
|
||||
shorten it to <strong>MTR</strong>.
|
||||
</p>
|
||||
<p>
|
||||
Please make sure to keep the styling. It's <b>MeowcaTheoRange</b>, not{" "}
|
||||
<strong>MeowcatHeOrange</strong>, <strong>MeowcaTheOrange</strong>,
|
||||
nor <strong>Meowca Theo Range</strong>.
|
||||
</p>
|
||||
<p className="hv">
|
||||
The styling <strong>meowcatheorange</strong> is OK if necessary.
|
||||
</p>
|
||||
<p>
|
||||
If you would like to refer to me in a more professional context, my
|
||||
name <strong>Iszac</strong> or the moniker <strong>Theo Range</strong>{" "}
|
||||
will work just as well.
|
||||
</p>
|
||||
<p className="hv">
|
||||
If you are a local organization and would like to refer to me by my
|
||||
legal name, please{" "}
|
||||
<a href="#top" className="inline">
|
||||
contact me
|
||||
</a>{" "}
|
||||
and we can probably figure something out.
|
||||
<br />
|
||||
Local means <strong>within Minnesota</strong>, by the way.
|
||||
</p>
|
||||
<h2>Colours</h2>
|
||||
<p>
|
||||
If you would like to use colours to refer to me, whether that be the
|
||||
primary color on a card or the color of my name, I recommend you use
|
||||
these colours:
|
||||
</p>
|
||||
|
||||
<ColourChip colour={new Color3(0, 0.75, 1)}>
|
||||
<b>Iszac Blue</b>
|
||||
<br />
|
||||
<small>Primary</small>
|
||||
</ColourChip>
|
||||
<ColourChip colour={new Color3(1, 0.25, 0)}>
|
||||
<b>Rocco Orange</b>
|
||||
<br />
|
||||
<small>Primary Negative</small>
|
||||
</ColourChip>
|
||||
<p style={{ color: "#00BFFF" }}>
|
||||
<b>Iszac Blue</b> is literally <strong>Rocco Orange</strong> but
|
||||
inverted. This colour is named after <strong>Iszac</strong>, an OC of
|
||||
mine that I came up with, for the setting of Ætherglow.
|
||||
<br />
|
||||
<small>
|
||||
...and then I shortly named myself after said character...
|
||||
</small>
|
||||
<br />
|
||||
If you were to use any of these colours to represnt me as a{" "}
|
||||
<strong>person</strong>, use this one.
|
||||
</p>
|
||||
<p style={{ color: "#FF4000" }}>
|
||||
<b>Rocco Orange</b> is my favourite colour orange, but now more red.
|
||||
The name comes from my character <strong>Rocco</strong>, whose hair is
|
||||
this colour - though this colour has been applied to more characters
|
||||
like <strong>BLEND-1020</strong>.<br />
|
||||
If you were to use any of these colours to represnt me as an{" "}
|
||||
<strong>entity</strong>, use this one whenever possible.
|
||||
</p>
|
||||
<br />
|
||||
<ColourChip colour={new Color3(0.5, 0, 1)}>
|
||||
<b>Grape Soda</b>
|
||||
<br />
|
||||
<small>Secondary</small>
|
||||
</ColourChip>
|
||||
<ColourChip colour={new Color3(0.5, 1, 0)}>
|
||||
<b>Avalonian Waste</b>
|
||||
<br />
|
||||
<small>Secondary Negative</small>
|
||||
</ColourChip>
|
||||
<p style={{ color: "#8000FF" }}>
|
||||
<b>Grape Soda</b> represents my love for grape soda.
|
||||
<br />
|
||||
My favourite is Fanta Grape. :]
|
||||
</p>
|
||||
<p style={{ color: "#80FF00" }}>
|
||||
<b>Avalonian Waste</b> is also literally <strong>Grape Soda</strong>{" "}
|
||||
but inverted. This represents some{" "}
|
||||
<a
|
||||
href="https://mlr.fandom.com/wiki/Ayden"
|
||||
target="_blank"
|
||||
className="inline"
|
||||
>
|
||||
old lore
|
||||
</a>{" "}
|
||||
that I'm still screwing with.
|
||||
</p>
|
||||
</Page>
|
||||
<Page scroll={body.current} color={Color3.fromHex("000000")}></Page>
|
||||
<Page
|
||||
scroll={body.current}
|
||||
color={Color3.fromHex("ffffff")}
|
||||
footer
|
||||
bg="center/50px 50px repeating-linear-gradient(45deg, #80808010, #80808010 25%, transparent 25%, transparent 50%, #80808010 50%, #80808010 75%, transparent 75%, transparent 100%)"
|
||||
id="footer"
|
||||
>
|
||||
<h1>
|
||||
ABTMTR
|
||||
<wbr />
|
||||
.LINK
|
||||
</h1>
|
||||
<Eighty img="/88x31/dotart.png" alt="blocked by dotart" />
|
||||
<Eighty
|
||||
url="https://www.mozilla.org/en-US/firefox/new/"
|
||||
img="/88x31/firefox4.gif"
|
||||
alt="tested on Firefox"
|
||||
/>
|
||||
<Eighty
|
||||
url="https://homestuck.com/"
|
||||
img="/88x31/sun_88x31_dual_border.png"
|
||||
alt="HOMESTUCK"
|
||||
/>
|
||||
<Eighty
|
||||
url="https://dimden.dev/"
|
||||
img="https://dimden.dev/services/images/88x31.gif"
|
||||
alt="DIMDEN"
|
||||
/>
|
||||
<Eighty
|
||||
url="https://park-city.club/~frix/"
|
||||
img="/88x31/pjfrix2023.png"
|
||||
alt="pjfrix"
|
||||
/>
|
||||
<Eighty
|
||||
url="https://invoxiplaygames.uk/"
|
||||
img="/88x31/ipg.png"
|
||||
alt="Invoxi PlayGames"
|
||||
/>
|
||||
<Eighty
|
||||
url="https://ioletsgo.gay/"
|
||||
img="/88x31/ivorybutton.gif"
|
||||
alt="ioletsgo.gay"
|
||||
/>
|
||||
<Eighty
|
||||
url="https://spacy.neocities.org/"
|
||||
img="/88x31/spacy_webbutton.png"
|
||||
alt="Spacy =)"
|
||||
/>
|
||||
<Eighty
|
||||
url="https://disqordia.space/"
|
||||
img="/88x31/disqordia-approved-border.png"
|
||||
alt="Disqordia Approved"
|
||||
/>
|
||||
<Eighty
|
||||
url="https://translunar.academy/"
|
||||
img="/88x31/tla.png"
|
||||
alt="TRANSLUNAR ACADEMY"
|
||||
/>
|
||||
<Eighty
|
||||
url="https://moth.zone/meowcatheorange"
|
||||
img="/88x31/kkdiagt.png"
|
||||
alt="KARKATDYINGIN AGLUETRAP.COM R.I.P"
|
||||
/>
|
||||
<p className="hv">© MeowcaTheoRange 2023</p>
|
||||
</Page>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
9
src/components/ColourChip/ColourChip.module.css
Normal file
|
@ -0,0 +1,9 @@
|
|||
.ColourChip {
|
||||
background-color: var(--mainColour);
|
||||
display: inline-block;
|
||||
width: 9em;
|
||||
aspect-ratio: 1 / 1;
|
||||
padding: 16px;
|
||||
vertical-align: top;
|
||||
margin: 1em;
|
||||
}
|
28
src/components/ColourChip/ColourChip.tsx
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { Color3 } from "@/utility/color";
|
||||
import styles from "./ColourChip.module.css";
|
||||
|
||||
export default function ColourChip({
|
||||
colour,
|
||||
children,
|
||||
}: {
|
||||
colour: Color3;
|
||||
children?: React.ReactNode;
|
||||
}) {
|
||||
const calcTextColor = +(Math.max(...colour.toRGB()) <= 127);
|
||||
return (
|
||||
<div
|
||||
style={
|
||||
{
|
||||
"--mainColour": "#" + colour.toHex(),
|
||||
color:
|
||||
"#" +
|
||||
new Color3(calcTextColor, calcTextColor, calcTextColor).toHex(),
|
||||
} as any
|
||||
}
|
||||
className={styles.ColourChip}
|
||||
>
|
||||
{children}
|
||||
<p>{"#" + colour.toHex()}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
11
src/components/Eighty/Eighty.module.css
Normal file
|
@ -0,0 +1,11 @@
|
|||
.Eighty {
|
||||
display: inline-block;
|
||||
margin: 5px 6px;
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
.Eighty img {
|
||||
display: inline-block;
|
||||
width: 88px;
|
||||
height: 31px;
|
||||
}
|
22
src/components/Eighty/Eighty.tsx
Normal file
|
@ -0,0 +1,22 @@
|
|||
import Link from "next/link";
|
||||
import styles from "./Eighty.module.css";
|
||||
|
||||
export default function Eighty({
|
||||
url,
|
||||
img,
|
||||
alt,
|
||||
}: {
|
||||
url?: string;
|
||||
img: string;
|
||||
alt?: string;
|
||||
}) {
|
||||
return url ? (
|
||||
<Link className={styles.Eighty} href={url} target="_blank">
|
||||
<img src={img} alt={alt} title={alt} />
|
||||
</Link>
|
||||
) : (
|
||||
<span className={styles.Eighty}>
|
||||
<img src={img} alt={alt} title={alt} />
|
||||
</span>
|
||||
);
|
||||
}
|
55
src/components/Floaty/Floaty.module.css
Normal file
|
@ -0,0 +1,55 @@
|
|||
.Floaty {
|
||||
display: inline-block;
|
||||
max-width: calc(50vw - 64px);
|
||||
min-width: 373px;
|
||||
/* height: calc(50vh - 64px); */
|
||||
position: absolute !important;
|
||||
right: 64px;
|
||||
bottom: 64px;
|
||||
text-align: end;
|
||||
/* background-color: #0001;
|
||||
box-shadow: 0 0 8px currentColor;
|
||||
padding: 16px; */
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.FloatySticky {
|
||||
position: sticky;
|
||||
}
|
||||
|
||||
.Floaty:dir(rtl) {
|
||||
right: unset;
|
||||
left: 64px;
|
||||
}
|
||||
|
||||
.FloatyTop {
|
||||
bottom: unset;
|
||||
top: 64px;
|
||||
}
|
||||
|
||||
.FloatyStart {
|
||||
right: unset;
|
||||
left: 64px;
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.FloatyStart:dir(rtl) {
|
||||
left: unset;
|
||||
right: 64px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 875px) {
|
||||
.Floaty {
|
||||
position: static !important;
|
||||
text-align: start;
|
||||
display: block;
|
||||
margin: 2em 0;
|
||||
max-width: none;
|
||||
}
|
||||
.FloatyKeep {
|
||||
position: absolute !important;
|
||||
text-align: unset;
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
27
src/components/Floaty/Floaty.tsx
Normal file
|
@ -0,0 +1,27 @@
|
|||
import styles from "./Floaty.module.css";
|
||||
|
||||
export default function Floaty({
|
||||
children,
|
||||
top = false,
|
||||
start = false,
|
||||
keepFloat = false,
|
||||
sticky = false,
|
||||
}: {
|
||||
children?: React.ReactNode;
|
||||
top?: boolean;
|
||||
start?: boolean;
|
||||
keepFloat?: boolean;
|
||||
sticky?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={`${styles.Floaty} ${top ? styles.FloatyTop : ""} ${
|
||||
start ? styles.FloatyStart : ""
|
||||
} ${keepFloat ? styles.FloatyKeep : ""} ${
|
||||
sticky ? styles.FloatySticky : ""
|
||||
}`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
0
src/components/Nav/Nav.module.css
Normal file
22
src/components/Nav/Nav.tsx
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { Color3 } from "@/utility/color";
|
||||
import styles from "./Nav.module.css";
|
||||
|
||||
export default function Nav({
|
||||
children,
|
||||
color,
|
||||
}: {
|
||||
children?: React.ReactNode;
|
||||
color?: Color3;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={styles.Nav}
|
||||
style={{
|
||||
backgroundColor: "#" + color?.darken(90).toHex(),
|
||||
color: "#" + color?.lighten(80).toHex(),
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
59
src/components/Page/Page.module.css
Normal file
|
@ -0,0 +1,59 @@
|
|||
.Page {
|
||||
box-sizing: border-box;
|
||||
width: 100vw;
|
||||
min-height: 100vh;
|
||||
/* overflow-y: auto; */
|
||||
overflow: hidden;
|
||||
padding: 64px;
|
||||
scroll-snap-align: start;
|
||||
scroll-snap-stop: always;
|
||||
position: relative;
|
||||
background-attachment: fixed;
|
||||
z-index: 1;
|
||||
scroll-margin-bottom: calc(64px + (3rem + 1.5rem));
|
||||
}
|
||||
|
||||
.Page > * {
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.Page .PageBgElements {
|
||||
z-index: 2;
|
||||
/* opacity: 0.5; */
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 200%;
|
||||
/* transform: translateY(-50%); */
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
speak: none;
|
||||
}
|
||||
|
||||
.PagePreview {
|
||||
min-height: calc(100vh - (64px + (3rem + 1.5rem)));
|
||||
}
|
||||
|
||||
.PageFooter {
|
||||
min-height: 50vh;
|
||||
scroll-snap-align: end;
|
||||
}
|
||||
|
||||
.PageSpace {
|
||||
padding-right: 128px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1024px) {
|
||||
.Page {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 875px) {
|
||||
.PageSpace {
|
||||
padding-right: 64px;
|
||||
}
|
||||
}
|
57
src/components/Page/Page.tsx
Normal file
|
@ -0,0 +1,57 @@
|
|||
import { Color3 } from "@/utility/color";
|
||||
import { useEffect, useRef } from "react";
|
||||
import styles from "./Page.module.css";
|
||||
|
||||
export default function Page({
|
||||
children,
|
||||
color,
|
||||
bg,
|
||||
scroll,
|
||||
preview = false,
|
||||
footer = false,
|
||||
floaty = false,
|
||||
id,
|
||||
}: {
|
||||
children?: React.ReactNode;
|
||||
color?: Color3;
|
||||
bg?: string;
|
||||
scroll: HTMLElement | null;
|
||||
preview?: boolean;
|
||||
footer?: boolean;
|
||||
floaty?: boolean;
|
||||
id?: string;
|
||||
}) {
|
||||
const pageobj = useRef<HTMLDivElement>(null);
|
||||
const pageScrollAnim = useRef(0);
|
||||
useEffect(() => {
|
||||
if (scroll == null) return;
|
||||
const handler = () => {
|
||||
if (pageobj.current == null) return;
|
||||
pageobj.current.style.backgroundPositionY =
|
||||
-(pageobj.current.offsetTop - scroll.scrollTop) / 2 + "px";
|
||||
pageScrollAnim.current = requestAnimationFrame(handler);
|
||||
};
|
||||
pageScrollAnim.current = requestAnimationFrame(handler);
|
||||
// return cancelAnimationFrame(pageScrollAnim.current);
|
||||
}, [scroll]);
|
||||
return (
|
||||
<div
|
||||
id={id}
|
||||
ref={pageobj}
|
||||
className={`${styles.Page} ${preview ? styles.PagePreview : ""} ${
|
||||
footer ? styles.PageFooter : ""
|
||||
} ${floaty ? styles.PageSpace : ""} block`}
|
||||
style={
|
||||
{
|
||||
"--backgroundColor": "#" + color?.darken(75).toHex(),
|
||||
"--color": "#" + color?.toHex(),
|
||||
background: bg,
|
||||
backgroundColor: "#" + color?.darken(90).toHex(),
|
||||
color: "var(--color)",
|
||||
} as any
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
113
src/components/ProjectList/ProjectList.module.css
Normal file
|
@ -0,0 +1,113 @@
|
|||
.ProjectList {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-auto-columns: 300px;
|
||||
gap: 16px;
|
||||
width: calc(100% + 128px);
|
||||
box-sizing: border-box;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
margin-left: -64px;
|
||||
padding: 8px 16px;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
|
||||
.ProjectListDoubleWide {
|
||||
grid-auto-columns: 616px;
|
||||
}
|
||||
|
||||
.ProjectList .ProjectListProject {
|
||||
display: inline-block;
|
||||
padding: 8px;
|
||||
aspect-ratio: 1 / 1;
|
||||
flex-shrink: 0;
|
||||
border: 1px solid var(--color);
|
||||
background-color: var(--backgroundColor);
|
||||
color: var(--color);
|
||||
box-shadow: 0 0 0 var(--color);
|
||||
font-size: 1rem;
|
||||
text-align: left;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: box-shadow 0.125s, transform 0.125s;
|
||||
}
|
||||
|
||||
.ProjectListDoubleWide .ProjectListProject {
|
||||
aspect-ratio: 2 / 1;
|
||||
}
|
||||
|
||||
.ProjectList .ProjectListProject:hover {
|
||||
box-shadow: 0 8px 0 var(--color);
|
||||
transform: translateY(-8px);
|
||||
}
|
||||
|
||||
.ProjectList .ProjectListProject:active {
|
||||
box-shadow: 0 4px 0 var(--color);
|
||||
transform: translateY(-4px);
|
||||
font-weight: unset;
|
||||
}
|
||||
|
||||
.ProjectListProject .ProjectListProjectImage {
|
||||
position: absolute;
|
||||
left: -8px;
|
||||
top: -8px;
|
||||
width: calc(100% + 16px);
|
||||
height: calc(100% + 16px);
|
||||
object-fit: cover;
|
||||
filter: blur(4px) brightness(30%);
|
||||
transition: top 0.125s, filter 0.125s;
|
||||
/* opacity: 0.75; */
|
||||
}
|
||||
|
||||
.ProjectListProject:hover .ProjectListProjectImage {
|
||||
top: 0px;
|
||||
filter: blur(8px) brightness(30%);
|
||||
/* opacity: 0.75; */
|
||||
}
|
||||
|
||||
.ProjectListProject .ProjectListProjectDescription {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.ProjectListProjectDescription .ProjectListProjectDescriptionTitle {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
font-family: "Renogare";
|
||||
font-size: 1.5em;
|
||||
max-lines: 2;
|
||||
line-clamp: 2;
|
||||
line-height: 1.25em;
|
||||
text-overflow: ellipsis;
|
||||
overflow-x: hidden;
|
||||
max-width: 100%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ProjectListProjectDescription .ProjectListProjectDescriptionDescription {
|
||||
margin: 0;
|
||||
margin-top: 0.25em;
|
||||
font-family: "Lexend Deca";
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 750px) {
|
||||
.ProjectList {
|
||||
grid-auto-columns: calc(50% - 8px);
|
||||
}
|
||||
|
||||
.ProjectListDoubleWide {
|
||||
grid-auto-columns: 100%;
|
||||
}
|
||||
|
||||
.ProjectList .ProjectListProject {
|
||||
}
|
||||
}
|
71
src/components/ProjectList/ProjectList.tsx
Normal file
|
@ -0,0 +1,71 @@
|
|||
/* eslint-disable @next/next/no-img-element */
|
||||
import { useRouter } from "next/navigation";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import rehypeRaw from "rehype-raw";
|
||||
import remarkBreaks from "remark-breaks";
|
||||
import styles from "./ProjectList.module.css";
|
||||
|
||||
export type Project = {
|
||||
name: string;
|
||||
url: string;
|
||||
description: string;
|
||||
image?: string;
|
||||
};
|
||||
|
||||
export default function ProjectList({
|
||||
projects,
|
||||
markdown = false,
|
||||
double = false,
|
||||
}: {
|
||||
projects: Project[];
|
||||
markdown?: boolean;
|
||||
double?: boolean;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
return (
|
||||
<div
|
||||
className={`${styles.ProjectList} ${
|
||||
double ? styles.ProjectListDoubleWide : ""
|
||||
}`}
|
||||
>
|
||||
{projects.map((project, i) => (
|
||||
<a
|
||||
key={i}
|
||||
className={`${styles.ProjectListProject} notInline`}
|
||||
href={project.url}
|
||||
target="_blank"
|
||||
>
|
||||
{project.image ? (
|
||||
<img
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
src={project.image}
|
||||
className={styles.ProjectListProjectImage}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<div className={styles.ProjectListProjectDescription}>
|
||||
<p className={styles.ProjectListProjectDescriptionTitle}>
|
||||
{project.name}
|
||||
</p>
|
||||
<p className={styles.ProjectListProjectDescriptionDescription}>
|
||||
{markdown ? (
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkBreaks]}
|
||||
rehypePlugins={[rehypeRaw]}
|
||||
allowedElements={["br"]}
|
||||
unwrapDisallowed
|
||||
>
|
||||
{project.description.replace(/\n/gi, " \n")}
|
||||
</ReactMarkdown>
|
||||
) : (
|
||||
project.description
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
65
src/components/ScrollBackInd/ScrollBackInd.module.css
Normal file
|
@ -0,0 +1,65 @@
|
|||
.ScrollBackInd {
|
||||
position: fixed;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
gap: 16px;
|
||||
left: 50vw;
|
||||
transform: translateX(-50%);
|
||||
bottom: 64px;
|
||||
/* width: 64px; */
|
||||
height: 64px;
|
||||
z-index: 9;
|
||||
background-color: #0008;
|
||||
color: #fff;
|
||||
border-radius: 16px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 8px;
|
||||
transition: opacity 0.25s;
|
||||
}
|
||||
|
||||
.ScrollBackInd span:first-child {
|
||||
display: inline-block;
|
||||
width: 32px;
|
||||
border-radius: 8px;
|
||||
font-size: 24px;
|
||||
transition: text-shadow 0.25s;
|
||||
font-family: "Material Symbols Outlined";
|
||||
}
|
||||
|
||||
.ScrollBackInd:hover span:first-child {
|
||||
text-shadow: 0 0 8px #fff;
|
||||
}
|
||||
|
||||
.ScrollBackInd span {
|
||||
font-size: 1rem;
|
||||
padding: 0 8px;
|
||||
font-family: var(--font-Lexend-Deca);
|
||||
}
|
||||
|
||||
.ScrollBackInd img {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
background: conic-gradient(
|
||||
from -90deg at 50% 50%,
|
||||
transparent 0%,
|
||||
transparent 25%,
|
||||
currentColor 30%,
|
||||
currentColor 45%,
|
||||
transparent 50%,
|
||||
transparent 75%,
|
||||
currentColor 80%,
|
||||
currentColor 95%,
|
||||
transparent 100%
|
||||
)
|
||||
border-box;
|
||||
}
|
||||
|
||||
.ScrollBackIndHidden {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
36
src/components/ScrollBackInd/ScrollBackInd.tsx
Normal file
|
@ -0,0 +1,36 @@
|
|||
"use client";
|
||||
import styles from "./ScrollBackInd.module.css";
|
||||
|
||||
export default function ScrollBackInd<T>({
|
||||
hide = false,
|
||||
scroll,
|
||||
player,
|
||||
time,
|
||||
}: {
|
||||
hide?: boolean;
|
||||
scroll: HTMLElement | null;
|
||||
player: { [key: string]: any } | null | undefined;
|
||||
time: string;
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
className={`${styles.ScrollBackInd} ${
|
||||
hide ? styles.ScrollBackIndHidden : ""
|
||||
}`}
|
||||
onClick={() => scroll?.scrollTo({ top: 0, left: 0, behavior: "smooth" })}
|
||||
>
|
||||
<span className={`icon`}>arrow_upward</span>
|
||||
<span>{time}</span>
|
||||
{player &&
|
||||
player?.recenttracks.track[0]["@attr"]?.nowplaying === "true" ? (
|
||||
<img
|
||||
className={styles.ScrollBackIndAlbumArt}
|
||||
src={player?.recenttracks.track[0].image[1]["#text"]}
|
||||
alt=""
|
||||
></img>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
27
src/components/Sides/Sides.module.css
Normal file
|
@ -0,0 +1,27 @@
|
|||
.Sides {
|
||||
display: grid;
|
||||
grid-template-columns: auto max-content;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.Sides .SidesStart {
|
||||
text-align: start;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.Sides .SidesEnd {
|
||||
text-align: end;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 750px) {
|
||||
.Sides {
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.Sides .SidesEnd {
|
||||
text-align: start;
|
||||
margin-top: 2em;
|
||||
}
|
||||
}
|
16
src/components/Sides/Sides.tsx
Normal file
|
@ -0,0 +1,16 @@
|
|||
import styles from "./Sides.module.css";
|
||||
|
||||
export default function Sides({
|
||||
start,
|
||||
end,
|
||||
}: {
|
||||
start?: React.ReactNode;
|
||||
end?: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div className={`${styles.Sides} block`}>
|
||||
<div className={`${styles.SidesStart} block`}>{start}</div>
|
||||
<div className={`${styles.SidesEnd} block`}>{end}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
18
src/components/SpeedDial/SpeedDial.module.css
Normal file
|
@ -0,0 +1,18 @@
|
|||
.SpeedDial {
|
||||
/* display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 0.5em; */
|
||||
}
|
||||
|
||||
.SpeedDialService {
|
||||
/* display: inline-block; */
|
||||
}
|
||||
|
||||
.SpeedDialServiceGolden {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
.SpeedDialServicePurpose {
|
||||
margin: 0;
|
||||
}
|
34
src/components/SpeedDial/SpeedDial.tsx
Normal file
|
@ -0,0 +1,34 @@
|
|||
import styles from "./SpeedDial.module.css";
|
||||
|
||||
export default function SpeedDial({
|
||||
services,
|
||||
}: {
|
||||
services: {
|
||||
name: string;
|
||||
url: string;
|
||||
golden?: boolean;
|
||||
purpose?: string;
|
||||
}[];
|
||||
}) {
|
||||
return (
|
||||
<ul className={styles.SpeedDial}>
|
||||
{services.map((service, iter) => (
|
||||
<li
|
||||
className={`${styles.SpeedDialService} ${
|
||||
service.golden ? styles.SpeedDialServiceGolden : ""
|
||||
}`}
|
||||
key={iter}
|
||||
>
|
||||
{service.purpose ? (
|
||||
<p className={styles.SpeedDialServicePurpose}>{service.purpose}</p>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<a href={service.url} className="special">
|
||||
{service.name}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
2
src/components/Time/Time.module.css
Normal file
|
@ -0,0 +1,2 @@
|
|||
.Time {
|
||||
}
|
11
src/components/Time/Time.tsx
Normal file
|
@ -0,0 +1,11 @@
|
|||
import styles from "./Time.module.css";
|
||||
|
||||
export default function Time({ time, date }: { time: string; date: string }) {
|
||||
return (
|
||||
<div className={styles.Time}>
|
||||
<h2>{time}</h2>
|
||||
<h3>{date}</h3>
|
||||
<h4 className="hv">Central Time (Minnesota)</h4>
|
||||
</div>
|
||||
);
|
||||
}
|
55
src/components/net/LastFM/LastFM.module.css
Normal file
|
@ -0,0 +1,55 @@
|
|||
.LastFM {
|
||||
display: inline-grid;
|
||||
grid-template-columns: auto 96px;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
border: 1px solid transparent;
|
||||
/* background: linear-gradient(
|
||||
0deg,
|
||||
var(--backgroundColor) 0%,
|
||||
var(--backgroundColor) 100%
|
||||
)
|
||||
padding-box,
|
||||
conic-gradient(
|
||||
from -90deg at 50% 50%,
|
||||
transparent 0%,
|
||||
transparent 35%,
|
||||
currentColor 40%,
|
||||
currentColor 45%,
|
||||
transparent 50%,
|
||||
transparent 85%,
|
||||
currentColor 90%,
|
||||
currentColor 95%,
|
||||
transparent 100%
|
||||
)
|
||||
border-box; */
|
||||
/* box-shadow: 0 0 4px currentColor; */
|
||||
box-sizing: border-box;
|
||||
/* padding: 16px; */
|
||||
}
|
||||
|
||||
.LastFM .LastFMAlbumArt {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.LastFM .LastFMMetadata {
|
||||
}
|
||||
|
||||
.LastFM .LastFMMetadata .LastFMMetadataTitle {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.LastFMError {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 875px) {
|
||||
.LastFM {
|
||||
grid-auto-flow: column;
|
||||
grid-template-columns: 96px auto;
|
||||
}
|
||||
|
||||
.LastFM .LastFMAlbumArt {
|
||||
order: -1;
|
||||
}
|
||||
}
|
38
src/components/net/LastFM/LastFM.tsx
Normal file
|
@ -0,0 +1,38 @@
|
|||
"use client";
|
||||
import styles from "./LastFM.module.css";
|
||||
|
||||
export default function LastFM({
|
||||
player,
|
||||
}: {
|
||||
player: { [key: string]: any } | null | undefined;
|
||||
}) {
|
||||
return player != null ? (
|
||||
<>
|
||||
{player?.recenttracks.track[0]["@attr"]?.nowplaying === "true" ? (
|
||||
<p>Currently listening to</p>
|
||||
) : (
|
||||
<p className={styles.LastFMError}>Last listened to</p>
|
||||
)}
|
||||
<div className={styles.LastFM}>
|
||||
<div className={styles.LastFMMetadata}>
|
||||
<p className={styles.LastFMMetadataTitle}>
|
||||
{player?.recenttracks.track[0].name}
|
||||
</p>
|
||||
<p className={styles.LastFMMetadataArtist}>
|
||||
{player?.recenttracks.track[0].artist.name} -{" "}
|
||||
{player?.recenttracks.track[0].album["#text"]}
|
||||
</p>
|
||||
</div>
|
||||
<img
|
||||
className={styles.LastFMAlbumArt}
|
||||
src={player?.recenttracks.track[0].image[2]["#text"]}
|
||||
alt=""
|
||||
width="96"
|
||||
height="96"
|
||||
></img>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<p className={styles.LastFMError}>Hold on...</p>
|
||||
);
|
||||
}
|
178
src/styles/globals.css
Normal file
|
@ -0,0 +1,178 @@
|
|||
:root {
|
||||
font-family: var(--font-Lexend-Deca);
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
body,
|
||||
html {
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
div.body {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: black;
|
||||
color: white;
|
||||
overflow-y: scroll;
|
||||
scroll-snap-type: y mandatory;
|
||||
}
|
||||
|
||||
/* Markup */
|
||||
|
||||
.block > h1,
|
||||
.block > h2,
|
||||
.block > h3,
|
||||
.block > h4 {
|
||||
max-width: 85%;
|
||||
}
|
||||
.block > p,
|
||||
.block > ul,
|
||||
.block > ol {
|
||||
max-width: 65%;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: var(--font-Renogare);
|
||||
font-size: 3rem;
|
||||
margin: 0 0;
|
||||
margin-bottom: 1rem;
|
||||
text-wrap: balance;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-family: var(--font-Renogare);
|
||||
font-size: 2rem;
|
||||
margin: 1rem 0;
|
||||
margin-bottom: 1rem;
|
||||
text-wrap: balance;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-family: var(--font-Renogare);
|
||||
font-size: 1.5rem;
|
||||
margin: 1rem 0;
|
||||
margin-bottom: 0.5rem;
|
||||
text-wrap: balance;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-family: var(--font-Renogare);
|
||||
font-size: 1rem;
|
||||
margin: 1rem 0;
|
||||
margin-bottom: 0.5rem;
|
||||
text-wrap: balance;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 1rem;
|
||||
margin: 0.5rem 0;
|
||||
line-height: 1.5rem;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
font-size: 1rem;
|
||||
margin: 0.5rem 1em;
|
||||
line-height: 1.5rem;
|
||||
}
|
||||
|
||||
li {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
b {
|
||||
font-size: 1.25em;
|
||||
line-height: 1em;
|
||||
}
|
||||
|
||||
a {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
color: currentColor;
|
||||
transform: skew(0);
|
||||
transform-origin: bottom left;
|
||||
transition: all 0.125s;
|
||||
}
|
||||
|
||||
a.special::after {
|
||||
content: " >>>";
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
width: 1ch;
|
||||
overflow: hidden;
|
||||
transform: scaleX(0);
|
||||
top: 0;
|
||||
right: 0;
|
||||
transition: all 0.125s;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration-color: transparent;
|
||||
transform: skew(-15deg, 0);
|
||||
}
|
||||
|
||||
a.special:hover {
|
||||
letter-spacing: 0.25ch;
|
||||
margin-right: 2ch;
|
||||
font-weight: bold;
|
||||
text-decoration-color: transparent;
|
||||
transform: skew(-15deg, 0);
|
||||
}
|
||||
|
||||
a.special:hover::after {
|
||||
right: -2ch;
|
||||
transform: scaleX(1);
|
||||
}
|
||||
|
||||
a:active {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
a.special:active {
|
||||
transform: skew(-30deg, 0);
|
||||
letter-spacing: 0.5ch;
|
||||
margin-right: 5ch;
|
||||
}
|
||||
|
||||
a.special:active::after {
|
||||
width: 4ch;
|
||||
right: -5ch;
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-family: "Material Symbols Outlined";
|
||||
font-size: 24px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
/* Stinky styling */
|
||||
|
||||
.hv {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.chip {
|
||||
display: inline-block;
|
||||
background-color: var(--backgroundColor);
|
||||
color: var(--color);
|
||||
padding: 0 1em;
|
||||
margin-left: 0.25em;
|
||||
margin-right: 0.25em;
|
||||
border-radius: 1em;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1024px) {
|
||||
:root {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
94
src/utility/color.ts
Normal file
|
@ -0,0 +1,94 @@
|
|||
export type ColorTypes = [number, number, number];
|
||||
|
||||
const clamp = (n: number, mi: number, ma: number) =>
|
||||
Math.max(mi, Math.min(n, ma));
|
||||
|
||||
export class Color3 {
|
||||
R: number;
|
||||
G: number;
|
||||
B: number;
|
||||
constructor(red: number, green: number, blue: number) {
|
||||
this.R = red;
|
||||
this.G = green;
|
||||
this.B = blue;
|
||||
}
|
||||
static clone(color3: Color3) {
|
||||
return new Color3(color3.R, color3.G, color3.B);
|
||||
}
|
||||
static fromRGB(red: number, green: number, blue: number) {
|
||||
return new Color3(red / 255, green / 255, blue / 255);
|
||||
}
|
||||
static fromHex(hex: string) {
|
||||
// @ts-ignore
|
||||
const hexSplit: [number, number, number] = (
|
||||
hex.match(new RegExp(`[0-9a-f]{1,${hex.length / 3}}`, "gi")) ?? [
|
||||
"0",
|
||||
"0",
|
||||
"0",
|
||||
]
|
||||
).map((x) => parseInt(x, 16) / 255);
|
||||
return new Color3(...hexSplit);
|
||||
}
|
||||
static fromInt(int: number) {
|
||||
return new Color3(
|
||||
(int & 0xff0000) >> 16,
|
||||
(int & 0x00ff00) >> 8,
|
||||
int & 0x0000ff
|
||||
);
|
||||
}
|
||||
static assumeColor(
|
||||
value: [number, number, number] | string | number,
|
||||
rgb?: boolean
|
||||
) {
|
||||
if (Color3.isColor(value)) {
|
||||
if (Array.isArray(value))
|
||||
return rgb ? Color3.fromRGB(...value) : new Color3(...value);
|
||||
else if (typeof value === "string") return Color3.fromHex(value);
|
||||
else if (typeof value === "number") return Color3.fromInt(value);
|
||||
}
|
||||
throw new Error("Not a valid color type");
|
||||
}
|
||||
static isColor(
|
||||
value: [number, number, number] | string | number,
|
||||
rgb?: boolean
|
||||
) {
|
||||
return (
|
||||
(Array.isArray(value) &&
|
||||
value.length === 3 &&
|
||||
value.every((x) => typeof x === "number" && !isNaN(x))) ||
|
||||
(typeof value === "string" && !isNaN(parseInt(value, 16))) ||
|
||||
(typeof value === "number" && !isNaN(value))
|
||||
);
|
||||
}
|
||||
|
||||
toHex() {
|
||||
return this.toInt().toString(16).padStart(6, "0");
|
||||
}
|
||||
toInt() {
|
||||
return (
|
||||
(Math.round(this.R * 255) << 16) +
|
||||
(Math.round(this.G * 255) << 8) +
|
||||
Math.round(this.B * 255)
|
||||
);
|
||||
}
|
||||
toRGB(): [number, number, number] {
|
||||
return [this.R * 255, this.G * 255, this.B * 255];
|
||||
}
|
||||
multiply(mult: number) {
|
||||
return new Color3(this.R * mult, this.G * mult, this.B * mult);
|
||||
}
|
||||
lighten(mult: number) {
|
||||
return new Color3(
|
||||
clamp(this.R + (mult / 100) * (1 - this.R), 0, 1),
|
||||
clamp(this.G + (mult / 100) * (1 - this.G), 0, 1),
|
||||
clamp(this.B + (mult / 100) * (1 - this.B), 0, 1)
|
||||
);
|
||||
}
|
||||
darken(mult: number) {
|
||||
return new Color3(
|
||||
clamp(this.R - (mult / 100) * this.R, 0, 1),
|
||||
clamp(this.G - (mult / 100) * this.G, 0, 1),
|
||||
clamp(this.B - (mult / 100) * this.B, 0, 1)
|
||||
);
|
||||
}
|
||||
}
|
19
src/utility/fonts.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { Lexend_Deca } from "next/font/google";
|
||||
import Renogare from "next/font/local";
|
||||
|
||||
const lexend_deca = Lexend_Deca({
|
||||
subsets: ["latin"],
|
||||
variable: "--font-Lexend-Deca",
|
||||
});
|
||||
const lexend_deca_backup = Lexend_Deca({
|
||||
// some dumb error
|
||||
subsets: ["latin"],
|
||||
variable: "--font-Lexend-Deca",
|
||||
});
|
||||
const renogare = Renogare({
|
||||
src: "./../../public/fonts/Renogare/Renogare.woff2",
|
||||
variable: "--font-Renogare",
|
||||
});
|
||||
|
||||
const fonts = `${lexend_deca.variable} ${lexend_deca_backup.variable} ${renogare.variable}`;
|
||||
export default fonts;
|