Add metro notification system in JS with styling, fix audio playback before first volume change, change tip.js to export function to register new element with hint.

This commit is contained in:
天クマ 2026-04-23 20:31:35 -03:00
commit e31bb2b40e
8 changed files with 164 additions and 70 deletions

View file

@ -184,8 +184,7 @@ aside.metromenu {
width: 30vw;
background-color: black;
height: 100vh;
transition: 0.2s;
transition-timing-function: cubic-bezier(0.1, 0.2, 0.3, 0.955);
transition: transform 0.6s cubic-bezier(0.19, 1, 0.22, 1);
padding: 2em;
}
@ -213,6 +212,28 @@ aside.metromenu #content {
/* Global classes and IDs */
.notificationBox {
transition: transform 1s cubic-bezier(0.19, 1, 0.22, 1);
width: fit-content;
position: fixed;
bottom: 2em;
left: 0;
background-color: black;
transform: translateX(-100%);
padding: 1em;
outline: thin solid var(--theme-color);
margin-right: 8em;
max-width: 40em;
}
.notificationBox.shown {
transform: none;
}
.notificationBox h1 {
font-size: x-large;
}
li.inlineList {
display: inline;
}

View file

@ -1,4 +1,5 @@
// This script handles the playback of music in the header's miniplayer ;)
import { showNotification } from './notification.js';
const body = document.querySelector("body");
const musicdiv = document.getElementById("music");
@ -42,7 +43,7 @@ optionsAside.classList.add("metromenu");
<div id="playlist"></div>
<div>
<p>Volume</p>
<input id="volume" oninput="setVolume(this.value / 100)" type="range" min="0" max="100"></input>
<input id="volume" type="range" min="0" max="100"></input>
</div>
<div class="checkbox">
<p>${headeri18n.hideBackground}</p>
@ -54,6 +55,9 @@ optionsAside.classList.add("metromenu");
}
body.appendChild(optionsAside);
document.getElementById("volume").addEventListener("input", (e) => {
setVolume(e.target.value / 100);
}); // dirty workaround to replace inline function calling in the volume input
const toggleIMG = document.querySelector('#sound');
toggleIMG.addEventListener('click', () => {
@ -146,11 +150,21 @@ let audio = new Audio(`/static/music/${audioSelect.value}`);
const savedTime = localStorage.getItem("audioTime");
const savedVolume = localStorage.getItem("volume");
if (savedVolume !== null) {
audio.volume = parseFloat(savedVolume);
} else {
audio.volume = 0.8;
}
const wasPlaying = localStorage.getItem("audioPlaying") === 'true';
function play() {
audio.volume = localStorage.getItem("volume");
audio.play();
audio.volume = localStorage.getItem("volume") ?? 0.8;
audio.play().catch(() => {
stop();
showNotification(headeri18n.permissionIssue, headeri18n.permissionIssueNotificationContent, 5000);
});;
localStorage.setItem("audioPlaying", "true")
toggleIMG.src = "/static/images/sound-on.png"
console.log(`[Music Player] playing ${audioSelect.value}`)

View file

@ -0,0 +1,45 @@
import { registerElementHint } from "./tips.js";
const notificationBox = document.createElement('div');
notificationBox.classList.add('notificationBox');
export async function showNotification(title, subtitle, time, hint) {
if (!hint) {
hint = headeri18n.notificationDefaultHint;
}
const notificationBox = document.createElement('div');
notificationBox.classList.add('notificationBox');
notificationBox.dataset.tip = hint;
const notificationTitle = document.createElement('h1');
notificationTitle.innerHTML = title;
const notificationSubtitle = document.createElement('p');
notificationSubtitle.innerHTML = subtitle;
notificationBox.appendChild(notificationTitle);
notificationBox.appendChild(notificationSubtitle);
document.querySelector('body').appendChild(notificationBox);
registerElementHint(notificationBox);
let clicked = false;
notificationBox.addEventListener('click', () => {
hideNotification(notificationBox);
})
requestAnimationFrame(() => {
notificationBox.classList.add('shown');
});
await new Promise(r => setTimeout(r, time));
if (!clicked) {
hideNotification(notificationBox);
}
}
async function hideNotification(notificationBox) {
notificationBox.classList.remove('shown');
await new Promise(r => setTimeout(r, 1000));
notificationBox.remove();
}

View file

@ -1,8 +1,4 @@
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
if (isMobile) {
return;
}
const body = document.querySelector('body');
const elements = document.querySelectorAll('[data-tip]');
const hint = document.querySelector("#headerSubtitle");
@ -10,36 +6,11 @@ const hintPanelDefaultText = hint.innerHTML;
let fixedHint;
let currentObserver;
elements.forEach(el => {
el.addEventListener('mouseenter', function() {
cleanup();
if (currentObserver) {
currentObserver.disconnect();
}
currentObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (!entry.isIntersecting) {
fixedHint = document.createElement('p');
fixedHint.id = "fixedHint";
fixedHint.innerHTML = el.dataset.tip;
fixedHint.setAttribute('aria-hidden', 'true');
body.appendChild(fixedHint);
} else {
hint.innerHTML = el.dataset.tip;
}
})
});
hint.innerHTML = el.dataset.tip;
currentObserver.observe(hint);
});
el.addEventListener('mouseleave', function() {
cleanup();
});
})
if (!isMobile) {
elements.forEach(el => {
registerElementHint(el);
})
}
function cleanup() {
hint.innerHTML = hintPanelDefaultText;
@ -52,3 +23,34 @@ function cleanup() {
currentObserver = null;
}
}
export function registerElementHint(el) {
el.addEventListener('mouseenter', function() {
cleanup();
if (currentObserver) {
currentObserver.disconnect();
}
currentObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (!entry.isIntersecting) {
fixedHint = document.createElement('p');
fixedHint.id = "fixedHint";
fixedHint.innerHTML = el.dataset.tip;
fixedHint.setAttribute('aria-hidden', 'true');
body.appendChild(fixedHint);
} else {
hint.innerHTML = el.dataset.tip;
}
})
});
hint.innerHTML = el.dataset.tip;
currentObserver.observe(hint);
});
el.addEventListener('mouseleave', function() {
cleanup();
});
}