add client side shuffel so every page refresh it stays fresh

This commit is contained in:
2026-03-18 15:54:29 +01:00
parent f1fbdbafdc
commit d10521f04b
11 changed files with 366 additions and 254 deletions

View File

@@ -1,14 +1,14 @@
---
// Import the global.css file here so that it is included on
// all pages through the use of the <BaseHead /> component.
import '@styles/global.css';
import type { ImageMetadata } from 'astro';
import { SITE_TITLE } from '@ptypes/consts.ts';
import "@styles/global.css";
import type { ImageMetadata } from "astro";
import { SITE_TITLE } from "@ptypes/consts.ts";
interface Props {
title: string;
description: string;
image?: ImageMetadata;
title: string;
description: string;
image?: ImageMetadata;
}
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
@@ -23,16 +23,28 @@ const { title, description } = Astro.props;
<link rel="icon" href="/favicon.ico" />
<link rel="sitemap" href="/sitemap-index.xml" />
<link
rel="alternate"
type="application/rss+xml"
title={SITE_TITLE}
href={new URL('rss.xml', Astro.site)}
rel="alternate"
type="application/rss+xml"
title={SITE_TITLE}
href={new URL("rss.xml", Astro.site)}
/>
<meta name="generator" content={Astro.generator} />
<!-- Font preloads -->
<link rel="preload" href="/fonts/atkinson-regular.woff" as="font" type="font/woff" crossorigin />
<link rel="preload" href="/fonts/atkinson-bold.woff" as="font" type="font/woff" crossorigin />
<link
rel="preload"
href="/fonts/atkinson-regular.woff"
as="font"
type="font/woff"
crossorigin
/>
<link
rel="preload"
href="/fonts/atkinson-bold.woff"
as="font"
type="font/woff"
crossorigin
/>
<!-- Canonical URL -->
<link rel="canonical" href={canonicalURL} />

View File

@@ -1,17 +1,17 @@
---
interface Props {
date: Date;
date: Date;
}
const { date } = Astro.props;
---
<time datetime={date.toISOString()}>
{
date.toLocaleDateString('en-us', {
year: 'numeric',
month: 'short',
day: 'numeric',
})
}
{
date.toLocaleDateString("en-us", {
year: "numeric",
month: "short",
day: "numeric",
})
}
</time>

View File

@@ -1,46 +1,49 @@
---
import { SITE_TITLE } from '@ptypes/consts.ts';
import HeaderLink from '@components/HeaderLink.astro';
import '@styles/indexHoverCss.css';
import { getCollection } from 'astro:content';
import { SITE_TITLE } from "@ptypes/consts.ts";
import HeaderLink from "@components/HeaderLink.astro";
import "@styles/indexHoverCss.css";
import { getCollection } from "astro:content";
const personalPosts = (await getCollection('personal')).sort(
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
const personalPosts = (await getCollection("personal")).sort(
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
);
const uniPosts = (await getCollection('uni')).sort(
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
const uniPosts = (await getCollection("uni")).sort(
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
);
---
<!DOCTYPE html>
<!doctype html>
<html>
<head>
<style>
</style>
</head>
<body>
<nav>
<h2><a href="/">{SITE_TITLE}</a></h2>
<ul>
<h4>Personal</h4>
{
personalPosts.map((post) => (
<li>
<HeaderLink href={`/blog/personal/${post.id}/`}>{post.data.title}</HeaderLink>
</li>
))
}
<hr>
<h4>University</h4>
<h6>(Group Projects)</h6>
{
uniPosts.map((post) => (
<li>
<HeaderLink href={`/blog/university/${post.id}/`}>{post.data.title}</HeaderLink>
</li>
))
}
</ul>
</div>
</nav>
</body>
<head>
<style></style>
</head>
<body>
<nav>
<h2><a href="/">{SITE_TITLE}</a></h2>
<ul>
<h4>Personal</h4>
{
personalPosts.map((post) => (
<li>
<HeaderLink href={`/blog/personal/${post.id}/`}>
{post.data.title}
</HeaderLink>
</li>
))
}
<hr />
<h4>University</h4>
<h6>(Group Projects)</h6>
{
uniPosts.map((post) => (
<li>
<HeaderLink href={`/blog/university/${post.id}/`}>
{post.data.title}
</HeaderLink>
</li>
))
}
</ul>
</nav>
</body>
</html>

View File

@@ -1,21 +1,20 @@
---
import type { HTMLAttributes } from 'astro/types';
import type { HTMLAttributes } from "astro/types";
type Props = HTMLAttributes<'a'>;
type Props = HTMLAttributes<"a">;
const { href, class: className, ...props } = Astro.props;
const pathname = Astro.url.pathname.replace(import.meta.env.BASE_URL, '');
const pathname = Astro.url.pathname.replace(import.meta.env.BASE_URL, "");
const subpath = pathname.match(/[^\/]+/g);
const isActive = href === pathname || href === '/' + (subpath?.[0] || '');
const isActive = href === pathname || href === "/" + (subpath?.[0] || "");
---
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
</head>
<body>
<a href={href} class:list={[className, { active: isActive }]} {...props}>
<slot />
</a>
</body>
<head> </head>
<body>
<a href={href} class:list={[className, { active: isActive }]} {...props}>
<slot />
</a>
</body>
</html>

View File

@@ -1,52 +1,58 @@
---
import BaseHead from '@components/BaseHead.astro';
import Header from '@components/Header.astro';
import GithubIcon from '@assets/github.svg'
import BaseHead from "@components/BaseHead.astro";
import Header from "@components/Header.astro";
import GithubIcon from "@assets/github.svg";
const { title, description, gitLink } = Astro.props;
---
<html lang="en">
<head>
<BaseHead title={title} description={description} />
<!-- To add meta data here later -->
<style>
body {
display:flex;
flex-direction: row;
}
main { display: flex;
flex-direction: row;
align-items: start;
justify-content: space-between;
padding: 0;
margin: 0;
flex-grow: 1;
}
<head>
<BaseHead title={title} description={description} />
<!-- To add meta data here later -->
<style>
body {
display: flex;
flex-direction: row;
}
main {
display: flex;
flex-direction: row;
align-items: start;
justify-content: space-between;
padding: 0;
margin: 0;
flex-grow: 1;
}
.wrapper {
display: flex;
flex-direction: column;
flex-grow: 1;
min-width: 0;
width: 0;
padding: 1cm;
margin: 1cm;
}
.wrapper {
display: flex;
flex-direction: column;
flex-grow: 1;
min-width: 0;
width: 0;
padding: 1cm;
margin: 1cm;
}
</style>
</head>
</style>
</head>
<body>
<main>
<Header />
<div class="wrapper">
<a href={gitLink ?? 'https://github.com/juli0n21'} style="position: relative;">
<GithubIcon style="position: absolute; right:0;" width="50px" height="50px" />
</a>
<slot />
</div>
</main>
</body>
<body>
<main>
<Header />
<div class="wrapper">
<a
href={gitLink ?? "https://github.com/juli0n21"}
style="position: relative;"
>
<GithubIcon
style="position: absolute; right:0;"
width="50px"
height="50px"
/>
</a>
<slot />
</div>
</main>
</body>
</html>

View File

@@ -1,6 +1,6 @@
---
import { getCollection, render } from 'astro:content';
import BlogPost from '@layouts/BlogPost.astro';
import { getCollection, render } from "astro:content";
import BlogPost from "@layouts/BlogPost.astro";
import type {
InferGetStaticParamsType,
InferGetStaticPropsType,
@@ -8,11 +8,11 @@ import type {
} from "astro";
export async function getStaticPaths() {
const posts = await getCollection('personal');
return posts.map(post => ({
params: { id: post.id },
props: { post },
}));
const posts = await getCollection("personal");
return posts.map((post) => ({
params: { id: post.id },
props: { post },
}));
}
getStaticPaths satisfies GetStaticPaths;
@@ -25,5 +25,5 @@ const { Content } = await render(post);
---
<BlogPost {...post.data}>
<Content />
<Content />
</BlogPost>

View File

@@ -1,6 +1,6 @@
---
import { getCollection, render } from 'astro:content';
import BlogPost from '@layouts/BlogPost.astro';
import { getCollection, render } from "astro:content";
import BlogPost from "@layouts/BlogPost.astro";
import type {
InferGetStaticParamsType,
InferGetStaticPropsType,
@@ -8,11 +8,11 @@ import type {
} from "astro";
export async function getStaticPaths() {
const posts = await getCollection('uni');
return posts.map(post => ({
params: { id: post.id },
props: { post },
}));
const posts = await getCollection("uni");
return posts.map((post) => ({
params: { id: post.id },
props: { post },
}));
}
getStaticPaths satisfies GetStaticPaths;
@@ -25,5 +25,5 @@ const { Content } = await render(post);
---
<BlogPost {...post.data}>
<Content />
<Content />
</BlogPost>

View File

@@ -1,143 +1,182 @@
---
import BaseHead from '@components/BaseHead.astro';
import Header from '@components/Header.astro';
import { SITE_DESCRIPTION, SITE_TITLE } from '@ptypes/consts.ts';
import BaseHead from "@components/BaseHead.astro";
import Header from "@components/Header.astro";
import { SITE_DESCRIPTION, SITE_TITLE } from "@ptypes/consts.ts";
let top: string[] = [];
let bottom: string[] = [];
const api: string =
"https://fshare.kami.boo/api/server/folder/cmm9jbqvt00025omwu5cnpw9i";
const response = await fetch(api);
let shuffled: string[] = [];
let images: string[] = [];
const api: string = 'https://fshare.kami.boo/api/server/folder/cmm9jbqvt00025omwu5cnpw9i';
const response = await fetch(api)
if (response.ok) {
const data = await response.json();
const files = data.files.map((f: Response) => `https://fshare.kami.boo/raw${f.url.substring(2)}`).sort(() => Math.random() - .5) as string[];
const middle = Math.round(files.length / 2);
shuffled = files.slice(0,middle);
images = files.slice(middle);
} else {
const errorBody = await response.text();
console.error("FAILED TO FETCH", response.status, response.statusText, errorBody);
process.exit(1);
}
const data = await response.json();
const files = data.files.map(
(f: Response) => `https://fshare.kami.boo/raw${f.url.substring(2)}`,
) as string[];
const middle = Math.round(files.length / 2);
top = files.slice(0, middle);
bottom = files.slice(middle);
} else {
const errorBody = await response.text();
console.error(
"FAILED TO FETCH",
response.status,
response.statusText,
errorBody,
);
process.exit(1);
}
---
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<BaseHead title={SITE_TITLE} description={SITE_DESCRIPTION} />
<style>
.layout {
display: flex;
height: 100%;
width: 100%;
}
<head>
<BaseHead title={SITE_TITLE} description={SITE_DESCRIPTION} />
<style>
.layout {
display: flex;
height: 100%;
width: 100%;
}
.sidebar {
height: 100vh;
display: flex;
flex-direction: column;
padding: 0;
box-sizing: border-box;
z-index: 10;
background: rgba(0, 0, 0, 0.5);
color: white;
}
.sidebar {
height: 100vh;
display: flex;
flex-direction: column;
padding: 0;
box-sizing: border-box;
z-index: 10;
background: rgba(0, 0, 0, 0.5);
color: white;
}
.scrolling-gallery {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
position: relative;
margin: 1cm;
}
.scrolling-gallery {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
position: relative;
margin: 1cm;
}
main {
background: white;
overflow: hidden;
}
main {
background: white;
overflow: hidden;
}
.gallery-wrapper {
display: flex;
flex-direction: column;
gap: 4rem;
transform: rotate(-8deg) scale(1.3);
}
.gallery-wrapper {
display: flex;
flex-direction: column;
gap: 4rem;
transform: rotate(-8deg) scale(1.3);
}
.scroll-track {
display: flex;
gap: 3rem;
width: max-content;
animation: infiniteScroll 500s linear infinite;
padding: 0 2rem;
}
.scroll-track {
display: flex;
gap: 3rem;
width: max-content;
animation: infiniteScroll 500s linear infinite;
padding: 0 2rem;
}
.track-reverse {
animation: infiniteScrollReverse 600s linear infinite;
}
.track-reverse {
animation: infiniteScrollReverse 600s linear infinite;
}
.tilted-container {
width: 220px;
height: 280px;
flex-shrink: 0;
border-radius: 12px;
overflow: hidden;
transition: transform 0.3s ease;
box-shadow: 0 10px 20px rgba(0,0,0,0.1);
}
.tilted-container {
width: 220px;
height: 280px;
flex-shrink: 0;
border-radius: 12px;
overflow: hidden;
transition: transform 0.3s ease;
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
}
.tilted-container:hover {
transform: scale(1.10);
z-index: 1;
}
.tilted-container:hover {
transform: scale(1.1);
z-index: 1;
}
.tilted-container img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.tilted-container img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
@keyframes infiniteScroll {
0% { transform: translateX(0%); }
100% { transform: translateX(-50%); }
}
@keyframes infiniteScroll {
0% {
transform: translateX(0%);
}
100% {
transform: translateX(-50%);
}
}
@keyframes infiniteScrollReverse {
0% { transform: translateX(0%); }
100% { transform: translateX(50%) }
}
</style>
</head>
<body>
<div class="layout">
<aside class="sidebar">
<Header />
</aside>
@keyframes infiniteScrollReverse {
0% {
transform: translateX(0%);
}
100% {
transform: translateX(50%);
}
}
</style>
</head>
<body>
<div class="layout">
<aside class="sidebar">
<Header />
</aside>
<main class="scrolling-gallery">
<div class="gallery-wrapper">
<div class="scroll-track">
{[...images, ...images].map((src) => (
<a class="tilted-container" href={src} target="_blank">
<img src={src} loading="lazy" />
</a>
))}
</div>
{
<main class="scrolling-gallery">
<div class="gallery-wrapper">
<div id="top-images" class="scroll-track">
{
[...top, ...top].map((src) => (
<a class="tilted-container" href={src} target="_blank">
<img src={src} loading="lazy" />
</a>
))
}
</div>
{}
<div id="bottom-images" class="scroll-track track-reverse">
{
[...bottom, ...bottom].map((src) => (
<a class="tilted-container" href={src} target="_blank">
<img src={src} loading="lazy" />
</a>
))
}
</div>
</div>
</main>
</div>
<script>
function shuffleImages(selector: string) {
const track = document.querySelector(selector);
if (!track) return;
}
<div class="scroll-track track-reverse">
{[...shuffled,...shuffled].map((src) => (
<a class="tilted-container" href={src} target="_blank">
<img src={src} loading="lazy" />
</a>
))
}
</div>
</div>
</main>
</div>
</body>
const items = Array.from(track.children);
const shuffled = items.sort(() => Math.random() - 0.5);
track.innerHTML = "";
const fragment = document.createDocumentFragment();
[...shuffled, ...shuffled].forEach((item) => {
fragment.appendChild(item.cloneNode(true));
});
track.appendChild(fragment);
}
shuffleImages("#top-images");
shuffleImages("#bottom-images");
</script>
</body>
</html>