infinite scroll marquee almost done

This commit is contained in:
Oleksandr 2025-12-17 20:13:00 +02:00
parent 3d615c4b5f
commit eca995ca2c
Signed by: Xanazf
GPG key ID: 821EEC32761AC17C
7 changed files with 136 additions and 77 deletions

View file

@ -2,6 +2,7 @@
import matrixLogo from "@icons/matrix-logo.svg?raw"; import matrixLogo from "@icons/matrix-logo.svg?raw";
import discordLogo from "@icons/discord-logo.svg?raw"; import discordLogo from "@icons/discord-logo.svg?raw";
import gitLogo from "@icons/git-logo.svg?raw"; import gitLogo from "@icons/git-logo.svg?raw";
import { ThemeSelect } from "./hooks/ThemeSwitch";
interface Props { interface Props {
class?: string; class?: string;
@ -18,6 +19,7 @@ const props = Astro.props;
and our contributors and our contributors
</a> </a>
</div> </div>
<ThemeSelect client:load />
<div class="socials-changelog"> <div class="socials-changelog">
<section class="socials"> <section class="socials">
<a href="https://matrix.to/#/#quickshell:outfoxxed.me" target="_blank" aria-label="Join our matrix space"> <a href="https://matrix.to/#/#quickshell:outfoxxed.me" target="_blank" aria-label="Join our matrix space">

View file

@ -20,7 +20,7 @@ import MarqueeContent from "./MarqueeContent.astro";
marquee.style.setProperty("--mult", "1") marquee.style.setProperty("--mult", "1")
window.addEventListener("load", autoplayInit, false); window.addEventListener("load", autoplayInit, false);
const videos = document.getElementsByClassName("marquee-item-content") as HTMLCollectionOf<HTMLVideoElement>; let videos = document.getElementsByClassName("marquee-item-content") as HTMLCollectionOf<HTMLVideoElement>;
const videoCount = videos.length; const videoCount = videos.length;
const lastVideoIndex = videos[videos.length - 1] const lastVideoIndex = videos[videos.length - 1]
let currentVideoIndex = 0; let currentVideoIndex = 0;
@ -38,6 +38,24 @@ import MarqueeContent from "./MarqueeContent.astro";
} }
currentVideoIndex = index; currentVideoIndex = index;
if (index === videos.length - 1) {
console.log("shift")
const shifted = videos.item(0);
marquee.firstElementChild.remove()
const video_div = document.createElement("div");
video_div.classList.add("marquee-item");
console.log("shift: ",video_div.classList.toString())
shifted.setAttribute("data-media-index", (index+1).toString())
video_div.appendChild(shifted);
marquee.appendChild(video_div);
videos = document.getElementsByClassName("marquee-item-content") as HTMLCollectionOf<HTMLVideoElement>;
currentVideoIndex = index - 1;
console.log("shift", marquee)
}
currentVideo = videos[currentVideoIndex]; currentVideo = videos[currentVideoIndex];
currentVideo.currentTime = 0; currentVideo.currentTime = 0;
@ -47,8 +65,8 @@ import MarqueeContent from "./MarqueeContent.astro";
function offsetCarousel(offset: number) { function offsetCarousel(offset: number) {
let nextIndex = currentVideoIndex + offset; let nextIndex = currentVideoIndex + offset;
if (nextIndex < 0) nextIndex += videoCount; // if (nextIndex < 0) nextIndex += videoCount;
nextIndex = nextIndex % videoCount; // nextIndex = nextIndex % videoCount;
setActiveVideo(nextIndex); setActiveVideo(nextIndex);
} }
@ -57,6 +75,7 @@ import MarqueeContent from "./MarqueeContent.astro";
rootMargin: "0px", rootMargin: "0px",
threshold: 0.1, threshold: 0.1,
}; };
const mult = marquee.style.getPropertyValue("--mult") ?? 0;
const observer = new IntersectionObserver((entries) => { const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => { entries.forEach((entry) => {
@ -86,28 +105,8 @@ import MarqueeContent from "./MarqueeContent.astro";
video.style.animationPlayState = "running"; video.style.animationPlayState = "running";
video.style.animationDirection = "normal"; video.style.animationDirection = "normal";
} }
if (entry.isIntersecting && video === lastVideoIndex) {
addNextVideo();
}
}); });
}, intersectionOptions); }, intersectionOptions);
function addNextVideo() {
const firstVideo = videos[0];
if (!firstVideo) return;
const newVideo = firstVideo.cloneNode(true) as HTMLVideoElement;
// IMPORTANT: Reset the state of the new video
newVideo.pause();
newVideo.currentTime = 0;
newVideo.style.animationName = "none"; // Reset any lingering animation styles
// append to the marquee
marquee.appendChild(newVideo);
// observe the new video
observer.observe(newVideo);
}
for (const video of videos) { for (const video of videos) {
observer.observe(video); observer.observe(video);

View file

@ -35,24 +35,26 @@ const videos = [
]; ];
--- ---
<div id="marquee-content" class="marquee-content" data-scroll="0" data-media-index="0"> <div id="marquee-content" class="marquee-content" data-scroll="0" data-media-index="0">
{videos.map(({ author, source, installable, path }, index) => {videos.map(({ author, source, installable, path }, index) => {
<div class="marquee-item"> return (
<video <div class=`marquee-item`>
data-media-index={index} <video
data-media-author={author} data-media-index={index}
id="showcase-video" data-media-author={author}
class="marquee-item-spacing marquee-item-content" id="showcase-video"
muted class="marquee-item-spacing marquee-item-content"
controls muted
playsinline controls
preload="metadata" playsinline
> preload="metadata"
<source src={path} type="video/mp4"/> >
<source src={path} type="video/mp4"/>
</video> </video>
<p> <p>
Configuration by <Fragment set:html={author}/> Configuration by <Fragment set:html={author}/>
{source && !installable && <>(<a href={source}>source code</a>)</>} {source && !installable && <>(<a href={source}>source code</a>)</>}
{source && installable && <>(<a href={source}>install</a>)</>} {source && installable && <>(<a href={source}>install</a>)</>}
</p> </p>
</div>)} </div>
)})}
</div> </div>

View file

@ -138,15 +138,20 @@
} }
@keyframes bounce { @keyframes bounce {
0%, 0% {
100% { transform: none;
transform: translateY(-25%); animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
} }
50% { 50% {
transform: none; transform: translateY(-12%);
animation-timing-function: cubic-bezier(0, 0, 0.2, 1); animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
color: hsla(var(--green) 100 69 / 0.75);
}
100% {
transform: none;
animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
} }
} }
@ -166,5 +171,6 @@
--animate-spin: spin 1s linear infinite; --animate-spin: spin 1s linear infinite;
--animate-ping: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite; --animate-ping: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
--animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; --animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
--animate-bounce: bounce 1s infinite; --animate-bounce: bounce 0.6s var(--ease-out) forwards;
--animate-fade: fade 0.3s cubic-bezier(0.4, 0, 0.6, 1);
} }

View file

@ -19,6 +19,7 @@ html {
position: relative; position: relative;
margin: 0; margin: 0;
padding: 0; padding: 0;
transition: all 0.3s var(--ease-in-out);
} }
body { body {

View file

@ -1,12 +1,12 @@
pre.shiki { pre.shiki {
margin-block: 1.618rem; margin-block: var(--lg);
} }
:where(p, li):has(> code) code { :where(p, li):has(> code) code {
padding-inline: 0.272rem; padding-inline: var(--2xl);
border-radius: 0.272rem; border-radius: var(--2xl);
color: hsl(var(--blue) 100% 69%); color: hsl(var(--blue) 100 69);
background-color: hsl(var(--blue) 85% 35% / 0.1); background-color: hsla(var(--blue) 85 35 / 0.1);
} }
.shiki, .shiki,
@ -26,6 +26,9 @@ pre {
border-radius: 0.618rem; border-radius: 0.618rem;
overflow: hidden; overflow: hidden;
text-wrap: wrap; text-wrap: wrap;
transition:
background-color 0.3s var(--ease-in-out),
color 0.3s var(--ease-in-out);
& .copy-button { & .copy-button {
all: unset; all: unset;
@ -41,18 +44,29 @@ pre {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
color: hsl(var(--blue) 100% 69%); color: hsla(var(--blue) 100 69 / 0.33);
background-color: hsl(var(--blue) 85% 35% / 0.1); background-color: hsla(var(--blue) 85 35 / 0.01);
cursor: pointer; cursor: pointer;
transition: color 0.25s; transition:
background-color 0.3s var(--ease-in-out),
color 0.3s var(--ease-in-out);
z-index: 10; z-index: 10;
&:hover { &:hover {
color: hsl(var(--blue) 100% 75%); color: hsla(var(--blue) 100 75 / 0.75);
background-color: hsla(var(--blue) 85 35 / 0.1);
} }
&.copied { &.copied {
animation: pulseGreen 0.5s cubic-bezier(0, 1, 0.6, 1); animation: var(--animate-bounce);
}
}
&.shiki {
box-shadow: var(--shadow-xl);
&:hover .copy-button {
background-color: hsla(var(--blue) 85 35 / 0.07);
} }
} }
} }

View file

@ -16,6 +16,17 @@
transition: none !important; transition: none !important;
} }
.theme-toggle {
height: 24px;
font-size: 1.614rem;
color: hsla(var(--signal-color) / 0.7);
&:hover {
cursor: pointer;
color: hsla(var(--signal-color) / 1);
}
}
/* color styling */ /* color styling */
.header { .header {
background-color: hsl(var(--bg-400)); background-color: hsl(var(--bg-400));
@ -49,6 +60,14 @@ html.dark {
background-color: hsl(var(--secondary-900)); background-color: hsl(var(--secondary-900));
color: hsl(var(--secondary-500)); color: hsl(var(--secondary-500));
} }
& .theme-toggle {
color: hsla(var(--func-color) / 0.7);
&:hover {
color: hsla(var(--func-color) / 1);
}
}
} }
/* layout and positioning */ /* layout and positioning */
@ -56,10 +75,12 @@ html.dark {
width: 75%; width: 75%;
height: 1px; height: 1px;
margin-block: 0.618rem; margin-block: 0.618rem;
background: linear-gradient(to right, background: linear-gradient(
transparent, to right,
hsl(var(--blue) 100% 59%), transparent,
transparent); hsl(var(--blue) 100% 59%),
transparent
);
} }
.unset { .unset {
@ -130,15 +151,6 @@ body.overflow-toc {
display: block; display: block;
} }
.theme-toggle {
height: 24px;
font-size: 1.614rem;
&:hover {
cursor: pointer;
}
}
footer { footer {
position: relative; position: relative;
width: 100%; width: 100%;
@ -146,7 +158,6 @@ footer {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
padding: 1rem 2rem; padding: 1rem 2rem;
overflow: hidden;
flex-shrink: 0; flex-shrink: 0;
background: hsl(var(--footer-bkg)); background: hsl(var(--footer-bkg));
@ -154,13 +165,38 @@ footer {
content: ""; content: "";
position: absolute; position: absolute;
top: 0; top: 0;
left: -1rem; left: 0;
right: 54%;
height: 1px; height: 1px;
width: calc(100% + 1rem); width: calc(48%);
background: linear-gradient(90deg, background: linear-gradient(
transparent 0%, 90deg,
hsl(var(--footer-bkg-border)) 50%, transparent 0%,
transparent 100%); hsl(var(--footer-bkg-border)) 100%
);
}
&::after {
content: "";
position: absolute;
top: 0;
right: 0;
left: 54%;
height: 1px;
width: calc(48% - 1rem);
background: linear-gradient(
90deg,
hsl(var(--footer-bkg-border)) 0%,
transparent 100%
);
}
& .theme-toggle {
position: absolute;
top: -12px;
left: 50%;
width: max-content;
height: max-content;
} }
& a { & a {
@ -199,12 +235,12 @@ footer {
gap: 0.2rem; gap: 0.2rem;
color: hsl(var(--text-dark)); color: hsl(var(--text-dark));
&>p { & > p {
margin-bottom: 0.2rem; margin-bottom: 0.2rem;
color: hsl(0deg 0% 40%); color: hsl(0deg 0% 40%);
} }
&>a { & > a {
text-decoration: none; text-decoration: none;
} }
} }
@ -218,7 +254,6 @@ footer {
gap: 0.373rem; gap: 0.373rem;
align-items: flex-start; align-items: flex-start;
font-size: 2.5rem; font-size: 2.5rem;
} }
& .changelog { & .changelog {
@ -235,7 +270,7 @@ footer {
footer { footer {
padding-inline: 0.75rem; padding-inline: 0.75rem;
& .credits>a { & .credits > a {
padding: 0.2rem 0; padding: 0.2rem 0;
} }
} }