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 discordLogo from "@icons/discord-logo.svg?raw";
import gitLogo from "@icons/git-logo.svg?raw";
import { ThemeSelect } from "./hooks/ThemeSwitch";
interface Props {
class?: string;
@ -18,6 +19,7 @@ const props = Astro.props;
and our contributors
</a>
</div>
<ThemeSelect client:load />
<div class="socials-changelog">
<section class="socials">
<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")
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 lastVideoIndex = videos[videos.length - 1]
let currentVideoIndex = 0;
@ -38,6 +38,24 @@ import MarqueeContent from "./MarqueeContent.astro";
}
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.currentTime = 0;
@ -47,8 +65,8 @@ import MarqueeContent from "./MarqueeContent.astro";
function offsetCarousel(offset: number) {
let nextIndex = currentVideoIndex + offset;
if (nextIndex < 0) nextIndex += videoCount;
nextIndex = nextIndex % videoCount;
// if (nextIndex < 0) nextIndex += videoCount;
// nextIndex = nextIndex % videoCount;
setActiveVideo(nextIndex);
}
@ -57,6 +75,7 @@ import MarqueeContent from "./MarqueeContent.astro";
rootMargin: "0px",
threshold: 0.1,
};
const mult = marquee.style.getPropertyValue("--mult") ?? 0;
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
@ -86,28 +105,8 @@ import MarqueeContent from "./MarqueeContent.astro";
video.style.animationPlayState = "running";
video.style.animationDirection = "normal";
}
if (entry.isIntersecting && video === lastVideoIndex) {
addNextVideo();
}
});
}, 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) {
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">
{videos.map(({ author, source, installable, path }, index) =>
<div class="marquee-item">
<video
data-media-index={index}
data-media-author={author}
id="showcase-video"
class="marquee-item-spacing marquee-item-content"
muted
controls
playsinline
preload="metadata"
>
<source src={path} type="video/mp4"/>
{videos.map(({ author, source, installable, path }, index) => {
return (
<div class=`marquee-item`>
<video
data-media-index={index}
data-media-author={author}
id="showcase-video"
class="marquee-item-spacing marquee-item-content"
muted
controls
playsinline
preload="metadata"
>
<source src={path} type="video/mp4"/>
</video>
<p>
Configuration by <Fragment set:html={author}/>
{source && !installable && <>(<a href={source}>source code</a>)</>}
{source && installable && <>(<a href={source}>install</a>)</>}
</p>
</div>)}
</div>
)})}
</div>

View file

@ -138,15 +138,20 @@
}
@keyframes bounce {
0%,
100% {
transform: translateY(-25%);
animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
0% {
transform: none;
animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
}
50% {
transform: none;
transform: translateY(-12%);
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-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-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;
margin: 0;
padding: 0;
transition: all 0.3s var(--ease-in-out);
}
body {

View file

@ -1,12 +1,12 @@
pre.shiki {
margin-block: 1.618rem;
margin-block: var(--lg);
}
:where(p, li):has(> code) code {
padding-inline: 0.272rem;
border-radius: 0.272rem;
color: hsl(var(--blue) 100% 69%);
background-color: hsl(var(--blue) 85% 35% / 0.1);
padding-inline: var(--2xl);
border-radius: var(--2xl);
color: hsl(var(--blue) 100 69);
background-color: hsla(var(--blue) 85 35 / 0.1);
}
.shiki,
@ -26,6 +26,9 @@ pre {
border-radius: 0.618rem;
overflow: hidden;
text-wrap: wrap;
transition:
background-color 0.3s var(--ease-in-out),
color 0.3s var(--ease-in-out);
& .copy-button {
all: unset;
@ -41,18 +44,29 @@ pre {
display: flex;
align-items: center;
justify-content: center;
color: hsl(var(--blue) 100% 69%);
background-color: hsl(var(--blue) 85% 35% / 0.1);
color: hsla(var(--blue) 100 69 / 0.33);
background-color: hsla(var(--blue) 85 35 / 0.01);
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;
&: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 {
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;
}
.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 */
.header {
background-color: hsl(var(--bg-400));
@ -49,6 +60,14 @@ html.dark {
background-color: hsl(var(--secondary-900));
color: hsl(var(--secondary-500));
}
& .theme-toggle {
color: hsla(var(--func-color) / 0.7);
&:hover {
color: hsla(var(--func-color) / 1);
}
}
}
/* layout and positioning */
@ -56,10 +75,12 @@ html.dark {
width: 75%;
height: 1px;
margin-block: 0.618rem;
background: linear-gradient(to right,
transparent,
hsl(var(--blue) 100% 59%),
transparent);
background: linear-gradient(
to right,
transparent,
hsl(var(--blue) 100% 59%),
transparent
);
}
.unset {
@ -130,15 +151,6 @@ body.overflow-toc {
display: block;
}
.theme-toggle {
height: 24px;
font-size: 1.614rem;
&:hover {
cursor: pointer;
}
}
footer {
position: relative;
width: 100%;
@ -146,7 +158,6 @@ footer {
display: flex;
justify-content: space-between;
padding: 1rem 2rem;
overflow: hidden;
flex-shrink: 0;
background: hsl(var(--footer-bkg));
@ -154,13 +165,38 @@ footer {
content: "";
position: absolute;
top: 0;
left: -1rem;
left: 0;
right: 54%;
height: 1px;
width: calc(100% + 1rem);
background: linear-gradient(90deg,
transparent 0%,
hsl(var(--footer-bkg-border)) 50%,
transparent 100%);
width: calc(48%);
background: linear-gradient(
90deg,
transparent 0%,
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 {
@ -199,12 +235,12 @@ footer {
gap: 0.2rem;
color: hsl(var(--text-dark));
&>p {
& > p {
margin-bottom: 0.2rem;
color: hsl(0deg 0% 40%);
}
&>a {
& > a {
text-decoration: none;
}
}
@ -218,7 +254,6 @@ footer {
gap: 0.373rem;
align-items: flex-start;
font-size: 2.5rem;
}
& .changelog {
@ -235,7 +270,7 @@ footer {
footer {
padding-inline: 0.75rem;
& .credits>a {
& .credits > a {
padding: 0.2rem 0;
}
}