style: visual polish — accent tokens, starfield, fixed card/output heights, focus rings, spatial fixes
This commit is contained in:
@@ -38,10 +38,10 @@ const { class: sizeClass, horizontal } = specs[size];
|
||||
href={ad.href}
|
||||
target="_blank"
|
||||
rel="noopener sponsored"
|
||||
class={`${sizeClass} flex ${horizontal ? "flex-row items-center gap-4 px-6" : "flex-col items-center justify-center gap-3 px-4"} rounded-xl border border-zinc-800 bg-zinc-900/50 hover:border-[#534AB7]/50 hover:bg-zinc-900 transition-colors duration-200 group`}
|
||||
class={`${sizeClass} flex ${horizontal ? "flex-row items-center gap-4 px-6" : "flex-col items-center justify-center gap-3 px-4"} rounded-xl border border-zinc-800 bg-zinc-900/50 hover:border-accent/50 hover:bg-zinc-900 transition-colors duration-200 group`}
|
||||
>
|
||||
<svg
|
||||
class="shrink-0 text-[#FC3F1D]"
|
||||
class="shrink-0 text-yandex"
|
||||
width={horizontal ? "32" : "40"}
|
||||
height={horizontal ? "32" : "40"}
|
||||
viewBox="0 0 40 40"
|
||||
@@ -62,12 +62,12 @@ const { class: sizeClass, horizontal } = specs[size];
|
||||
<div class="text-xs font-medium text-zinc-500 uppercase tracking-widest mb-0.5">
|
||||
Реклама
|
||||
</div>
|
||||
<div class="text-sm font-semibold text-zinc-100 group-hover:text-[#534AB7] transition-colors leading-tight">
|
||||
<div class="text-sm font-semibold text-zinc-100 group-hover:text-accent transition-colors leading-tight">
|
||||
{ad.label}
|
||||
</div>
|
||||
<div class="text-xs text-zinc-500 mt-0.5">{ad.sublabel}</div>
|
||||
</div>
|
||||
<span class="shrink-0 text-xs font-semibold text-white bg-[#FC3F1D] px-3 py-1.5 rounded-lg group-hover:bg-[#e03518] transition-colors whitespace-nowrap">
|
||||
<span class="shrink-0 text-xs font-semibold text-white bg-yandex px-3 py-1.5 rounded-lg group-hover:bg-yandex-hover transition-colors whitespace-nowrap">
|
||||
{ad.cta}
|
||||
</span>
|
||||
</>
|
||||
@@ -77,12 +77,12 @@ const { class: sizeClass, horizontal } = specs[size];
|
||||
<div class="text-xs font-medium text-zinc-500 uppercase tracking-widest mb-2">
|
||||
Реклама
|
||||
</div>
|
||||
<div class="text-base font-semibold text-zinc-100 group-hover:text-[#534AB7] transition-colors leading-snug">
|
||||
<div class="text-base font-semibold text-zinc-100 group-hover:text-accent transition-colors leading-snug">
|
||||
{ad.label}
|
||||
</div>
|
||||
<div class="text-xs text-zinc-500 mt-1">{ad.sublabel}</div>
|
||||
</div>
|
||||
<span class="text-xs font-semibold text-white bg-[#FC3F1D] px-4 py-2 rounded-lg group-hover:bg-[#e03518] transition-colors">
|
||||
<span class="text-xs font-semibold text-white bg-yandex px-4 py-2 rounded-lg group-hover:bg-yandex-hover transition-colors">
|
||||
{ad.ctaMobile}
|
||||
</span>
|
||||
</>
|
||||
|
||||
@@ -36,31 +36,31 @@ const icons: Record<string, string> = {
|
||||
isLive ? (
|
||||
<a
|
||||
href={href}
|
||||
class="group block border border-zinc-800 rounded-xl p-5 hover:border-[#534AB7] transition-colors duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#534AB7]"
|
||||
class="group block h-full border border-zinc-800 rounded-xl p-5 shadow-lg shadow-black/20 hover:border-accent hover:shadow-xl hover:shadow-black/30 hover:-translate-y-0.5 transition-colors transition-shadow transition-transform duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-zinc-950"
|
||||
>
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div
|
||||
class="text-[#534AB7]"
|
||||
class="inline-flex w-8 h-8 rounded-lg bg-accent/10 items-center justify-center text-accent shrink-0"
|
||||
aria-hidden="true"
|
||||
set:html={icons[icon] ?? icons["hash"]}
|
||||
/>
|
||||
<span class="text-xs font-medium text-[#534AB7] bg-[#534AB7]/10 px-2 py-0.5 rounded-full">
|
||||
<span class="text-xs font-medium text-accent bg-accent/10 px-2 py-0.5 rounded-full">
|
||||
{T.live}
|
||||
</span>
|
||||
</div>
|
||||
<h2 class="mt-3 text-base font-semibold text-zinc-100 group-hover:text-[#534AB7] transition-colors duration-150">
|
||||
<h2 class="mt-3 text-base font-semibold text-zinc-100 group-hover:text-accent transition-colors duration-150">
|
||||
{title}
|
||||
</h2>
|
||||
<p class="mt-1 text-sm text-zinc-400 leading-relaxed">{description}</p>
|
||||
</a>
|
||||
) : (
|
||||
<div
|
||||
class="block border border-zinc-800/50 rounded-xl p-5 opacity-40 cursor-default"
|
||||
class="block h-full border border-zinc-800/50 rounded-xl p-5 shadow-lg shadow-black/10 opacity-40 cursor-default"
|
||||
aria-label={`${title} — ${T.comingSoon}`}
|
||||
>
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div
|
||||
class="text-zinc-500"
|
||||
class="inline-flex w-8 h-8 rounded-lg bg-zinc-800 items-center justify-center text-zinc-500 shrink-0"
|
||||
aria-hidden="true"
|
||||
set:html={icons[icon] ?? icons["hash"]}
|
||||
/>
|
||||
|
||||
@@ -12,7 +12,7 @@ const alternatePath = isRu
|
||||
<a
|
||||
href={isRu ? alternatePath : "#"}
|
||||
id="lang-en"
|
||||
class={`px-2 py-1 rounded-md transition-colors ${isRu ? "text-zinc-400 hover:text-zinc-200" : "bg-[#534AB7] text-white"}`}
|
||||
class={`px-2 py-1 rounded-md transition-colors ${isRu ? "text-zinc-400 hover:text-zinc-200" : "bg-accent text-white"}`}
|
||||
aria-current={isRu ? undefined : "true"}
|
||||
data-target-lang="en"
|
||||
data-alternate={isRu ? alternatePath : ""}>EN</a
|
||||
@@ -20,7 +20,7 @@ const alternatePath = isRu
|
||||
<a
|
||||
href={isRu ? "#" : alternatePath}
|
||||
id="lang-ru"
|
||||
class={`px-2 py-1 rounded-md transition-colors ${isRu ? "bg-[#534AB7] text-white" : "text-zinc-400 hover:text-zinc-200"}`}
|
||||
class={`px-2 py-1 rounded-md transition-colors ${isRu ? "bg-accent text-white" : "text-zinc-400 hover:text-zinc-200"}`}
|
||||
aria-current={isRu ? "true" : undefined}
|
||||
data-target-lang="ru"
|
||||
data-alternate={isRu ? "" : alternatePath}>RU</a
|
||||
|
||||
@@ -13,22 +13,24 @@ const T = useT(lang);
|
||||
---
|
||||
|
||||
<section
|
||||
class="mt-12 border-t border-zinc-800/60 pt-8 text-sm text-zinc-500 space-y-4"
|
||||
class="mt-12 border-t border-zinc-800/60 pt-8 text-sm text-zinc-500 flex flex-col gap-4"
|
||||
>
|
||||
<div>
|
||||
<div class="rounded-xl bg-zinc-900/50 border border-zinc-800/60 p-5">
|
||||
<h2
|
||||
class="text-xs font-semibold uppercase tracking-widest text-zinc-600 mb-2"
|
||||
class="flex items-center gap-2 text-xs font-semibold uppercase tracking-widest text-zinc-400 mb-2"
|
||||
>
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-accent" aria-hidden="true"></span>
|
||||
{T.howToUse}
|
||||
</h2>
|
||||
<ol class="space-y-1 list-decimal list-inside">
|
||||
{howTo.map((step) => <li>{step}</li>)}
|
||||
</ol>
|
||||
</div>
|
||||
<div>
|
||||
<div class="rounded-xl bg-zinc-900/50 border border-zinc-800/60 p-5">
|
||||
<h2
|
||||
class="text-xs font-semibold uppercase tracking-widest text-zinc-600 mb-2"
|
||||
class="flex items-center gap-2 text-xs font-semibold uppercase tracking-widest text-zinc-400 mb-2"
|
||||
>
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-accent" aria-hidden="true"></span>
|
||||
{T.whenToUse}
|
||||
</h2>
|
||||
<ul class="space-y-1 list-disc list-inside">
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
<canvas
|
||||
id="starfield"
|
||||
class="fixed inset-0 w-full h-full pointer-events-none"
|
||||
style="z-index: 0;"
|
||||
aria-hidden="true"
|
||||
></canvas>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
const canvas = document.getElementById("starfield") as HTMLCanvasElement;
|
||||
if (!canvas) return;
|
||||
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (!ctx) return;
|
||||
|
||||
let width = 0;
|
||||
let height = 0;
|
||||
let stars: { x: number; y: number; size: number; speed: number; opacity: number; phase: number }[] = [];
|
||||
let animationId: number;
|
||||
|
||||
const STAR_COUNT = 180;
|
||||
const TWINKLE_SPEED = 0.0008;
|
||||
|
||||
function resize() {
|
||||
const dpr = Math.min(window.devicePixelRatio || 1, 2);
|
||||
width = window.innerWidth;
|
||||
height = window.innerHeight;
|
||||
canvas.width = width * dpr;
|
||||
canvas.height = height * dpr;
|
||||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||||
}
|
||||
|
||||
function initStars() {
|
||||
stars = [];
|
||||
for (let i = 0; i < STAR_COUNT; i++) {
|
||||
stars.push({
|
||||
x: Math.random() * width,
|
||||
y: Math.random() * height,
|
||||
size: Math.random() < 0.95 ? 1 : Math.random() < 0.7 ? 1.5 : 2,
|
||||
speed: 0.02 + Math.random() * 0.04,
|
||||
opacity: 0.15 + Math.random() * 0.55,
|
||||
phase: Math.random() * Math.PI * 2,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function draw(time: number) {
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
|
||||
for (const star of stars) {
|
||||
const twinkle =
|
||||
0.6 + 0.4 * Math.sin(time * TWINKLE_SPEED + star.phase);
|
||||
const alpha = star.opacity * twinkle;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(star.x, star.y, star.size, 0, Math.PI * 2);
|
||||
ctx.fillStyle = `rgba(210, 210, 220, ${alpha})`;
|
||||
ctx.fill();
|
||||
|
||||
star.y -= star.speed;
|
||||
if (star.y < -2) {
|
||||
star.y = height + 2;
|
||||
star.x = Math.random() * width;
|
||||
}
|
||||
}
|
||||
|
||||
animationId = requestAnimationFrame(draw);
|
||||
}
|
||||
|
||||
function start() {
|
||||
resize();
|
||||
initStars();
|
||||
animationId = requestAnimationFrame(draw);
|
||||
}
|
||||
|
||||
function stop() {
|
||||
cancelAnimationFrame(animationId);
|
||||
}
|
||||
|
||||
start();
|
||||
|
||||
window.addEventListener("resize", () => {
|
||||
stop();
|
||||
resize();
|
||||
initStars();
|
||||
start();
|
||||
});
|
||||
|
||||
document.addEventListener("visibilitychange", () => {
|
||||
if (document.hidden) {
|
||||
stop();
|
||||
} else {
|
||||
start();
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
@@ -5,6 +5,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
---
|
||||
|
||||
<div id="card-generator" class="mt-8">
|
||||
<div class="rounded-xl bg-zinc-900/50 border border-zinc-800/60 p-5">
|
||||
<div class="flex flex-col sm:flex-row gap-4 items-end">
|
||||
<div class="flex-1">
|
||||
<label
|
||||
@@ -18,7 +19,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
value="1"
|
||||
min="1"
|
||||
max="52"
|
||||
class="w-full bg-zinc-900 border border-zinc-700 rounded-lg px-3 py-2 text-zinc-100 text-base focus:outline-none focus:border-[#534AB7] focus:ring-1 focus:ring-[#534AB7] transition-colors"
|
||||
class="w-full bg-zinc-900 border border-zinc-700 rounded-lg px-3 py-2 text-zinc-100 text-base focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent transition-colors"
|
||||
aria-label={T.cardsToDraw}
|
||||
/>
|
||||
</div>
|
||||
@@ -28,7 +29,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
<input
|
||||
id="crd-replace"
|
||||
type="checkbox"
|
||||
class="w-4 h-4 rounded accent-[#534AB7] cursor-pointer"
|
||||
class="w-4 h-4 rounded accent-accent cursor-pointer"
|
||||
/>
|
||||
<span
|
||||
class="text-sm text-zinc-400 group-hover:text-zinc-200 transition-colors"
|
||||
@@ -36,6 +37,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p
|
||||
id="crd-error"
|
||||
@@ -45,7 +47,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
>
|
||||
</p>
|
||||
|
||||
<div class="my-10 min-h-32 flex flex-col items-center justify-center gap-4">
|
||||
<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="crd-cards"
|
||||
class="flex flex-wrap justify-center gap-2"
|
||||
@@ -58,7 +60,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
id="crd-copy-btn"
|
||||
type="button"
|
||||
aria-label="Copy cards 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-[#534AB7] rounded"
|
||||
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="crd-copy-label">{T.copy}</span>
|
||||
<span id="crd-copy-icon" aria-hidden="true">
|
||||
@@ -80,7 +82,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
</span>
|
||||
<span
|
||||
id="crd-check-icon"
|
||||
class="hidden text-[#534AB7]"
|
||||
class="hidden text-accent"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<svg
|
||||
@@ -104,7 +106,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
<button
|
||||
id="crd-btn"
|
||||
type="button"
|
||||
class="w-full sm:w-auto px-8 py-3 bg-[#534AB7] text-white font-semibold rounded-lg hover:bg-[#4740a0] active:bg-[#3d3990] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#534AB7] focus-visible:ring-offset-2 focus-visible:ring-offset-zinc-950 transition-colors duration-100 cursor-pointer"
|
||||
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.draw}
|
||||
</button>
|
||||
|
||||
@@ -5,7 +5,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
---
|
||||
|
||||
<div id="coin-generator" class="mt-8">
|
||||
<div>
|
||||
<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
|
||||
@@ -21,7 +21,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
min="1"
|
||||
max="20"
|
||||
value="1"
|
||||
class="w-full accent-[#534AB7] cursor-pointer"
|
||||
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">
|
||||
@@ -29,7 +29,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="my-10 min-h-28 flex flex-col items-center justify-center gap-4">
|
||||
<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"
|
||||
@@ -45,7 +45,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
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-[#534AB7] rounded"
|
||||
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">
|
||||
@@ -67,7 +67,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
</span>
|
||||
<span
|
||||
id="coin-check-icon"
|
||||
class="hidden text-[#534AB7]"
|
||||
class="hidden text-accent"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<svg
|
||||
@@ -91,7 +91,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
<button
|
||||
id="coin-btn"
|
||||
type="button"
|
||||
class="w-full sm:w-auto px-8 py-3 bg-[#534AB7] text-white font-semibold rounded-lg hover:bg-[#4740a0] active:bg-[#3d3990] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#534AB7] focus-visible:ring-offset-2 focus-visible:ring-offset-zinc-950 transition-colors duration-100 cursor-pointer"
|
||||
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>
|
||||
@@ -147,7 +147,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
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-[#534AB7] bg-[#534AB7]/10 text-[#534AB7]"
|
||||
? "border-accent bg-accent/10 text-accent"
|
||||
: "border-zinc-600 bg-zinc-800 text-zinc-400",
|
||||
].join(" ");
|
||||
coin.textContent = isHeads ? HEADS_LABEL : TAILS_LABEL;
|
||||
|
||||
@@ -5,6 +5,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
---
|
||||
|
||||
<div id="color-generator" class="mt-8">
|
||||
<div class="my-6 rounded-2xl bg-zinc-900/80 border border-zinc-800/80 p-8">
|
||||
<!-- Swatch -->
|
||||
<div class="flex justify-center mb-8">
|
||||
<div
|
||||
@@ -41,7 +42,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
id={`${id}-copy`}
|
||||
type="button"
|
||||
aria-label={`Copy ${label} value`}
|
||||
class="group shrink-0 p-1 rounded text-zinc-600 hover:text-zinc-300 focus:outline-none focus-visible:ring-1 focus-visible:ring-[#534AB7] transition-colors cursor-pointer"
|
||||
class="group shrink-0 p-1 rounded text-zinc-600 hover:text-zinc-300 focus:outline-none focus-visible:ring-1 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-zinc-950 transition-colors cursor-pointer"
|
||||
>
|
||||
<svg
|
||||
id={`${id}-copy-icon`}
|
||||
@@ -60,7 +61,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
</svg>
|
||||
<svg
|
||||
id={`${id}-check-icon`}
|
||||
class="hidden text-[#534AB7]"
|
||||
class="hidden text-accent"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="14"
|
||||
height="14"
|
||||
@@ -79,12 +80,14 @@ const T = useT(isRu ? "ru" : "en");
|
||||
}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Generate button -->
|
||||
<div class="flex justify-center">
|
||||
<button
|
||||
id="cg-btn"
|
||||
type="button"
|
||||
class="w-full sm:w-auto px-8 py-3 bg-[#534AB7] text-white font-semibold rounded-lg hover:bg-[#4740a0] active:bg-[#3d3990] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#534AB7] focus-visible:ring-offset-2 focus-visible:ring-offset-zinc-950 transition-colors duration-100 cursor-pointer"
|
||||
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.generate}
|
||||
</button>
|
||||
|
||||
@@ -27,12 +27,13 @@ const presets = [
|
||||
---
|
||||
|
||||
<div id="dice-generator" class="mt-8">
|
||||
<div class="rounded-xl bg-zinc-900/50 border border-zinc-800/60 p-5">
|
||||
<!-- Presets toggle -->
|
||||
<div class="mb-2">
|
||||
<button
|
||||
id="dg-presets-toggle"
|
||||
type="button"
|
||||
class="inline-flex items-center gap-1 text-sm font-medium text-zinc-500 hover:text-zinc-300 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#534AB7] rounded transition-colors cursor-pointer"
|
||||
class="inline-flex items-center gap-1 text-sm font-medium text-zinc-500 hover:text-zinc-300 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-zinc-950 rounded transition-colors cursor-pointer"
|
||||
>
|
||||
<span>{presetsLabel}</span>
|
||||
<svg
|
||||
@@ -60,7 +61,7 @@ const presets = [
|
||||
presets.map((p) => (
|
||||
<button
|
||||
type="button"
|
||||
class="dg-preset px-3 py-1.5 rounded-lg border border-zinc-700 text-sm font-medium text-zinc-400 hover:border-[#534AB7] hover:text-[#534AB7] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#534AB7] transition-colors cursor-pointer"
|
||||
class="dg-preset px-3 py-1.5 rounded-lg border border-zinc-700 text-sm font-medium text-zinc-400 hover:border-accent hover:text-accent focus:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-zinc-950 transition-colors cursor-pointer"
|
||||
data-preset={p.notation}
|
||||
>
|
||||
{p.label}
|
||||
@@ -79,7 +80,7 @@ const presets = [
|
||||
placeholder={notationPlaceholder}
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
class="w-full bg-zinc-900 border border-zinc-700 rounded-lg px-3 py-2 text-zinc-100 text-base font-mono focus:outline-none focus:border-[#534AB7] focus:ring-1 focus:ring-[#534AB7] transition-colors"
|
||||
class="w-full bg-zinc-900 border border-zinc-700 rounded-lg px-3 py-2 text-zinc-100 text-base font-mono focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent transition-colors"
|
||||
/>
|
||||
<p id="dg-notation-err" class="mt-1 text-xs text-red-500 hidden"></p>
|
||||
</div>
|
||||
@@ -96,7 +97,7 @@ const presets = [
|
||||
value="2"
|
||||
min="1"
|
||||
max="20"
|
||||
class="w-full bg-zinc-900 border border-zinc-700 rounded-lg px-3 py-2 text-zinc-100 text-base focus:outline-none focus:border-[#534AB7] focus:ring-1 focus:ring-[#534AB7] transition-colors"
|
||||
class="w-full bg-zinc-900 border border-zinc-700 rounded-lg px-3 py-2 text-zinc-100 text-base focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent transition-colors"
|
||||
aria-label={T.numberOfDice}
|
||||
/>
|
||||
</div>
|
||||
@@ -115,7 +116,7 @@ const presets = [
|
||||
class="sr-only peer"
|
||||
checked={s === "6"}
|
||||
/>
|
||||
<span class="inline-flex items-center justify-center w-10 h-10 rounded-lg border border-zinc-700 text-sm font-semibold text-zinc-400 peer-checked:border-[#534AB7] peer-checked:text-[#534AB7] peer-checked:bg-[#534AB7]/10 hover:border-zinc-500 transition-colors select-none">
|
||||
<span class="inline-flex items-center justify-center w-10 h-10 rounded-lg border border-zinc-700 text-sm font-semibold text-zinc-400 peer-checked:border-accent peer-checked:text-accent peer-checked:bg-accent/10 hover:border-zinc-500 transition-colors select-none">
|
||||
d{s}
|
||||
</span>
|
||||
</label>
|
||||
@@ -151,7 +152,7 @@ const presets = [
|
||||
checked={value === "normal"}
|
||||
/>
|
||||
<span
|
||||
class={`inline-flex items-center justify-center px-3 sm:px-4 h-9 text-sm font-medium text-zinc-400 peer-checked:text-[#534AB7] peer-checked:bg-[#534AB7]/10 hover:text-zinc-200 transition-colors select-none whitespace-nowrap${i < 2 ? " border-r border-zinc-700" : ""}`}
|
||||
class={`inline-flex items-center justify-center px-3 sm:px-4 h-9 text-sm font-medium text-zinc-400 peer-checked:text-accent peer-checked:bg-accent/10 hover:text-zinc-200 transition-colors select-none whitespace-nowrap${i < 2 ? " border-r border-zinc-700" : ""}`}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
@@ -170,9 +171,10 @@ const presets = [
|
||||
class="mt-3 text-sm text-red-500 hidden"
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Results -->
|
||||
<div class="my-10 min-h-28 flex flex-col items-center justify-center gap-3">
|
||||
<div class="my-6 h-52 overflow-hidden flex flex-col items-center justify-center gap-3 rounded-2xl bg-zinc-900/80 border border-zinc-800/80 p-8">
|
||||
<div
|
||||
id="dg-dice"
|
||||
class="flex flex-wrap justify-center gap-2"
|
||||
@@ -199,7 +201,7 @@ const presets = [
|
||||
id="dg-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-[#534AB7] rounded"
|
||||
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="dg-copy-label">{T.copy}</span>
|
||||
<span id="dg-copy-icon" aria-hidden="true">
|
||||
@@ -219,7 +221,7 @@ const presets = [
|
||||
></path>
|
||||
</svg>
|
||||
</span>
|
||||
<span id="dg-check-icon" class="hidden text-[#534AB7]" aria-hidden="true">
|
||||
<span id="dg-check-icon" class="hidden text-accent" aria-hidden="true">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="14"
|
||||
@@ -242,7 +244,7 @@ const presets = [
|
||||
<button
|
||||
id="dg-btn"
|
||||
type="button"
|
||||
class="w-full sm:w-auto px-8 py-3 bg-[#534AB7] text-white font-semibold rounded-lg hover:bg-[#4740a0] active:bg-[#3d3990] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#534AB7] focus-visible:ring-offset-2 focus-visible:ring-offset-zinc-950 transition-colors duration-100 cursor-pointer"
|
||||
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.roll}
|
||||
</button>
|
||||
@@ -257,7 +259,7 @@ const presets = [
|
||||
<button
|
||||
id="dg-clear-btn"
|
||||
type="button"
|
||||
class="text-xs text-zinc-600 hover:text-zinc-300 transition-colors focus:outline-none focus-visible:ring-1 focus-visible:ring-[#534AB7] rounded"
|
||||
class="text-xs text-zinc-600 hover:text-zinc-300 transition-colors focus:outline-none focus-visible:ring-1 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-zinc-950 rounded"
|
||||
>
|
||||
{clearHistory}
|
||||
</button>
|
||||
@@ -563,7 +565,7 @@ const presets = [
|
||||
const text = String(v);
|
||||
const color =
|
||||
v === sides
|
||||
? "border-[#534AB7] text-[#534AB7] bg-[#534AB7]/10"
|
||||
? "border-accent text-accent bg-accent/10"
|
||||
: v === 1
|
||||
? "border-red-700/50 text-red-400 bg-red-900/10"
|
||||
: "border-zinc-700 text-zinc-100 bg-zinc-900";
|
||||
@@ -576,7 +578,7 @@ const presets = [
|
||||
function dieHtml(v, sides) {
|
||||
const cls =
|
||||
v === sides
|
||||
? "text-[#534AB7] font-bold"
|
||||
? "text-accent font-bold"
|
||||
: v === 1
|
||||
? "text-red-400 font-bold"
|
||||
: "text-zinc-300";
|
||||
@@ -632,7 +634,7 @@ const presets = [
|
||||
}
|
||||
let color =
|
||||
d.value === sides
|
||||
? "text-[#534AB7]"
|
||||
? "text-accent"
|
||||
: d.value === 1
|
||||
? "text-red-400"
|
||||
: "text-zinc-100";
|
||||
|
||||
@@ -5,6 +5,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
---
|
||||
|
||||
<div id="list-generator" class="mt-8">
|
||||
<div class="rounded-xl bg-zinc-900/50 border border-zinc-800/60 p-5">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div>
|
||||
<label
|
||||
@@ -21,7 +22,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
Bob
|
||||
Carol
|
||||
David"
|
||||
class="w-full bg-zinc-900 border border-zinc-700 rounded-lg px-3 py-2 text-zinc-100 text-sm font-mono focus:outline-none focus:border-[#534AB7] focus:ring-1 focus:ring-[#534AB7] transition-colors resize-y"
|
||||
class="w-full bg-zinc-900 border border-zinc-700 rounded-lg px-3 py-2 text-zinc-100 text-sm font-mono focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent transition-colors resize-y"
|
||||
aria-label={T.items}></textarea>
|
||||
</div>
|
||||
|
||||
@@ -36,7 +37,7 @@ David"
|
||||
type="number"
|
||||
value="1"
|
||||
min="1"
|
||||
class="w-full bg-zinc-900 border border-zinc-700 rounded-lg px-3 py-2 text-zinc-100 text-base focus:outline-none focus:border-[#534AB7] focus:ring-1 focus:ring-[#534AB7] transition-colors"
|
||||
class="w-full bg-zinc-900 border border-zinc-700 rounded-lg px-3 py-2 text-zinc-100 text-base focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent transition-colors"
|
||||
aria-label={T.pick}
|
||||
/>
|
||||
</div>
|
||||
@@ -46,7 +47,7 @@ David"
|
||||
<input
|
||||
id="lst-replace"
|
||||
type="checkbox"
|
||||
class="w-4 h-4 rounded accent-[#534AB7] cursor-pointer"
|
||||
class="w-4 h-4 rounded accent-accent cursor-pointer"
|
||||
/>
|
||||
<span
|
||||
class="text-sm text-zinc-400 group-hover:text-zinc-200 transition-colors"
|
||||
@@ -63,8 +64,9 @@ David"
|
||||
class="mt-3 text-sm text-red-500 hidden"
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="my-8 min-h-16 flex flex-col items-center justify-center gap-3">
|
||||
<div class="my-6 h-52 overflow-hidden flex flex-col items-center justify-center gap-3 rounded-2xl bg-zinc-900/80 border border-zinc-800/80 p-8">
|
||||
<div
|
||||
id="lst-result"
|
||||
class="flex flex-wrap justify-center gap-2"
|
||||
@@ -77,7 +79,7 @@ David"
|
||||
id="lst-copy-btn"
|
||||
type="button"
|
||||
aria-label="Copy picked items 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-[#534AB7] rounded"
|
||||
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="lst-copy-label">{T.copy}</span>
|
||||
<span id="lst-copy-icon" aria-hidden="true">
|
||||
@@ -99,7 +101,7 @@ David"
|
||||
</span>
|
||||
<span
|
||||
id="lst-check-icon"
|
||||
class="hidden text-[#534AB7]"
|
||||
class="hidden text-accent"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<svg
|
||||
@@ -123,7 +125,7 @@ David"
|
||||
<button
|
||||
id="lst-btn"
|
||||
type="button"
|
||||
class="w-full sm:w-auto px-8 py-3 bg-[#534AB7] text-white font-semibold rounded-lg hover:bg-[#4740a0] active:bg-[#3d3990] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#534AB7] focus-visible:ring-offset-2 focus-visible:ring-offset-zinc-950 transition-colors duration-100 cursor-pointer"
|
||||
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.pick}
|
||||
</button>
|
||||
@@ -212,7 +214,7 @@ David"
|
||||
const chip = document.createElement("span");
|
||||
chip.textContent = item;
|
||||
chip.className =
|
||||
"inline-flex items-center px-3 py-1.5 rounded-full border border-[#534AB7]/40 bg-[#534AB7]/10 text-zinc-100 text-sm font-medium";
|
||||
"inline-flex items-center px-3 py-1.5 rounded-full border border-accent/40 bg-accent/10 text-zinc-100 text-sm font-medium";
|
||||
resultEl.appendChild(chip);
|
||||
});
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
---
|
||||
|
||||
<div id="lottery-generator" class="mt-8">
|
||||
<div class="rounded-xl bg-zinc-900/50 border border-zinc-800/60 p-5">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label for="lg-from" class="block text-sm font-medium text-zinc-400 mb-1"
|
||||
@@ -14,7 +15,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
id="lg-from"
|
||||
type="number"
|
||||
value="1"
|
||||
class="w-full bg-zinc-900 border border-zinc-700 rounded-lg px-3 py-2 text-zinc-100 text-base focus:outline-none focus:border-[#534AB7] focus:ring-1 focus:ring-[#534AB7] transition-colors"
|
||||
class="w-full bg-zinc-900 border border-zinc-700 rounded-lg px-3 py-2 text-zinc-100 text-base focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent transition-colors"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
@@ -25,7 +26,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
id="lg-to"
|
||||
type="number"
|
||||
value="49"
|
||||
class="w-full bg-zinc-900 border border-zinc-700 rounded-lg px-3 py-2 text-zinc-100 text-base focus:outline-none focus:border-[#534AB7] focus:ring-1 focus:ring-[#534AB7] transition-colors"
|
||||
class="w-full bg-zinc-900 border border-zinc-700 rounded-lg px-3 py-2 text-zinc-100 text-base focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent transition-colors"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
@@ -37,7 +38,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
type="number"
|
||||
value="6"
|
||||
min="1"
|
||||
class="w-full bg-zinc-900 border border-zinc-700 rounded-lg px-3 py-2 text-zinc-100 text-base focus:outline-none focus:border-[#534AB7] focus:ring-1 focus:ring-[#534AB7] transition-colors"
|
||||
class="w-full bg-zinc-900 border border-zinc-700 rounded-lg px-3 py-2 text-zinc-100 text-base focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent transition-colors"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -49,8 +50,9 @@ const T = useT(isRu ? "ru" : "en");
|
||||
class="mt-3 text-sm text-red-500 hidden"
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="my-10 min-h-24 flex flex-col items-center justify-center gap-4">
|
||||
<div class="my-6 h-52 overflow-hidden flex flex-col items-center justify-center gap-4 rounded-2xl bg-zinc-900/80 border border-zinc-800/80 p-8">
|
||||
<div
|
||||
id="lg-result"
|
||||
class="flex flex-wrap justify-center gap-2"
|
||||
@@ -60,7 +62,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
<button
|
||||
id="lg-copy-btn"
|
||||
type="button"
|
||||
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-[#534AB7] rounded"
|
||||
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="lg-copy-label">{T.copy}</span>
|
||||
<span id="lg-copy-icon" aria-hidden="true"
|
||||
@@ -79,7 +81,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
></path></svg
|
||||
></span
|
||||
>
|
||||
<span id="lg-check-icon" class="hidden text-[#534AB7]" aria-hidden="true"
|
||||
<span id="lg-check-icon" class="hidden text-accent" aria-hidden="true"
|
||||
><svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="14"
|
||||
@@ -99,7 +101,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
<button
|
||||
id="lg-btn"
|
||||
type="button"
|
||||
class="w-full sm:w-auto px-8 py-3 bg-[#534AB7] text-white font-semibold rounded-lg hover:bg-[#4740a0] active:bg-[#3d3990] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#534AB7] focus-visible:ring-offset-2 focus-visible:ring-offset-zinc-950 transition-colors duration-100 cursor-pointer"
|
||||
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.draw}
|
||||
</button>
|
||||
@@ -178,7 +180,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
const ball = document.createElement("span");
|
||||
ball.textContent = String(n);
|
||||
ball.className =
|
||||
"inline-flex items-center justify-center min-w-[2.5rem] h-10 px-2 rounded-full bg-[#534AB7]/15 border border-[#534AB7]/40 text-zinc-100 font-bold tabular-nums text-sm";
|
||||
"inline-flex items-center justify-center min-w-[2.5rem] h-10 px-2 rounded-full bg-accent/15 border border-accent/40 text-zinc-100 font-bold tabular-nums text-sm";
|
||||
resultEl.appendChild(ball);
|
||||
});
|
||||
copyBtn.classList.remove("invisible");
|
||||
|
||||
@@ -5,6 +5,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
---
|
||||
|
||||
<div id="number-generator" class="mt-8" data-ru={isRu ? "1" : "0"}>
|
||||
<div class="rounded-xl bg-zinc-900/50 border border-zinc-800/60 p-5">
|
||||
<div class="flex flex-col sm:flex-row gap-4">
|
||||
<div class="flex-1">
|
||||
<label for="ng-from" class="block text-sm font-medium text-zinc-400 mb-1"
|
||||
@@ -14,7 +15,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
id="ng-from"
|
||||
type="number"
|
||||
value="1"
|
||||
class="w-full bg-zinc-900 border border-zinc-700 rounded-lg px-3 py-2 text-zinc-100 text-base focus:outline-none focus:border-[#534AB7] focus:ring-1 focus:ring-[#534AB7] transition-colors"
|
||||
class="w-full bg-zinc-900 border border-zinc-700 rounded-lg px-3 py-2 text-zinc-100 text-base focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent transition-colors"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
@@ -25,10 +26,11 @@ const T = useT(isRu ? "ru" : "en");
|
||||
id="ng-to"
|
||||
type="number"
|
||||
value="100"
|
||||
class="w-full bg-zinc-900 border border-zinc-700 rounded-lg px-3 py-2 text-zinc-100 text-base focus:outline-none focus:border-[#534AB7] focus:ring-1 focus:ring-[#534AB7] transition-colors"
|
||||
class="w-full bg-zinc-900 border border-zinc-700 rounded-lg px-3 py-2 text-zinc-100 text-base focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent transition-colors"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p
|
||||
id="ng-error"
|
||||
@@ -38,11 +40,12 @@ const T = useT(isRu ? "ru" : "en");
|
||||
>
|
||||
</p>
|
||||
|
||||
<div class="my-10 min-h-36 flex flex-col items-center justify-center gap-3">
|
||||
<div class="my-6 rounded-2xl bg-zinc-900/80 border border-zinc-800/80 p-8 h-52 overflow-hidden">
|
||||
<div class="h-full flex flex-col items-center justify-center gap-3">
|
||||
<button
|
||||
id="ng-copy-btn"
|
||||
type="button"
|
||||
class="group relative invisible cursor-copy rounded-xl px-3 py-1 -mx-3 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#534AB7]"
|
||||
class="group relative invisible cursor-copy rounded-xl px-3 py-1 -mx-3 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-zinc-950"
|
||||
aria-live="polite"
|
||||
>
|
||||
<span
|
||||
@@ -72,7 +75,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
</span>
|
||||
<span
|
||||
id="ng-check-icon"
|
||||
class="absolute -right-7 top-1/2 -translate-y-1/2 hidden text-[#534AB7]"
|
||||
class="absolute -right-7 top-1/2 -translate-y-1/2 hidden text-accent"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<svg
|
||||
@@ -90,17 +93,18 @@ const T = useT(isRu ? "ru" : "en");
|
||||
</button>
|
||||
<span
|
||||
id="ng-copied-label"
|
||||
class="text-xs font-medium text-[#534AB7] opacity-0 transition-opacity duration-200"
|
||||
class="text-xs font-medium text-accent opacity-0 transition-opacity duration-200"
|
||||
aria-live="polite"
|
||||
aria-atomic="true">{T.copied}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center">
|
||||
<button
|
||||
id="ng-btn"
|
||||
type="button"
|
||||
class="w-full sm:w-auto px-8 py-3 bg-[#534AB7] text-white font-semibold rounded-lg hover:bg-[#4740a0] active:bg-[#3d3990] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#534AB7] focus-visible:ring-offset-2 focus-visible:ring-offset-zinc-950 transition-colors duration-100 cursor-pointer"
|
||||
class="w-full sm:w-auto px-8 py-3 bg-accent text-white font-semibold rounded-xl 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 shadow-lg shadow-accent/25"
|
||||
>
|
||||
{T.generate}
|
||||
</button>
|
||||
|
||||
@@ -5,6 +5,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
---
|
||||
|
||||
<div id="password-generator" class="mt-8">
|
||||
<div class="rounded-xl bg-zinc-900/50 border border-zinc-800/60 p-5">
|
||||
<div class="flex flex-col gap-5">
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
@@ -22,7 +23,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
min="4"
|
||||
max="64"
|
||||
value="16"
|
||||
class="w-full accent-[#534AB7] cursor-pointer"
|
||||
class="w-full accent-accent cursor-pointer"
|
||||
/>
|
||||
<div class="flex justify-between text-xs text-zinc-600 mt-1 select-none">
|
||||
<span>4</span><span>64</span>
|
||||
@@ -41,7 +42,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
id={id}
|
||||
type="checkbox"
|
||||
checked
|
||||
class="w-4 h-4 rounded accent-[#534AB7] cursor-pointer"
|
||||
class="w-4 h-4 rounded accent-accent cursor-pointer"
|
||||
/>
|
||||
<span class="text-sm text-zinc-400 group-hover:text-zinc-200 transition-colors">
|
||||
{label}
|
||||
@@ -51,6 +52,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p
|
||||
id="pg-error"
|
||||
@@ -60,11 +62,11 @@ const T = useT(isRu ? "ru" : "en");
|
||||
>
|
||||
</p>
|
||||
|
||||
<div class="my-10 min-h-24 flex flex-col items-center justify-center gap-3">
|
||||
<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-3">
|
||||
<button
|
||||
id="pg-copy-btn"
|
||||
type="button"
|
||||
class="group relative invisible cursor-copy rounded-xl px-3 py-2 -mx-3 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#534AB7]"
|
||||
class="group relative invisible cursor-copy rounded-xl px-3 py-2 -mx-3 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-zinc-950"
|
||||
>
|
||||
<span
|
||||
id="pg-result"
|
||||
@@ -92,7 +94,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
</span>
|
||||
<span
|
||||
id="pg-check-icon"
|
||||
class="absolute -right-7 top-1/2 -translate-y-1/2 hidden text-[#534AB7]"
|
||||
class="absolute -right-7 top-1/2 -translate-y-1/2 hidden text-accent"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<svg
|
||||
@@ -110,7 +112,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
</button>
|
||||
<span
|
||||
id="pg-copied-label"
|
||||
class="text-xs font-medium text-[#534AB7] opacity-0 transition-opacity duration-200"
|
||||
class="text-xs font-medium text-accent opacity-0 transition-opacity duration-200"
|
||||
aria-live="polite"
|
||||
aria-atomic="true">{T.copied}</span
|
||||
>
|
||||
@@ -120,7 +122,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
<button
|
||||
id="pg-btn"
|
||||
type="button"
|
||||
class="w-full sm:w-auto px-8 py-3 bg-[#534AB7] text-white font-semibold rounded-lg hover:bg-[#4740a0] active:bg-[#3d3990] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#534AB7] focus-visible:ring-offset-2 focus-visible:ring-offset-zinc-950 transition-colors duration-100 cursor-pointer"
|
||||
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.generate}
|
||||
</button>
|
||||
|
||||
@@ -5,6 +5,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
---
|
||||
|
||||
<div id="uuid-generator" class="mt-8">
|
||||
<div class="rounded-xl bg-zinc-900/50 border border-zinc-800/60 p-5">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-zinc-400 mb-2"
|
||||
@@ -25,7 +26,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
class="sr-only peer"
|
||||
checked={value === "uuid"}
|
||||
/>
|
||||
<span class="inline-flex items-center justify-center px-4 h-9 rounded-lg border border-zinc-700 text-sm font-medium text-zinc-400 peer-checked:border-[#534AB7] peer-checked:text-[#534AB7] peer-checked:bg-[#534AB7]/10 hover:border-zinc-500 transition-colors select-none cursor-pointer">
|
||||
<span class="inline-flex items-center justify-center px-4 h-9 rounded-lg border border-zinc-700 text-sm font-medium text-zinc-400 peer-checked:border-accent peer-checked:text-accent peer-checked:bg-accent/10 hover:border-zinc-500 transition-colors select-none cursor-pointer">
|
||||
{label}
|
||||
</span>
|
||||
</label>
|
||||
@@ -47,7 +48,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
value="16"
|
||||
min="4"
|
||||
max="64"
|
||||
class="w-full bg-zinc-900 border border-zinc-700 rounded-lg px-3 py-2 text-zinc-100 text-base focus:outline-none focus:border-[#534AB7] focus:ring-1 focus:ring-[#534AB7] transition-colors"
|
||||
class="w-full bg-zinc-900 border border-zinc-700 rounded-lg px-3 py-2 text-zinc-100 text-base focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent transition-colors"
|
||||
aria-label={T.lengthBytes}
|
||||
/>
|
||||
</div>
|
||||
@@ -62,15 +63,16 @@ const T = useT(isRu ? "ru" : "en");
|
||||
value="1"
|
||||
min="1"
|
||||
max="20"
|
||||
class="w-full bg-zinc-900 border border-zinc-700 rounded-lg px-3 py-2 text-zinc-100 text-base focus:outline-none focus:border-[#534AB7] focus:ring-1 focus:ring-[#534AB7] transition-colors"
|
||||
class="w-full bg-zinc-900 border border-zinc-700 rounded-lg px-3 py-2 text-zinc-100 text-base focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent transition-colors"
|
||||
aria-label={T.count}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="my-8 min-h-16 flex flex-col gap-2"
|
||||
class="my-6 rounded-2xl bg-zinc-900/80 border border-zinc-800/80 p-8 h-52 overflow-hidden flex flex-col gap-2"
|
||||
aria-live="polite"
|
||||
aria-label="Generated tokens"
|
||||
>
|
||||
@@ -80,7 +82,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
id="ug-copy-btn"
|
||||
type="button"
|
||||
aria-label="Copy all tokens to clipboard"
|
||||
class="invisible self-start 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-[#534AB7] rounded mt-1"
|
||||
class="invisible self-start 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 mt-1"
|
||||
>
|
||||
<span id="ug-copy-label">{T.copyAll}</span>
|
||||
<span id="ug-copy-icon" aria-hidden="true">
|
||||
@@ -100,7 +102,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
></path>
|
||||
</svg>
|
||||
</span>
|
||||
<span id="ug-check-icon" class="hidden text-[#534AB7]" aria-hidden="true">
|
||||
<span id="ug-check-icon" class="hidden text-accent" aria-hidden="true">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="14"
|
||||
@@ -122,7 +124,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
<button
|
||||
id="ug-btn"
|
||||
type="button"
|
||||
class="w-full sm:w-auto px-8 py-3 bg-[#534AB7] text-white font-semibold rounded-lg hover:bg-[#4740a0] active:bg-[#3d3990] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#534AB7] focus-visible:ring-offset-2 focus-visible:ring-offset-zinc-950 transition-colors duration-100 cursor-pointer"
|
||||
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.generate}
|
||||
</button>
|
||||
|
||||
@@ -6,6 +6,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
|
||||
<div id="wheel-spinner" class="mt-8">
|
||||
<!-- Items input -->
|
||||
<div class="rounded-xl bg-zinc-900/50 border border-zinc-800/60 p-5">
|
||||
<div class="flex flex-col gap-3">
|
||||
<div>
|
||||
<label
|
||||
@@ -18,7 +19,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
<textarea
|
||||
id="ws-items"
|
||||
rows="6"
|
||||
class="w-full bg-zinc-900 border border-zinc-700 rounded-lg px-3 py-2 text-zinc-100 text-sm font-mono focus:outline-none focus:border-[#534AB7] focus:ring-1 focus:ring-[#534AB7] transition-colors resize-y"
|
||||
class="w-full bg-zinc-900 border border-zinc-700 rounded-lg px-3 py-2 text-zinc-100 text-sm font-mono focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent transition-colors resize-y"
|
||||
>Alice Bob Carol Dave Eve Frank</textarea
|
||||
>
|
||||
</div>
|
||||
@@ -30,6 +31,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Wheel area -->
|
||||
<div class="flex flex-col items-center mt-8 gap-6">
|
||||
@@ -49,7 +51,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
<button
|
||||
id="ws-spin-btn"
|
||||
type="button"
|
||||
class="w-full sm:w-auto px-10 py-3 bg-[#534AB7] text-white font-semibold rounded-lg hover:bg-[#4740a0] active:bg-[#3d3990] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#534AB7] focus-visible:ring-offset-2 focus-visible:ring-offset-zinc-950 transition-colors duration-100 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
class="w-full sm:w-auto px-10 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 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{T.spin}
|
||||
</button>
|
||||
@@ -58,7 +60,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
<!-- Result card (hidden until first spin) -->
|
||||
<div
|
||||
id="ws-result-card"
|
||||
class="hidden mt-10 flex flex-col items-center gap-3"
|
||||
class="hidden my-6 rounded-2xl bg-zinc-900/80 border border-zinc-800/80 p-8 h-52 overflow-hidden flex flex-col items-center gap-3"
|
||||
aria-live="polite"
|
||||
>
|
||||
<p
|
||||
@@ -74,7 +76,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
<button
|
||||
id="ws-copy-btn"
|
||||
type="button"
|
||||
class="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-[#534AB7] rounded"
|
||||
class="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="ws-copy-label">{T.copy}</span>
|
||||
<span id="ws-copy-icon" aria-hidden="true">
|
||||
@@ -94,7 +96,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
></path>
|
||||
</svg>
|
||||
</span>
|
||||
<span id="ws-check-icon" class="hidden text-[#534AB7]" aria-hidden="true">
|
||||
<span id="ws-check-icon" class="hidden text-accent" aria-hidden="true">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="14"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
import { getRelativeLocaleUrl } from "astro:i18n";
|
||||
import LanguageSwitcher from "../components/LanguageSwitcher.astro";
|
||||
import Starfield from "../components/Starfield.astro";
|
||||
import Analytics from "../components/Analytics.astro";
|
||||
import { useT } from "../i18n/translations";
|
||||
import type { Lang } from "../i18n/translations";
|
||||
@@ -31,7 +32,7 @@ const webPageSchema = {
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang={lang}>
|
||||
<html lang={lang} style="scroll-behavior:smooth;scrollbar-gutter:stable">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
@@ -51,7 +52,7 @@ const webPageSchema = {
|
||||
type="application/ld+json"
|
||||
set:html={JSON.stringify(webPageSchema)}
|
||||
/>
|
||||
<meta name="theme-color" content="#534AB7" />
|
||||
<meta name="theme-color" content="#534ab7" />
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
||||
<slot name="head" />
|
||||
@@ -64,8 +65,11 @@ const webPageSchema = {
|
||||
</script>
|
||||
</head>
|
||||
<body class="bg-zinc-950 text-zinc-100 min-h-screen font-sans antialiased">
|
||||
<LanguageSwitcher />
|
||||
<slot />
|
||||
<Starfield />
|
||||
<div class="relative z-10">
|
||||
<LanguageSwitcher />
|
||||
<slot />
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -84,6 +88,11 @@ const webPageSchema = {
|
||||
|
||||
@theme {
|
||||
--font-sans: "Space Grotesk", sans-serif;
|
||||
--color-accent: #534ab7;
|
||||
--color-accent-hover: #4740a0;
|
||||
--color-accent-active: #3d3990;
|
||||
--color-yandex: #fc3f1d;
|
||||
--color-yandex-hover: #e03518;
|
||||
}
|
||||
|
||||
:root {
|
||||
@@ -106,6 +115,7 @@ const webPageSchema = {
|
||||
rgba(83, 74, 183, 0.13) 0%,
|
||||
transparent 65%
|
||||
);
|
||||
background-size: auto, auto;
|
||||
}
|
||||
|
||||
input[type="number"] {
|
||||
|
||||
@@ -68,7 +68,7 @@ const breadcrumbSchema = {
|
||||
<nav aria-label="Breadcrumb" class="mb-10">
|
||||
<a
|
||||
href={isRu ? "/ru/" : "/"}
|
||||
class="inline-flex items-center gap-1.5 text-sm text-zinc-500 hover:text-zinc-200 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[#534AB7] rounded"
|
||||
class="inline-flex items-center gap-1.5 text-sm text-zinc-600 hover:text-zinc-300 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"
|
||||
aria-label={T.backToAll}
|
||||
>
|
||||
<svg
|
||||
@@ -92,7 +92,7 @@ const breadcrumbSchema = {
|
||||
<header class="mb-2">
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
<span
|
||||
class="inline-block w-2.5 h-2.5 rounded-full bg-[#534AB7]"
|
||||
class="inline-block w-2.5 h-2.5 rounded-full bg-accent"
|
||||
aria-hidden="true"></span>
|
||||
<span
|
||||
class="text-sm font-medium text-zinc-400 uppercase tracking-widest"
|
||||
@@ -102,14 +102,17 @@ const breadcrumbSchema = {
|
||||
<h1 class="text-3xl sm:text-4xl font-bold text-zinc-100">
|
||||
{isRu ? generator.ruPageTitle : generator.pageTitle}
|
||||
</h1>
|
||||
<div class="w-16 h-0.5 bg-accent/60 rounded-full mt-4" aria-hidden="true"></div>
|
||||
<p class="mt-2 text-base text-zinc-400">
|
||||
{isRu ? generator.ruDescription : generator.description}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<div class="border-t border-zinc-800/60 pt-8 mt-8">
|
||||
<main>
|
||||
<slot />
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<div class="mt-12 flex justify-center">
|
||||
<AdBanner size="tile" />
|
||||
|
||||
@@ -27,9 +27,13 @@ const webSiteSchema = {
|
||||
/>
|
||||
<div class="max-w-4xl mx-auto px-4 py-12 sm:py-20">
|
||||
<header class="mb-12">
|
||||
<div class="flex items-center gap-2 mb-6">
|
||||
<div class="relative flex items-center gap-2 mb-6">
|
||||
<div
|
||||
class="absolute -left-16 -top-12 w-64 h-64 rounded-full bg-accent/20 blur-3xl pointer-events-none"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
<span
|
||||
class="inline-block w-3 h-3 rounded-full bg-[#534AB7]"
|
||||
class="inline-block w-3 h-3 rounded-full bg-accent"
|
||||
aria-hidden="true"></span>
|
||||
<span class="text-xl font-bold tracking-tight text-zinc-100"
|
||||
>{T.brandLabel}</span
|
||||
@@ -38,7 +42,7 @@ const webSiteSchema = {
|
||||
<h1 class="text-3xl sm:text-4xl font-bold text-zinc-100 leading-tight">
|
||||
{T.homeTitle}
|
||||
</h1>
|
||||
<p class="mt-3 text-base text-zinc-400 max-w-md">
|
||||
<p class="mt-3 text-base text-zinc-300 max-w-md">
|
||||
{T.homeSubtitle}
|
||||
</p>
|
||||
</header>
|
||||
@@ -48,7 +52,7 @@ const webSiteSchema = {
|
||||
<main class="mt-8">
|
||||
<div
|
||||
class="grid gap-3"
|
||||
style="grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));"
|
||||
style="grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); grid-auto-rows: 1fr;"
|
||||
role="list"
|
||||
aria-label="Generator catalog"
|
||||
>
|
||||
|
||||
@@ -29,7 +29,7 @@ const webSiteSchema = {
|
||||
<header class="mb-12">
|
||||
<div class="flex items-center gap-2 mb-6">
|
||||
<span
|
||||
class="inline-block w-3 h-3 rounded-full bg-[#534AB7]"
|
||||
class="inline-block w-3 h-3 rounded-full bg-accent"
|
||||
aria-hidden="true"></span>
|
||||
<span class="text-xl font-bold tracking-tight text-zinc-100"
|
||||
>{T.brandLabel}</span
|
||||
@@ -48,7 +48,7 @@ const webSiteSchema = {
|
||||
<main class="mt-8">
|
||||
<div
|
||||
class="grid gap-3"
|
||||
style="grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));"
|
||||
style="grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); grid-auto-rows: 1fr;"
|
||||
role="list"
|
||||
aria-label="Generator catalog"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user