From 78c2eb454587f31a051ad996954fb0cc148f0e3c Mon Sep 17 00:00:00 2001 From: creations Date: Wed, 9 Apr 2025 18:23:52 -0400 Subject: [PATCH] add rain and snow kv options fix issue with activity header not showing when no initial activity --- public/css/index.css | 24 ++++ public/js/index.js | 9 +- public/js/rain.js | 77 +++++++++++++ public/js/snow.js | 84 ++++++++++++++ src/routes/[id].ts | 2 + src/routes/index.ts | 2 + src/views/index.ejs | 254 +++++++++++++++++++++---------------------- 7 files changed, 319 insertions(+), 133 deletions(-) create mode 100644 public/js/rain.js create mode 100644 public/js/snow.js diff --git a/public/css/index.css b/public/css/index.css index 7a1a288..1d8ad00 100644 --- a/public/css/index.css +++ b/public/css/index.css @@ -40,6 +40,30 @@ body { align-items: center; } +.snowflake { + position: absolute; + background-color: white; + border-radius: 50%; + pointer-events: none; + z-index: 1; +} + +.raindrop { + position: absolute; + background-color: white; + border-radius: 50%; + pointer-events: none; + z-index: 1; +} + +.hidden { + display: none; +} + +.activity-header.hidden { + display: none; +} + .user-card { display: flex; flex-direction: column; diff --git a/public/js/index.js b/public/js/index.js index e6a9afe..22e2dc6 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -291,7 +291,6 @@ function updatePresence(data) { }; let status = "offline"; - console.log(data.activities.some((activity) => activity.type === 1)); if (data.activities.some((activity) => activity.type === 1)) { status = "streaming"; } else { @@ -339,11 +338,15 @@ function updatePresence(data) { }); const activityList = document.querySelector(".activities"); + const activitiesTitle = document.querySelector(".activity-header"); - if (activityList) { - activityList.innerHTML = ""; + if (activityList && activitiesTitle) { if (filtered?.length) { activityList.innerHTML = filtered.map(buildActivityHTML).join(""); + activitiesTitle.classList.remove("hidden"); + } else { + activityList.innerHTML = ""; + activitiesTitle.classList.add("hidden"); } updateElapsedAndProgress(); } diff --git a/public/js/rain.js b/public/js/rain.js new file mode 100644 index 0000000..27c0d8f --- /dev/null +++ b/public/js/rain.js @@ -0,0 +1,77 @@ +const rainContainer = document.createElement("div"); +rainContainer.style.position = "fixed"; +rainContainer.style.top = "0"; +rainContainer.style.left = "0"; +rainContainer.style.width = "100vw"; +rainContainer.style.height = "100vh"; +rainContainer.style.pointerEvents = "none"; +document.body.appendChild(rainContainer); + +const maxRaindrops = 100; +const raindrops = []; +const mouse = { x: -100, y: -100 }; + +document.addEventListener("mousemove", (e) => { + mouse.x = e.clientX; + mouse.y = e.clientY; +}); + +const getRaindropColor = () => { + const htmlTag = document.documentElement; + return htmlTag.getAttribute("data-theme") === "dark" + ? "rgba(173, 216, 230, 0.8)" + : "rgba(70, 130, 180, 0.8)"; +}; + +const createRaindrop = () => { + if (raindrops.length >= maxRaindrops) { + const oldestRaindrop = raindrops.shift(); + rainContainer.removeChild(oldestRaindrop); + } + + const raindrop = document.createElement("div"); + raindrop.classList.add("raindrop"); + raindrop.style.position = "absolute"; + raindrop.style.width = "2px"; + raindrop.style.height = `${Math.random() * 10 + 10}px`; + raindrop.style.background = getRaindropColor(); + raindrop.style.borderRadius = "1px"; + raindrop.style.left = `${Math.random() * window.innerWidth}px`; + raindrop.style.top = `-${raindrop.style.height}`; + raindrop.style.opacity = Math.random() * 0.5 + 0.3; + raindrop.speed = Math.random() * 6 + 4; + raindrop.directionX = (Math.random() - 0.5) * 0.2; + raindrop.directionY = Math.random() * 0.5 + 0.8; + + raindrops.push(raindrop); + rainContainer.appendChild(raindrop); +}; + +setInterval(createRaindrop, 50); + +function updateRaindrops() { + raindrops.forEach((raindrop, index) => { + const rect = raindrop.getBoundingClientRect(); + + raindrop.style.left = `${rect.left + raindrop.directionX * raindrop.speed}px`; + raindrop.style.top = `${rect.top + raindrop.directionY * raindrop.speed}px`; + + if (rect.top + rect.height >= window.innerHeight) { + rainContainer.removeChild(raindrop); + raindrops.splice(index, 1); + } + + if ( + rect.left > window.innerWidth || + rect.top > window.innerHeight || + rect.left < 0 + ) { + raindrop.style.left = `${Math.random() * window.innerWidth}px`; + raindrop.style.top = `-${raindrop.style.height}`; + } + }); + + requestAnimationFrame(updateRaindrops); +} + +updateRaindrops(); diff --git a/public/js/snow.js b/public/js/snow.js new file mode 100644 index 0000000..05048a8 --- /dev/null +++ b/public/js/snow.js @@ -0,0 +1,84 @@ +document.addEventListener("DOMContentLoaded", () => { + const snowContainer = document.createElement("div"); + snowContainer.style.position = "fixed"; + snowContainer.style.top = "0"; + snowContainer.style.left = "0"; + snowContainer.style.width = "100vw"; + snowContainer.style.height = "100vh"; + snowContainer.style.pointerEvents = "none"; + document.body.appendChild(snowContainer); + + const maxSnowflakes = 60; + const snowflakes = []; + const mouse = { x: -100, y: -100 }; + + document.addEventListener("mousemove", (e) => { + mouse.x = e.clientX; + mouse.y = e.clientY; + }); + + const createSnowflake = () => { + if (snowflakes.length >= maxSnowflakes) { + const oldestSnowflake = snowflakes.shift(); + snowContainer.removeChild(oldestSnowflake); + } + + const snowflake = document.createElement("div"); + snowflake.classList.add("snowflake"); + snowflake.style.position = "absolute"; + snowflake.style.width = `${Math.random() * 3 + 2}px`; + snowflake.style.height = snowflake.style.width; + snowflake.style.background = "white"; + snowflake.style.borderRadius = "50%"; + snowflake.style.opacity = Math.random(); + snowflake.style.left = `${Math.random() * window.innerWidth}px`; + snowflake.style.top = `-${snowflake.style.height}`; + snowflake.speed = Math.random() * 3 + 2; + snowflake.directionX = (Math.random() - 0.5) * 0.5; + snowflake.directionY = Math.random() * 0.5 + 0.5; + + snowflakes.push(snowflake); + snowContainer.appendChild(snowflake); + }; + + setInterval(createSnowflake, 80); + + function updateSnowflakes() { + snowflakes.forEach((snowflake, index) => { + const rect = snowflake.getBoundingClientRect(); + + const dx = rect.left + rect.width / 2 - mouse.x; + const dy = rect.top + rect.height / 2 - mouse.y; + const distance = Math.sqrt(dx * dx + dy * dy); + + if (distance < 30) { + snowflake.directionX += (dx / distance) * 0.02; + snowflake.directionY += (dy / distance) * 0.02; + } else { + snowflake.directionX += (Math.random() - 0.5) * 0.01; + snowflake.directionY += (Math.random() - 0.5) * 0.01; + } + + snowflake.style.left = `${rect.left + snowflake.directionX * snowflake.speed}px`; + snowflake.style.top = `${rect.top + snowflake.directionY * snowflake.speed}px`; + + if (rect.top + rect.height >= window.innerHeight) { + snowContainer.removeChild(snowflake); + snowflakes.splice(index, 1); + } + + if ( + rect.left > window.innerWidth || + rect.top > window.innerHeight || + rect.left < 0 + ) { + snowflake.style.left = `${Math.random() * window.innerWidth}px`; + snowflake.style.top = `-${snowflake.style.height}`; + } + }); + + requestAnimationFrame(updateSnowflakes); + } + + updateSnowflakes(); +}); diff --git a/src/routes/[id].ts b/src/routes/[id].ts index bdfc975..16ea3c0 100644 --- a/src/routes/[id].ts +++ b/src/routes/[id].ts @@ -52,6 +52,8 @@ async function handler(request: ExtendedRequest): Promise { }, instance, readme, + allowSnow: presence.kv.snow || false, + allowRain: presence.kv.rain || false, }; return await renderEjsTemplate("index", ejsTemplateData); diff --git a/src/routes/index.ts b/src/routes/index.ts index 829d105..7f12f0a 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -51,6 +51,8 @@ async function handler(): Promise { }, instance, readme, + allowSnow: presence.kv.snow || false, + allowRain: presence.kv.rain || false, }; return await renderEjsTemplate("index", ejsTemplateData); diff --git a/src/views/index.ejs b/src/views/index.ejs index 0611579..d4f2be3 100644 --- a/src/views/index.ejs +++ b/src/views/index.ejs @@ -13,6 +13,13 @@ + <% if (allowSnow) { %> + + <% } %> + <% if(allowRain) { %> + + <% } %> + @@ -59,152 +66,139 @@ let filtered = activities .filter(a => a.type !== 4) .sort((a, b) => { - const priority = { 2: 0, 1: 1, 3: 2 }; // Listening, Streaming, Watching ? should i keep this + const priority = { 2: 0, 1: 1, 3: 2 }; const aPriority = priority[a.type] ?? 99; const bPriority = priority[b.type] ?? 99; return aPriority - bPriority; }); %> - <% if (filtered.length > 0) { %> -

Activities

-
    - <% filtered.forEach(activity => { - const start = activity.timestamps?.start; - const end = activity.timestamps?.end; - const now = Date.now(); - const elapsed = start ? now - start : 0; - const total = (start && end) ? end - start : null; - const progress = (total && elapsed > 0) ? Math.min(100, Math.floor((elapsed / total) * 100)) : null; - let art = null; - let smallArt = null; +

    Activities

    +
      + <% filtered.forEach(activity => { + const start = activity.timestamps?.start; + const end = activity.timestamps?.end; + const now = Date.now(); + const elapsed = start ? now - start : 0; + const total = (start && end) ? end - start : null; + const progress = (total && elapsed > 0) ? Math.min(100, Math.floor((elapsed / total) * 100)) : null; - function resolveActivityImage(img, applicationId) { - if (!img) return null; + let art = null; + let smallArt = null; - if (img.startsWith("mp:external/")) { - return `https://media.discordapp.net/external/${img.slice("mp:external/".length)}`; - } - - if (img.includes("/https/")) { - const clean = img.split("/https/")[1]; - return clean ? `https://${clean}` : null; - } - - if (img.startsWith("spotify:")) { - return `https://i.scdn.co/image/${img.split(":")[1]}`; - } - - return `https://cdn.discordapp.com/app-assets/${applicationId}/${img}.png`; + function resolveActivityImage(img, applicationId) { + if (!img) return null; + if (img.startsWith("mp:external/")) { + return `https://media.discordapp.net/external/${img.slice("mp:external/".length)}`; } - - if (activity.assets) { - art = resolveActivityImage(activity.assets.large_image, activity.application_id); - smallArt = resolveActivityImage(activity.assets.small_image, activity.application_id); + if (img.includes("/https/")) { + const clean = img.split("/https/")[1]; + return clean ? `https://${clean}` : null; } + if (img.startsWith("spotify:")) { + return `https://i.scdn.co/image/${img.split(":")[1]}`; + } + return `https://cdn.discordapp.com/app-assets/${applicationId}/${img}.png`; + } - const activityTypeMap = { - 0: "Playing", - 1: "Streaming", - 2: "Listening", - 3: "Watching", - 4: "Custom Status", - 5: "Competing", - }; + if (activity.assets) { + art = resolveActivityImage(activity.assets.large_image, activity.application_id); + smallArt = resolveActivityImage(activity.assets.small_image, activity.application_id); + } - const activityType = activity.name === "Spotify" - ? "Listening to Spotify" - : activity.name === "TIDAL" - ? "Listening to TIDAL" - : activityTypeMap[activity.type] || "Playing"; - %> -
    • -
      -
      - - <%= activityType %> - - <% if (start && progress === null) { %> -
      - <% const started = new Date(start); %> - - Since: <%= started.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit', second: '2-digit' }) %> - -
      - <% } %> -
      + const activityTypeMap = { + 0: "Playing", + 1: "Streaming", + 2: "Listening", + 3: "Watching", + 4: "Custom Status", + 5: "Competing", + }; -
      - <% if (art) { %> -
      - Art> - <% if (smallArt) { %> - Small Art> - <% } %> -
      - <% } %> - -
      -
      - <% - const isMusic = activity.type === 2 || activity.type === 3; - const primaryLine = isMusic ? activity.details : activity.name; - const secondaryLine = isMusic ? activity.state : activity.details; - const tertiaryLine = isMusic ? activity.assets?.large_text : activity.state; - %> -
      -
      - <%= primaryLine %> -
      - - <% if (secondaryLine) { %> -
      <%= secondaryLine %>
      - <% } %> - <% if (tertiaryLine) { %> -
      <%= tertiaryLine %>
      - <% } %> -
      -
      - <% if (activity.buttons && activity.buttons.length > 0) { %> -
      - <% activity.buttons.forEach((button, index) => { - const buttonLabel = typeof button === 'string' ? button : button.label; - let buttonUrl = null; - if (typeof button === 'object' && button.url) { - buttonUrl = button.url; - } - else if (index === 0 && activity.url) { - buttonUrl = activity.url; - } - %> - <% if (buttonUrl) { %> - <%= buttonLabel %> - <% } %> - <% }); %> -
      - <% } %> -
      -
      + const activityType = activity.name === "Spotify" + ? "Listening to Spotify" + : activity.name === "TIDAL" + ? "Listening to TIDAL" + : activityTypeMap[activity.type] || "Playing"; + %> +
    • +
      +
      + <%= activityType %> + <% if (start && progress === null) { %> +
      + <% const started = new Date(start); %> + + Since: <%= started.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit', second: '2-digit' }) %> +
      -
      - - <% if (progress !== null) { %> -
      -
      >
      -
      - - <% if (start && end) { %> -
      - - <%= Math.floor((end - start) / 60000) %>:<%= String(Math.floor(((end - start) % 60000) / 1000)).padStart(2, "0") %> -
      - <% } %> <% } %>
      -
    • - <% }) %> -
    - <% } %> +
    + <% if (art) { %> +
    + Art> + <% if (smallArt) { %> + Small Art> + <% } %> +
    + <% } %> +
    +
    + <% + const isMusic = activity.type === 2 || activity.type === 3; + const primaryLine = isMusic ? activity.details : activity.name; + const secondaryLine = isMusic ? activity.state : activity.details; + const tertiaryLine = isMusic ? activity.assets?.large_text : activity.state; + %> +
    +
    + <%= primaryLine %> +
    + <% if (secondaryLine) { %> +
    <%= secondaryLine %>
    + <% } %> + <% if (tertiaryLine) { %> +
    <%= tertiaryLine %>
    + <% } %> +
    +
    + <% if (activity.buttons && activity.buttons.length > 0) { %> +
    + <% activity.buttons.forEach((button, index) => { + const buttonLabel = typeof button === 'string' ? button : button.label; + let buttonUrl = null; + if (typeof button === 'object' && button.url) { + buttonUrl = button.url; + } else if (index === 0 && activity.url) { + buttonUrl = activity.url; + } + %> + <% if (buttonUrl) { %> + <%= buttonLabel %> + <% } %> + <% }) %> +
    + <% } %> +
    +
    +
    +
    + <% if (progress !== null) { %> +
    +
    >
    +
    + <% if (start && end) { %> +
    + + <%= Math.floor((end - start) / 60000) %>:<%= String(Math.floor(((end - start) % 60000) / 1000)).padStart(2, "0") %> +
    + <% } %> + <% } %> + + + <% }); %> +
<% if (readme) { %>