ok i should actually save this

This commit is contained in:
MeowcaTheoRange 2024-04-01 23:32:50 -05:00
parent a44ff5bade
commit f3528185a4
82 changed files with 3093 additions and 447 deletions

47
LICENSE Normal file
View file

@ -0,0 +1,47 @@
THE KARKAT PUBLIC LICENSE (KKPL)
Version 2.2, January 2024
Copyright (C) 2023-2024 MeowcaTheoRange
<me@abtmtr.link>
THIS LICENSE GRANTS PERMISSION TO ANY
TROLL, HUMAN, GROUP, OR ANY OTHER
LEGALLY RECOGNIZED ENTITY ("YOU") TO
MODIFY, USE, AND DISTRIBUTE
("SCHMEEVE", "SCHMEEVING") THIS WORK
WITHIN THE DESIRED SCOPE DEFINED BELOW,
AS LONG AS THE KARKAT PUBLIC LICENSE
PERSISTS WITHIN.
BY SCHMEEVING THIS WORK, YOU AGREE TO
BE BOUND BY THIS LICENSE'S TERMS AND
CONDITIONS:
0. YOU WON'T BE A WUSS ABOUT THIS
LICENSE.
1. YOU WILL ONLY SCHMEEVE WITHIN THE
WORK'S DESIRED SCOPE FOR THIS
LICENSE.
2. YOU ACKNOWLEDGE THAT THE DEFINED
SCOPE OF THIS WORK COMES WITH NO
WARRANTY.
3. YOU FROND PROMISE THAT YOU WON'T
SUE IF YOUR COMPUTER EXPLODES
BECAUSE OF THIS PROGRAM.
4. YOU AGREE THAT KARKAT IS THE BEST
HACKER ON ALTERNIA.
5. YOU AGREE THAT BY WRITING IN THE
FORM BELOW, YOU ARE THE RIGHTFUL
OWNER OF THIS WORK.
WHAT PARTS OF THE WORK DOES THIS DAMNED
LICENSE APPLY TO:
All of it, except /public.
WRITE THE WORK'S NAME HERE:
abtmtr.link
(Version 9, April 2024)
WRITE THE NAME(S) OF THE WORK'S
AUTHOR(S) HERE:
MeowcaTheoRange <me@abtmtr.link>
FUCK FUCK FUCK FUCK FUCK FUCK FUCK.

View file

@ -1,4 +1,6 @@
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = {}; const nextConfig = {
trailingSlash: true
};
export default nextConfig; export default nextConfig;

1570
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -9,14 +9,17 @@
"lint": "next lint" "lint": "next lint"
}, },
"dependencies": { "dependencies": {
"next": "14.1.4",
"react": "^18", "react": "^18",
"react-dom": "^18", "react-dom": "^18",
"next": "14.1.4" "react-markdown": "^9.0.1",
"rehype-raw": "^7.0.0",
"remark-gfm": "^4.0.0"
}, },
"devDependencies": { "devDependencies": {
"typescript": "^5",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^18", "@types/react": "^18",
"@types/react-dom": "^18" "@types/react-dom": "^18",
"typescript": "^5"
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
public/88x31/jaiden_sh.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
public/88x31/mae_wtf.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 690 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
public/headers/about.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
public/headers/blog.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
public/headers/gallery.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

BIN
public/headers/links.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
public/headers/oao.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
public/headers/projects.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
public/headers/stories.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View 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

96
public/robots.txt Normal file
View file

@ -0,0 +1,96 @@
# https://seirdy.one/robots.txt
User-agent: *
Disallow: /noindex/
Disallow: /misc/
# I opt out of online advertising so malware that injects ads on my site won't get paid.
# You should do the same. my ads.txt file contains a standard placeholder to forbid any
# compliant ad networks from paying for ad placement on my domain.
User-Agent: Adsbot
Disallow: /
Allow: /ads.txt
Allow: /app-ads.txt
## IP-violation scanners ##
# The next three are borrowed from https://www.videolan.org/robots.txt
# > This robot collects content from the Internet for the sole purpose of # helping educational institutions prevent plagiarism. [...] we compare student papers against the content we find on the Internet to see if we # can find similarities. (http://www.turnitin.com/robot/crawlerinfo.html)
# --> fuck off.
User-Agent: TurnitinBot
Disallow: /
# > NameProtect engages in crawling activity in search of a wide range of brand and other intellectual property violations that may be of interest to our clients. (http://www.nameprotect.com/botinfo.html)
# --> fuck off.
User-Agent: NPBot
Disallow: /
# iThenticate is a new service we have developed to combat the piracy of intellectual property and ensure the originality of written work for# publishers, non-profit agencies, corporations, and newspapers. (http://www.slysearch.com/)
# --> fuck off.
User-Agent: SlySearch
Disallow: /
# BLEXBot assists internet marketers to get information on the link structure of sites and their interlinking on the web, to avoid any technical and possible legal issues and improve overall online experience. (http://webmeup-crawler.com/)
# --> fuck off.
User-Agent: BLEXBot
Disallow: /
# Providing Intellectual Property professionals with superior brand protection services by artfully merging the latest technology with expert analysis. (https://www.checkmarknetwork.com/spider.html/)
# "The Internet is just way to big to effectively police alone." (ACTUAL quote)
# --> fuck off.
User-agent: CheckMarkNetwork/1.0 (+https://www.checkmarknetwork.com/spider.html)
Disallow: /
# Stop trademark violations and affiliate non-compliance in paid search. Automatically monitor your partner and affiliates online marketing to protect yourself from harmful brand violations and regulatory risks. We regularly crawl websites on behalf of our clients to ensure content compliance with brand and regulatory guidelines. (https://www.brandverity.com/why-is-brandverity-visiting-me)
# --> fuck off.
User-agent: BrandVerity/1.0
Disallow: /
## Misc. icky stuff ##
# Pipl assembles online identity information from multiple independent sources to create the most complete picture of a digital identity and connect it to real people and their offline identity records. When all the fragments of online identity data are collected, connected, and corroborated, the result is a more trustworthy identity.
# --> fuck off.
User-agent: PiplBot
Disallow: /
## Gen-AI data scrapers ##
# Eat shit, OpenAI.
User-agent: ChatGPT-User
Disallow: /
User-agent: GPTBot
Disallow: /
# Official way to opt-out of Google's generative AI training:
# <https://developers.google.com/search/docs/crawling-indexing/overview-google-crawlers>
User-agent: Google-Extended
Disallow: /
# There isn't any public documentation for this AFAICT.
# Reuters thinks this works so I might as well give it a shot.
User-agent: anthropic-ai
Disallow: /
User-agent: Claude-Web
Disallow: /
# FacebookBot crawls public web pages to improve language models for our speech recognition technology.
# <https://developers.facebook.com/docs/sharing/bot/?_fb_noscript=1>
User-Agent: FacebookBot
Disallow: /
# I'm not blocking CCBot for now. It publishes a free index for anyone to use.
# Googe used this to train the initial version of Bard (now called Gemini).
# I allow CCBot since its index is also used for upstart/hobbyist search engines
# like Alexandria and for genuinely useful academic work I personally like.
# I allow Owler for similar reasons:
# <https://openwebsearch.eu/owler/#owler-opt-out>
# <https://openwebsearch.eu/common-goals-with-common-crawl/>.
# Omgilibot/Omgili is similar to CCBot, except it sells the scrape results.
# I'm not familiar enough with Omgili to make a call here.
# In the long run, my embedded robots meta-tags and headers could cover gen-AI
# I don't block cohere-ai or Perplexitybot: they don't appear to actually scrape data for LLM training purposes. The crawling powers search engines with integrated pre-trained LLMs.
# TODO: investigate whether YouBot scrapes to train its own in-house LLM.
Sitemap: https://seirdy.one/sitemap.xml

Binary file not shown.

BIN
public/sfx/s_busy.mp3 Normal file

Binary file not shown.

BIN
public/sfx/s_happy.mp3 Normal file

Binary file not shown.

BIN
public/sfx/s_hover.mp3 Normal file

Binary file not shown.

BIN
public/sfx/s_impossible.mp3 Normal file

Binary file not shown.

BIN
public/sfx/s_notice.mp3 Normal file

Binary file not shown.

BIN
public/sfx/s_open.mp3 Normal file

Binary file not shown.

Binary file not shown.

BIN
public/sfx/s_sad.mp3 Normal file

Binary file not shown.

View file

@ -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

BIN
public/welcome.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

17
src/app/about/page.tsx Normal file
View file

@ -0,0 +1,17 @@
import { MainLayout } from "@/layout/MainLayout/MainLayout";
import Link from "next/link";
export default async function Home() {
return (
<MainLayout currentPage="/about/" title="About" subtitle="abtmtr.link v9">
<img style={{
marginTop: 16
}} src="/headers/about.png" alt="ABOUT"></img>
<h1>abtmtr.link</h1>
<p>Created by <b>MeowcaTheoRange</b></p>
<p>Licensed under the <Link href="https://git.abtmtr.link/MeowcaTheoRange/KarkatPublicLicense/src/branch/master/LICENSE-v2.2" target="_blank">Karkat Public License (KKPL) v2.2</Link>.</p>
<p><Link href="https://www.creativefabrica.com/product/renogare/" target="_blank">Renogare</Link> font by Graphite</p>
<p><Link href="https://fonts.google.com/specimen/Lexend+Deca" target="_blank">Lexend Deca</Link> font from Google Fonts</p>
</MainLayout>
)
}

View file

@ -0,0 +1,27 @@
import { MainLayout } from "@/layout/MainLayout/MainLayout";
import { notFound } from "next/navigation";
import Markdown from 'react-markdown';
import rehypeRaw from "rehype-raw";
export default async function Home({
params
}:{
params: {
slug: string
}
}) {
const blog = await fetch(`https://blog.abtmtr.link/api/collections/mtr/posts/${params.slug}`).then(x=>x.json());
if (blog.data == null)
return notFound();
return (
<MainLayout currentPage={`/blog/${params.slug}`} title="Blog" subtitle="via blog.abtmtr.link" backButton>
<h1>{blog.data.title}</h1>
<p><small>Posted {new Date(blog.data.created).toLocaleString()} - {blog.data.views} views</small></p>
<div style={{
margin: "16px 0"
}}>
<Markdown rehypePlugins={[rehypeRaw]}>{blog.data.body}</Markdown>
</div>
</MainLayout>
)
}

57
src/app/blog/page.tsx Normal file
View file

@ -0,0 +1,57 @@
import { ConditionalNull } from "@/components/utility/Conditional";
import { MainLayout } from "@/layout/MainLayout/MainLayout";
import Link from "next/link";
import { notFound } from "next/navigation";
import Markdown from "react-markdown";
import rehypeRaw from "rehype-raw";
export default async function Home({
searchParams
}: {
searchParams: {
page: string
}
}) {
const curPage = parseInt(searchParams?.page) || 1;
const blogs = await fetch(`https://blog.abtmtr.link/api/collections/mtr/posts?page=${curPage}`).then(x=>x.json());
return (
<MainLayout currentPage="/blog/" title="Blog" subtitle="via blog.abtmtr.link">
<img style={{
marginTop: 16
}} src="/headers/blog.png" alt="BLOG"></img>
{blogs.data.posts.map((post:{
title: string,
body: string,
created: string,
updated: string,
tags: string[],
views: number,
slug: string
}) => (
<div style={{
margin: "16px 0"
}}>
<h2><Link href={`/blog/${post.slug}`}>{post.title}</Link></h2>
<p><small>Posted {new Date(post.created).toLocaleString()} - {post.views} views</small></p>
<Markdown
rehypePlugins={[rehypeRaw]}
allowedElements={[]}
unwrapDisallowed={true}
>
{post.body.split("\n").slice(0, 2).join("\n")}
</Markdown>
</div>
))}
<p>
<ConditionalNull condition={curPage > 1}>
<Link href={`?page=${curPage - 1}`}>Previous</Link>
</ConditionalNull>
{" "}
<ConditionalNull condition={curPage * 10 < blogs.data.total_posts}>
<Link href={`?page=${curPage + 1}`}>Next</Link>
</ConditionalNull>
</p>
</MainLayout>
)
}

View file

@ -0,0 +1,94 @@
import { ConditionalNull } from "@/components/utility/Conditional";
import { MainLayout } from "@/layout/MainLayout/MainLayout";
import { HeightConverter } from "@/lib/utility/language";
import Link from "next/link";
import Markdown from "react-markdown";
import rehypeRaw from "rehype-raw";
import styles from "../style.module.css";
import { notFound } from "next/navigation";
export default async function Home({
params
}: {
params: {
slug: string
}
}) {
const charblogs = await fetch(`https://blog.abtmtr.link/api/collections/characters/posts/${params.slug}`).then(x=>x.json());
if (charblogs.data == null) return notFound();
const character:{
id: string,
name: string,
description?: string[],
picture: string,
gender: string,
pronunciation: string,
pronouns: string,
spec?: string,
species: string,
age: number,
height: number,
relationships: {
id: string,
status: string,
}[]
} = JSON.parse(charblogs.data.body.replace(/[“”]/g, "\""));
const images = await fetch(`https://img.abtmtr.link/api/collections/mtr/posts/${character.picture}`).then(x=>x.json());
// // REQSPAMMER
// let allPosts = [];
// let currentPage = 1;
// const initReq = await fetch(`https://img.abtmtr.link/api/collections/mtr/posts`).then(x=>x.json());
// const totalPosts:number = initReq.data.total_posts;
// while (currentPage * 10 < totalPosts) {
// const newPostList = await fetch(`https://img.abtmtr.link/api/collections/mtr/posts?page=${currentPage}`).then(x=>x.json());
// allPosts.push(...newPostList.data.posts);
// currentPage++;
// }
// const myPosts = allPosts.filter(post => post.tags?.includes(character.id));
return (
<MainLayout currentPage="/characters/" title="Characters" backButton>
<div style={{display:"block",marginBottom:16,overflow:"auto"}}>
<img style={{float:"right",width:"60%", marginLeft:16, marginTop:16}} src={images.data.images[0]}></img>
<h1 style={{marginBottom: 0}}>{character.name}</h1>
<small>{character.pronunciation}</small>
<ConditionalNull condition={character.spec != null}>
<p>Spec: <Link href={`https://spec.abtmtr.link/#${character.spec}`} target="_blank">#{character.spec}</Link></p>
</ConditionalNull>
<p>{character.pronouns}, {character.age}</p>
<p>{character.species}, {character.gender}, {HeightConverter(character.height)}</p>
<ConditionalNull condition={character.description != null}>
<Markdown rehypePlugins={[rehypeRaw]}>{`*${character.description?.join("\n")}*`}</Markdown>
</ConditionalNull>
</div>
<h2>Gallery</h2>
<p><i>Feature coming soon...</i></p>
{/* <div className={styles.CharacterContainerGrid}>
{myPosts.map(async (img:{
slug: string,
title: string,
created: string,
images: string[]
}) => {
return (
<Link href={`/gallery/${img.slug}`} className={styles.CharacterContainerLink}>
<div style={{
backgroundImage: `linear-gradient(180deg, #000c 0%, #000c 50%, #0004 100%), url(${img.images[0]})`,
}} className={styles.CharacterContainer}>
<h2>{img.title}</h2>
<p>{new Date(img.created).toLocaleDateString()}</p>
</div>
</Link>
);
})}
</div> */}
</MainLayout>
)
}

View file

@ -0,0 +1,66 @@
import { MainLayout } from "@/layout/MainLayout/MainLayout";
import styles from "./style.module.css";
import Link from "next/link";
import { ConditionalNull } from "@/components/utility/Conditional";
export default async function Home({
searchParams
}: {
searchParams: {
page: string
}
}) {
const curPage = parseInt(searchParams?.page) || 1;
const charblogs = await fetch(`https://blog.abtmtr.link/api/collections/characters/posts?page=${curPage}`).then(x=>x.json());
const characters = charblogs.data.posts.map((post:{body:string}) => JSON.parse(post.body.replace(/[“”]/g, "\"")));
return (
<MainLayout currentPage="/characters/" title="Characters" subtitle="">
<img style={{
marginTop: 16
}} src="/headers/characters.png" alt="CHARACTERS"></img>
<div className={styles.CharacterContainerGrid}>
{characters.map(async (character:{
id: string,
name: string,
description?: string[],
picture: string,
gender: string,
pronunciation: string,
pronouns: string,
spec?: string,
species: string,
age: number,
height: number,
relationships: {
id: string,
status: string,
}[]
}) => {
const charblogs = await fetch(`https://img.abtmtr.link/api/collections/mtr/posts/${character.picture}`).then(x=>x.json());
console.log(charblogs?.data?.images?.[0]);
return (
<Link href={`/characters/${character.id}`} className={styles.CharacterContainerLink}>
<div style={{
backgroundImage: `linear-gradient(180deg, #000c 0%, #000c 50%, #0004 100%), url(${charblogs.data.images[0]})`,
}} className={styles.CharacterContainer}>
<h2>{character.name}</h2>
<p>{character.pronouns}</p>
</div>
</Link>
);
})}
</div>
<p>
<ConditionalNull condition={curPage > 1}>
<Link href={`?page=${curPage - 1}`}>Previous</Link>
</ConditionalNull>
{" "}
<ConditionalNull condition={curPage * 10 < charblogs.data.total_posts}>
<Link href={`?page=${curPage + 1}`}>Next</Link>
</ConditionalNull>
</p>
</MainLayout>
)
}

View file

@ -0,0 +1,18 @@
.CharacterContainerGrid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
margin: 16px 0;
}
.CharacterContainerLink {
color: white;
text-decoration: none;
}
.CharacterContainer {
aspect-ratio: 1/1;
overflow: hidden;
padding: 16px;
background-size: cover;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

View file

@ -0,0 +1,27 @@
import { MainLayout } from "@/layout/MainLayout/MainLayout";
import { notFound } from "next/navigation";
import Markdown from "react-markdown";
import rehypeRaw from "rehype-raw";
export default async function Home({
params
}:{
params: {
slug: string
}
}) {
const blog = await fetch(`https://img.abtmtr.link/api/collections/mtr/posts/${params.slug}`).then(x=>x.json());
if (blog.data == null)
return notFound();
return (
<MainLayout currentPage={`/gallery/${params.slug}`} title="Gallery" subtitle="via img.abtmtr.link" backButton>
<h1>{blog.data.title}</h1>
<p><small>Posted {new Date(blog.data.created).toLocaleString()} - {blog.data.views} views</small></p>
<div style={{
margin: "16px 0"
}}>
<Markdown rehypePlugins={[rehypeRaw]}>{blog.data.body}</Markdown>
</div>
</MainLayout>
)
}

56
src/app/gallery/page.tsx Normal file
View file

@ -0,0 +1,56 @@
import { ConditionalNull } from "@/components/utility/Conditional";
import { MainLayout } from "@/layout/MainLayout/MainLayout";
import Link from "next/link";
import Markdown from "react-markdown";
import rehypeRaw from "rehype-raw";
export default async function Home({
searchParams
}: {
searchParams: {
page: string
}
}) {
const curPage = parseInt(searchParams?.page) || 1;
const blogs = await fetch(`https://img.abtmtr.link/api/collections/mtr/posts?page=${curPage}`).then(x=>x.json());
return (
<MainLayout currentPage="/gallery/" title="Gallery" subtitle="via img.abtmtr.link">
<img style={{
marginTop: 16
}} src="/headers/gallery.png" alt="GALLERY"></img>
{blogs.data.posts.map((post:{
title: string,
body: string,
created: string,
updated: string,
tags: string[],
views: number,
slug: string
}) => (
<div style={{
margin: "16px 0"
}}>
<h2><Link href={`/gallery/${post.slug}`}>{post.title}</Link></h2>
<p><small>Posted {new Date(post.created).toLocaleString()} - {post.views} views</small></p>
<Markdown
rehypePlugins={[rehypeRaw]}
allowedElements={[]}
unwrapDisallowed={true}
>
{post.body.split("\n").slice(0, 2).join("\n")}
</Markdown>
</div>
))}
<p>
<ConditionalNull condition={curPage > 1}>
<Link href={`?page=${curPage - 1}`}>Previous</Link>
</ConditionalNull>
{" "}
<ConditionalNull condition={curPage * 10 < blogs.data.total_posts}>
<Link href={`?page=${curPage + 1}`}>Next</Link>
</ConditionalNull>
</p>
</MainLayout>
)
}

View file

@ -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;
}
}

View file

@ -1,12 +1,13 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import { Inter } from "next/font/google"; import localFont from "next/font/local";
import "./globals.css"; import "@/styles/style.css";
const inter = Inter({ subsets: ["latin"] }); const lexendDeca = localFont({ src: "../../public/fonts/Lexend Deca/Variable.ttf"});
const materialSymbols = localFont({ src: "../../public/fonts/Material Symbols/Variable.ttf"});
const renogare = localFont({ src: "../../public/fonts/Renogare/Regular.otf"});
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Create Next App", title: "abtmtr.link",
description: "Generated by create next app",
}; };
export default function RootLayout({ export default function RootLayout({
@ -15,8 +16,12 @@ export default function RootLayout({
children: React.ReactNode; children: React.ReactNode;
}>) { }>) {
return ( return (
<html lang="en"> <html lang="en" style={{
<body className={inter.className}>{children}</body> "--font-LexendDeca": lexendDeca.style.fontFamily,
"--font-MaterialSymbols": materialSymbols.style.fontFamily,
"--font-Renogare": renogare.style.fontFamily
} as {[key: string]: string}}>
<body>{children}</body>
</html> </html>
); );
} }

22
src/app/links/page.tsx Normal file
View file

@ -0,0 +1,22 @@
import { MainLayout } from "@/layout/MainLayout/MainLayout";
import Link from "next/link";
export default async function Home() {
const data = await fetch("https://pronouns.cc/api/v1/users/mtr").then(x=>x.json());
return (
<MainLayout currentPage="/links/" title="Links" subtitle="to other places I've been">
<img style={{
marginTop: 16
}} src="/headers/links.png" alt="LINKS"></img>
{data.links.map((link:string) => (
<div style={{
margin: "16px 0"
}}>
<h2>{new URL(link).host}</h2>
<p><Link href={link} target="_blank">{link}</Link></p>
</div>
))}
</MainLayout>
)
}

9
src/app/not-found.tsx Normal file
View file

@ -0,0 +1,9 @@
import { MainLayout } from "@/layout/MainLayout/MainLayout";
export default function NotFound() {
return (
<MainLayout currentPage="/404/" title="404 - Not found">
<p>Page not found.</p>
</MainLayout>
);
}

28
src/app/oao/page.tsx Normal file
View file

@ -0,0 +1,28 @@
import { MainLayout } from "@/layout/MainLayout/MainLayout";
import Link from "next/link";
export default async function Home() {
return (
<MainLayout currentPage="/oao/" title="Opinions and Objections">
<img style={{
marginTop: 16
}} src="/headers/oao.png" alt="OPINIONS & OBJECTIONS"></img>
<p>As a sentient, living being with thoughts, I have opinions on certain things. Here are mine.</p>
<ul>
<li>
<p>
<b>I don't mind Chromium-based browsers.</b> Google is evil, sure, but I think it's good to design for what's popular. Plus, I like using Vivaldi over Firefox.
Vivaldi crashes less <i>(except when I open a private tab for some reason)</i>, works smoothly, is incredibly customizable, and even though it's not open source, the community is small enough where I can get a reasonable amount of dynamic contact with the creators/maintainers.
</p>
<p>Really, it's all a matter of personal opinion. (oh my gosh like the page name !!!)</p>
</li>
<li>
<p>
<b>I do mind it when people don't put a CW certain things.</b> USpol, lewdness, that kind of thing.
</p>
</li>
</ul>
<p>More may be coming soon.</p>
</MainLayout>
)
}

View file

@ -1,230 +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;
text-wrap: balance;
}
.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);
}
}

View file

@ -1,95 +1,133 @@
import Image from "next/image"; import { MainLayout } from "@/layout/MainLayout/MainLayout";
import styles from "./page.module.css"; import Link from "next/link";
import buttons from "@/buttons.json";
export default function Home() { export default async function Home() {
const data = await fetch("https://pronouns.cc/api/v1/users/mtr").then(x=>x.json());
const domainreq = await fetch("https://blog.abtmtr.link/api/collections/paste/posts/domain")
.then(x=>x.json());
const domains = JSON.parse(domainreq?.data?.body.replace(/[“”]/g, "\"")) ?? [];
const goodNames = data.names.filter((x:{status:string})=>x.status == "okay");
return ( return (
<main className={styles.main}> <MainLayout currentPage="/" title="Root">
<div className={styles.description}> <img style={{
<p> marginTop: 16
Get started by editing&nbsp; }} src="/welcome.png" alt="WELCOME! Enjoy your stay at abtmtr.link!"></img>
<code className={styles.code}>src/app/page.tsx</code> <p>abtmtr.link is a domain for a suite of services, ranging from micro-blogging to web search.</p>
</p> <p>I'm {goodNames[0].value}, otherwise known as {goodNames.slice(1).map((name:{value:string}, idx:number) => (
<div> idx == goodNames.length - 2 ? (<>or <b>{name.value}</b></>) : (<><b>{name.value}</b>{goodNames.length <= 2 ? ", " : " "}</>)
<a ))}.</p>
href="https://vercel.com?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app" <p>It proves very useful for anything and everything that I want to do. It also tends to provide service for others when they need it.</p>
target="_blank" <h2>Subdomains</h2>
rel="noopener noreferrer" {domains.map((domain:{
> name: string,
By{" "} description: string[],
<Image url: string,
src="/vercel.svg" src: string
alt="Vercel Logo" }) => (
className={styles.vercelLogo} <div style={{
width={100} margin: "16px 0"
height={24} }}>
priority <h2><Link href={domain.url}>{domain.name}</Link></h2>
/> <p>{domain.description.join("\n")}</p>
</a>
</div> </div>
</div> ))}
<div style={{
<div className={styles.center}> display: "flex",
<Image justifyContent: "center",
className={styles.logo} flexWrap: "wrap",
src="/next.svg" padding: 8,
alt="Next.js Logo" gap: 8
width={180} }}>{buttons.map((button) => (
height={37} <Link href={button.href} target="_blank">
priority <img src={button.img} title={button.alt} alt={button.alt} width="88" height="31" />
/> </Link>
</div> ))}</div>
</MainLayout>
<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>-&gt;</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>-&gt;</span>
</h2>
<p>Learn about Next.js in an interactive course with&nbsp;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>-&gt;</span>
</h2>
<p>Explore starter templates for Next.js.</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>-&gt;</span>
</h2>
<p>
Instantly deploy your Next.js site to a shareable URL with Vercel.
</p>
</a>
</div>
</main>
);
} }
/*
{
"id": "clhd8desmggedm3q1t3g",
"id_new": "251576323070604865",
"sid": "qftsk",
"name": "mtr",
"display_name": "abtmtr.link: FURRY REVOLUTION",
"bio": "https://abtmtr.link/\nminor (16)\nabtmtr.link sysadmin\n🇩🇴 & 🏳️‍⚧️-ish & pluralflux ND 󠁛󠀣󠀰󠀰󠀰󠀰󠀰󠀰󠀬󠀣󠁦󠁦󠀴󠀰󠀰󠀰󠁝",
"member_title": "Managers",
"avatar": "dfa7038db301508df749772f07ae9302fb0b9d71dff6770f9a6fefb6cf16f2e3",
"links": [
"https://last.fm/user/MeowcaTheoRange",
"https://liberapay.com/abtmtr.link/",
"https://beta.trollcall.xyz/clan/meowcatheorange",
"https://movie-web.app/",
"https://picrew.me/en/image_maker/92646",
"https://pronouns.cc/@mtr"
],
"names": [
{
"value": "MeowcaTheoRange",
"status": "okay"
},
{
"value": "abtmtr.link",
"status": "okay"
},
{
"value": "Theo",
"status": "okay"
}
],
"pronouns": [
{
"pronouns": "[any]",
"display_text": null,
"status": "okay"
}
],
"members": [
{
"id": "cng1h37m985pb3k8ohe0",
"id_new": "286009441537261082",
"sid": "shdvoo",
"name": "MeowcaTheoRange",
"display_name": "MeowcaTheoRange",
"bio": "totality in association.",
"avatar": "a686a0bfeeb19ce6591a3bef6bb0f4ff38586f0d75b857cb21c3d86ae42b39e4",
"links": [
],
"names": [
{
"value": "Theo",
"status": "okay"
},
{
"value": "Thea",
"status": "okay"
}
],
"pronouns": [
{
"pronouns": "[any]",
"display_text": null,
"status": "okay"
}
]
}
],
"fields": [
],
"custom_preferences": {
},
"flags": [
],
"badges": 0,
"utc_offset": -18000
}
*/

View file

@ -0,0 +1,17 @@
import { notFound, redirect, RedirectType } from "next/navigation";
export default async function Home({
params
}:{
params: {
slug: string
}
}) {
const projects = await fetch("https://projects.abtmtr.link/projects.json").then(x=>x.json());
const curProject = projects.items.find((x:{url:string}) => x.url.includes("projects.abtmtr.link/item/" + params.slug));
if (curProject)
return redirect(curProject.url, RedirectType.replace);
return notFound();
}

30
src/app/projects/page.tsx Normal file
View file

@ -0,0 +1,30 @@
import { MainLayout } from "@/layout/MainLayout/MainLayout";
import Link from "next/link";
import Markdown from "react-markdown";
import rehypeRaw from "rehype-raw";
export default async function Home() {
const projects = await fetch("https://projects.abtmtr.link/projects.json").then(x=>x.json());
return (
<MainLayout currentPage="/projects/" title="Projects" subtitle="by MeowcaTheoRange">
<img style={{
marginTop: 16
}} src="/headers/projects.png" alt="PROJECTS"></img>
{projects.items.map((project:{
name: string,
date: number,
description: string[],
url: string,
hotlink?: boolean
}) => (
<div style={{
margin: "16px 0"
}}>
<h2><Link href={project.url} target="_blank">{project.name}</Link></h2>
<p><small>Hosted on {new URL(project.url).host} - Created {new Date(project.date).toLocaleDateString()}</small></p>
<Markdown rehypePlugins={[rehypeRaw]}>{project.description.join("\n")}</Markdown>
</div>
))}
</MainLayout>
)
}

View file

@ -0,0 +1,27 @@
import { MainLayout } from "@/layout/MainLayout/MainLayout";
import { notFound } from "next/navigation";
import Markdown from "react-markdown";
import rehypeRaw from "rehype-raw";
export default async function Home({
params
}:{
params: {
slug: string
}
}) {
const blog = await fetch(`https://blog.abtmtr.link/api/collections/stories/posts/${params.slug}`).then(x=>x.json());
if (blog.data == null)
return notFound();
return (
<MainLayout currentPage={`/stories/${params.slug}`} title="Stories" subtitle="via blog.abtmtr.link" backButton>
<h1>{blog.data.title}</h1>
<p><small>Posted {new Date(blog.data.created).toLocaleString()} - {blog.data.views} views</small></p>
<div style={{
margin: "16px 0"
}}>
<Markdown rehypePlugins={[rehypeRaw]}>{blog.data.body}</Markdown>
</div>
</MainLayout>
)
}

55
src/app/stories/page.tsx Normal file
View file

@ -0,0 +1,55 @@
import { ConditionalNull } from "@/components/utility/Conditional";
import { MainLayout } from "@/layout/MainLayout/MainLayout";
import Link from "next/link";
import Markdown from "react-markdown";
import rehypeRaw from "rehype-raw";
export default async function Home({
searchParams
}: {
searchParams: {
page: string
}
}) {
const curPage = parseInt(searchParams?.page) || 1;
const blogs = await fetch(`https://blog.abtmtr.link/api/collections/stories/posts?page=${curPage}`).then(x=>x.json());
return (
<MainLayout currentPage="/stories/" title="Stories" subtitle="via blog.abtmtr.link">
<img style={{
marginTop: 16
}} src="/headers/stories.png" alt="STORIES"></img>
{blogs.data.posts.map((post:{
title: string,
body: string,
created: string,
updated: string,
tags: string[],
views: number,
slug: string
}) => (
<div style={{
margin: "16px 0"
}}>
<h2><Link href={`/stories/${post.slug}`}>{post.title}</Link></h2>
<p><small>Posted {new Date(post.created).toLocaleString()} - {post.views} views</small></p>
<Markdown
rehypePlugins={[rehypeRaw]}
allowedElements={[]}
unwrapDisallowed={true}
>
{post.body.split("\n").slice(0, 2).join("\n")}
</Markdown>
</div>
))}
<p>
<ConditionalNull condition={curPage > 1}>
<Link href={`?page=${curPage - 1}`}>Previous</Link>
</ConditionalNull>
{" "}
<ConditionalNull condition={curPage * 10 < blogs.data.total_posts}>
<Link href={`?page=${curPage + 1}`}>Next</Link>
</ConditionalNull>
</p>
</MainLayout>
)
}

92
src/buttons.json Normal file
View file

@ -0,0 +1,92 @@
[
{
"href": "https://jaiden.sh/",
"img": "/88x31/jaiden_sh.png",
"alt": "jaiden.sh"
},
{
"href": "https://owenzimmerman.com/",
"img": "/88x31/owenzimmerman_com.png",
"alt": "owenzimmerman.com"
},
{
"href": "https://acidicalchemist.neocities.org/",
"img": "/88x31/acidicalchemist_neocities_org.gif",
"alt": "acidicalchemist.neocities.org"
},
{
"href": "https://arimelody.me/",
"img": "/88x31/arimelody_me.gif",
"alt": "arimelody.me"
},
{
"href": "https://freeplay.floof.company/",
"img": "/88x31/freeplay_floof_company.png",
"alt": "freeplay.floof.company"
},
{
"href": "https://invoxiplaygames.uk/",
"img": "/88x31/invoxiplaygames_uk.png",
"alt": "invoxiplaygames.uk"
},
{
"href": "https://ioletsgo.gay/",
"img": "/88x31/ioletsgo_gay.gif",
"alt": "ioletsgo.gay"
},
{
"href": "https://mae.wtf/",
"img": "/88x31/mae_wtf.png",
"alt": "mae.wtf"
},
{
"href": "https://micro.pages.gay/",
"img": "/88x31/micro_pages_gay.png",
"alt": "micro.pages.gay"
},
{
"href": "https://sneexy.pages.gay/",
"img": "/88x31/sneexy_pages_gay.gif",
"alt": "sneexy.pages.gay"
},
{
"href": "https://whois.slipfox.xyz/",
"img": "/88x31/whois_slipfox_xyz.png",
"alt": "whois.slipfox.xyz"
},
{
"href": "https://moth.monster/",
"img": "/88x31/moth_monster.png",
"alt": "moth.monster"
},
{
"href": "https://translunar.academy/",
"img": "/88x31/translunar_academy.png",
"alt": "translunar.academy"
},
{
"href": "https://ultramarine-linux.org/",
"img": "/88x31/esoteric/gnu-linux.gif",
"alt": "Made on GNU/Linux"
},
{
"href": "https://abtmtr.link/projects/item/normalize",
"img": "/88x31/esoteric/html.gif",
"alt": "<HTML> - Learn it today!"
},
{
"href": "https://vivaldi.com",
"img": "/88x31/esoteric/vivaldi.gif",
"alt": "I use Vivaldi"
},
{
"href": "https://ublockorigin.com",
"img": "/88x31/esoteric/ublock.png",
"alt": "uBlock Origin Now!"
},
{
"href": "https://channelstore.roku.com/details/7da3fa0c2209746730df8a4e21e83b02",
"img": "/88x31/esoteric/xkcd.gif",
"alt": "xkcd"
}
]

View file

@ -0,0 +1,35 @@
import React from "react";
export function Conditional({
condition,
truthy,
falsy
}:{
condition: boolean,
truthy: React.ReactNode,
falsy: React.ReactNode
}) {
return condition ? truthy : falsy;
}
export function ConditionalNull({
condition,
children
}:{
condition: boolean,
children: React.ReactNode,
}) {
return condition ? children : <></>;
}
export function ConditionalParent({
condition,
parent,
children
}:{
condition: boolean,
parent: (childNode:React.ReactNode) => React.ReactNode,
children: React.ReactNode
}) {
return condition ? parent(children) : children;
}

View file

@ -0,0 +1,62 @@
'use client';
import { ConditionalNull } from "@/components/utility/Conditional";
import { useState } from "react";
import styles from "./MainLayout.module.css";
import { useRouter } from "next/navigation";
export function Desktop({
title,
subtitle,
backButton = false,
children,
sidebar
}:{
title: string,
subtitle?: string,
backButton?: boolean,
children: React.ReactNode,
sidebar: React.ReactNode
}) {
const [menuOpen, setMenuOpen] = useState<boolean>(false);
const router = useRouter();
return (
<div className={`${styles.MainLayout}`}>
<aside className={`${styles.MainLayout_Aside} ${menuOpen ? styles.Modifier_Open : ""}`}>
{sidebar}
</aside>
<div className={styles.MainLayout_Inner}>
<div className={`${
styles.MainLayout_Inner_Header
} ${
backButton ? styles.Modifier_Back : ""
}`}>
<ConditionalNull condition={!backButton}>
<div className={`${styles.MainLayout_Inner_Header_Back} ${styles.MainLayout_Inner_Header_Menu}`}>
<button className={styles.MainLayout_Inner_Header_Back_Button} onClick={() => setMenuOpen(!menuOpen)}>
menu
</button>
</div>
</ConditionalNull>
<ConditionalNull condition={backButton}>
<div className={styles.MainLayout_Inner_Header_Back}>
<button className={styles.MainLayout_Inner_Header_Back_Button} onClick={() => router.back()}>
arrow_back
</button>
</div>
</ConditionalNull>
<div className={styles.MainLayout_Inner_Header_Title}>
<h1>{title}</h1>
<span>{subtitle}</span>
</div>
</div>
<main className={styles.MainLayout_Inner_Main}>
<div className={`${styles.MainLayout_Inner_Main_Content} MainContent`}>
{children}
</div>
</main>
</div>
</div>
)
}

View file

@ -0,0 +1,186 @@
@keyframes slidein {
0% {
position: relative;
top: 10px;
opacity: 0;
}
99% {
position: relative;
top: 0;
opacity: 1;
}
100% {
position: static;
top: unset;
}
}
@keyframes slidein_header {
0% {
position: relative;
left: -10px;
opacity: 0;
}
99% {
position: relative;
left: 0;
opacity: 1;
}
100% {
position: static;
top: unset;
}
}
.MainLayout {
width: 100vw;
max-width: 100vw;
height: 100vh;
max-height: 100vh;
margin: 0;
padding: 0;
overflow: hidden;
display: grid;
grid-template-columns: 256px 1fr;
}
.MainLayout_Aside {
padding: 4px 16px;
background-color: var(--bg-1);
color: var(--fg-1);
border-right: 1px solid var(--neutral);
}
.MainLayout_Inner {
max-width: 100%;
max-height: 100%;
overflow: hidden;
display: grid;
grid-template-rows: 48px auto;
}
.MainLayout_Inner_Header {
padding: 6px 16px;
background-color: var(--bg-2);
color: var(--fg-2);
border-bottom: 1px solid var(--neutral);
display: grid;
grid-template-columns: auto;
gap: 8px;
align-items: center;
}
.MainLayout_Inner_Header>* {
animation-duration: 0.25s;
animation-name: slidein_header;
}
.MainLayout_Inner_Header.Modifier_Back {
padding: 6px 8px;
grid-template-columns: 32px auto;
}
.MainLayout_Inner_Header_Back_Button {
font-family: var(--font-MaterialSymbols);
font-size: 24px;
width: 32px;
height: 32px;
}
.MainLayout_Inner_Header_Menu {
display: none;
}
.MainLayout_Inner_Header_Title {
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
align-content: center;
}
.MainLayout_Inner_Header_Title h1 {
margin: 0;
vertical-align: middle;
font-size: 1em;
}
.MainLayout_Inner_Header_Title span {
margin: 0;
vertical-align: middle;
font-size: 0.75em;
}
.MainLayout_Inner_Main {
display: inline-block;
background-color: var(--bg-1);
color: var(--fg-1);
width: 100%;
overflow: hidden;
overflow-y: auto;
text-align: center;
position: relative;
}
.MainLayout_Inner_Main_Content {
display: inline-block;
text-align: left;
overflow: hidden;
background-color: var(--bg-2);
color: var(--fg-2);
padding: 0 16px;
max-width: 50em;
width: 100%;
min-height: 100%;
margin-bottom: -4px;
/* border-left: 1px solid var(--neutral);
border-right: 1px solid var(--neutral); */
}
.MainLayout_Inner_Main_Content>* {
animation-duration: 0.25s;
animation-name: slidein;
}
.MainLayout_Inner_Main_Content img {
max-width: 100%;
}
@media screen and (max-width: 800px) {
.MainLayout {
grid-template-columns: 1fr;
}
.MainLayout_Aside {
position: absolute;
height: calc(100% - 48px);
width: 100%;
top: 48px;
z-index: 50;
left: -100%;
transition: left 0.125s;
}
.MainLayout_Inner_Header_Menu {
display: inline-block;
}
.MainLayout_Inner_Header {
padding: 6px 8px;
grid-template-columns: 32px auto;
}
.MainLayout_Aside.Modifier_Open {
left: 0;
}
}

View file

@ -0,0 +1,22 @@
import { SidebarMain } from "./Sidebar/Main/Main";
import { Desktop } from "./Desktop";
export function MainLayout({
title,
subtitle,
backButton = false,
children,
sidebar,
currentPage
}:{
title: string,
subtitle?: string,
backButton?: boolean,
children: React.ReactNode,
sidebar?: React.ReactNode,
currentPage: string
}) {
return (<>
<Desktop title={title} subtitle={subtitle} backButton={backButton} sidebar={sidebar ?? SidebarMain({currentPage})}>{children}</Desktop>
</>)
}

View file

@ -0,0 +1,26 @@
.Main_Image {
text-align: center;
margin: 16px 0;
}
.Main_Image img {
border-radius: 8px;
}
.Main_List {
display: flex;
flex-direction: column;
gap: 8px;
}
.Main_List_CurrentLink button {
background-color: var(--bg-2);
}
.Main_List_Button {
transition: scale 0.0625s;
}
.Main_List_Button:active {
scale: 0.95;
}

View file

@ -0,0 +1,58 @@
import Link from "next/link";
import styles from "./Main.module.css";
export function SidebarMain({currentPage}:{currentPage:string}) {
return <>
<h1>abtmtr.link</h1>
{/* <div className={styles.Main_Image}>
<img src="/favicon.ico" width="128" height="128" />
</div> */}
<div className={styles.Main_List}>
<Link href="/" className={currentPage === "/" ? styles.Main_List_CurrentLink : ""}>
<button className={`fw ${styles.Main_List_Button}`}>
Root
</button>
</Link>
<Link href="/characters/" className={currentPage.includes("/characters/") ? styles.Main_List_CurrentLink : ""}>
<button className={`fw ${styles.Main_List_Button}`}>
Characters
</button>
</Link>
<Link href="/links/" className={currentPage.includes("/links/") ? styles.Main_List_CurrentLink : ""}>
<button className={`fw ${styles.Main_List_Button}`}>
Links
</button>
</Link>
<Link href="/oao/" className={currentPage.includes("/oao/") ? styles.Main_List_CurrentLink : ""}>
<button className={`fw ${styles.Main_List_Button}`}>
Opinions & Objections
</button>
</Link>
<Link href="/projects/" className={currentPage.includes("/projects/") ? styles.Main_List_CurrentLink : ""}>
<button className={`fw ${styles.Main_List_Button}`}>
Projects
</button>
</Link>
<Link href="/blog/" className={currentPage.includes("/blog/") ? styles.Main_List_CurrentLink : ""}>
<button className={`fw ${styles.Main_List_Button}`}>
Blog
</button>
</Link>
<Link href="/stories/" className={currentPage.includes("/stories/") ? styles.Main_List_CurrentLink : ""}>
<button className={`fw ${styles.Main_List_Button}`}>
Stories
</button>
</Link>
<Link href="/gallery/" className={currentPage.includes("/gallery/") ? styles.Main_List_CurrentLink : ""}>
<button className={`fw ${styles.Main_List_Button}`}>
Gallery
</button>
</Link>
<Link href="/about/" className={currentPage.includes("/about/") ? styles.Main_List_CurrentLink : ""}>
<button className={`fw ${styles.Main_List_Button}`}>
About
</button>
</Link>
</div>
</>
}

View file

@ -0,0 +1,12 @@
export class LanguageUtils_ENG {
static possess(owner: string, possession: string) {
return owner + (owner.endsWith("s") ? "'" : "'s") + " " + possession;
}
static pluralize(string: string) {
if (string.match(/(?:s|ch|sh|x|z|[^aeiou]o)$/)) return string + "es";
else if (string.match(/[aeiou](?:y|o)$/)) return string + "s";
else if (string.match(/[^aeiou]y$/)) return string.slice(0, -1) + "ies";
else if (string.match(/(?:f|fe)$/)) return string.slice(0, -1) + "ves";
else return string + "s";
}
}

View file

@ -0,0 +1,47 @@
export class ArrayUtils {
static pick<T>(array: T[]): T {
return array[Math.round(Math.random() * (array.length - 1))];
}
static pickMultiple<T>(array: T[], amount: number): T[] {
return array.toSorted(() => 0.5 - Math.random()).slice(0, amount);
}
}
export class NumberUtils {
static clamp(number: number, min: number, max: number) {
return Math.max(min, Math.min(number, max));
}
static isBetween(number: number, min: number, max: number) {
return number >= min && number <= max;
}
static pickRandom(min: number, max: number) {
return min + (Math.random() * (max - min));
}
}
export class StringUtils {
static truncate(string: string, length: number) {
if (string.length <= length) {
return string;
} else {
return string.slice(0, length - 3) + "...";
}
}
}
export class ObjectUtils {
static isObject(object: any) {
if (object == null) return false;
return object.constructor === Object;
}
static isEmpty(object: any) {
return Object.keys(object).length === 0;
}
static navigatePath(object: any, path: string) {
return path.split(".").reduce((objnav: any, pathseg: string) => {
return objnav[pathseg];
}, object);
}
}

View file

@ -0,0 +1,62 @@
export function AdaptivePossessive(owner: string, possession: string) {
return owner + (owner.endsWith("s") ? "'" : "'s") + " " + possession;
}
export function PronounGrouper(
pronouns: [string, string, string][],
sep?: string,
amount?: number
) {
if (pronouns.length > 1)
return pronouns.map(pronounSet => pronounSet[0]).join("/");
else return pronouns[0].slice(0, amount ?? 2).join(sep ?? "/");
}
export function HeightConverter(inches: number) {
var feetandinches = Math.floor(inches / 12) + "'" + (inches % 12) + '"';
var meters = Math.floor((inches / 39.37) * 100) / 100 + "m";
return feetandinches + " (" + meters + ")";
}
export function HeightConverterImperial(inches: number) {
return Math.floor(inches / 12) + "'" + (inches % 12) + '"';
}
export function HeightConverterMetric(inches: number) {
return Math.floor((inches / 39.37) * 100) / 100 + "m";
}
export function AgeConverter(age: number, years?: boolean) {
if (years) return `${Math.round((age / 2.1667) * 10) / 10} sweeps`;
else if (years == null) return `${Math.round(age)} years`;
else return `${Math.round(age * 2.1667 * 10) / 10} years`;
}
export function Pluralize(stringe: string) {
if (stringe.match(/(?:s|ch|sh|x|z|[^aeiou]o)$/)) return stringe + "es";
else if (stringe.match(/[aeiou](?:y|o)$/)) return stringe + "s";
else if (stringe.match(/[^aeiou]y$/)) return stringe.slice(0, -1) + "ies";
else if (stringe.match(/(?:f|fe)$/)) return stringe.slice(0, -1) + "ves";
else return stringe + "s";
}
export function ArraySample(array: any[]) {
return array[Math.floor(Math.random() * array.length)];
}
export function ProperNounCase(string: string) {
return string
.split(" ")
.map(x => x.charAt(0).toUpperCase() + x.slice(1).toLowerCase())
.join(" ");
}
export function PesterchumNameFormatter(string: string) {
return (
string +
" [" +
string
.replace(/^(([a-z])[a-z]+)(([A-Z])[a-z]+)$/, "$2$4")
.toUpperCase() +
"]"
);
}

84
src/styles/style.css Normal file
View file

@ -0,0 +1,84 @@
:root {
--accent-color: hsl(120, 40%, 75%);
--bg-0: hsl(120, 20%, 0%);
--bg-1: hsl(120, 20%, 6.25%);
--bg-2: hsl(120, 20%, 12.5%);
--bg-3: hsl(120, 20%, 25%);
--neutral: #80808080;
--fg-0: hsl(120, 20%, 50%);
--fg-1: hsl(120, 20%, 62.5%);
--fg-2: hsl(120, 20%, 75%);
--fg-3: hsl(120, 20%, 87.5%);
}
body {
width: 100vw;
height: 100vh;
margin: 0;
padding: 0;
box-sizing: border-box;
background-color: var(--bg-2);
color: var(--fg-2);
}
* {
box-sizing: border-box;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: var(--font-Renogare);
font-weight: normal;
}
a {
color: var(--accent-color);
}
h1,
h2,
h3,
p {
margin: 0.5em 0;
}
p {
white-space: pre-wrap;
}
button {
border: none;
padding: 0;
margin: 0;
background-color: transparent;
color: unset;
font-family: unset;
font-size: unset;
cursor: pointer;
transition: all 0.125s;
}
button:hover {
opacity: 0.75;
}
button.fw {
padding: 8px;
width: 100%;
border-radius: 4px;
text-align: left;
text-decoration: none;
}
button.fw a {
text-decoration: none;
}
button.fw:hover {
background-color: var(--bg-2);
opacity: 1;
}