mirror of
https://github.com/nuttylmao/nutty.gg.git
synced 2026-09-18 19:50:58 -04:00
@@ -337,9 +337,10 @@ function RefreshWidgetPreview() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function UpdateStreamerBotConnection() {
|
function UpdateStreamerBotConnection() {
|
||||||
|
// If Streamer.bot is not being used, exit early
|
||||||
if (!useStreamerBot)
|
if (!useStreamerBot)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
let addressElement = document.getElementById('address');
|
let addressElement = document.getElementById('address');
|
||||||
let portElement = document.getElementById('port');
|
let portElement = document.getElementById('port');
|
||||||
|
|
||||||
@@ -359,40 +360,43 @@ function UpdateStreamerBotConnection() {
|
|||||||
//////////////////
|
//////////////////
|
||||||
|
|
||||||
// Connect to Streamer.bot and get list of actions
|
// Connect to Streamer.bot and get list of actions
|
||||||
let sbServerAddress = '127.0.0.1';
|
if (useStreamerBot)
|
||||||
let sbServerPort = '8080';
|
{
|
||||||
let client = new StreamerbotClient({
|
let sbServerAddress = '127.0.0.1';
|
||||||
host: sbServerAddress,
|
let sbServerPort = '8080';
|
||||||
port: sbServerPort,
|
let client = new StreamerbotClient({
|
||||||
|
host: sbServerAddress,
|
||||||
|
port: sbServerPort,
|
||||||
|
|
||||||
onConnect: (data) => {
|
onConnect: (data) => {
|
||||||
console.log(`Streamer.bot successfully connected to ${sbServerAddress}:${sbServerPort}`)
|
console.log(`Streamer.bot successfully connected to ${sbServerAddress}:${sbServerPort}`)
|
||||||
console.debug(data);
|
console.debug(data);
|
||||||
|
|
||||||
// Get list of actions
|
// Get list of actions
|
||||||
GetSBActions();
|
GetSBActions();
|
||||||
},
|
},
|
||||||
|
|
||||||
onDisconnect: () => {
|
onDisconnect: () => {
|
||||||
console.error(`Streamer.bot disconnected from ${sbServerAddress}:${sbServerPort}`)
|
console.error(`Streamer.bot disconnected from ${sbServerAddress}:${sbServerPort}`)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function GetSBActions() {
|
||||||
|
const response = await client.getActions();
|
||||||
|
|
||||||
|
console.debug(response);
|
||||||
|
|
||||||
|
const datalistElement = document.createElement('datalist');
|
||||||
|
datalistElement.id = 'streamer-bot-actions';
|
||||||
|
|
||||||
|
for (const action of response.actions) {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = action.name;
|
||||||
|
datalistElement.appendChild(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.body.appendChild(datalistElement);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
async function GetSBActions() {
|
|
||||||
const response = await client.getActions();
|
|
||||||
|
|
||||||
console.debug(response);
|
|
||||||
|
|
||||||
const datalistElement = document.createElement('datalist');
|
|
||||||
datalistElement.id = 'streamer-bot-actions';
|
|
||||||
|
|
||||||
for (const action of response.actions) {
|
|
||||||
const option = document.createElement('option');
|
|
||||||
option.value = action.name;
|
|
||||||
datalistElement.appendChild(option);
|
|
||||||
}
|
|
||||||
|
|
||||||
document.body.appendChild(datalistElement);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function PopulateFontDatalist() {
|
async function PopulateFontDatalist() {
|
||||||
|
|||||||
+3
-2
@@ -1,6 +1,7 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
|
<title>nutty | Clocko</title>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<link rel="shortcut icon" href="#" />
|
<link rel="shortcut icon" href="#" />
|
||||||
<link rel="stylesheet" href="./style.css" />
|
<link rel="stylesheet" href="./style.css" />
|
||||||
@@ -19,6 +20,6 @@
|
|||||||
<script src="/.common/utils/helpers.js"></script>
|
<script src="/.common/utils/helpers.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/dayjs@1/dayjs.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/dayjs@1/dayjs.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/dayjs@1/plugin/utc.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/dayjs@1/plugin/utc.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/dayjs@1/plugin/timezone.min.js"></script>'
|
<script src="https://cdn.jsdelivr.net/npm/dayjs@1/plugin/timezone.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/dayjs@1/plugin/advancedFormat.js"></script>'
|
<script src="https://cdn.jsdelivr.net/npm/dayjs@1/plugin/advancedFormat.js"></script>
|
||||||
<script src="./script.js"></script>
|
<script src="./script.js"></script>
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
{ "value": "800", "label": "Extra Bold" },
|
{ "value": "800", "label": "Extra Bold" },
|
||||||
{ "value": "900", "label": "Black" }
|
{ "value": "900", "label": "Black" }
|
||||||
],
|
],
|
||||||
"defaultValue": "700",
|
"defaultValue": "800",
|
||||||
"showIf": "enableLine1",
|
"showIf": "enableLine1",
|
||||||
"group": "Appearance"
|
"group": "Appearance"
|
||||||
},
|
},
|
||||||
@@ -119,7 +119,7 @@
|
|||||||
"label": "Format",
|
"label": "Format",
|
||||||
"description": "<a href='https://day.js.org/docs/en/display/format' target='_blank'>Click here for formatting options</a>",
|
"description": "<a href='https://day.js.org/docs/en/display/format' target='_blank'>Click here for formatting options</a>",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"defaultValue": "ddd, MMM D",
|
"defaultValue": "ddd D MMM YY",
|
||||||
"showIf": "enableLine2",
|
"showIf": "enableLine2",
|
||||||
"group": "Appearance"
|
"group": "Appearance"
|
||||||
},
|
},
|
||||||
@@ -168,7 +168,7 @@
|
|||||||
"label": "Font Opacity",
|
"label": "Font Opacity",
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"defaultValue": "0.7",
|
"defaultValue": "0.9",
|
||||||
"min": 0,
|
"min": 0,
|
||||||
"max": 1,
|
"max": 1,
|
||||||
"step": ".01",
|
"step": ".01",
|
||||||
|
|||||||
+63
-14
@@ -475,9 +475,6 @@ function SetVisibility(visible) {
|
|||||||
// This is a more advanced marquee implementation that calculates the overflow distance and adjusts the scroll speed accordingly
|
// This is a more advanced marquee implementation that calculates the overflow distance and adjusts the scroll speed accordingly
|
||||||
const labelData = new Map();
|
const labelData = new Map();
|
||||||
|
|
||||||
// Adjust this value to change scroll speed (pixels per second)
|
|
||||||
const SCROLL_SPEED_PX_PER_SEC = 40;
|
|
||||||
|
|
||||||
function SetLabelText(elementId, text) {
|
function SetLabelText(elementId, text) {
|
||||||
const label = document.getElementById(elementId);
|
const label = document.getElementById(elementId);
|
||||||
if (!label) return;
|
if (!label) return;
|
||||||
@@ -495,8 +492,11 @@ function SetLabelText(elementId, text) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Adjust this value to change scroll speed (pixels per second)
|
||||||
|
const SCROLL_SPEED_PX_PER_SEC = 30;
|
||||||
|
const PAUSE_SEC = 5;
|
||||||
|
|
||||||
function ApplyMarquee(label, text) {
|
function ApplyMarquee(label, text) {
|
||||||
// 1. Render single span to measure dimensions
|
|
||||||
label.classList.remove('is-overflowing');
|
label.classList.remove('is-overflowing');
|
||||||
label.innerHTML = `<span class="scroll-content"></span>`;
|
label.innerHTML = `<span class="scroll-content"></span>`;
|
||||||
label.querySelector('.scroll-content').textContent = text;
|
label.querySelector('.scroll-content').textContent = text;
|
||||||
@@ -507,20 +507,69 @@ function ApplyMarquee(label, text) {
|
|||||||
const containerWidth = label.clientWidth;
|
const containerWidth = label.clientWidth;
|
||||||
const overflowDistance = textWidth - containerWidth;
|
const overflowDistance = textWidth - containerWidth;
|
||||||
|
|
||||||
// 2. Check if text exceeds container
|
|
||||||
if (overflowDistance > 0) {
|
if (overflowDistance > 0) {
|
||||||
// Calculate travel distance in percentage relative to scrollContent's width
|
|
||||||
const distancePercent = (overflowDistance / textWidth) * 100;
|
const distancePercent = (overflowDistance / textWidth) * 100;
|
||||||
|
|
||||||
// Compute duration to keep speed consistent regardless of length
|
// --- CONSTANT TIMINGS ---
|
||||||
// We multiply travel time by 2 (for both directions) plus 3 seconds for pauses
|
const travelSec = overflowDistance / SCROLL_SPEED_PX_PER_SEC;
|
||||||
const travelTime = overflowDistance / SCROLL_SPEED_PX_PER_SEC;
|
const pauseSec = PAUSE_SEC; // Always constant at each end
|
||||||
const totalDuration = (travelTime * 2) + 3;
|
|
||||||
|
|
||||||
// Pass calculated variables to CSS
|
|
||||||
scrollContent.style.setProperty('--scroll-distance', `-${distancePercent}%`);
|
|
||||||
label.style.setProperty('--marquee-duration', `${totalDuration}s`);
|
|
||||||
|
|
||||||
|
const totalSec = (travelSec * 2) + (pauseSec * 2);
|
||||||
|
const totalMs = totalSec * 1000;
|
||||||
|
|
||||||
|
const p1 = (pauseSec / totalSec);
|
||||||
|
const p2 = ((pauseSec + travelSec) / totalSec);
|
||||||
|
const p3 = ((pauseSec * 2 + travelSec) / totalSec);
|
||||||
|
|
||||||
|
scrollContent.style.setProperty('--scroll-distance', `-${distancePercent}%`);
|
||||||
|
|
||||||
|
// 1. Animate Text Translation
|
||||||
|
scrollContent.animate([
|
||||||
|
// Stay at start during pause 1 (linear/instant transition into movement)
|
||||||
|
{ transform: 'translateX(0%)', offset: 0, easing: 'linear' },
|
||||||
|
{ transform: 'translateX(0%)', offset: p1, easing: 'ease-in-out' }, // <--- Easing starts HERE as it leaves the edge
|
||||||
|
|
||||||
|
// Travel to the far end with smooth ease-in-out
|
||||||
|
{ transform: `translateX(-${distancePercent}%)`, offset: p2, easing: 'linear' }, // <--- Easing ends HERE as it arrives
|
||||||
|
|
||||||
|
// Stay at far end during pause 2
|
||||||
|
{ transform: `translateX(-${distancePercent}%)`, offset: p3, easing: 'ease-in-out' }, // <--- Easing starts HERE for return trip
|
||||||
|
|
||||||
|
// Return to start with smooth ease-in-out
|
||||||
|
{ transform: 'translateX(0%)', offset: 1, easing: 'linear' } // <--- Easing ends HERE back at start
|
||||||
|
], {
|
||||||
|
duration: totalMs,
|
||||||
|
iterations: Infinity
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. Animate Mask Variables simultaneously to keep soft edges synced
|
||||||
|
const maxOffset = 1;
|
||||||
|
// Dynamically scale buffer so it never overlaps text segments if overflow is small
|
||||||
|
const fadeBuffer = Math.min(0.02, (p2 - p1) / 4);
|
||||||
|
|
||||||
|
const off0 = 0;
|
||||||
|
const off1 = p1;
|
||||||
|
const off2 = Math.min(p1 + fadeBuffer, p2);
|
||||||
|
const off3 = Math.max(off2, p2 - fadeBuffer);
|
||||||
|
const off4 = p2;
|
||||||
|
const off5 = p3;
|
||||||
|
const off6 = Math.min(p3 + fadeBuffer, maxOffset);
|
||||||
|
const off7 = 1;
|
||||||
|
|
||||||
|
label._anim = label.animate([
|
||||||
|
{ '--mask-left': 'black', '--mask-right': 'transparent', offset: off0 },
|
||||||
|
{ '--mask-left': 'black', '--mask-right': 'transparent', offset: off1 },
|
||||||
|
{ '--mask-left': 'transparent', '--mask-right': 'transparent', offset: off2 },
|
||||||
|
{ '--mask-left': 'transparent', '--mask-right': 'transparent', offset: off3 },
|
||||||
|
{ '--mask-left': 'transparent', '--mask-right': 'black', offset: off4 },
|
||||||
|
{ '--mask-left': 'transparent', '--mask-right': 'black', offset: off5 },
|
||||||
|
{ '--mask-left': 'transparent', '--mask-right': 'transparent', offset: off6 },
|
||||||
|
{ '--mask-left': 'black', '--mask-right': 'transparent', offset: off7 }
|
||||||
|
], {
|
||||||
|
duration: totalMs,
|
||||||
|
iterations: Infinity
|
||||||
|
});
|
||||||
|
|
||||||
label.classList.add('is-overflowing');
|
label.classList.add('is-overflowing');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-51
@@ -183,13 +183,7 @@ body {
|
|||||||
will-change: transform;
|
will-change: transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-label.is-overflowing .scroll-content {
|
.text-label.is-overflowing {
|
||||||
animation: ping-pong-scroll var(--marquee-duration, 8s) ease-in-out infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-label.is-overflowing {
|
|
||||||
animation: ping-pong-mask var(--marquee-duration, 8s) ease-in-out infinite;
|
|
||||||
|
|
||||||
/* Static gradient structure referencing dynamic color variables */
|
/* Static gradient structure referencing dynamic color variables */
|
||||||
mask-image: linear-gradient(
|
mask-image: linear-gradient(
|
||||||
to right,
|
to right,
|
||||||
@@ -205,48 +199,4 @@ body {
|
|||||||
black calc(100% - var(--fade-width)),
|
black calc(100% - var(--fade-width)),
|
||||||
var(--mask-right) 100%
|
var(--mask-right) 100%
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes ping-pong-scroll {
|
|
||||||
0%, 15% {
|
|
||||||
transform: translateX(0%);
|
|
||||||
}
|
|
||||||
50%, 65% {
|
|
||||||
transform: translateX(var(--scroll-distance, -100%));
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: translateX(0%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes ping-pong-mask {
|
|
||||||
/* 1. AT START: Left is solid (black), Right is faded (transparent) */
|
|
||||||
0%, 15% {
|
|
||||||
--mask-left: black;
|
|
||||||
--mask-right: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 2. MOVING LEFT: Left smoothly fades in (transparent), Right stays faded */
|
|
||||||
22%, 43% {
|
|
||||||
--mask-left: transparent;
|
|
||||||
--mask-right: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 3. AT FAR END: Right smoothly solidifies (black), Left stays faded */
|
|
||||||
50%, 65% {
|
|
||||||
--mask-left: transparent;
|
|
||||||
--mask-right: black;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 4. MOVING RIGHT: Right smoothly fades back out (transparent) */
|
|
||||||
72%, 93% {
|
|
||||||
--mask-left: transparent;
|
|
||||||
--mask-right: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 5. RETURN TO START: Left smoothly solidifies (black) */
|
|
||||||
100% {
|
|
||||||
--mask-left: black;
|
|
||||||
--mask-right: transparent;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -81,8 +81,8 @@
|
|||||||
#progress-bar {
|
#progress-bar {
|
||||||
height: 1%;
|
height: 1%;
|
||||||
width: 0%; /* JS will control this */
|
width: 0%; /* JS will control this */
|
||||||
background: linear-gradient(to top, #ffffff00, var(--accent-color, #ffffff));
|
/* background: linear-gradient(to top, #ffffff00, var(--accent-color, #ffffff)); */
|
||||||
/* background-color: var(--accent-color, #ffffff); */
|
background-color: var(--accent-color, #ffffff);
|
||||||
transition: width 1s ease-in-out, background 1s ease-in-out;
|
transition: width 1s ease-in-out, background 1s ease-in-out;
|
||||||
/* border-radius: 10000px; */
|
/* border-radius: 10000px; */
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@@ -102,19 +102,14 @@ function SetProgressInfo(timelineProps, currentPositionMs, accentColorPalette) {
|
|||||||
// Ensure we don't divide by zero or exceed 100%
|
// Ensure we don't divide by zero or exceed 100%
|
||||||
const durationMs = timelineProps.EndTime;
|
const durationMs = timelineProps.EndTime;
|
||||||
|
|
||||||
// If the duration is 0, that means the session isn't returning any timeline info, so just put the progress at 100%
|
|
||||||
if (durationMs <= 0 || !showProgressBar)
|
|
||||||
{
|
|
||||||
progressBarTrack.style.display = 'none';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
progressBarTrack.style.display = '';
|
|
||||||
|
|
||||||
// Calculate the progress percentage
|
// Calculate the progress percentage
|
||||||
let progressPercent = durationMs > 0 ? (currentPositionMs / durationMs) * 100 : 0;
|
let progressPercent = durationMs > 0 ? (currentPositionMs / durationMs) * 100 : 0;
|
||||||
progressPercent = Math.min(100, Math.max(0, progressPercent));
|
progressPercent = Math.min(100, Math.max(0, progressPercent));
|
||||||
|
|
||||||
|
// If the duration is 0, that means the session isn't returning any timeline info, so just put the progress at 100%
|
||||||
|
if (durationMs <= 0 || !showProgressBar)
|
||||||
|
progressPercent = 100;
|
||||||
|
|
||||||
// Calculate how much needs to be hidden (the right-side offset)
|
// Calculate how much needs to be hidden (the right-side offset)
|
||||||
const clipRight = 100 - progressPercent;
|
const clipRight = 100 - progressPercent;
|
||||||
|
|
||||||
@@ -145,7 +140,6 @@ function UpdateSingleSongLabel(containerId, trackName, artistName) {
|
|||||||
|
|
||||||
let scrollContent = container.querySelector('.scroll-content');
|
let scrollContent = container.querySelector('.scroll-content');
|
||||||
|
|
||||||
// Only rebuild the HTML structure if the text content actually changed or doesn't exist yet
|
|
||||||
const currentTrackEl = container.querySelector('.track-label');
|
const currentTrackEl = container.querySelector('.track-label');
|
||||||
const currentArtistEl = container.querySelector('.artist-label');
|
const currentArtistEl = container.querySelector('.artist-label');
|
||||||
|
|
||||||
@@ -160,7 +154,6 @@ function UpdateSingleSongLabel(containerId, trackName, artistName) {
|
|||||||
scrollContent = container.querySelector('.scroll-content');
|
scrollContent = container.querySelector('.scroll-content');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encapsulate measurement logic so it can be called safely by the observer
|
|
||||||
const updateMetrics = () => {
|
const updateMetrics = () => {
|
||||||
scrollContent = container.querySelector('.scroll-content');
|
scrollContent = container.querySelector('.scroll-content');
|
||||||
|
|
||||||
@@ -170,20 +163,73 @@ function UpdateSingleSongLabel(containerId, trackName, artistName) {
|
|||||||
|
|
||||||
if (overflowDistance > 0) {
|
if (overflowDistance > 0) {
|
||||||
const distancePercent = (overflowDistance / textWidth) * 100;
|
const distancePercent = (overflowDistance / textWidth) * 100;
|
||||||
const travelTime = overflowDistance / SCROLL_SPEED_PX_PER_SEC;
|
|
||||||
const totalDuration = (travelTime * 2) + 3;
|
// --- CONSTANT TIMINGS ---
|
||||||
|
const travelSec = overflowDistance / SCROLL_SPEED_PX_PER_SEC;
|
||||||
|
const pauseSec = PAUSE_SEC;
|
||||||
|
|
||||||
|
const totalSec = (travelSec * 2) + (pauseSec * 2);
|
||||||
|
const totalMs = totalSec * 1000;
|
||||||
|
|
||||||
|
const p1 = (pauseSec / totalSec);
|
||||||
|
const p2 = ((pauseSec + travelSec) / totalSec);
|
||||||
|
const p3 = ((pauseSec * 2 + travelSec) / totalSec);
|
||||||
|
|
||||||
|
scrollContent.style.setProperty('--scroll-distance', `-${distancePercent}%`);
|
||||||
|
|
||||||
|
// Clear any previous active animations to prevent stacking on resize
|
||||||
|
if (scrollContent._anim) scrollContent._anim.cancel();
|
||||||
|
if (container._anim) container._anim.cancel();
|
||||||
|
|
||||||
|
// 1. Animate Text Translation inside container
|
||||||
|
scrollContent._anim = scrollContent.animate([
|
||||||
|
{ transform: 'translateX(0%)', offset: 0, easing: 'linear' },
|
||||||
|
{ transform: 'translateX(0%)', offset: p1, easing: 'ease-in-out' },
|
||||||
|
{ transform: `translateX(-${distancePercent}%)`, offset: p2, easing: 'linear' },
|
||||||
|
{ transform: `translateX(-${distancePercent}%)`, offset: p3, easing: 'ease-in-out' },
|
||||||
|
{ transform: 'translateX(0%)', offset: 1, easing: 'linear' }
|
||||||
|
], {
|
||||||
|
duration: totalMs,
|
||||||
|
iterations: Infinity
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. Animate Mask Variables simultaneously to keep soft edges synced
|
||||||
|
const maxOffset = 1;
|
||||||
|
const fadeBuffer = Math.min(0.02, (p2 - p1) / 4); // Dynamically scale buffer if text overflow is tiny
|
||||||
|
|
||||||
|
const off0 = 0;
|
||||||
|
const off1 = p1;
|
||||||
|
const off2 = Math.min(p1 + fadeBuffer, p2);
|
||||||
|
const off3 = Math.max(off2, p2 - fadeBuffer);
|
||||||
|
const off4 = p2;
|
||||||
|
const off5 = p3;
|
||||||
|
const off6 = Math.min(p3 + fadeBuffer, maxOffset);
|
||||||
|
const off7 = 1;
|
||||||
|
|
||||||
|
container._anim = container.animate([
|
||||||
|
{ '--mask-left': 'black', '--mask-right': 'transparent', offset: off0 },
|
||||||
|
{ '--mask-left': 'black', '--mask-right': 'transparent', offset: off1 },
|
||||||
|
{ '--mask-left': 'transparent', '--mask-right': 'transparent', offset: off2 },
|
||||||
|
{ '--mask-left': 'transparent', '--mask-right': 'transparent', offset: off3 },
|
||||||
|
{ '--mask-left': 'transparent', '--mask-right': 'black', offset: off4 },
|
||||||
|
{ '--mask-left': 'transparent', '--mask-right': 'black', offset: off5 },
|
||||||
|
{ '--mask-left': 'transparent', '--mask-right': 'transparent', offset: off6 },
|
||||||
|
{ '--mask-left': 'black', '--mask-right': 'transparent', offset: off7 }
|
||||||
|
], {
|
||||||
|
duration: totalMs,
|
||||||
|
iterations: Infinity
|
||||||
|
});
|
||||||
|
|
||||||
container.style.setProperty('--scroll-distance', `-${distancePercent}%`);
|
|
||||||
container.style.setProperty('--marquee-duration', `${totalDuration}s`);
|
|
||||||
container.classList.add('is-overflowing');
|
container.classList.add('is-overflowing');
|
||||||
} else {
|
} else {
|
||||||
container.classList.remove('is-overflowing');
|
container.classList.remove('is-overflowing');
|
||||||
|
if (scrollContent._anim) scrollContent._anim.cancel();
|
||||||
|
if (container._anim) container._anim.cancel();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
updateMetrics();
|
updateMetrics();
|
||||||
|
|
||||||
// Attach ResizeObserver once to update metrics only (No recursive function resets!)
|
|
||||||
if (!container._resizeObserver) {
|
if (!container._resizeObserver) {
|
||||||
container._resizeObserver = new ResizeObserver(() => {
|
container._resizeObserver = new ResizeObserver(() => {
|
||||||
updateMetrics();
|
updateMetrics();
|
||||||
|
|||||||
@@ -87,16 +87,4 @@
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
display: var(--show-secondary);
|
display: var(--show-secondary);
|
||||||
}
|
|
||||||
|
|
||||||
/* Marquee scroll animation */
|
|
||||||
#song-label.is-overflowing .scroll-content,
|
|
||||||
#song-label-background.is-overflowing .scroll-content {
|
|
||||||
animation: ping-pong-scroll var(--marquee-duration, 8s) ease-in-out infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Mask animation applied to container */
|
|
||||||
#song-label.is-overflowing,
|
|
||||||
#song-label-background.is-overflowing {
|
|
||||||
animation: ping-pong-mask var(--marquee-duration, 8s) ease-in-out infinite;
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user