A fast, local-first "redirection engine" for !bang users with a few extra features ^-^

feat: add more sounds for clipboard and modal close

Changed files
+61 -11
public
src
public/foot-switch.mp3

This is a binary file and will not be displayed.

+28 -2
src/global.css
···
color: var(--text-color);
}
+
.flash-white {
+
outline: 2px solid var(--text-color-secondary);
+
background: color-mix(in srgb, var(--text-color) 75%, var(--bg-color));
+
opacity: 0.2;
+
animation: flash 0.275s forwards;
+
}
+
+
@keyframes flash {
+
0% {
+
opacity: 0.2;
+
outline: 2px solid var(--text-color-secondary);
+
background: color-mix(in srgb, var(--text-color) 55%, var(--bg-color));
+
}
+
50% {
+
opacity: 1;
+
outline: 2px solid var(--text-color-hover);
+
background: color-mix(in srgb, var(--text-color) 75%, var(--bg-color));
+
}
+
100% {
+
opacity: 0.2;
+
outline: 2px solid var(--text-color-secondary);
+
background: color-mix(in srgb, var(--text-color) 55%, var(--bg-color));
+
}
+
}
+
.copy-button {
padding: 8px;
color: var(--text-color-secondary);
···
background: var(--bg-color-active);
}
-
.settings-button:hover img {
+
.settings-button:hover img,
+
.settings-button.rotate img {
transform: rotate(180deg);
transition: transform 0.6s ease;
}
···
transition: transform 0.6s ease;
}
-
.settings-button:not(:hover) .settings {
+
.settings-button:not(:hover):not(.rotate) .settings {
transform: rotate(0deg);
}
+33 -9
src/main.ts
···
</div>
`;
+
const copyInput = app.querySelector<HTMLInputElement>(".url-input");
+
if (!copyInput) throw new Error("Copy input not found");
const copyButton = app.querySelector<HTMLButtonElement>(".copy-button");
if (!copyButton) throw new Error("Copy button not found");
const copyIcon = copyButton.querySelector("img");
···
if (!clearHistory) throw new Error("Clear history button not found");
urlInput.value = `${window.location.protocol}//${window.location.host}?q=%s`;
-
-
copyButton.addEventListener("click", async () => {
-
await navigator.clipboard.writeText(urlInput.value);
-
copyIcon.src = "/clipboard-check.svg";
-
-
setTimeout(() => {
-
copyIcon.src = "/clipboard.svg";
-
}, 2000);
-
});
const prefersReducedMotion = window.matchMedia(
"(prefers-reduced-motion: reduce)",
···
const toggleOnAudio = new Audio("/toggle-button-on.mp3");
const clickAudio = new Audio("/click-button.mp3");
const warningAudio = new Audio("/double-button.mp3");
+
const copyAudio = new Audio("/foot-switch.mp3");
+
+
copyButton.addEventListener("click", () => {
+
copyAudio.currentTime = 0;
+
copyAudio.play();
+
});
settingsButton.addEventListener("mouseenter", () => {
spinAudio.play();
···
clickAudio.currentTime = 0;
clickAudio.play();
});
+
+
closeModal.addEventListener("closed", () => {
+
settingsButton.classList.remove("rotate");
+
spinAudio.playbackRate = 0.7;
+
spinAudio.currentTime = 0;
+
spinAudio.play();
+
spinAudio.onended = () => {
+
spinAudio.playbackRate = 1;
+
};
+
});
}
+
copyButton.addEventListener("click", async () => {
+
await navigator.clipboard.writeText(urlInput.value);
+
copyIcon.src = "/clipboard-check.svg";
+
+
if (!prefersReducedMotion) copyInput.classList.add("flash-white");
+
+
setTimeout(() => {
+
copyInput.classList.remove("flash-white");
+
copyIcon.src = "/clipboard.svg";
+
}, 375);
+
});
+
settingsButton.addEventListener("click", () => {
+
settingsButton.classList.add("rotate");
modal.style.display = "block";
setOutsideElementsTabindex(modal, -1);
});
closeModal.addEventListener("click", () => {
+
closeModal.dispatchEvent(new Event("closed"));
modal.style.display = "none";
setOutsideElementsTabindex(modal, 0);
});
window.addEventListener("click", (event) => {
if (event.target === modal) {
+
closeModal.dispatchEvent(new Event("closed"));
modal.style.display = "none";
setOutsideElementsTabindex(modal, 0);
}