Compare commits

...

5 Commits

Author SHA1 Message Date
081e709ba5
refactor: format stuffs fix warnings 2025-04-02 07:47:53 +03:00
25ee5d47e3
feat: more tooltips 2025-04-02 07:30:55 +03:00
e76819be04
style: format 2025-04-02 06:37:49 +03:00
22507a6ec3
refactor: smol changes 2025-04-02 05:24:31 +03:00
0e46dc943e
feat: update texT 2025-04-02 05:13:38 +03:00
19 changed files with 1010 additions and 852 deletions

@ -1,24 +1,19 @@
<script lang="ts"> <script lang="ts">
import Window from './window.svelte' import Window from './window.svelte';
import '../styles/app.css' import '../styles/app.css';
interface Props { interface Props {
title: any; title: string;
sticky: any; sticky: boolean;
prose?: boolean; prose?: boolean;
children?: import('svelte').Snippet; children?: import('svelte').Snippet;
} }
let { let { title, sticky, prose = true, children }: Props = $props();
title,
sticky,
prose = true,
children
}: Props = $props();
</script> </script>
<Window {title} {sticky}> <Window {title} {sticky}>
<div class="{prose ? "prose prose-ralsei leading-6 prose-ul:leading-5" : ""}"> <div class={prose ? 'prose prose-ralsei leading-6 prose-ul:leading-5' : ''}>
{@render children?.()} {@render children?.()}
</div> </div>
</Window> </Window>

@ -3,7 +3,7 @@
import { draggable } from '@neodrag/svelte'; import { draggable } from '@neodrag/svelte';
interface Props { interface Props {
title?: string | undefined; title?: string;
iconUri?: string; iconUri?: string;
id?: string; id?: string;
sticky?: boolean; sticky?: boolean;
@ -25,31 +25,33 @@
removePadding = false, removePadding = false,
center = false, center = false,
layered = false, layered = false,
style = "", style = '',
tooltip = false, tooltip = false,
children children
}: Props = $props(); }: Props = $props();
const scaleKeyframes = [ const scaleKeyframes = [
"window-open", 'window-open',
"window-open-vertical", 'window-open-vertical',
"window-open-vertical", 'window-open-vertical',
"window-open-horizontal", 'window-open-horizontal',
"window-open-horizontal", 'window-open-horizontal',
"window-open-move-up", 'window-open-move-up',
"window-open-move-down", 'window-open-move-down',
"window-open-move-left", 'window-open-move-left',
"window-open-move-right", 'window-open-move-right'
]; ];
let chosenKeyframe = $derived(scaleKeyframes.at(Math.floor(Math.random() * scaleKeyframes.length))) let chosenKeyframe = $derived(
scaleKeyframes.at(Math.floor(Math.random() * scaleKeyframes.length))
);
const isOnMobile = isMobile() const isOnMobile = isMobile();
const _draggable = isOnMobile ? () => {} : draggable; const _draggable = isOnMobile ? () => {} : draggable;
const focusWindow = (node: HTMLElement) => { const focusWindow = (node: HTMLElement) => {
$highestZIndex += 1 $highestZIndex += 1;
node.style.zIndex = $highestZIndex.toString() node.style.zIndex = $highestZIndex.toString();
} };
</script> </script>
<!-- svelte-ignore a11y_no_static_element_interactions --> <!-- svelte-ignore a11y_no_static_element_interactions -->
@ -59,13 +61,23 @@
disabled: isOnMobile, disabled: isOnMobile,
applyUserSelectHack: true, applyUserSelectHack: true,
handle: '.window-titlebar', handle: '.window-titlebar',
onDragStart: (data) => {focusWindow(data.currentNode)}, onDragStart: (data) => {
focusWindow(data.currentNode);
}
}}
onclick={(data) => {
focusWindow(data.currentTarget);
}} }}
onclick={(data) => {focusWindow(data.currentTarget)}}
class=" class="
relative {layered ? "col-[1] row-[1]" : ""} flex flex-col {sticky ? 'md:sticky md:-top-9' : ''} {center ? "mx-auto" : ""} relative {layered ? 'col-[1] row-[1]' : ''} flex flex-col {sticky
max-w-screen-md xl:max-w-screen-lg 2xl:max-w-screen-xl {tooltip ? "min-w-fit" : "min-w-[30ch] lg:min-w-[40ch]"} w-full md:w-fit [height:fit-content] ? 'md:sticky md:-top-9'
bg-ralsei-black border-ralsei-white border-ridge {tooltip ? "border-[6px] border-t-[9px]" : "border-8 border-t-[12px]"} : ''} {center ? 'mx-auto' : ''}
max-w-screen-md xl:max-w-screen-lg 2xl:max-w-screen-xl {tooltip
? 'min-w-fit'
: 'min-w-[30ch] lg:min-w-[40ch]'} w-full md:w-fit [height:fit-content]
bg-ralsei-black border-ralsei-white border-ridge {tooltip
? 'border-[6px] border-t-[9px]'
: 'border-8 border-t-[12px]'}
animate-{chosenKeyframe} drop-shadow-[24px_24px_24px_rgba(1,1,1,0.8)] animate-{chosenKeyframe} drop-shadow-[24px_24px_24px_rgba(1,1,1,0.8)]
{style} {style}
" "
@ -76,7 +88,7 @@
class=" class="
window-titlebar p-1 border-ralsei-white border-8 window-titlebar p-1 border-ralsei-white border-8
bg-gradient-to-l from-ralsei-pink-neon to-ralsei-black to-75% bg-gradient-to-l from-ralsei-pink-neon to-ralsei-black to-75%
{!isOnMobile ? "cursor-move" : ""} {!isOnMobile ? 'cursor-move' : ''}
" "
style="border-style: hidden hidden ridge hidden;" style="border-style: hidden hidden ridge hidden;"
> >
@ -100,10 +112,12 @@
</div> </div>
</div> </div>
{/if} {/if}
<div class=" <div
{removePadding ? "" : tooltip ? "p-1" : "p-2"} bg-gradient-to-tl class="
{removePadding ? '' : tooltip ? 'p-1' : 'p-2'} bg-gradient-to-tl
to-ralsei-pink-neon/15 from-ralsei-pink-regular/20 to-ralsei-pink-neon/15 from-ralsei-pink-regular/20
"> "
>
{@render children?.()} {@render children?.()}
</div> </div>
</div> </div>

@ -1,48 +1,56 @@
import { env } from '$env/dynamic/private' import { env } from '$env/dynamic/private';
import { Bot, type Post } from "@skyware/bot"; import { Bot, type Post } from '@skyware/bot';
import { get, writable } from 'svelte/store' import { get, writable } from 'svelte/store';
const bskyClient = writable<null | Bot>(null) const bskyClient = writable<null | Bot>(null);
export const getBskyClient = async () => { export const getBskyClient = async () => {
let client = get(bskyClient) let client = get(bskyClient);
if (client === null) { if (client === null) {
client = await loginToBsky() client = await loginToBsky();
bskyClient.set(client) bskyClient.set(client);
} }
return client return client;
} };
const loginToBsky = async () => { const loginToBsky = async () => {
const bot = new Bot({ service: "https://gaze.systems" }) const bot = new Bot({ service: 'https://gaze.systems' });
await bot.login({ identifier: 'guestbook.gaze.systems', password: env.BSKY_PASSWORD ?? "" }) await bot.login({ identifier: 'guestbook.gaze.systems', password: env.BSKY_PASSWORD ?? '' });
return bot return bot;
} };
export const getUserPosts = async (did: string, count: number = 10, cursor: string | null = null) => { export const getUserPosts = async (
const client = await getBskyClient() did: string,
let feedCursor: string | null | undefined = cursor; count: number = 10,
let posts: Post[] = [] cursor: string | null = null
// fetch requested amount of posts ) => {
while (posts.length < count - 1 && (typeof feedCursor === "string" || feedCursor === null)) { const client = await getBskyClient();
let feedData = await client.getUserPosts( let feedCursor: string | null | undefined = cursor;
did, { limit: count, filter: 'posts_no_replies', cursor: feedCursor === null ? undefined : feedCursor } const posts: Post[] = [];
) // fetch requested amount of posts
posts.push(...feedData.posts.filter((post) => post.author.did === did)) while (posts.length < count - 1 && (typeof feedCursor === 'string' || feedCursor === null)) {
feedCursor = feedData.cursor const feedData = await client.getUserPosts(did, {
} limit: count,
return { posts, cursor: feedCursor === null ? undefined : feedCursor } filter: 'posts_no_replies',
} cursor: feedCursor === null ? undefined : feedCursor
});
posts.push(...feedData.posts.filter((post) => post.author.did === did));
feedCursor = feedData.cursor;
}
return { posts, cursor: feedCursor === null ? undefined : feedCursor };
};
const lastPosts = writable<Post[]>([]) const lastPosts = writable<Post[]>([]);
export const updateLastPosts = async () => { export const updateLastPosts = async () => {
try { try {
const { posts } = await getUserPosts("did:plc:dfl62fgb7wtjj3fcbb72naae", 13) const { posts } = await getUserPosts('did:plc:dfl62fgb7wtjj3fcbb72naae', 13);
lastPosts.set(posts) lastPosts.set(posts);
} catch (err) { } catch (err) {
console.log(`can't update last posts ${err}`) console.log(`can't update last posts ${err}`);
} }
} };
export const getLastPosts = () => { return get(lastPosts) } export const getLastPosts = () => {
return get(lastPosts);
};

@ -1,37 +1,45 @@
import { get, writable } from "svelte/store" import { get, writable } from 'svelte/store';
const GET_RECENT_TRACKS_ENDPOINT = "https://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=yusdacra&api_key=da1911d405b5b37383e200b8f36ee9ec&format=json&limit=1" const GET_RECENT_TRACKS_ENDPOINT =
'https://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=yusdacra&api_key=da1911d405b5b37383e200b8f36ee9ec&format=json&limit=1';
type LastTrack = { type LastTrack = {
name: string, name: string;
artist: string, artist: string;
image: string | null, image: string | null;
link: string, link: string;
when: number, when: number;
playing: boolean, playing: boolean;
} };
const lastTrack = writable<LastTrack | null>(null) const lastTrack = writable<LastTrack | null>(null);
export const lastFmUpdateNowPlaying = async () => { export const lastFmUpdateNowPlaying = async () => {
try { try {
var resp = await (await fetch(GET_RECENT_TRACKS_ENDPOINT)).json() const resp = await (await fetch(GET_RECENT_TRACKS_ENDPOINT)).json();
var track = resp.recenttracks.track[0] ?? null const track = resp.recenttracks.track[0] ?? null;
if (!((track['@attr'] ?? {}).nowplaying ?? null)) { if (!((track['@attr'] ?? {}).nowplaying ?? null)) {
throw "no nowplaying track found" throw 'no nowplaying track found';
} }
var data = { const data = {
name: track.name, name: track.name,
artist: track.artist['#text'], artist: track.artist['#text'],
image: track.image[2]['#text'] ?? null, image: track.image[2]['#text'] ?? null,
link: track.url, link: track.url,
when: Date.now(), when: Date.now(),
playing: true, playing: true
} };
lastTrack.set(data) lastTrack.set(data);
} catch(why) { } catch (why) {
console.log("could not fetch last fm: ", why) console.log('could not fetch last fm: ', why);
lastTrack.update((t) => { if (t !== null) { t.playing = false; } return t }) lastTrack.update((t) => {
} if (t !== null) {
} t.playing = false;
}
return t;
});
}
};
export const getNowPlaying = () => { return get(lastTrack) } export const getNowPlaying = () => {
return get(lastTrack);
};

@ -1,48 +1,55 @@
import { env } from "$env/dynamic/private"; import { env } from '$env/dynamic/private';
import SGDB from "steamgriddb"; import SGDB from 'steamgriddb';
import { get, writable } from "svelte/store"; import { get, writable } from 'svelte/store';
const STEAM_ID = "76561198106829949" const STEAM_ID = '76561198106829949';
const GET_PLAYER_SUMMARY_ENDPOINT = `http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=${env.STEAM_API_KEY}&steamids=${STEAM_ID}&format=json` const GET_PLAYER_SUMMARY_ENDPOINT = `http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=${env.STEAM_API_KEY}&steamids=${STEAM_ID}&format=json`;
type LastGame = { type LastGame = {
name: string, name: string;
link: string, link: string;
icon: string, icon: string;
pfp: string, pfp: string;
when: number, when: number;
playing: boolean, playing: boolean;
} };
const steamgriddbClient = writable<SGDB | null>(null) const steamgriddbClient = writable<SGDB | null>(null);
const lastGame = writable<LastGame | null>(null) const lastGame = writable<LastGame | null>(null);
export const steamUpdateNowPlaying = async () => { export const steamUpdateNowPlaying = async () => {
var griddbClient = get(steamgriddbClient) let griddbClient = get(steamgriddbClient);
if (griddbClient === null) { if (griddbClient === null) {
griddbClient = new SGDB(env.STEAMGRIDDB_API_KEY) griddbClient = new SGDB(env.STEAMGRIDDB_API_KEY);
steamgriddbClient.set(griddbClient) steamgriddbClient.set(griddbClient);
} }
try { try {
var profile = (await (await fetch(GET_PLAYER_SUMMARY_ENDPOINT)).json()).response.players[0] const profile = (await (await fetch(GET_PLAYER_SUMMARY_ENDPOINT)).json()).response.players[0];
if (!profile.gameid) { if (!profile.gameid) {
throw "no game is being played" throw 'no game is being played';
} }
var icons = await griddbClient.getIconsBySteamAppId(profile.gameid, ['official', 'custom']) const icons = await griddbClient.getIconsBySteamAppId(profile.gameid, ['official', 'custom']);
//console.log(icons) //console.log(icons)
var game: LastGame = { const game: LastGame = {
name: profile.gameextrainfo, name: profile.gameextrainfo,
link: `https://store.steampowered.com/app/${profile.gameid}`, link: `https://store.steampowered.com/app/${profile.gameid}`,
icon: icons[0].thumb.toString(), icon: icons[0].thumb.toString(),
pfp: profile.avatarmedium, pfp: profile.avatarmedium,
when: Date.now(), when: Date.now(),
playing: true, playing: true
} };
lastGame.set(game) lastGame.set(game);
} catch(why) { } catch (why) {
console.log("could not fetch steam: ", why) console.log('could not fetch steam: ', why);
lastGame.update((t) => { if (t !== null) { t.playing = false; } return t }) lastGame.update((t) => {
} if (t !== null) {
} t.playing = false;
}
return t;
});
}
};
export const getLastGame = () => { return get(lastGame) } export const getLastGame = () => {
return get(lastGame);
};

@ -1,110 +1,122 @@
import { env } from "$env/dynamic/private"; import { env } from '$env/dynamic/private';
import { scopeCookies } from "$lib"; import { scopeCookies } from '$lib';
import type { Cookies } from "@sveltejs/kit"; import type { Cookies } from '@sveltejs/kit';
import { existsSync, readFileSync, writeFileSync } from "fs"; import { existsSync, readFileSync, writeFileSync } from 'fs';
import { nanoid } from "nanoid"; import { nanoid } from 'nanoid';
import { get, writable } from "svelte/store"; import { get, writable } from 'svelte/store';
const visitCountFile = `${env.WEBSITE_DATA_DIR}/visitcount` const visitCountFile = `${env.WEBSITE_DATA_DIR}/visitcount`;
const visitCount = writable(parseInt(existsSync(visitCountFile) ? readFileSync(visitCountFile).toString() : '0')) const visitCount = writable(
parseInt(existsSync(visitCountFile) ? readFileSync(visitCountFile).toString() : '0')
);
type Visitor = { visits: number[] } type Visitor = { visits: number[] };
const lastVisitors = writable<Map<string, Visitor>>(new Map()) const lastVisitors = writable<Map<string, Visitor>>(new Map());
const VISITOR_EXPIRY_SECONDS = 60 * 60 // an hour seems reasonable const VISITOR_EXPIRY_SECONDS = 60 * 60; // an hour seems reasonable
export const incrementVisitCount = (request: Request, cookies: Cookies) => { export const incrementVisitCount = (request: Request, cookies: Cookies) => {
let currentVisitCount = get(visitCount) let currentVisitCount = get(visitCount);
// check whether the request is from a bot or not (this doesnt need to be accurate we just want to filter out honest bots) // check whether the request is from a bot or not (this doesnt need to be accurate we just want to filter out honest bots)
if (isBot(request)) { return currentVisitCount } if (isBot(request)) {
const scopedCookies = scopeCookies(cookies, '/') return currentVisitCount;
// parse the last visit timestamp from cookies if it exists }
const visitedTimestamp = parseInt(scopedCookies.get('visitedTimestamp') || "0") const scopedCookies = scopeCookies(cookies, '/');
// get unix timestamp // parse the last visit timestamp from cookies if it exists
const currentTime = Date.now() const visitedTimestamp = parseInt(scopedCookies.get('visitedTimestamp') || '0');
const timeSinceVisit = currentTime - visitedTimestamp // get unix timestamp
// check if this is the first time a client is visiting or if an hour has passed since they last visited const currentTime = Date.now();
if (visitedTimestamp === 0 || timeSinceVisit > 1000 * 60 * 60 * 24) { const timeSinceVisit = currentTime - visitedTimestamp;
// increment current and write to the store // check if this is the first time a client is visiting or if an hour has passed since they last visited
currentVisitCount += 1; visitCount.set(currentVisitCount) if (visitedTimestamp === 0 || timeSinceVisit > 1000 * 60 * 60 * 24) {
// update the cookie with the current timestamp // increment current and write to the store
scopedCookies.set('visitedTimestamp', currentTime.toString()) currentVisitCount += 1;
// write the visit count to a file so we can load it later again visitCount.set(currentVisitCount);
writeFileSync(visitCountFile, currentVisitCount.toString()) // update the cookie with the current timestamp
} scopedCookies.set('visitedTimestamp', currentTime.toString());
return currentVisitCount // write the visit count to a file so we can load it later again
} writeFileSync(visitCountFile, currentVisitCount.toString());
}
return currentVisitCount;
};
export const addLastVisitor = (request: Request, cookies: Cookies) => { export const addLastVisitor = (request: Request, cookies: Cookies) => {
let visitors = get(lastVisitors) let visitors = get(lastVisitors);
visitors = _addLastVisitor(visitors, request, cookies) visitors = _addLastVisitor(visitors, request, cookies);
lastVisitors.set(visitors) lastVisitors.set(visitors);
return visitors return visitors;
} };
export const getVisitorId = (cookies: Cookies) => { export const getVisitorId = (cookies: Cookies) => {
const scopedCookies = scopeCookies(cookies, '/') const scopedCookies = scopeCookies(cookies, '/');
// parse the last visit timestamp from cookies if it exists // parse the last visit timestamp from cookies if it exists
return scopedCookies.get('visitorId') return scopedCookies.get('visitorId');
} };
// why not use this for incrementVisitCount? cuz i wanna have separate visit counts (one per hour and one per day, per hour being recent visitors) // why not use this for incrementVisitCount? cuz i wanna have separate visit counts (one per hour and one per day, per hour being recent visitors)
const _addLastVisitor = (visitors: Map<string, Visitor>, request: Request, cookies: Cookies) => { const _addLastVisitor = (visitors: Map<string, Visitor>, request: Request, cookies: Cookies) => {
const currentTime = Date.now() const currentTime = Date.now();
// filter out old entries // filter out old entries
visitors.forEach((visitor, id, map) => { visitors.forEach((visitor, id, map) => {
if (currentTime - visitor.visits[0] > 1000 * VISITOR_EXPIRY_SECONDS) if (currentTime - visitor.visits[0] > 1000 * VISITOR_EXPIRY_SECONDS) map.delete(id);
map.delete(id) else {
else { visitor.visits = visitor.visits.filter((since) => {
visitor.visits = visitor.visits.filter((since) => { return currentTime - since < 1000 * VISITOR_EXPIRY_SECONDS;
return currentTime - since < 1000 * VISITOR_EXPIRY_SECONDS });
}) map.set(id, visitor);
map.set(id, visitor) }
} });
}) // check whether the request is from a bot or not (this doesnt need to be accurate we just want to filter out honest bots)
// check whether the request is from a bot or not (this doesnt need to be accurate we just want to filter out honest bots) if (isBot(request)) {
if (isBot(request)) { return visitors } return visitors;
const scopedCookies = scopeCookies(cookies, '/') }
// parse the last visit timestamp from cookies if it exists const scopedCookies = scopeCookies(cookies, '/');
let visitorId = scopedCookies.get('visitorId') || "" // parse the last visit timestamp from cookies if it exists
// if no such id exists, create one and assign it to the client let visitorId = scopedCookies.get('visitorId') || '';
if (! visitors.has(visitorId)) { // if no such id exists, create one and assign it to the client
visitorId = nanoid() if (!visitors.has(visitorId)) {
scopedCookies.set('visitorId', visitorId) visitorId = nanoid();
console.log(`new client visitor id ${visitorId}`) scopedCookies.set('visitorId', visitorId);
} console.log(`new client visitor id ${visitorId}`);
// update the entry }
let visitorEntry = visitors.get(visitorId) || {visits: []} // update the entry
// put new visit in the front const visitorEntry = visitors.get(visitorId) || { visits: [] };
visitorEntry.visits = [currentTime].concat(visitorEntry.visits) // put new visit in the front
visitors.set(visitorId, visitorEntry); visitorEntry.visits = [currentTime].concat(visitorEntry.visits);
return visitors visitors.set(visitorId, visitorEntry);
} return visitors;
};
const isBot = (request: Request) => { const isBot = (request: Request) => {
const ua = request.headers.get('user-agent') const ua = request.headers.get('user-agent');
return ua ? ua.toLowerCase().match(/(bot|crawl|spider|walk|fetch|scrap|proxy|image)/) !== null : true return ua
} ? ua.toLowerCase().match(/(bot|crawl|spider|walk|fetch|scrap|proxy|image)/) !== null
: true;
};
export const notifyDarkVisitors = (url: URL, request: Request) => { export const notifyDarkVisitors = (url: URL, request: Request) => {
fetch('https://api.darkvisitors.com/visits', { fetch('https://api.darkvisitors.com/visits', {
method: 'POST', method: 'POST',
headers: { headers: {
authorization: `Bearer ${env.DARK_VISITORS_TOKEN}`, authorization: `Bearer ${env.DARK_VISITORS_TOKEN}`,
'content-type': 'application/json', 'content-type': 'application/json'
}, },
body: JSON.stringify({ body: JSON.stringify({
request_path: url.pathname, request_path: url.pathname,
request_method: request.method, request_method: request.method,
request_headers: request.headers, request_headers: request.headers
}) })
}).catch((why) => { })
console.log("failed sending dark visitors analytics:", why) .catch((why) => {
return null console.log('failed sending dark visitors analytics:', why);
}).then(async (resp) => { return null;
if (resp !== null) { })
const msg = await resp.json() .then(async (resp) => {
const host = `(${request.headers.get('host')} ${request.headers.get('x-real-ip')})` if (resp !== null) {
console.log(`sent visitor analytic to dark visitors: ${resp.statusText}; ${msg.message ?? ''}${host}`) const msg = await resp.json();
} const host = `(${request.headers.get('host')} ${request.headers.get('x-real-ip')})`;
}) console.log(
} `sent visitor analytic to dark visitors: ${resp.statusText}; ${msg.message ?? ''}${host}`
);
}
});
};

@ -1,9 +1,20 @@
import { writable } from "svelte/store"; /* eslint-disable no-useless-escape */
import { writable } from 'svelte/store';
export const highestZIndex = writable(0) export const highestZIndex = writable(0);
export const isMobile = () => { export const isMobile = () => {
let check = false; let check = false;
(function (a) { if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4))) check = true; })(navigator.userAgent || navigator.vendor); (function (a) {
return check; if (
} /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(
a
) ||
/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(
a.slice(0, 4)
)
)
check = true;
})(navigator.userAgent || navigator.vendor);
return check;
};

@ -5,26 +5,26 @@ import { error } from '@sveltejs/kit';
export const csr = true; export const csr = true;
export const ssr = true; export const ssr = true;
export const prerender = false; export const prerender = false;
export const trailingSlash = 'always'; export const trailingSlash = 'always';
export async function load({ request, cookies, url }) { export async function load({ request, cookies, url }) {
notifyDarkVisitors(url, request) // no await so it doesnt block load notifyDarkVisitors(url, request); // no await so it doesnt block load
// block any requests if the user agent is disallowed by our robots txt // block any requests if the user agent is disallowed by our robots txt
if (await testUa(url.toString(), request.headers.get('user-agent') ?? "") === false) { if ((await testUa(url.toString(), request.headers.get('user-agent') ?? '')) === false) {
throw error(403, "get a better user agent silly") throw error(403, 'get a better user agent silly');
} }
const lastVisitors = addLastVisitor(request, cookies) const lastVisitors = addLastVisitor(request, cookies);
let recentVisitCount = 0 let recentVisitCount = 0;
for (const [_, visitor] of lastVisitors) { for (const [, visitor] of lastVisitors) {
recentVisitCount += visitor.visits.length recentVisitCount += visitor.visits.length;
} }
return { return {
route: url.pathname, route: url.pathname,
visitCount: incrementVisitCount(request, cookies), visitCount: incrementVisitCount(request, cookies),
lastVisitors, lastVisitors,
recentVisitCount, recentVisitCount
} };
} }

@ -5,6 +5,7 @@
import '../styles/app.css'; import '../styles/app.css';
interface Props { interface Props {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data: any; data: any;
children?: import('svelte').Snippet; children?: import('svelte').Snippet;
} }
@ -73,7 +74,7 @@
<filter id="squiggly-{index}"> <filter id="squiggly-{index}">
<feTurbulence <feTurbulence
id="turbulence" id="turbulence"
baseFrequency=0.03 baseFrequency="0.03"
numOctaves="3" numOctaves="3"
result="noise" result="noise"
seed={index} seed={index}
@ -134,7 +135,9 @@
</defs> </defs>
</svg> </svg>
<div class="md:h-[96vh] pb-[8vh] lg:px-[1vw] 2xl:px-[2vw] lg:pb-[3vh] lg:pt-[1vh] overflow-x-hidden [scrollbar-gutter:stable]"> <div
class="md:h-[96vh] pb-[8vh] lg:px-[1vw] 2xl:px-[2vw] lg:pb-[3vh] lg:pt-[1vh] overflow-x-hidden [scrollbar-gutter:stable]"
>
{@render children?.()} {@render children?.()}
</div> </div>
@ -152,7 +155,12 @@
{@const highlight = isRoute(item.href)} {@const highlight = isRoute(item.href)}
<NavButton {highlight} {...item} /> <NavButton {highlight} {...item} />
{#if doAddPostItem && menuIdx == 1} {#if doAddPostItem && menuIdx == 1}
<NavButton highlight name={routeComponents[2]} href={data.route.slice(1)} iconUri='/icons/entry.webp'/> <NavButton
highlight
name={routeComponents[2]}
href={data.route.slice(1)}
iconUri="/icons/entry.webp"
/>
{/if} {/if}
{/each} {/each}
<div class="hidden md:block grow"></div> <div class="hidden md:block grow"></div>
@ -163,22 +171,33 @@
</div> </div>
<Tooltip> <Tooltip>
{#snippet tooltipContent()} {#snippet tooltipContent()}
<p class="font-monospace">
<p class="font-monospace"> <nobr
<nobr>total visits = <span class="text-ralsei-green-light text-shadow-green">{data.visitCount.toString().padStart(9, ".")}</span></nobr> >total visits = <span class="text-ralsei-green-light text-shadow-green"
<nobr>uniq recent visits = <span class="text-ralsei-green-light text-shadow-green">{data.lastVisitors.size.toString().padStart(3, ".")}</span></nobr> >{data.visitCount.toString().padStart(9, '.')}</span
</p> ></nobr
>
{/snippet} <nobr
<div class="navbox"><p><span class="text-ralsei-green-light text-shadow-green">{data.recentVisitCount}</span> recent clicks</p></div> >uniq recent visits = <span class="text-ralsei-green-light text-shadow-green"
>{data.lastVisitors.size.toString().padStart(3, '.')}</span
></nobr
>
</p>
{/snippet}
<div class="navbox">
<p>
<span class="text-ralsei-green-light text-shadow-green">{data.recentVisitCount}</span> recent
clicks
</p>
</div>
</Tooltip> </Tooltip>
{#if isRoute("entries") || isRoute("log")} {#if isRoute('entries') || isRoute('log')}
<div class="navbox !gap-1"> <div class="navbox !gap-1">
rss: rss:
<a class="align-middle hover:underline" href="/entries/_rss">posts</a> <a class="align-middle hover:underline" href="/entries/_rss">posts</a>
/ /
<a class="align-middle hover:underline" href="/log/_rss">log</a> <a class="align-middle hover:underline" href="/log/_rss">log</a>
</div> </div>
{/if} {/if}
</div> </div>
</div> </div>
@ -189,4 +208,4 @@
@apply flex gap-3 px-1.5 text-nowrap align-middle items-center text-center place-content-center border-ralsei-white border-4; @apply flex gap-3 px-1.5 text-nowrap align-middle items-center text-center place-content-center border-ralsei-white border-4;
border-style: groove; border-style: groove;
} }
</style> </style>

@ -3,9 +3,11 @@
import Note from '../components/note.svelte'; import Note from '../components/note.svelte';
import Window from '../components/window.svelte'; import Window from '../components/window.svelte';
import LatestStuff from './lateststuff.md'; import LatestStuff from './lateststuff.md';
import {renderDate, renderRelativeDate} from '$lib/dateFmt'; import { renderDate, renderRelativeDate } from '$lib/dateFmt';
import Tooltip from '../components/tooltip.svelte';
interface Props { interface Props {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data: any; data: any;
} }
@ -16,9 +18,7 @@
<div class="flex flex-col gap-y-2 lg:gap-y-0 mx-auto"> <div class="flex flex-col gap-y-2 lg:gap-y-0 mx-auto">
<Window title="readme?" iconUri="/icons/question.webp" removePadding> <Window title="readme?" iconUri="/icons/question.webp" removePadding>
<div class="flex flex-col p-1.5 gap-1.5 prose prose-ralsei prose-img:m-0 leading-none"> <div class="flex flex-col p-1.5 gap-1.5 prose prose-ralsei prose-img:m-0 leading-none">
<div <div class="flex flex-row gap-3 mx-auto bg-ralsei-black/20 overflow-hidden">
class="flex flex-row gap-3 mx-auto bg-ralsei-black/20 overflow-hidden"
>
{#each data.banners as bannerNo, index} {#each data.banners as bannerNo, index}
{@const hideIfMobile = index === data.banners.length - 1} {@const hideIfMobile = index === data.banners.length - 1}
<img <img
@ -35,11 +35,20 @@
{/each} {/each}
</div> </div>
<div class="flex flex-grow"> <div class="flex flex-grow">
<div <Tooltip>
class="w-36 [padding:8px] place-content-center place-self-center bg-ralsei-black/20" {#snippet tooltipContent()}
> that's me! my angelsona :3c
<img class="w-36 u-photo hover:invert transition-all [transition-duration:300ms]" src="/pfp-iojkqpwerojnasduijf.webp" alt="my character" title="hi ;)"/> {/snippet}
</div> <div
class="w-36 [padding:8px] place-content-center place-self-center bg-ralsei-black/20"
>
<img
class="w-36 u-photo hover:invert transition-all [transition-duration:300ms]"
src="/pfp-iojkqpwerojnasduijf.webp"
alt="my angelsona"
/>
</div>
</Tooltip>
<div <div
class="flex flex-row flex-grow place-content-center ml-1.5 [padding:8px] bg-ralsei-black/20" class="flex flex-row flex-grow place-content-center ml-1.5 [padding:8px] bg-ralsei-black/20"
> >
@ -47,7 +56,19 @@
class="place-self-center m-0 mr-4 [padding-left:1em] sm:[padding-left:0.5em] leading-none marker:[content:'->'] [list-style-type:'->']" class="place-self-center m-0 mr-4 [padding-left:1em] sm:[padding-left:0.5em] leading-none marker:[content:'->'] [list-style-type:'->']"
> >
<li class="[list-style-type:'->'] p-note">trying to do stuff</li> <li class="[list-style-type:'->'] p-note">trying to do stuff</li>
<li class="[list-style-type:'->'] p-note">is a thing that exists</li> <li class="[list-style-type:'->'] p-note">
<Tooltip
x="translate-x-none"
y="translate-y-none"
targetX="group-hover:translate-x-[80%]"
targetY="group-hover:-translate-y-[70%]"
>
{#snippet tooltipContent()}
angelrobotdollpuppything
{/snippet}
is a thing (it/they)
</Tooltip>
</li>
<li class="[list-style-type:'->']"> <li class="[list-style-type:'->']">
<span class="p-category">software engineer</span>, <span class="p-category">software engineer</span>,
<span class="p-category">indie game dev</span> <span class="p-category">indie game dev</span>
@ -95,7 +116,9 @@
</div> </div>
<div class="flex flex-col gap-y-2 lg:gap-y-0 mx-auto w-full md:w-fit place-items-end"> <div class="flex flex-col gap-y-2 lg:gap-y-0 mx-auto w-full md:w-fit place-items-end">
<Window title="links!" iconUri="/icons/contact.webp"> <Window title="links!" iconUri="/icons/contact.webp">
<div class="prose prose-ralsei prose-ul:px-[0.9rem] prose-ul:leading-[1.1rem] prose-headings:leading-none"> <div
class="prose prose-ralsei prose-ul:px-[0.9rem] prose-ul:leading-[1.1rem] prose-headings:leading-none"
>
<ul> <ul>
<li>discord: yusdacra</li> <li>discord: yusdacra</li>
<li> <li>
@ -135,102 +158,110 @@
</ul> </ul>
<h4>88x31</h4> <h4>88x31</h4>
<div class="flex flex-row flex-wrap gap-1 prose-img:m-0"> <div class="flex flex-row flex-wrap gap-1 prose-img:m-0">
<img src="/88x31.gif" alt="88x31 banner" title="midnight AND sunrise! woaw"/> <img src="/88x31.gif" alt="88x31 banner" title="midnight AND sunrise! woaw" />
<img src="/88x31_midnight.gif" alt="88x31 banner (midnight only)" title="it's midnight!"/> <img
<img src="/88x31_sunrise.gif" alt="88x31 banner (sunrise only)" title="it's sunrise!"/> src="/88x31_midnight.gif"
alt="88x31 banner (midnight only)"
title="it's midnight!"
/>
<img src="/88x31_sunrise.gif" alt="88x31 banner (sunrise only)" title="it's sunrise!" />
</div> </div>
</div> </div>
</Window> </Window>
<Window title="status" style="mt-auto" removePadding> <Window title="status" style="mt-auto" removePadding>
{#if data.lastNote} {#if data.lastNote}
<div class="m-1.5 flex flex-col font-monospace text-sm"> <div class="m-1.5 flex flex-col font-monospace text-sm">
<p <p
class="prose prose-ralsei p-1 border-4 text-sm bg-ralsei-black" class="prose prose-ralsei p-1 border-4 text-sm bg-ralsei-black"
style="border-style: double double none double;" style="border-style: double double none double;"
title={renderDate(data.lastNote.published)} title={renderDate(data.lastNote.published)}
> >
<a href="/entries">last log was…</a> <a href="/entries">last log was…</a>
published {renderRelativeDate(data.lastNote.published)}! published {renderRelativeDate(data.lastNote.published)}!
</p> </p>
<div class="mt-0 p-1.5 border-4 border-double bg-ralsei-black min-w-full max-w-[40ch]"> <div class="mt-0 p-1.5 border-4 border-double bg-ralsei-black min-w-full max-w-[40ch]">
<Note note={data.lastNote} onlyContent/> <Note note={data.lastNote} onlyContent />
</div>
</div> </div>
</div>
{/if} {/if}
{#if data.lastTrack} {#if data.lastTrack}
<div class="flex flex-row gap-0.5 m-1.5 border-4 border-double bg-ralsei-black"> <div class="flex flex-row gap-0.5 m-1.5 border-4 border-double bg-ralsei-black">
<!-- svelte-ignore a11y_missing_attribute --> <!-- svelte-ignore a11y_missing_attribute -->
{#if data.lastTrack.image} {#if data.lastTrack.image}
<img
class="border-4 w-[4.5rem] h-[4.5rem]"
style="border-style: none double none none;"
src={data.lastTrack.image}
/>
{:else}
<img
class="border-4 w-[4.5rem] h-[4.5rem] p-2"
style="border-style: none double none none; image-rendering: pixelated;"
src="/icons/cd_audio.webp"
/>
{/if}
<div class="flex flex-col max-w-[40ch] p-2">
<p
class="text-shadow-green text-ralsei-green-light text-sm text-ellipsis text-nowrap overflow-hidden max-w-[30ch]"
>
<span class="text-sm text-shadow-white text-ralsei-white"
>{data.lastTrack.playing ? 'listening to' : 'listened to'}</span
>
<a
title={data.lastTrack.name}
href="https://www.last.fm/user/yusdacra"
class="hover:underline motion-safe:hover:animate-squiggle">{data.lastTrack.name}</a
>
</p>
<p
class="text-shadow-pink text-ralsei-pink-regular text-sm text-ellipsis text-nowrap overflow-hidden max-w-[30ch]"
>
<span class="text-shadow-white text-ralsei-white">by</span>
<span title={data.lastTrack.artist}>{data.lastTrack.artist}</span>
</p>
<p
class="text-shadow-white text-ralsei-white text-xs text-ellipsis text-nowrap overflow-hidden max-w-[30ch]"
>
{renderRelativeDate(data.lastTrack.when)}
</p>
</div>
</div>
{/if}
{#if data.lastGame}
<div class="flex flex-row m-1.5 border-4 border-double bg-ralsei-black">
<!-- svelte-ignore a11y_missing_attribute -->
<img <img
class="border-4 w-[4.5rem] h-[4.5rem]" class="border-4 w-[4.5rem] h-[4.5rem]"
style="border-style: none double none none;" style="border-style: none double none none;"
src={data.lastTrack.image} width="64"
height="64"
src={data.lastGame.icon}
/> />
{:else} <div class="flex flex-col max-w-[40ch] p-2 gap-0.5 overflow-hidden">
<img <p
class="border-4 w-[4.5rem] h-[4.5rem] p-2" class="text-shadow-green text-ralsei-green-light text-sm text-ellipsis text-nowrap overflow-hidden max-w-[30ch]"
style="border-style: none double none none; image-rendering: pixelated;" >
src="/icons/cd_audio.webp" <span class="text-sm text-shadow-white text-ralsei-white"
/> >{data.lastGame.playing ? 'playing' : 'played'}</span
{/if} >
<div class="flex flex-col max-w-[40ch] p-2"> <a title={data.lastGame.name} class="hover:underline" href={data.lastGame.link}
<p >{data.lastGame.name}</a
class="text-shadow-green text-ralsei-green-light text-sm text-ellipsis text-nowrap overflow-hidden max-w-[30ch]" >
> </p>
<span class="text-sm text-shadow-white text-ralsei-white">{data.lastTrack.playing ? "listening to" : "listened to"}</span> <p
class="text-shadow-white text-ralsei-white text-xs text-ellipsis text-nowrap overflow-hidden max-w-[30ch]"
>
{renderRelativeDate(data.lastGame.when)}
</p>
<!-- svelte-ignore a11y_missing_attribute -->
<a <a
title={data.lastTrack.name} href="https://steamcommunity.com/id/yusdacra"
href="https://www.last.fm/user/yusdacra" class="text-xs hover:underline text-shadow-green text-ralsei-green-light"
class="hover:underline motion-safe:hover:animate-squiggle">{data.lastTrack.name}</a ><img class="inline w-4" src={data.lastGame.pfp} />
<span class="align-middle">steam profile</span></a
> >
</p> </div>
<p
class="text-shadow-pink text-ralsei-pink-regular text-sm text-ellipsis text-nowrap overflow-hidden max-w-[30ch]"
>
<span class="text-shadow-white text-ralsei-white">by</span>
<span title={data.lastTrack.artist}>{data.lastTrack.artist}</span>
</p>
<p
class="text-shadow-white text-ralsei-white text-xs text-ellipsis text-nowrap overflow-hidden max-w-[30ch]"
>
{renderRelativeDate(data.lastTrack.when)}
</p>
</div> </div>
</div>
{/if}
{#if data.lastGame}
<div class="flex flex-row m-1.5 border-4 border-double bg-ralsei-black">
<!-- svelte-ignore a11y_missing_attribute -->
<img
class="border-4 w-[4.5rem] h-[4.5rem]"
style="border-style: none double none none;"
width="64"
height="64"
src={data.lastGame.icon}
/>
<div class="flex flex-col max-w-[40ch] p-2 gap-0.5 overflow-hidden">
<p
class="text-shadow-green text-ralsei-green-light text-sm text-ellipsis text-nowrap overflow-hidden max-w-[30ch]"
>
<span class="text-sm text-shadow-white text-ralsei-white">{data.lastGame.playing ? "playing" : "played"}</span>
<a title={data.lastGame.name} class="hover:underline" href={data.lastGame.link}
>{data.lastGame.name}</a
>
</p>
<p
class="text-shadow-white text-ralsei-white text-xs text-ellipsis text-nowrap overflow-hidden max-w-[30ch]"
>
{renderRelativeDate(data.lastGame.when)}
</p>
<!-- svelte-ignore a11y_missing_attribute -->
<a
href="https://steamcommunity.com/id/yusdacra"
class="text-xs hover:underline text-shadow-green text-ralsei-green-light"
><img class="inline w-4" src={data.lastGame.pfp} />
<span class="align-middle">steam profile</span></a
>
</div>
</div>
{/if} {/if}
</Window> </Window>
</div> </div>

@ -1,34 +1,37 @@
import convertDate from "$lib/convertDate"; import convertDate from '$lib/convertDate';
export interface PostData { export interface PostData {
path: string, path: string;
published: string, published: string;
metadata: Record<string, string>, metadata: Record<string, string>;
} }
const allPostFiles: Record<string, any> = import.meta.glob('./*/+page.md', { eager: true }); const allPostFiles: Record<string, any> = import.meta.glob('./*/+page.md', { eager: true });
const allPosts: PostData[] = Object.entries(allPostFiles).map(([path, post]) => { const allPosts: PostData[] = Object.entries(allPostFiles)
const postPath = path.slice(2, -8); .map(([path, post]) => {
return { const postPath = path.slice(2, -8);
metadata: post.metadata, return {
path: postPath, metadata: post.metadata,
published: convertDate(post.metadata.date) path: postPath,
}; published: convertDate(post.metadata.date)
}).map((post) => { };
if (!("excerpt" in post.metadata)) { })
post.metadata.excerpt = "" .map((post) => {
} if (!('excerpt' in post.metadata)) {
return post; post.metadata.excerpt = '';
}).toSorted((post, opost) => { }
const date = new Date(post.metadata.date); return post;
const odate = new Date(opost.metadata.date); })
return odate.getTime() - date.getTime() .toSorted((post, opost) => {
}); const date = new Date(post.metadata.date);
const odate = new Date(opost.metadata.date);
return odate.getTime() - date.getTime();
});
export const _allPosts = allPosts; export const _allPosts = allPosts;
export async function load({}) { export async function load() {
if (!allPosts.length) { if (!allPosts.length) {
return { status: 404 }; return { status: 404 };
} }
return { posts: allPosts }; return { posts: allPosts };
} }

@ -15,22 +15,24 @@
<div class="mx-auto md:max-w-fit flex flex-col-reverse md:flex-row gap-y-4 gap-x-16"> <div class="mx-auto md:max-w-fit flex flex-col-reverse md:flex-row gap-y-4 gap-x-16">
<div class="flex flex-col gap-y-4"> <div class="flex flex-col gap-y-4">
{#each posts as post} {#each posts as post}
<Window title={post.metadata.title} iconUri='/icons/entry.webp'> <Window title={post.metadata.title} iconUri="/icons/entry.webp">
<a <a
href="/entries/{post.path}" href="/entries/{post.path}"
title="cd /entries/{post.path}" title="cd /entries/{post.path}"
data-sveltekit-preload-data="off" data-sveltekit-preload-data="off"
> >
<div class="flex flex-col prose prose-ralsei leading-5"> <div class="flex flex-col prose prose-ralsei leading-5">
<ul> <ul>
<li>published on: <time datetime="{post.metadata.date} 00:00:00">{post.published}</time></li> <li>
<li class="max-w-[34ch] text-wrap">excerpt: {post.metadata.excerpt}</li> published on: <time datetime="{post.metadata.date} 00:00:00">{post.published}</time>
</ul> </li>
<strong class="place-self-end text-ralsei-green-light"> read more... </strong> <li class="max-w-[34ch] text-wrap">excerpt: {post.metadata.excerpt}</li>
</div> </ul>
</a> <strong class="place-self-end text-ralsei-green-light"> read more... </strong>
</Window> </div>
</a>
</Window>
{/each} {/each}
</div> </div>
<LogPage {data}/> <LogPage {data} />
</div> </div>

@ -3,15 +3,13 @@ import { _allPosts, type PostData } from '../+layout.server.ts';
const entriesUrl = `${PUBLIC_BASE_URL}/entries`; const entriesUrl = `${PUBLIC_BASE_URL}/entries`;
export const GET = async ({ }) => { export const GET = async () => {
return new Response( return new Response(render(_allPosts), {
render(_allPosts), headers: {
{ 'content-type': 'application/xml',
headers: { 'cache-control': 'no-store'
'content-type': 'application/xml', }
'cache-control': 'no-store', });
}
})
}; };
const render = (posts: PostData[]) => `<?xml version="1.0" encoding="UTF-8" ?> const render = (posts: PostData[]) => `<?xml version="1.0" encoding="UTF-8" ?>
@ -21,13 +19,17 @@ const render = (posts: PostData[]) => `<?xml version="1.0" encoding="UTF-8" ?>
<title>dusk's posts (@gaze.systems)</title> <title>dusk's posts (@gaze.systems)</title>
<link>${entriesUrl}</link> <link>${entriesUrl}</link>
<description>posts from my website</description> <description>posts from my website</description>
${posts.map((post) => `<item> ${posts
.map(
(post) => `<item>
<guid>${entriesUrl}/${post.path}</guid> <guid>${entriesUrl}/${post.path}</guid>
<title>${post.metadata.title}</title> <title>${post.metadata.title}</title>
<link>${entriesUrl}/${post.path}</link> <link>${entriesUrl}/${post.path}</link>
<description>${post.metadata.excerpt}</description> <description>${post.metadata.excerpt}</description>
<pubDate>${new Date(post.metadata.date).toUTCString()}</pubDate> <pubDate>${new Date(post.metadata.date).toUTCString()}</pubDate>
</item>`).join('')} </item>`
)
.join('')}
</channel> </channel>
</rss> </rss>
`; `;

@ -1,12 +1,12 @@
import images from './images.json' import images from './images.json';
export async function load({}) { export async function load() {
return { return {
images: images.map((id) => { images: images.map((id) => {
return { return {
og: `https://res.cloudinary.com/dgtwf7mar/image/upload/v1/${id}`, og: `https://res.cloudinary.com/dgtwf7mar/image/upload/v1/${id}`,
thumb: `https://res.cloudinary.com/dgtwf7mar/image/upload/c_fill,w_480,h_480,g_center/c_limit,w_480/f_auto/q_auto/v1/${id}`, thumb: `https://res.cloudinary.com/dgtwf7mar/image/upload/c_fill,w_480,h_480,g_center/c_limit,w_480/f_auto/q_auto/v1/${id}`
} };
}) })
}; };
} }

@ -1,12 +1,12 @@
import { getLastPosts } from '$lib/bluesky.js'; import { getLastPosts } from '$lib/bluesky.js';
import { noteFromBskyPost } from '../../components/note.svelte'; import { noteFromBskyPost } from '../../components/note.svelte';
export const load = async ({ }) => { export const load = async () => {
return _load() return _load();
} };
export const _load = async () => { export const _load = async () => {
return { return {
feedPosts: getLastPosts().map(noteFromBskyPost), feedPosts: getLastPosts().map(noteFromBskyPost)
} };
} };

@ -1,37 +1,64 @@
<script lang="ts"> <script lang="ts">
import Window from '../../components/window.svelte'; import Window from '../../components/window.svelte';
import Token from '../../components/token.svelte'; import Token from '../../components/token.svelte';
import Note from '../../components/note.svelte'; import Note, { type NoteData } from '../../components/note.svelte';
interface Props { interface Props {
data: any; data: {
} feedPosts: NoteData[];
};
}
let { data }: Props = $props(); let { data }: Props = $props();
</script> </script>
<Window title="terminal" removePadding> <Window title="terminal" removePadding>
<div <div
class=" class="
prose prose-ralsei prose prose-ralsei
prose-pre:rounded-none prose-pre:!m-0 prose-pre:!p-2 prose-pre:rounded-none prose-pre:!m-0 prose-pre:!p-2
prose-pre:!bg-ralsei-black prose-code:!bg-ralsei-black prose-pre:!bg-ralsei-black prose-code:!bg-ralsei-black
" "
> >
<pre class="language-bash"><code class="language-bash"><nobr> <pre class="language-bash"><code class="language-bash"
<Token v="[" punct/>gazesystems <Token v="/" keywd/><Token v="]$" punct/> <Token v="source" funct/> <Token v="scripts/log.nu" /> ><nobr>
<br> <Token v="[" punct />gazesystems <Token v="/" keywd /><Token v="]$" punct /> <Token
<Token v="[" punct/>gazesystems <Token v="/" keywd/><Token v="]$" punct/> <Token v="let" funct/> <Token v="entries"/> <Token v="=" punct/> <Token v="(" punct/><Token v="ls" funct/> <Token v="logs" /> <Token v="|" punct/> <Token v="reverse" funct/> <Token v="|" punct/> <Token v="take" funct/> <Token v="13"/><Token v=")" punct/> v="source"
<br> funct
<Token v="[" punct/>gazesystems <Token v="/" keywd/><Token v="]$" punct/> <Token v="$entries" /> <Token v="|" punct/> <Token v="each" funct/> <Token v="&#123;" punct/><Token v="|" punct/><Token v="file"/><Token v="|" punct/> <Token v="render" funct/> <Token v="(" punct/><Token v="open" funct/> <Token v="$file.name" /><Token v=")" punct/><Token v="&#125;" punct/> /> <Token v="scripts/log.nu" />
<br> <br />
<br> <Token v="[" punct />gazesystems <Token v="/" keywd /><Token v="]$" punct /> <Token
v="let"
funct
/> <Token v="entries" /> <Token v="=" punct /> <Token v="(" punct /><Token
v="ls"
funct
/> <Token v="logs" /> <Token v="|" punct /> <Token v="reverse" funct /> <Token
v="|"
punct
/> <Token v="take" funct /> <Token v="13" /><Token v=")" punct />
<br />
<Token v="[" punct />gazesystems <Token v="/" keywd /><Token v="]$" punct /> <Token
v="$entries"
/> <Token v="|" punct /> <Token v="each" funct /> <Token v="&#123;" punct /><Token
v="|"
punct
/><Token v="file" /><Token v="|" punct /> <Token v="render" funct /> <Token
v="("
punct
/><Token v="open" funct /> <Token v="$file.name" /><Token v=")" punct /><Token
v="&#125;"
punct
/>
<br />
<br />
{#each data.feedPosts as note, index} {#each data.feedPosts as note, index}
<Note {note}/> <Note {note} />
{#if index < data.feedPosts.length - 1} {#if index < data.feedPosts.length - 1}
<div class="mt-3"></div> <div class="mt-3"></div>
{/if} {/if}
{/each} {/each}
</nobr></code></pre> </nobr></code
</div> ></pre>
</div>
</Window> </Window>

@ -1,5 +1,5 @@
import { redirect } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit';
export const GET = async ({ }) => { export const GET = async () => {
redirect(301, "https://bsky.app/profile/did:plc:dfl62fgb7wtjj3fcbb72naae/rss") redirect(301, 'https://bsky.app/profile/did:plc:dfl62fgb7wtjj3fcbb72naae/rss');
}; };

@ -5,375 +5,398 @@
@tailwind utilities; @tailwind utilities;
@layer base { @layer base {
:root { :root {
@apply font-sans-serif bg-ralsei-black text-ralsei-white; @apply font-sans-serif bg-ralsei-black text-ralsei-white;
@apply prose-code:font-monospace prose-headings:font-monospace; @apply prose-code:font-monospace prose-headings:font-monospace;
cursor: url('/icons/gaze_closed.webp'), default; cursor: url('/icons/gaze_closed.webp'), default;
scrollbar-color: theme(colors.ralsei.green.dark) transparent; scrollbar-color: theme(colors.ralsei.green.dark) transparent;
-webkit-font-smoothing: none !important; -webkit-font-smoothing: none !important;
font-smooth: never !important; font-smooth: never !important;
font-smoothing: none !important; font-smoothing: none !important;
} }
@font-face { @font-face {
font-family: 'Fusion Pixel 10px Monospaced zh_hans'; font-family: 'Fusion Pixel 10px Monospaced zh_hans';
src: url('/fonts/fusion-pixel-sc-monospaced.woff2') format('woff2'); src: url('/fonts/fusion-pixel-sc-monospaced.woff2') format('woff2');
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
font-display: swap; font-display: swap;
} }
@font-face { @font-face {
font-family: 'Fusion Pixel 10px Proportional zh_hans'; font-family: 'Fusion Pixel 10px Proportional zh_hans';
src: url('/fonts/fusion-pixel-sc-proportional.woff2') format('woff2'); src: url('/fonts/fusion-pixel-sc-proportional.woff2') format('woff2');
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
font-display: swap; font-display: swap;
} }
@font-face { @font-face {
font-family: 'Doll Mono'; font-family: 'Doll Mono';
src: url('/fonts/dollmonoopt.woff2') format('woff2'); src: url('/fonts/dollmonoopt.woff2') format('woff2');
} }
.prose h1::before { .prose h1::before {
content: '[ '; content: '[ ';
} }
.prose h1::after { .prose h1::after {
content: ' ]'; content: ' ]';
} }
.prose h2::before { .prose h2::before {
content: '[= '; content: '[= ';
} }
.prose h2::after { .prose h2::after {
content: ' =]'; content: ' =]';
} }
.prose h3::before {
content: '[== ';
}
.prose h3::after {
content: ' ==]';
}
.prose h4::before {
content: '[=== ';
}
.prose h4::after {
content: ' ===]';
}
/* .prose h1::after,.prose h2::after,.prose h3::after,.prose h4::after { .prose h3::before {
content: '[== ';
}
.prose h3::after {
content: ' ==]';
}
.prose h4::before {
content: '[=== ';
}
.prose h4::after {
content: ' ===]';
}
/* .prose h1::after,.prose h2::after,.prose h3::after,.prose h4::after {
@apply motion-safe:animate-blink; @apply motion-safe:animate-blink;
content: '_'; content: '_';
} */ } */
.prose a { .prose a {
text-decoration: none; text-decoration: none;
} }
.prose a:hover { .prose a:hover {
@apply motion-safe:animate-squiggle; @apply motion-safe:animate-squiggle;
text-decoration: underline; text-decoration: underline;
} }
h1,h2,h3,h4,h5,h6,.text-shadow-pink { h1,
text-shadow: 0 0 3px theme(colors.ralsei.black), 0 0 6px theme(colors.ralsei.pink.neon), 0 0 10px #fff3; h2,
} h3,
h4,
h5,
h6,
.text-shadow-pink {
text-shadow:
0 0 3px theme(colors.ralsei.black),
0 0 6px theme(colors.ralsei.pink.neon),
0 0 10px #fff3;
}
.text-shadow-red { .text-shadow-red {
text-shadow: 0 0 1px theme(colors.ralsei.black), 0 0 5px theme(colors.red.600); text-shadow:
} 0 0 1px theme(colors.ralsei.black),
0 0 5px theme(colors.red.600);
}
.text-shadow-none { .text-shadow-none {
text-shadow: none; text-shadow: none;
} }
.prose ul, ul { .prose ul,
list-style-type: '>>'; ul {
} list-style-type: '>>';
}
.text-shadow-green { .text-shadow-green {
text-shadow: 0 0 2px theme(colors.ralsei.black), 0 0 5px theme(colors.ralsei.green.light); text-shadow:
} 0 0 2px theme(colors.ralsei.black),
0 0 5px theme(colors.ralsei.green.light);
}
a,button,input[type=submit] { a,
@apply text-shadow-green; button,
cursor: url('/icons/gaze.webp'), pointer; input[type='submit'] {
} @apply text-shadow-green;
cursor: url('/icons/gaze.webp'), pointer;
}
.animate-squiggle { .animate-squiggle {
animation: squigglevision 0.3s infinite; animation: squigglevision 0.3s infinite;
} }
@keyframes squigglevision { @keyframes squigglevision {
0% { 0% {
filter: url("#squiggly-0"); filter: url('#squiggly-0');
} }
25% { 25% {
filter: url("#squiggly-1"); filter: url('#squiggly-1');
} }
50% { 50% {
filter: url("#squiggly-2"); filter: url('#squiggly-2');
} }
75% { 75% {
filter: url("#squiggly-3"); filter: url('#squiggly-3');
} }
100% { 100% {
filter: url("#squiggly-4"); filter: url('#squiggly-4');
} }
} }
@keyframes blink { @keyframes blink {
0% { 0% {
opacity: 1.0; opacity: 1;
} }
50% { 50% {
opacity: 0.0; opacity: 0;
} }
100% { 100% {
opacity: 1.0; opacity: 1;
} }
} }
} }
@layer utilities { @layer utilities {
.text-error { .text-error {
@apply text-xl text-red-600 text-shadow-red; @apply text-xl text-red-600 text-shadow-red;
} }
.border-groove { .border-groove {
border-style: groove; border-style: groove;
} }
.border-ridge { .border-ridge {
border-style: ridge; border-style: ridge;
} }
.app-grid-background-anim { .app-grid-background-anim {
animation: 4s linear app-grid-move-first-layer infinite; animation: 4s linear app-grid-move-first-layer infinite;
} }
.app-grid-background-second-layer-anim {
animation: 12s linear app-grid-move-second-layer infinite;
}
@keyframes app-grid-move-first-layer { .app-grid-background-second-layer-anim {
0% { animation: 12s linear app-grid-move-second-layer infinite;
background-position: 0px 0px; }
}
100% {
background-position: 126px 84px;
}
}
@keyframes app-grid-move-second-layer {
0% {
background-position: 96px 120px;
}
100% {
background-position: 0px 0px;
}
}
@media (prefers-reduced-motion: no-preference) { @keyframes app-grid-move-first-layer {
@keyframes bounce-reverse { 0% {
0%, 100% { background-position: 0px 0px;
transform: none; }
animation-timing-function: cubic-bezier(0,0,0.2,1); 100% {
} background-position: 126px 84px;
50% { }
transform: translateY(-25%); }
animation-timing-function: cubic-bezier(0.8,0,1,1);
} @keyframes app-grid-move-second-layer {
} 0% {
} background-position: 96px 120px;
@media (prefers-reduced-motion: no-preference) { }
.animate-bounce-reverse:hover { 100% {
animation: bounce-reverse 1s infinite; background-position: 0px 0px;
} }
} }
@media (prefers-reduced-motion: no-preference) {
@keyframes bounce-reverse {
0%,
100% {
transform: none;
animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
}
50% {
transform: translateY(-25%);
animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
}
}
}
@media (prefers-reduced-motion: no-preference) {
.animate-bounce-reverse:hover {
animation: bounce-reverse 1s infinite;
}
}
} }
a.app-selected-route { a.app-selected-route {
text-shadow: 0 0 2px theme(colors.ralsei.black), 0 0 5px theme(colors.ralsei.pink.regular); text-shadow:
0 0 2px theme(colors.ralsei.black),
0 0 5px theme(colors.ralsei.pink.regular);
} }
.app-grid-background { .app-grid-background {
background-image: background-image:
linear-gradient(theme(colors.ralsei.green.light / 0.4), transparent 2px), linear-gradient(theme(colors.ralsei.green.light / 0.4), transparent 2px),
linear-gradient(to right, theme(colors.ralsei.green.light / 0.4), transparent 2px); linear-gradient(to right, theme(colors.ralsei.green.light / 0.4), transparent 2px);
background-size: 100% 42px, 42px 100%; background-size:
100% 42px,
42px 100%;
} }
.app-grid-background-second-layer { .app-grid-background-second-layer {
background-image: background-image:
linear-gradient(theme(colors.ralsei.pink.neon / 0.4), transparent 1px), linear-gradient(theme(colors.ralsei.pink.neon / 0.4), transparent 1px),
linear-gradient(to right, theme(colors.ralsei.pink.neon / 0.4), transparent 1px); linear-gradient(to right, theme(colors.ralsei.pink.neon / 0.4), transparent 1px);
background-size: 100% 24px, 24px 100%; background-size:
100% 24px,
24px 100%;
} }
@media (prefers-reduced-motion: no-preference) { @media (prefers-reduced-motion: no-preference) {
.animate-window-open { .animate-window-open {
animation: 0.5s ease-out window-open-scale forwards; animation: 0.5s ease-out window-open-scale forwards;
} }
.animate-window-open-vertical {
animation: 0.5s ease-out window-open-scale-vertical forwards;
transform-origin: bottom;
}
.animate-window-open-horizontal {
animation: 0.5s ease-out window-open-scale-horizontal forwards;
transform-origin: left;
}
.animate-window-open-move-up { .animate-window-open-vertical {
animation: 0.5s ease-out window-open-move-up forwards; animation: 0.5s ease-out window-open-scale-vertical forwards;
} transform-origin: bottom;
}
.animate-window-open-move-down { .animate-window-open-horizontal {
animation: 0.5s ease-out window-open-move-down forwards; animation: 0.5s ease-out window-open-scale-horizontal forwards;
} transform-origin: left;
}
.animate-window-open-move-left { .animate-window-open-move-up {
animation: 0.5s ease-out window-open-move-left forwards; animation: 0.5s ease-out window-open-move-up forwards;
} }
.animate-window-open-move-right { .animate-window-open-move-down {
animation: 0.5s ease-out window-open-move-right forwards; animation: 0.5s ease-out window-open-move-down forwards;
} }
.animate-overflow-keep-hidden { .animate-window-open-move-left {
animation: 0.6s linear overflow-keep-hidden forwards; animation: 0.5s ease-out window-open-move-left forwards;
} }
@keyframes window-open-scale {
0% {
scale: 0.0;
opacity: 0.0;
}
20% {
scale: 0.0;
}
60% {
opacity: 0.5;
}
100% {
scale: 1.0;
opacity: 1.0;
}
}
@keyframes window-open-scale-vertical {
0% {
scale: 1.0 0.0;
opacity: 0.0;
}
20% {
scale: 1.0 0.0;
}
60% {
opacity: 0.5;
}
100% {
scale: 1.0 1.0;
opacity: 1.0;
}
}
@keyframes window-open-scale-horizontal {
0% {
scale: 0.0 1.0;
opacity: 0.0;
}
20% {
scale: 0.0 1.0;
}
60% {
opacity: 0.5;
}
100% {
scale: 1.0 1.0;
opacity: 1.0;
}
}
@keyframes window-open-move-down { .animate-window-open-move-right {
0% { animation: 0.5s ease-out window-open-move-right forwards;
translate: 0 10rem; }
opacity: 0.0;
}
20% {
translate: 0 10rem;
}
60% {
opacity: 0.5;
}
100% {
translate: normal;
opacity: 1.0;
}
}
@keyframes window-open-move-up { .animate-overflow-keep-hidden {
0% { animation: 0.6s linear overflow-keep-hidden forwards;
translate: 0 -10rem; }
opacity: 0.0;
}
20% {
translate: 0 -10rem;
}
60% {
opacity: 0.5;
}
100% {
translate: normal;
opacity: 1.0;
}
}
@keyframes window-open-move-left { @keyframes window-open-scale {
0% { 0% {
translate: 10rem 0; scale: 0;
opacity: 0.0; opacity: 0;
} }
20% { 20% {
translate: 10rem 0; scale: 0;
} }
60% { 60% {
opacity: 0.5; opacity: 0.5;
} }
100% { 100% {
translate: normal; scale: 1;
opacity: 1.0; opacity: 1;
} }
} }
@keyframes window-open-move-right { @keyframes window-open-scale-vertical {
0% { 0% {
translate: -10rem 0; scale: 1 0;
opacity: 0.0; opacity: 0;
} }
20% { 20% {
translate: -10rem 0; scale: 1 0;
} }
60% { 60% {
opacity: 0.5; opacity: 0.5;
} }
100% { 100% {
translate: normal; scale: 1 1;
opacity: 1.0; opacity: 1;
} }
} }
@keyframes overflow-keep-hidden { @keyframes window-open-scale-horizontal {
0% { 0% {
overflow: hidden; scale: 0 1;
} opacity: 0;
100% { }
overflow: auto; 20% {
} scale: 0 1;
} }
60% {
opacity: 0.5;
}
100% {
scale: 1 1;
opacity: 1;
}
}
@keyframes window-open-move-down {
0% {
translate: 0 10rem;
opacity: 0;
}
20% {
translate: 0 10rem;
}
60% {
opacity: 0.5;
}
100% {
translate: normal;
opacity: 1;
}
}
@keyframes window-open-move-up {
0% {
translate: 0 -10rem;
opacity: 0;
}
20% {
translate: 0 -10rem;
}
60% {
opacity: 0.5;
}
100% {
translate: normal;
opacity: 1;
}
}
@keyframes window-open-move-left {
0% {
translate: 10rem 0;
opacity: 0;
}
20% {
translate: 10rem 0;
}
60% {
opacity: 0.5;
}
100% {
translate: normal;
opacity: 1;
}
}
@keyframes window-open-move-right {
0% {
translate: -10rem 0;
opacity: 0;
}
20% {
translate: -10rem 0;
}
60% {
opacity: 0.5;
}
100% {
translate: normal;
opacity: 1;
}
}
@keyframes overflow-keep-hidden {
0% {
overflow: hidden;
}
100% {
overflow: auto;
}
}
} }

@ -1,61 +1,57 @@
const colors = require('tailwindcss/colors') const colors = require('tailwindcss/colors');
const plugin = require('tailwindcss/plugin') const plugin = require('tailwindcss/plugin');
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
export default { export default {
content: ['./src/**/*.{html,js,svelte,ts,md}'], content: ['./src/**/*.{html,js,svelte,ts,md}'],
theme: { theme: {
extend: { extend: {
typography: ({ theme }) => ({ typography: ({ theme }) => ({
ralsei: { ralsei: {
css: { css: {
'--tw-prose-body': theme('colors.ralsei.white'), '--tw-prose-body': theme('colors.ralsei.white'),
'--tw-prose-headings': theme('colors.ralsei.pink.neon'), '--tw-prose-headings': theme('colors.ralsei.pink.neon'),
'--tw-prose-lead': theme('colors.ralsei.white'), '--tw-prose-lead': theme('colors.ralsei.white'),
'--tw-prose-links': theme('colors.ralsei.green.light'), '--tw-prose-links': theme('colors.ralsei.green.light'),
'--tw-prose-bold': theme('colors.ralsei.white'), '--tw-prose-bold': theme('colors.ralsei.white'),
'--tw-prose-counters': theme('colors.ralsei.pink.regular'), '--tw-prose-counters': theme('colors.ralsei.pink.regular'),
'--tw-prose-bullets': theme('colors.ralsei.pink.regular'), '--tw-prose-bullets': theme('colors.ralsei.pink.regular'),
'--tw-prose-hr': theme('colors.ralsei.white'), '--tw-prose-hr': theme('colors.ralsei.white'),
'--tw-prose-quotes': theme('colors.ralsei.white'), '--tw-prose-quotes': theme('colors.ralsei.white'),
'--tw-prose-quote-borders': theme('colors.ralsei.white'), '--tw-prose-quote-borders': theme('colors.ralsei.white'),
'--tw-prose-captions': theme('colors.ralsei.white'), '--tw-prose-captions': theme('colors.ralsei.white'),
'--tw-prose-code': theme('colors.ralsei.pink.regular'), '--tw-prose-code': theme('colors.ralsei.pink.regular'),
'--tw-prose-pre-code': theme('colors.ralsei.white'), '--tw-prose-pre-code': theme('colors.ralsei.white'),
'--tw-prose-pre-bg': theme('colors.ralsei.green.dark'), '--tw-prose-pre-bg': theme('colors.ralsei.green.dark'),
'--tw-prose-th-borders': theme('colors.ralsei.white'), '--tw-prose-th-borders': theme('colors.ralsei.white'),
'--tw-prose-td-borders': theme('colors.ralsei.white'), '--tw-prose-td-borders': theme('colors.ralsei.white')
}, }
}, }
}), }),
animation: { animation: {
'bounce-slow': 'bounce 3s infinite', 'bounce-slow': 'bounce 3s infinite',
'pulse-fast': 'pulse 1s cubic-bezier(0.4, 0, 0.6, 1) infinite', 'pulse-fast': 'pulse 1s cubic-bezier(0.4, 0, 0.6, 1) infinite',
'blink': 'blink 1s step-start infinite', blink: 'blink 1s step-start infinite'
}, },
colors: { colors: {
ralsei: { ralsei: {
pink: { pink: {
regular: '#fe96e0', regular: '#fe96e0',
neon: '#ff3eb7', neon: '#ff3eb7'
}, },
white: '#fff9fe', white: '#fff9fe',
black: '#000801', black: '#000801',
green: { green: {
light: '#4dcc8e', light: '#4dcc8e',
dark: '#162d26', dark: '#162d26'
} }
} }
} }
}, },
fontFamily: { fontFamily: {
'sans-serif': ['"Fusion Pixel 10px Proportional zh_hans", sans-serif'], 'sans-serif': ['"Fusion Pixel 10px Proportional zh_hans", sans-serif'],
monospace: ['"Fusion Pixel 10px Monospaced zh_hans", monospace'], monospace: ['"Fusion Pixel 10px Monospaced zh_hans", monospace']
} }
}, },
plugins: [ plugins: [require('@tailwindcss/typography'), require('@tailwindcss/forms')]
require('@tailwindcss/typography'), };
require('@tailwindcss/forms'),
],
}