website: redesign part 33

This commit is contained in:
nym21
2026-06-21 17:12:25 +02:00
parent 2e401379a0
commit b3031b3375
31 changed files with 818 additions and 417 deletions
+130
View File
@@ -0,0 +1,130 @@
import { createElement } from "../../dom.js";
const FILL_MS = 2_000;
const DRAIN_MS = 600;
const LABEL = "Reset vault";
/**
* @param {number} value
*/
function clampProgress(value) {
return Math.max(0, Math.min(1, value));
}
/**
* @param {HTMLButtonElement} button
* @param {() => void} onReset
*/
function bindHold(button, onReset) {
/** @type {number | undefined} */
let frame;
let holding = false;
let progress = 0;
let previous = 0;
function render() {
button.style.setProperty("--reset-progress", String(progress));
button.style.setProperty("--reset-progress-width", `${progress * 100}%`);
button.classList.toggle("active", progress > 0);
}
function stop() {
if (frame === undefined) return;
cancelAnimationFrame(frame);
frame = undefined;
}
/**
* @param {number} now
*/
function tick(now) {
const elapsed = now - previous;
const rate = elapsed / (holding ? FILL_MS : DRAIN_MS);
previous = now;
progress = clampProgress(progress + (holding ? rate : -rate));
render();
if (holding && progress === 1) {
stop();
holding = false;
progress = 0;
button.classList.remove("holding");
render();
onReset();
return;
}
if (!holding && progress === 0) {
stop();
return;
}
frame = requestAnimationFrame(tick);
}
function run() {
if (frame !== undefined) return;
previous = performance.now();
frame = requestAnimationFrame(tick);
}
function release() {
if (!holding) return;
holding = false;
button.classList.remove("holding");
run();
}
function hold() {
stop();
holding = true;
button.classList.add("holding");
run();
}
render();
button.addEventListener("pointerdown", (event) => {
if (event.button !== 0) return;
button.setPointerCapture(event.pointerId);
hold();
});
button.addEventListener("pointerup", release);
button.addEventListener("pointercancel", release);
button.addEventListener("lostpointercapture", release);
button.addEventListener("keydown", (event) => {
if (event.repeat || (event.key !== " " && event.key !== "Enter")) return;
event.preventDefault();
hold();
});
button.addEventListener("keyup", (event) => {
if (event.key === " " || event.key === "Enter") {
release();
}
});
button.addEventListener("blur", release);
}
/**
* @param {() => void} onReset
*/
export function createResetButton(onReset) {
const button = createElement("button", "reset");
const label = document.createElement("span");
button.type = "button";
button.dataset.label = LABEL;
button.title = "Hold to reset";
label.append(LABEL);
button.append(label);
bindHold(button, onReset);
return button;
}
@@ -0,0 +1,62 @@
main.wallets {
.start {
.reset {
position: relative;
isolation: isolate;
justify-self: start;
overflow: hidden;
width: 100%;
border-color: transparent;
color: color-mix(in oklch, var(--gray) 76%, transparent);
background: transparent;
font-size: var(--font-size-sm);
--reset-progress: 0;
--reset-progress-width: 0%;
&::before,
&::after {
content: "";
position: absolute;
inset: 0;
opacity: 0;
}
&::before {
z-index: -1;
background: var(--red);
transform: scaleX(var(--reset-progress));
transform-origin: left;
}
&::after {
content: attr(data-label);
display: flex;
align-items: center;
justify-content: center;
padding: inherit;
color: var(--black);
pointer-events: none;
white-space: nowrap;
clip-path: inset(0 calc(100% - var(--reset-progress-width)) 0 0);
}
span {
color: inherit;
}
&:is(:hover, :focus-visible, :active):not(.holding) {
color: var(--red);
background: transparent;
}
&.active {
color: var(--red);
}
&.active::before,
&.active::after {
opacity: 1;
}
}
}
}