first commit

This commit is contained in:
Quinten0508 2024-06-22 20:58:35 +02:00
commit 9a8690ea68
43 changed files with 1572 additions and 0 deletions

175
.gitignore vendored Normal file
View File

@ -0,0 +1,175 @@
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
# Logs
logs
_.log
npm-debug.log_
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Caches
.cache
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# Runtime data
pids
_.pid
_.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# IntelliJ based IDEs
.idea
# Finder (MacOS) folder config
.DS_Store

BIN
.unused/bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 B

11
.unused/dither copy.svg Normal file
View File

@ -0,0 +1,11 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="2vh"
height="2vh"
>
<rect x="0" y="0" width="1vh" height="1vh" fill="#262626"/>
<rect x="1vh" y="0" width="1vh" height="1vh" fill="#242424"/>
<rect x="0" y="1vh" width="1vh" height="1vh" fill="#242424"/>
<rect x="1vh" y="1vh" width="1vh" height="1vh" fill="#262626"/>
</svg>

After

Width:  |  Height:  |  Size: 345 B

11
.unused/dither.svg Normal file
View File

@ -0,0 +1,11 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="10px"
height="10px"
>
<rect x="5" y="0" width="5" height="5" fill="#212121"/>
<rect x="0" y="0" width="5" height="5" fill="#262626"/>
<rect x="0" y="5" width="5" height="5" fill="#212121"/>
<rect x="5" y="5" width="5" height="5" fill="#262626"/>
</svg>

After

Width:  |  Height:  |  Size: 339 B

11
.unused/dithertest1.svg Normal file
View File

@ -0,0 +1,11 @@
<svg class="dithering-pattern" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="dither" patternUnits="userSpaceOnUse" width="8" height="8">
<rect width="4" height="4" fill="#232323"></rect>
<rect x="4" y="4" width="4" height="4" fill="#505758"></rect>
<rect x="4" width="4" height="4" fill="#232323"></rect>
<rect y="4" width="4" height="4" fill="#232323"></rect>
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#dither)"></rect>
</svg>

After

Width:  |  Height:  |  Size: 517 B

23
.unused/index2.html Normal file
View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SVG Background</title>
<style>
/* CSS to apply background image */
body {
margin: 0;
padding: 0;
width: 100vw;
height: 100vh;
background-image: url('noise.svg');
background-size: cover;
background-repeat: no-repeat;
}
</style>
</head>
<body>
<h1>hi :3</h1>>
</body>
</html>

67
.unused/noise copy.svg Normal file
View File

@ -0,0 +1,67 @@
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:svgjs="http://svgjs.dev/svgjs"
viewBox="0 0 700 700"
width="700"
height="700"
opacity="1">
<defs>
<filter
id="noise-filter"
x="-20%"
y="-20%"
width="140%"
height="140%"
filterUnits="objectBoundingBox"
primitiveUnits="userSpaceOnUse"
color-interpolation-filters="linearRGB">
<feTurbulence
type="fractalNoise"
baseFrequency="0.1"
numOctaves="10" seed="2"
stitchTiles="stitch"
x="0%"
y="0%"
width="100%"
height="100%"
result="turbulence">
</feTurbulence>
<feSpecularLighting
surfaceScale="14"
specularConstant="1.3"
specularExponent="20"
lighting-color="#232323"
x="0%"
y="0%"
width="100%"
height="100%"
in="turbulence"
result="specularLighting">
<feDistantLight
azimuth="3"
elevation="96">
</feDistantLight>
</feSpecularLighting>
</filter>
</defs>
<rect
width="700"
height="700"
fill="#262626">
</rect>
<rect
width="700"
height="700"
fill="#232323"
filter="url(#noise-filter)">
</rect>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

51
.unused/noise.svg Normal file
View File

@ -0,0 +1,51 @@
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:svgjs="http://svgjs.dev/svgjs"
>
<defs>
<!-- Define noise -->
<filter id="noise-filter">
<feTurbulence
type="fractalNoise"
baseFrequency="0.1"
numOctaves="10"
seed="2"
stitchTiles="stitch"
x="0%"
y="0%"
width="100%"
height="100%"
result="turbulence">
</feTurbulence>
<feSpecularLighting
surfaceScale="14"
specularConstant="1.3"
specularExponent="20"
lighting-color="#232323"
x="0%"
y="0%"
width="100%"
height="100%"
in="turbulence"
result="specularLighting">
<feDistantLight
azimuth="3"
elevation="96"
></feDistantLight>
</feSpecularLighting>
</filter>
</defs>
<!-- Define the background -->
<rect width="100%" height="100%" fill="#262626"></rect>
<!-- Apply noise -->
<rect
width="100%"
height="100%"
fill="#232323"
filter="url(#noise-filter)"></rect>
</svg>

After

Width:  |  Height:  |  Size: 1011 B

37
404.html Normal file
View File

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en">
<head>
<style>
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap');
</style>
<title>quinten0508.com</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<div class="box" id="maintitlebox">
<h1 id="maintitle">Quinten</h1>
</div>
</div>
<div id="stripes">
<img src="stripes.svg" id="stripes">
</div>
</body>
</html>
<!-- class -> .class and id -> #id in css
use id for one-time and classes for re-useable stuff -->

BIN
assets/bg.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 B

BIN
assets/buttons/firefox.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
assets/buttons/piracy.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 910 B

BIN
assets/buttons/yippeee.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
assets/capy.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 KiB

BIN
assets/cat-hammer-404.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 MiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
assets/icons/lastfm.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
assets/icons/moon_small.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

BIN
assets/icons/sun_small.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

1
assets/icons/twitter.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="#E9E9E9" d="M19.913 5.322a1.034 1.034 0 0 1 .837 1.629l-1.042 1.481c-.064 5.086-1.765 8.539-5.056 10.264A10.9 10.9 0 0 1 9.6 19.835a12.23 12.23 0 0 1-6.2-1.524a.76.76 0 0 1-.317-.8a.77.77 0 0 1 .63-.6a20.6 20.6 0 0 0 3.745-.886C2 13.5 3.19 7.824 3.71 6.081a1.028 1.028 0 0 1 1.729-.422a9.93 9.93 0 0 0 5.995 2.95A4.19 4.19 0 0 1 12.725 5.3a4.125 4.125 0 0 1 5.7.02ZM4.521 17.794c1.862.872 6.226 1.819 9.667.016c2.955-1.549 4.476-4.732 4.521-9.461a.77.77 0 0 1 .142-.436l1.081-1.538l-.041-.053c-.518-.007-1.029-.014-1.55 0a.84.84 0 0 1-.547-.221a3.13 3.13 0 0 0-4.383-.072a3.17 3.17 0 0 0-.935 2.87a.65.65 0 0 1-.154.545a.59.59 0 0 1-.516.205a10.92 10.92 0 0 1-7.084-3.295c-.67 2.078-1.52 7.094 3.869 9.065a.63.63 0 0 1 .416.538a.63.63 0 0 1-.3.6a13.2 13.2 0 0 1-4.186 1.237m15.147-9.305"/></svg>

After

Width:  |  Height:  |  Size: 892 B

BIN
assets/image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 MiB

BIN
assets/oneko-tux-old.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
assets/oneko-tux.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
assets/oneko.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

27
assets/scripts/clock.js Normal file
View File

@ -0,0 +1,27 @@
function updateClock() {
const now = new Date().toLocaleString("en-US", {timeZone: "Europe/Amsterdam"});
const cestTime = new Date(now);
let hours = cestTime.getHours();
let minutes = cestTime.getMinutes();
let seconds = cestTime.getSeconds();
hours = hours < 10 ? '0' + hours : hours;
minutes = minutes < 10 ? '0' + minutes : minutes;
seconds = seconds < 10 ? '0' + seconds : seconds;
const timeString = hours + ':' + minutes + ':' + seconds;
//12pm //am
const isDaytime = hours >= 0 && hours > 7;
const imageElement = document.getElementById('clock-icon');
imageElement.src = isDaytime ? '/assets/icons/sun_small.png' : 'assets/icons/moon_small.png';
document.getElementById('clock').textContent = timeString;
}
document.addEventListener("DOMContentLoaded", function(event) {
updateClock();
setInterval(updateClock, 1000);
});

View File

@ -0,0 +1,42 @@
document.addEventListener('DOMContentLoaded', async function() {
async function fetchLastBeat() {
const heartbeatElement = document.getElementById('heartbeat-time');
try {
const response = await fetch('https://quinten0508.com/api/heartbeat');
const data = await response.json();
const phoneDevice = data.find(device => device.device_name === 'Phone');
if (phoneDevice) {
const phoneTimestamp = phoneDevice.last_beat.timestamp;
const timestampMilliseconds = phoneTimestamp * 1000;
const currentTime = Date.now();
const difference = currentTime - timestampMilliseconds;
const isDead = difference > (1000 * 60 * 60 * 48);
const online = difference < (1000 * 60 * 5);
if (online) {
heartbeatElement.textContent = 'Online!';
heartbeatElement.style = 'color: #00b400';
return;
} else if (isDead) {
heartbeatElement.textContent = 'Dead';
heartbeatElement.style = 'color: #b30000';
} else {
heartbeatElement.textContent = 'Alive';
heartbeatElement.style = 'color: #00b400';
}
} else {
console.error('Device "Phone" not found.');
}
} catch (error) {
console.error('Error fetching last beat:', error);
}
}
// Fetch the last beat immediately and then every 10 seconds
fetchLastBeat();
setInterval(fetchLastBeat, 10000);
});

81
assets/scripts/lastfm.js Normal file
View File

@ -0,0 +1,81 @@
document.addEventListener('DOMContentLoaded', () => {
const nowPlayingElement = document.getElementById('lastfm-contents');
const lastfmArtElement = document.getElementById('lastfm-artbox');
function fetchNowPlaying() {
fetch('https://quinten0508.com/api/nowplaying')
.then(response => response.json())
.then(data => {
if (data.recenttracks.track && data.recenttracks.track.length > 0) {
const tracks = data.recenttracks.track;
const nowPlaying = tracks[0];
const lastPlayedSong = tracks[1];
const lastlastPlayedSong = tracks[2];
if (nowPlaying['@attr'] && nowPlaying['@attr'].nowplaying) {
const nowPlayingHtml = `
<strong>Now Playing:</strong><br>
<span id="lastfm-artist">1. ${nowPlaying.artist['#text']}</span> - ${nowPlaying.name}<br>
<div id="lastfm-lastplayedsong">
<span id="lastfm-artist">2. ${lastPlayedSong.artist['#text']}</span> - ${lastPlayedSong.name}
</div>
<div id="lastfm-lastlastplayedsong">
<span id="lastfm-artist">3. ${lastlastPlayedSong.artist['#text']}</span> - ${lastlastPlayedSong.name}
</div>
`;
nowPlayingElement.innerHTML = nowPlayingHtml;
const albumArt = nowPlaying.image.find(img => img.size === 'large')['#text'];
lastfmArtElement.innerHTML = `<img src="${albumArt}" id="lastfm-art">`;
} else {
const lastPlayed = tracks[0];
const lastPlayedTime = new Date(lastPlayed.date.uts * 1000);
const currentTime = new Date();
const timeDiff = Math.floor((currentTime - lastPlayedTime) / 1000);
const timeSince = formatTimeSince(timeDiff);
const lastPlayedHtml = `
<strong>Last Played:</strong> <br>
<span id="lastfm-artist">${lastPlayed.artist['#text']}</span> - ${lastPlayed.name} <br>
<span id="lastfm-timesinceplay">${timeSince} ago</span>
`;
nowPlayingElement.innerHTML = lastPlayedHtml;
const albumArt = lastPlayed.image.find(img => img.size === 'large')['#text'];
lastfmArtElement.innerHTML = `<img src="${albumArt}" id="lastfm-art"> `;
}
} else {
nowPlayingElement.innerHTML = `<strong>Now Playing:</strong> <br> Nothing`;
lastfmArtElement.innerHTML = '<div id="lastfm-art"></div>';
}
})
.catch(error => {
nowPlayingElement.textContent = 'Error fetching last.fm status.';
lastfmArtElement.innerHTML = '<div id="lastfm-art"></div>';
console.error('Error fetching now playing status:', error);
});
}
function formatTimeSince(seconds) {
const intervals = [
{ label: 'year', seconds: 31536000 },
{ label: 'month', seconds: 2592000 },
{ label: 'day', seconds: 86400 },
{ label: 'hour', seconds: 3600 },
{ label: 'minute', seconds: 60 },
{ label: 'second', seconds: 1 }
];
for (const interval of intervals) {
const count = Math.floor(seconds / interval.seconds);
if (count > 0) {
return `${count} ${interval.label}${count !== 1 ? 's' : ''}`;
}
}
return 'just now';
}
fetchNowPlaying();
setInterval(fetchNowPlaying, 10000);
});

View File

@ -0,0 +1,93 @@
document.addEventListener('DOMContentLoaded', () => {
const apiKey = 'APIKEY CHANGE ME'; //these are free, why would you...
const username = 'Quinten0508';
const nowPlayingElement = document.getElementById('lastfm-contents');
const lastfmArtElement = document.getElementById('lastfm-artbox');
const topTrackElement = document.getElementById('lastfm-toptrack');
function fetchNowPlaying() {
fetch(`https://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=${username}&api_key=${apiKey}&format=json`)
.then(response => response.json())
.then(data => {
if (data.recenttracks.track && data.recenttracks.track.length > 0) {
const tracks = data.recenttracks.track;
const nowPlaying = tracks[0];
const lastPlayedSong = tracks[1];
const lastlastPlayedSong = tracks[2];
if (nowPlaying['@attr'] && nowPlaying['@attr'].nowplaying) { //absolute clusterfuck incoming SORRY - rewrite and send this mess to the css file instead!
nowPlayingElement.innerHTML = `<strong>Now Playing:</strong> <br> <span id=lastfm-artist>1. ${nowPlaying.artist['#text']}</span> - ${nowPlaying.name} <div id=lastfm-lastplayedsong>2. ${lastPlayedSong.artist['#text']}</span> - ${lastPlayedSong.name} </div><div id=lastfm-lastplayedsong>3. ${lastlastPlayedSong.artist['#text']}</span> - ${lastlastPlayedSong.name}</div>`;
const albumArt = nowPlaying.image.find(img => img.size === 'large')['#text'];
lastfmArtElement.innerHTML = `<img src="${albumArt}" id="lastfm-art">`;
} else {
const lastPlayed = tracks[0];
const lastPlayedTime = new Date(lastPlayed.date.uts * 1000);
const currentTime = new Date();
const timeDiff = Math.floor((currentTime - lastPlayedTime) / 1000);
const timeSince = formatTimeSince(timeDiff);
nowPlayingElement.innerHTML = `<strong>Last Played:</strong> <br> <span id=lastfm-artist>${lastPlayed.artist['#text']}</span> - ${lastPlayed.name} <br> <span id="lastfm-timesinceplay">${timeSince} ago</span>`;
const albumArt = lastPlayed.image.find(img => img.size === 'large')['#text'];
lastfmArtElement.innerHTML = `<img src="${albumArt}" id="lastfm-art"> `;
}
} else {
nowPlayingElement.innerHTML = `<strong>Now Playing:</strong> <br> Nothing`;
lastfmArtElement.innerHTML = '<div id=lastfm-art></div>';
}
})
.catch(error => {
nowPlayingElement.textContent = 'Error fetching last.fm status.';
lastfmArtElement.innerHTML = '<div id=lastfm-art></div>';
console.error('Error fetching now playing status:', error);
});
}
function formatTimeSince(seconds) {
const intervals = [
{ label: 'year', seconds: 31536000 },
{ label: 'month', seconds: 2592000 },
{ label: 'day', seconds: 86400 },
{ label: 'hour', seconds: 3600 },
{ label: 'minute', seconds: 60 },
{ label: 'second', seconds: 1 }
];
for (const interval of intervals) {
const count = Math.floor(seconds / interval.seconds);
if (count > 0) {
return `${count} ${interval.label}${count !== 1 ? 's' : ''}`;
}
}
return 'just now';
}
fetchNowPlaying();
setInterval(fetchNowPlaying, 10000);
});
/*
// fetchTopTrack();
function fetchTopTrack() {
fetch(`https://ws.audioscrobbler.com/2.0/?method=user.gettoptracks&user=${username}&api_key=${apiKey}&format=json&period=1month&limit=1`)
.then(response => response.json())
.then(data => {
if (data.toptracks.track && data.toptracks.track.length > 0) {
const topTrack = data.toptracks.track[0];
const topTrackName = topTrack.name;
const topTrackArtist = topTrack.artist.name;
const topTrackPlayCount = topTrack.playcount;
topTrackElement.innerHTML = `<strong>Top Track This Month:</strong> <br> ${topTrackArtist} - ${topTrackName} <br> Play Count: ${topTrackPlayCount}`;
nowPlayingElement.innerHTML(topTrackElement);
} else {
console.error('No top track found.');
}
})
.catch(error => {
console.error('Error fetching top track:', error);
});
}
*/

239
assets/scripts/oneko.js Normal file
View File

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

View File

@ -0,0 +1,15 @@
document.addEventListener('DOMContentLoaded', () => {
var onekos = [
'/assets/oneko.gif',
'/assets/oneko-tux.gif',
// Add more file paths here if needed
];
var currentIndex = 0;
document.getElementById('oneko-button').addEventListener('click', function() {
var oneko = document.getElementById('oneko');
currentIndex = (currentIndex + 1) % onekos.length;
var newOneko = onekos[currentIndex];
oneko.style.backgroundImage = 'url("' + newOneko + '")';
});
});

53
assets/scripts/uptime.js Normal file
View File

@ -0,0 +1,53 @@
document.addEventListener('DOMContentLoaded', async function() {
async function fetchUptime() {
const uptimeElement = document.getElementById('uptime');
const names = {
"Zipline": "https://zip.quinten0508.com"
};
try {
const response = await fetch('https://quinten0508.com/api/uptime');
const data = await response.json();
// Clear previous content
uptimeElement.innerHTML = '';
data.forEach(element => {
// Create parent div for each pair
const pairDiv = document.createElement('div');
pairDiv.className = 'uptime-pair';
// Create div for name
const nameDiv = document.createElement('div');
nameDiv.className = 'uptime-name-element';
// Check if there is a specific URL for this monitor name
if (names.hasOwnProperty(element.monitor_name)) {
const url = names[element.monitor_name];
nameDiv.innerHTML = `<a href="${url}" class="link" target="_blank">${element.monitor_name}</a>`;
} else {
nameDiv.textContent = element.monitor_name;
}
// Create div for status
const statusDiv = document.createElement('div');
statusDiv.className = 'uptime-status-element';
statusDiv.textContent = element.status;
// Append name and status divs to pair div
pairDiv.appendChild(nameDiv);
pairDiv.appendChild(statusDiv);
// Append pair div to uptime container
uptimeElement.appendChild(pairDiv);
});
} catch (error) {
console.error('Error fetching uptime data:', error);
}
}
// Fetch the uptime immediately and then every 10 seconds
fetchUptime();
setInterval(fetchUptime, 10000);
});

18
colorscheme.css Normal file
View File

@ -0,0 +1,18 @@
/*
COLOR SCHEME
Colors
#B1C6CB
#74C1B1
#8FC8B9
#799ACD
Dark-gray
#262626
Off-white
#E9E9E9
#e9e9e9a2
*/

BIN
favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

104
index.html Normal file
View File

@ -0,0 +1,104 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Quintens Outpost</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="style.css">
<script src="/assets/scripts/lastfm.js"></script>
<script src="/assets/scripts/clock.js"></script>
<script src="/assets/scripts/heartbeat.js"></script>
<script src="/assets/scripts/onekoswap.js"></script>
<script src="/assets/scripts/uptime.js"></script>
</head>
<body>
<div class="container">
<div id="maintitlebox">
<h1 id="maintitle" class="box">Quinten's Outpost</h1>
</div>
<div id="subtitlebox">
<div id="links" class="box">
<a class="linkbox link" href="https://x.com/quinten0508" rel="me" target="_blank">
<p>twitter</p></a>
<a class="linkbox link" href="https://www.last.fm/user/Quinten0508" rel="me" target="_blank">
<p>last.fm</p></a>
<a class="linkbox link" href="https://github.com/Quinten0508" rel="me" target="_blank">
<p>github</p></a>
<a class="linkbox link" href="https://t.me/quinten0508" rel="me" target="_blank">
<p>telegram</p></a>
<!-- :3 13 year old firefox bug https://bugzilla.mozilla.org/show_bug.cgi?id=646552 -->
<a class="linkbox link" onClick="javascript:window.open('mailto:quinten0508@protonmail.com', 'mail');event.preventDefault()" href="mailto:quinten0508@protonmail.com" rel="me" target="_blank">
<p>email</p></a>
<!-- new button template
<a class="linkbox link" href="" target="_blank">
<p></p></a>
-->
</div>
<div id="heartbeatbox" class="box">
<a href="https://heartbeat.quinten0508.com" target="_blank" class="link"><p>Status</p></a><p>:&nbsp;</p>
<p id="heartbeat-time">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</p> <!-- &nbsp; prevents text shift after load, doenst work with online T-T -->
</div>
</div>
<div id="header">
<div id="lastfm-box">
<div id="lastfm-contents"><p>Loading...</p></div>
<div id="lastfm-artbox"><div style="background-color: #000000;"></div></div>
</div>
<div id="clock-box">
<p id="clocktitle"> My time (UTC+2)</p>
<div id="clock-contents">
<p id="clock">Loading...</p>
<img id="clock-icon"></img>
</div>
</div>
</div>
<div class="box">
<h3 style="padding-bottom: 1rem;">Status</h3>
<h5 style="color: red;">TEMPORARY DOWNTIME</h5>
<p>Accidentally nuked all 700 server configuration files, expect wonky behaviour across services while i figure this out...</p>
<div id="uptime"></div>
</div>
<div class="box">
<button id="oneko-button" class="button">Click me :3</button>
<h3>titleee</h3>
<h5>caption</h5>
<p> this text will automatically fill up the box and has decent spacing?? </p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
</div>
<div class="box">
<p> heres some more text and shit</p>
</div>
<div class="box">
<p> heres some more text and shit</p>
</div>
<div class="box">
<p> heres some more text and shit</p>
<!--<img src="/assets/capy.jpg" class="image">-->
</div>
</div>
<div id="stripes">
<img src="stripes.svg" id="stripes">
</div>
</body>
<script src="/assets/scripts/oneko.js" data-cat="/assets/oneko.gif"></script>
</html>

33
robots.txt Normal file
View File

@ -0,0 +1,33 @@
User-agent: AdsBot-Google
User-agent: Amazonbot
User-agent: anthropic-ai
User-agent: Applebot
User-agent: AwarioRssBot
User-agent: AwarioSmartBot
User-agent: Bytespider
User-agent: CCBot
User-agent: ChatGPT-User
User-agent: ClaudeBot
User-agent: Claude-Web
User-agent: cohere-ai
User-agent: DataForSeoBot
User-agent: Diffbot
User-agent: FacebookBot
User-agent: FriendlyCrawler
User-agent: Google-Extended
User-agent: GoogleOther
User-agent: GPTBot
User-agent: img2dataset
User-agent: ImagesiftBot
User-agent: magpie-crawler
User-agent: Meltwater
User-agent: omgili
User-agent: omgilibot
User-agent: peer39_crawler
User-agent: peer39_crawler/1.0
User-agent: PerplexityBot
User-agent: PiplBot
User-agent: scoop.it
User-agent: Seekr
User-agent: YouBot
Disallow: /

23
stripes.svg Normal file
View File

@ -0,0 +1,23 @@
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:svgjs="http://svgjs.dev/svgjs"
>
<rect x="0" y="0" width="100%" height="25%"
fill="#B1C6CB"
></rect>
<rect x="0" y="25%" width="100%" height="25%"
fill="#8FC8B9"
></rect>
<rect x="0" y="50%" width="100%" height="25%"
fill="#74C1B1"
></rect>
<rect x="0" y="75%" width="100%" height="25%"
fill="#799ACD"
></rect>
</svg>

After

Width:  |  Height:  |  Size: 473 B

457
style.css Normal file
View File

@ -0,0 +1,457 @@
/* ibm-plex-mono-regular - latin */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'IBM Plex Mono';
font-style: normal;
font-weight: 400;
src: url('/assets/fonts/ibm-plex-mono-v19-latin-regular.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* ibm-plex-mono-italic - latin */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'IBM Plex Mono';
font-style: italic;
font-weight: 400;
src: url('/assets/fonts/ibm-plex-mono-v19-latin-italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* ibm-plex-mono-500 - latin */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'IBM Plex Mono';
font-style: normal;
font-weight: 500;
src: url('/assets/fonts/ibm-plex-mono-v19-latin-500.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* ibm-plex-mono-500italic - latin */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'IBM Plex Mono';
font-style: italic;
font-weight: 500;
src: url('/assets/fonts/ibm-plex-mono-v19-latin-500italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
@media (max-width: 1400px) {
html {
font-size: 25px;
}
}
@media (max-width: 768px) {
html {
font-size: 16px;
}
}
@media (max-width: 450px) {
html {
font-size: 10px;
}
#subtitlebox{
height: 6rem !important;
}
#lastfm-box, #links {
flex: 25 !important;
}
#heartbeatbox, #clock-box {
flex: 13 !important;
}
}
/*
Rules for thy Sanity
h1 = title
h3 = paragraph
h5 = caption
paragraph = text
3rem vertical space between standard boxes (split into 1.5rem top and bottom)
*/
/*
ideas
* navbar as vertical element on left side
* status bar under title for lastfm, mood?, misc
* services uptime - uptime kuma integration ---- sad, no api or anything for kuma...
* socials/links/platforms in first right-aligned box
** wont work well on mobile, instead just attach it under title within same box?
* left side of that box as bio
* current weather widget to right of lastfm status -- naur, too much effort and weird?
* comets and blinking stars in background
* more animation in general - make this mf ALIVE!!!!
* fancy borders see bookmark
* make a little place for the kitty in top left (or right?)
*/
/*
The container query length units are:
cqw: 1% of a query container's width
cqh: 1% of a query container's height
cqi: 1% of a query container's inline size
cqb: 1% of a query container's block size
cqmin: The smaller value of either cqi or cqb
cqmax: The larger value of either cqi or cqb
*/
html {
background-color: #000000;
background-image: url('/assets/bg.gif');
background-repeat: repeat;
background-color: transparent;
margin: 0px;
border: 0px;
padding: 0px;
min-height: 100vh;
font-family: "IBM Plex Mono", monospace;
font-style: normal;
color: #E9E9E9;
font-weight: 400;
}
body {
margin: 0px;
border: 0px;
padding: 0px;
min-height: 100vh;
}
.container {
width: min(900px,92vw);
margin: auto;
}
#maintitlebox {
margin-top: 1rem;
margin-bottom: 0;
}
#maintitle {
font-size: 3.5rem;
text-align: center;
margin: 0;
margin: auto;
}
#subtitlebox {
display: flex;
justify-content: stretch;
align-items: stretch;
height: 4rem;
margin: 0;
padding: 0;
margin-bottom: 1.5rem;
}
#links {
padding: 0;
margin: 0;
height: 100%;
flex: 1 1 auto;
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
flex: 3;
}
.linkbox {
margin-right: 1rem;
background: #000;
padding: 0.5rem;
}
#heartbeatbox {
margin: 0;
padding: 0;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
flex: 1
}
#heartbeat-time {
font-size: 1.2rem;
}
.link {
color: #E9E9E9;
text-decoration: none;
position: relative;
}
.link::after {
content: '';
position: absolute;
width: 0;
height: 0.1rem;
display: block;
left: 0;
background: #E9E9E9;
transition: width 0.3s ease, left 0.3s ease;
}
.link:hover::after {
width: 100%;
left: 0;
}
.box {
background: #000;
border: 1px solid #E9E9E9;
margin-top: 1.5rem;
margin-bottom: 1.5rem;
padding: 1rem;
align-self: center;
justify-self: center;
max-height: 100%;
}
.button {
background: #000;
border: 1px solid #E9E9E9;
margin-top: 1.5rem;
margin-bottom: 1.5rem;
padding: 1rem;
align-self: center;
justify-self: center;
max-height: 100%;
cursor: pointer;
display: inline-block;
text-align: center;
text-decoration: none;
color: inherit;
font: inherit;
}
.button:active {
background: #333333; /* Lighter grey color */
}
.box:last-child {
margin-bottom: 3rem;
}
#header {
display: flex;
flex-direction: row;
margin-top: 0.75rem;
height: 8rem;
}
#lastfm-box {
background: #000;
border: 1px solid #E9E9E9;
justify-content: space-between;
display: flex;
align-items: center;
flex: 1 1 auto;
height: 8rem;
flex: 3;
}
#lastfm-contents {
font-family: "IBM Plex Mono", monospace;
font-weight: 400;
font-style: normal;
color: #E9E9E9;
line-height: 1.8;
font-size: min(1rem, 2.2cm);
overflow: hidden;
max-height: 100%; /* Adjusted to 100% */
padding: 1rem;
padding-top: 0.5rem;
display: block;
box-sizing: border-box;
}
#lastfm-artist {
font-style: normal;
}
#lastfm-timesinceplay {
font-style:italic;
color:#e9e9e9a2;
font-size: min(1rem, 2.2cqmin);
}
#lastfm-lastplayedsong, #lastfm-lastlastplayedsong{
font-style:italic;
color: #e9e9e9a2;
font-size: min(1rem, 2.2cqmin);
}
#lastfm-artbox {
padding: 1rem;
}
#lastfm-art {
border: 1px solid #E9E9E9;
padding: 0.2rem;
width: 5rem;
height: 5rem;
}
#clock-box {
background: #000;
border: 1px solid #E9E9E9;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 8rem;
gap: 0.3rem;
flex: 1;
}
#clocktitle {
padding: 0;
margin: 0;
font-size: 0.9rem;
}
#clock-contents {
display: flex;
gap: 0.3rem;
align-items: top;
justify-content: center;
}
#clock {
padding: 0;
margin: 0;
font-size: 1.8rem;
flex: 9;
padding-left: 0.5rem;
}
#clock-icon {
padding: 0;
margin: 0;
padding-top: 0.4rem;
flex: 1;
width: 1.2rem;
height: 1.2rem;
padding-right: 0.5rem;
}
.image {
width: 100%;
border: 1rem;
padding-top: 1rem;
}
#uptime {
display: flex;
align-items: stretch;
justify-content: left;
flex-direction: column;
}
.uptime-pair {
display: flex;
border: 1px solid #E9E9E9;
padding: 1rem;
flex-direction: row;
}
.uptime-name-element, .uptime-status-element {
border: 1px solid red;
padding: 1rem;
}
/* #uptime-name, #uptime-status {
align-items: center;
height: 100%;
margin: 0;
}
.uptime-name-element, .uptime-status-element {
height: 2rem;
padding: 0;
margin: 0;
border: 0;
}
.uptime-name-element {
font-style: bold;
border-bottom: 1px solid #E9E9E9;
}
.uptime-name-element:last-of-type {
border-bottom: 0;
}
.uptime-box {
background: #000;
border: 1px solid #E9E9E9;
margin-top: 1.5rem;
margin-bottom: 1.5rem;
padding: 1rem;
max-height: 100%;
}
*/
h1 {
font-weight: 500;
font-size: 4rem;
letter-spacing: 0.2rem;
margin: 0;
padding: 0;
}
h3 {
font-weight: 500;
font-size: 2.4rem;
margin: 0;
padding: 0;
padding-bottom: 0.2rem;
text-align: left;
}
h5 {
font-size: 1.2rem;
font-style: italic;
margin: 0;
padding: 0;
padding-bottom: 1.2rem;
}
p, pre {
font-size: 1.2rem;
margin: 0;
padding: 0;
}
button {
color: #E9E9E9;
}
#stripes {
width: 100vw;
height: max(16vh, 176px);
}