- refactor(generators): migrate Card, Coin, Color, Country to crypto random - refactor(generators): migrate Date, Emoji, FontPair to crypto random - refactor(dice): deduplicate DiceGenerator by importing from dice-engine.ts
188 lines
6.1 KiB
Plaintext
188 lines
6.1 KiB
Plaintext
---
|
|
import { useT } from "../../i18n/translations";
|
|
const isRu = Astro.url.pathname.startsWith("/ru");
|
|
const T = useT(isRu ? "ru" : "en");
|
|
---
|
|
|
|
<div id="coin-generator" class="mt-8">
|
|
<div class="rounded-xl bg-zinc-900/50 border border-zinc-800/60 p-5">
|
|
<div class="flex items-center justify-between mb-1">
|
|
<label for="coin-count" class="text-sm font-medium text-zinc-400"
|
|
>{T.numberOfCoins}</label
|
|
>
|
|
<span
|
|
id="coin-count-val"
|
|
class="text-sm font-semibold tabular-nums text-zinc-100">1</span
|
|
>
|
|
</div>
|
|
<input
|
|
id="coin-count"
|
|
type="range"
|
|
min="1"
|
|
max="20"
|
|
value="1"
|
|
class="w-full accent-accent cursor-pointer"
|
|
aria-label={T.numberOfCoins}
|
|
/>
|
|
<div class="flex justify-between text-xs text-zinc-600 mt-1 select-none">
|
|
<span>1</span><span>20</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="my-6 rounded-2xl bg-zinc-900/80 border border-zinc-800/80 p-8 h-52 overflow-hidden flex flex-col items-center justify-center gap-4">
|
|
<div
|
|
id="coin-coins"
|
|
class="flex flex-wrap justify-center gap-2"
|
|
aria-live="polite"
|
|
aria-label="Coin results"
|
|
>
|
|
</div>
|
|
|
|
<div id="coin-summary" class="hidden text-sm text-zinc-500 tabular-nums">
|
|
</div>
|
|
|
|
<button
|
|
id="coin-copy-btn"
|
|
type="button"
|
|
aria-label="Copy results to clipboard"
|
|
class="invisible inline-flex items-center gap-1.5 text-xs font-medium text-zinc-500 hover:text-zinc-200 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-zinc-950 rounded"
|
|
>
|
|
<span id="coin-copy-label">{T.copy}</span>
|
|
<span id="coin-copy-icon" aria-hidden="true">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
width="14"
|
|
height="14"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
>
|
|
<rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect>
|
|
<path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"
|
|
></path>
|
|
</svg>
|
|
</span>
|
|
<span
|
|
id="coin-check-icon"
|
|
class="hidden text-accent"
|
|
aria-hidden="true"
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
width="14"
|
|
height="14"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2.5"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
>
|
|
<path d="M20 6 9 17l-5-5"></path>
|
|
</svg>
|
|
</span>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="flex justify-center">
|
|
<button
|
|
id="coin-btn"
|
|
type="button"
|
|
class="w-full sm:w-auto px-8 py-3 bg-accent text-white font-semibold rounded-xl shadow-lg shadow-accent/25 hover:bg-accent-hover active:bg-accent-active focus:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-zinc-950 transition-colors duration-100 cursor-pointer"
|
|
>
|
|
{T.flip}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
import { CopyFeedback } from "@/lib/client/clipboard";
|
|
import { randomFloat } from "@/lib/client/random";
|
|
import { getClientT } from "@/lib/client/i18n";
|
|
|
|
const T = getClientT();
|
|
const COPY_LABEL = T.copy;
|
|
const COPIED_LABEL = T.copied;
|
|
const HEADS_LABEL = T.headsLabel;
|
|
const TAILS_LABEL = T.tailsLabel;
|
|
const HEADS_WORD = T.headsWord;
|
|
const TAILS_WORD = T.tailsWord;
|
|
const HEADS_SUMMARY = T.headsWord;
|
|
const TAILS_SUMMARY = T.tailsWord;
|
|
|
|
const countInput = document.getElementById("coin-count") as HTMLInputElement;
|
|
const countVal = document.getElementById("coin-count-val") as HTMLSpanElement;
|
|
const btn = document.getElementById("coin-btn") as HTMLButtonElement;
|
|
const copyBtn = document.getElementById("coin-copy-btn") as HTMLButtonElement;
|
|
const coinsEl = document.getElementById("coin-coins") as HTMLDivElement;
|
|
const summaryEl = document.getElementById("coin-summary") as HTMLDivElement;
|
|
const copyLabel = document.getElementById(
|
|
"coin-copy-label",
|
|
) as HTMLSpanElement;
|
|
const copyIcon = document.getElementById("coin-copy-icon") as HTMLSpanElement;
|
|
const checkIcon = document.getElementById(
|
|
"coin-check-icon",
|
|
) as HTMLSpanElement;
|
|
|
|
let lastResults: boolean[] = [];
|
|
|
|
const clipboard = new CopyFeedback(copyIcon, checkIcon);
|
|
|
|
countInput.addEventListener("input", () => {
|
|
countVal.textContent = countInput.value;
|
|
});
|
|
|
|
function flip() {
|
|
const count = parseInt(countInput.value, 10);
|
|
lastResults = Array.from({ length: count }, () => randomFloat() < 0.5);
|
|
|
|
const heads = lastResults.filter(Boolean).length;
|
|
const tails = count - heads;
|
|
|
|
coinsEl.innerHTML = "";
|
|
lastResults.forEach((isHeads) => {
|
|
const coin = document.createElement("div");
|
|
coin.setAttribute("aria-label", isHeads ? HEADS_WORD : TAILS_WORD);
|
|
coin.className = [
|
|
"flex flex-col items-center justify-center w-14 h-14 rounded-full border-2 font-bold text-xs select-none",
|
|
isHeads
|
|
? "border-accent bg-accent/10 text-accent"
|
|
: "border-zinc-600 bg-zinc-800 text-zinc-400",
|
|
].join(" ");
|
|
coin.textContent = isHeads ? HEADS_LABEL : TAILS_LABEL;
|
|
coinsEl.appendChild(coin);
|
|
});
|
|
|
|
if (count > 1) {
|
|
summaryEl.textContent = `${heads} ${HEADS_SUMMARY} · ${tails} ${TAILS_SUMMARY}`;
|
|
summaryEl.classList.remove("hidden");
|
|
} else {
|
|
summaryEl.classList.add("hidden");
|
|
}
|
|
|
|
copyBtn.classList.remove("invisible");
|
|
copyLabel.textContent = COPY_LABEL;
|
|
}
|
|
|
|
async function copyResults() {
|
|
if (!lastResults.length) return;
|
|
const text = lastResults
|
|
.map((h) => (h ? HEADS_WORD : TAILS_WORD))
|
|
.join(", ");
|
|
await navigator.clipboard.writeText(text);
|
|
|
|
clipboard.showCopied();
|
|
copyLabel.textContent = COPIED_LABEL;
|
|
|
|
setTimeout(() => {
|
|
copyLabel.textContent = COPY_LABEL;
|
|
}, 1500);
|
|
}
|
|
|
|
btn.addEventListener("click", flip);
|
|
copyBtn.addEventListener("click", copyResults);
|
|
</script>
|