Merge pull request #3 from emil28092005/dev

feat: coin flip, list picker, uuid/token generators
This commit is contained in:
Emil Shanaty
2026-05-09 14:09:12 +03:00
committed by GitHub
8 changed files with 639 additions and 0 deletions
+3
View File
@@ -17,6 +17,9 @@ const icons: Record<string, string> = {
ticket: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 9a3 3 0 0 1 0 6v2a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-2a3 3 0 0 1 0-6V7a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z"/><path d="M13 5v2"/><path d="M13 17v2"/><path d="M13 11v2"/></svg>`,
'dice-6': `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><path d="M16 8h.01"/><path d="M16 12h.01"/><path d="M16 16h.01"/><path d="M8 8h.01"/><path d="M8 12h.01"/><path d="M8 16h.01"/></svg>`,
'square-stack': `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 10c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h4c1.1 0 2 .9 2 2"/><path d="M10 16c-1.1 0-2-.9-2-2v-4c0-1.1.9-2 2-2h4c1.1 0 2 .9 2 2"/><rect width="8" height="8" x="14" y="14" rx="2"/></svg>`,
'circle-dollar-sign': `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M16 8h-6a2 2 0 1 0 0 4h4a2 2 0 1 1 0 4H8"/><path d="M12 18V6"/></svg>`,
'list': `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="8" x2="21" y1="6" y2="6"/><line x1="8" x2="21" y1="12" y2="12"/><line x1="8" x2="21" y1="18" y2="18"/><line x1="3" x2="3.01" y1="6" y2="6"/><line x1="3" x2="3.01" y1="12" y2="12"/><line x1="3" x2="3.01" y1="18" y2="18"/></svg>`,
'fingerprint': `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 10a2 2 0 0 0-2 2c0 1.02-.1 2.51-.26 4"/><path d="M14 13.12c0 2.38 0 6.38-1 8.88"/><path d="M17.29 21.02c.12-.6.43-2.3.5-3.02"/><path d="M2 12a10 10 0 0 1 18-6"/><path d="M2 17c1 .5 2.25 1 4 1 1.5 0 3-.5 4-1"/><path d="M20 12c0 2-.5 4.5-2 6"/><path d="M7 13.02c0 2.38 0 5.5 1 7.48"/><path d="M8.5 8.5A5 5 0 0 1 17 12"/></svg>`,
};
---
@@ -0,0 +1,136 @@
---
// Interactive coin flip generator — runs on the client via inline script
---
<div id="coin-generator" class="mt-8">
<div>
<div class="flex items-center justify-between mb-1">
<label for="cg-count" class="text-sm font-medium text-zinc-400">Number of coins</label>
<span id="cg-count-val" class="text-sm font-semibold tabular-nums text-zinc-100">1</span>
</div>
<input
id="cg-count"
type="range"
min="1"
max="20"
value="1"
class="w-full accent-[#534AB7] cursor-pointer"
aria-label="Number of coins"
/>
<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-10 min-h-28 flex flex-col items-center justify-center gap-4">
<div
id="cg-coins"
class="flex flex-wrap justify-center gap-2"
aria-live="polite"
aria-label="Coin results"
></div>
<div id="cg-summary" class="hidden text-sm text-zinc-500 tabular-nums"></div>
<button
id="cg-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"
>
<span id="cg-copy-label">Copy</span>
<span id="cg-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"/>
<path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>
</svg>
</span>
<span id="cg-check-icon" class="hidden text-[#534AB7]" 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"/>
</svg>
</span>
</button>
</div>
<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"
>
Flip
</button>
</div>
</div>
<script>
const countInput = document.getElementById('cg-count') as HTMLInputElement;
const countVal = document.getElementById('cg-count-val') as HTMLSpanElement;
const btn = document.getElementById('cg-btn') as HTMLButtonElement;
const copyBtn = document.getElementById('cg-copy-btn') as HTMLButtonElement;
const coinsEl = document.getElementById('cg-coins') as HTMLDivElement;
const summaryEl = document.getElementById('cg-summary') as HTMLDivElement;
const copyLabel = document.getElementById('cg-copy-label') as HTMLSpanElement;
const copyIcon = document.getElementById('cg-copy-icon') as HTMLSpanElement;
const checkIcon = document.getElementById('cg-check-icon') as HTMLSpanElement;
let lastResults: boolean[] = [];
let copyRevertTimer: ReturnType<typeof setTimeout> | null = null;
countInput.addEventListener('input', () => {
countVal.textContent = countInput.value;
});
function flip() {
const count = parseInt(countInput.value, 10);
lastResults = Array.from({ length: count }, () => Math.random() < 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' : 'Tails');
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-zinc-600 bg-zinc-800 text-zinc-400',
].join(' ');
coin.textContent = isHeads ? 'H' : 'T';
coinsEl.appendChild(coin);
});
if (count > 1) {
summaryEl.textContent = `${heads} heads · ${tails} tails`;
summaryEl.classList.remove('hidden');
} else {
summaryEl.classList.add('hidden');
}
copyBtn.classList.remove('invisible');
copyLabel.textContent = 'Copy';
}
async function copyResults() {
if (!lastResults.length) return;
const text = lastResults.map((h) => (h ? 'Heads' : 'Tails')).join(', ');
await navigator.clipboard.writeText(text);
copyIcon.classList.add('hidden');
checkIcon.classList.remove('hidden');
copyLabel.textContent = 'Copied';
if (copyRevertTimer) clearTimeout(copyRevertTimer);
copyRevertTimer = setTimeout(() => {
checkIcon.classList.add('hidden');
copyIcon.classList.remove('hidden');
copyLabel.textContent = 'Copy';
}, 1500);
}
btn.addEventListener('click', flip);
copyBtn.addEventListener('click', copyResults);
</script>
@@ -0,0 +1,174 @@
---
// Interactive list picker — runs on the client via inline script
---
<div id="list-generator" class="mt-8">
<div class="flex flex-col gap-4">
<div>
<label for="lg-items" class="block text-sm font-medium text-zinc-400 mb-1">
Items <span class="text-zinc-600">(one per line)</span>
</label>
<textarea
id="lg-items"
rows="6"
placeholder="Alice&#10;Bob&#10;Carol&#10;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"
aria-label="List of items to pick from"
></textarea>
</div>
<div class="flex flex-col sm:flex-row gap-4 items-end">
<div class="flex-1">
<label for="lg-pick" class="block text-sm font-medium text-zinc-400 mb-1">Pick</label>
<input
id="lg-pick"
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"
aria-label="How many items to pick"
/>
</div>
<label class="flex items-center gap-2.5 cursor-pointer select-none group pb-2">
<input
id="lg-replace"
type="checkbox"
class="w-4 h-4 rounded accent-[#534AB7] cursor-pointer"
/>
<span class="text-sm text-zinc-400 group-hover:text-zinc-200 transition-colors">Allow duplicates</span>
</label>
</div>
</div>
<p
id="lg-error"
role="alert"
aria-live="polite"
class="mt-3 text-sm text-red-500 hidden"
></p>
<div class="my-8 min-h-16 flex flex-col items-center justify-center gap-3">
<div
id="lg-result"
class="flex flex-wrap justify-center gap-2"
aria-live="polite"
aria-label="Picked items"
></div>
<button
id="lg-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"
>
<span id="lg-copy-label">Copy</span>
<span id="lg-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"/>
<path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>
</svg>
</span>
<span id="lg-check-icon" class="hidden text-[#534AB7]" 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"/>
</svg>
</span>
</button>
</div>
<div class="flex justify-center">
<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"
>
Pick
</button>
</div>
</div>
<script>
const itemsInput = document.getElementById('lg-items') as HTMLTextAreaElement;
const pickInput = document.getElementById('lg-pick') as HTMLInputElement;
const replaceCb = document.getElementById('lg-replace') as HTMLInputElement;
const btn = document.getElementById('lg-btn') as HTMLButtonElement;
const copyBtn = document.getElementById('lg-copy-btn') as HTMLButtonElement;
const resultEl = document.getElementById('lg-result') as HTMLDivElement;
const errorEl = document.getElementById('lg-error') as HTMLParagraphElement;
const copyLabel = document.getElementById('lg-copy-label') as HTMLSpanElement;
const copyIcon = document.getElementById('lg-copy-icon') as HTMLSpanElement;
const checkIcon = document.getElementById('lg-check-icon') as HTMLSpanElement;
let lastPicked: string[] = [];
let copyRevertTimer: ReturnType<typeof setTimeout> | null = null;
function showError(msg: string) {
errorEl.textContent = msg;
errorEl.classList.remove('hidden');
}
function clearError() {
errorEl.textContent = '';
errorEl.classList.add('hidden');
}
function pick() {
const items = itemsInput.value.split('\n').map((s) => s.trim()).filter(Boolean);
const count = parseInt(pickInput.value, 10);
const withReplacement = replaceCb.checked;
if (items.length === 0) { showError('Add at least one item to the list.'); return; }
if (!Number.isInteger(count) || count < 1) { showError('Pick at least 1 item.'); return; }
if (!withReplacement && count > items.length) {
showError(`Cannot pick ${count} unique items from a list of ${items.length}.`);
return;
}
clearError();
if (withReplacement) {
lastPicked = Array.from({ length: count }, () => items[Math.floor(Math.random() * items.length)]);
} else {
const pool = [...items];
for (let i = 0; i < count; i++) {
const j = i + Math.floor(Math.random() * (pool.length - i));
[pool[i], pool[j]] = [pool[j], pool[i]];
}
lastPicked = pool.slice(0, count);
}
resultEl.innerHTML = '';
lastPicked.forEach((item) => {
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';
resultEl.appendChild(chip);
});
copyBtn.classList.remove('invisible');
copyLabel.textContent = 'Copy';
}
async function copyPicked() {
if (!lastPicked.length) return;
await navigator.clipboard.writeText(lastPicked.join(', '));
copyIcon.classList.add('hidden');
checkIcon.classList.remove('hidden');
copyLabel.textContent = 'Copied';
if (copyRevertTimer) clearTimeout(copyRevertTimer);
copyRevertTimer = setTimeout(() => {
checkIcon.classList.add('hidden');
copyIcon.classList.remove('hidden');
copyLabel.textContent = 'Copy';
}, 1500);
}
btn.addEventListener('click', pick);
copyBtn.addEventListener('click', copyPicked);
itemsInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && e.ctrlKey) pick();
});
</script>
@@ -0,0 +1,185 @@
---
// Interactive UUID / token generator — runs on the client via inline script
---
<div id="uuid-generator" class="mt-8">
<div class="flex flex-col gap-4">
<div>
<label class="block text-sm font-medium text-zinc-400 mb-2">Type</label>
<div class="flex flex-wrap gap-2" role="radiogroup" aria-label="Token type">
{[
{ value: 'uuid', label: 'UUID v4' },
{ value: 'hex', label: 'Hex' },
{ value: 'base64', label: 'Base64' },
].map(({ value, label }) => (
<label class="cursor-pointer">
<input type="radio" name="ug-type" value={value} 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">
{label}
</span>
</label>
))}
</div>
</div>
<div class="flex flex-col sm:flex-row gap-4">
<div id="ug-length-wrap" class="flex-1 hidden">
<label for="ug-length" class="block text-sm font-medium text-zinc-400 mb-1">Length (bytes)</label>
<input
id="ug-length"
type="number"
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"
aria-label="Token length in bytes"
/>
</div>
<div class="flex-1">
<label for="ug-count" class="block text-sm font-medium text-zinc-400 mb-1">Count</label>
<input
id="ug-count"
type="number"
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"
aria-label="Number of tokens to generate"
/>
</div>
</div>
</div>
<div class="my-8 min-h-16 flex flex-col gap-2" aria-live="polite" aria-label="Generated tokens">
<div id="ug-result" class="flex flex-col gap-2"></div>
<button
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"
>
<span id="ug-copy-label">Copy all</span>
<span id="ug-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"/>
<path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>
</svg>
</span>
<span id="ug-check-icon" class="hidden text-[#534AB7]" 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"/>
</svg>
</span>
</button>
</div>
<div class="flex justify-center">
<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"
>
Generate
</button>
</div>
</div>
<script>
const lengthWrap = document.getElementById('ug-length-wrap') as HTMLDivElement;
const lengthInput = document.getElementById('ug-length') as HTMLInputElement;
const countInput = document.getElementById('ug-count') as HTMLInputElement;
const btn = document.getElementById('ug-btn') as HTMLButtonElement;
const copyBtn = document.getElementById('ug-copy-btn') as HTMLButtonElement;
const resultEl = document.getElementById('ug-result') as HTMLDivElement;
const copyLabel = document.getElementById('ug-copy-label') as HTMLSpanElement;
const copyIcon = document.getElementById('ug-copy-icon') as HTMLSpanElement;
const checkIcon = document.getElementById('ug-check-icon') as HTMLSpanElement;
let lastTokens: string[] = [];
let copyRevertTimer: ReturnType<typeof setTimeout> | null = null;
function getType(): string {
return (document.querySelector('input[name="ug-type"]:checked') as HTMLInputElement)?.value ?? 'uuid';
}
document.querySelectorAll('input[name="ug-type"]').forEach((radio) => {
radio.addEventListener('change', () => {
const type = getType();
lengthWrap.classList.toggle('hidden', type === 'uuid');
});
});
function uuidV4(): string {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
const r = (crypto.getRandomValues(new Uint8Array(1))[0] & 0xf);
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
});
}
function randomBytes(n: number): Uint8Array {
const buf = new Uint8Array(n);
crypto.getRandomValues(buf);
return buf;
}
function toHex(bytes: Uint8Array): string {
return Array.from(bytes).map((b) => b.toString(16).padStart(2, '0')).join('');
}
function toBase64(bytes: Uint8Array): string {
return btoa(String.fromCharCode(...bytes))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
function generate() {
const type = getType();
const count = parseInt(countInput.value, 10);
const length = parseInt(lengthInput.value, 10);
lastTokens = Array.from({ length: count }, () => {
if (type === 'uuid') return uuidV4();
if (type === 'hex') return toHex(randomBytes(length));
return toBase64(randomBytes(length));
});
resultEl.innerHTML = '';
lastTokens.forEach((token) => {
const row = document.createElement('div');
row.className =
'flex items-center justify-between gap-3 bg-zinc-900 border border-zinc-700 rounded-lg px-3 py-2';
const code = document.createElement('code');
code.textContent = token;
code.className = 'text-sm font-mono text-zinc-100 break-all';
row.appendChild(code);
resultEl.appendChild(row);
});
copyBtn.classList.remove('invisible');
copyLabel.textContent = 'Copy all';
}
async function copyAll() {
if (!lastTokens.length) return;
await navigator.clipboard.writeText(lastTokens.join('\n'));
copyIcon.classList.add('hidden');
checkIcon.classList.remove('hidden');
copyLabel.textContent = 'Copied';
if (copyRevertTimer) clearTimeout(copyRevertTimer);
copyRevertTimer = setTimeout(() => {
checkIcon.classList.add('hidden');
copyIcon.classList.remove('hidden');
copyLabel.textContent = 'Copy all';
}, 1500);
}
btn.addEventListener('click', generate);
copyBtn.addEventListener('click', copyAll);
[countInput, lengthInput].forEach((el) => {
el.addEventListener('keydown', (e) => { if (e.key === 'Enter') generate(); });
});
</script>
+21
View File
@@ -49,4 +49,25 @@ export const generators: Generator[] = [
icon: 'square-stack',
status: 'live',
},
{
slug: 'coin',
title: 'Coin Flip',
description: 'Flip one or more coins and see heads or tails.',
icon: 'circle-dollar-sign',
status: 'live',
},
{
slug: 'list',
title: 'List Picker',
description: 'Paste a list of items and pick random winners.',
icon: 'list',
status: 'live',
},
{
slug: 'uuid',
title: 'UUID / Token',
description: 'Generate UUIDs, hex tokens, or random base64 strings.',
icon: 'fingerprint',
status: 'live',
},
];
+40
View File
@@ -0,0 +1,40 @@
---
import BaseLayout from '../../layouts/BaseLayout.astro';
import CoinGenerator from '../../components/generators/CoinGenerator.astro';
import AdBanner from '../../components/AdBanner.astro';
import { generators } from '../../data/generators';
const generator = generators.find((g) => g.slug === 'coin')!;
---
<BaseLayout
title={`${generator.title} — Randify`}
description={generator.description}
>
<div class="max-w-2xl mx-auto px-4 py-12 sm:py-20">
<nav aria-label="Breadcrumb" class="mb-10">
<a
href="/"
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"
aria-label="Back to all generators"
>
<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" aria-hidden="true">
<path d="m15 18-6-6 6-6"/>
</svg>
All generators
</a>
</nav>
<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]" aria-hidden="true"></span>
<span class="text-sm font-medium text-zinc-400 uppercase tracking-widest">randify</span>
</div>
<h1 class="text-3xl sm:text-4xl font-bold text-zinc-100">{generator.title}</h1>
<p class="mt-2 text-base text-zinc-400">{generator.description}</p>
</header>
<main><CoinGenerator /></main>
<div class="mt-12 grid grid-cols-3 gap-3">
<AdBanner size="tile" /><AdBanner size="tile" /><AdBanner size="tile" />
</div>
</div>
</BaseLayout>
+40
View File
@@ -0,0 +1,40 @@
---
import BaseLayout from '../../layouts/BaseLayout.astro';
import ListGenerator from '../../components/generators/ListGenerator.astro';
import AdBanner from '../../components/AdBanner.astro';
import { generators } from '../../data/generators';
const generator = generators.find((g) => g.slug === 'list')!;
---
<BaseLayout
title={`${generator.title} — Randify`}
description={generator.description}
>
<div class="max-w-2xl mx-auto px-4 py-12 sm:py-20">
<nav aria-label="Breadcrumb" class="mb-10">
<a
href="/"
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"
aria-label="Back to all generators"
>
<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" aria-hidden="true">
<path d="m15 18-6-6 6-6"/>
</svg>
All generators
</a>
</nav>
<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]" aria-hidden="true"></span>
<span class="text-sm font-medium text-zinc-400 uppercase tracking-widest">randify</span>
</div>
<h1 class="text-3xl sm:text-4xl font-bold text-zinc-100">{generator.title}</h1>
<p class="mt-2 text-base text-zinc-400">{generator.description}</p>
</header>
<main><ListGenerator /></main>
<div class="mt-12 grid grid-cols-3 gap-3">
<AdBanner size="tile" /><AdBanner size="tile" /><AdBanner size="tile" />
</div>
</div>
</BaseLayout>
+40
View File
@@ -0,0 +1,40 @@
---
import BaseLayout from '../../layouts/BaseLayout.astro';
import UuidGenerator from '../../components/generators/UuidGenerator.astro';
import AdBanner from '../../components/AdBanner.astro';
import { generators } from '../../data/generators';
const generator = generators.find((g) => g.slug === 'uuid')!;
---
<BaseLayout
title={`${generator.title} — Randify`}
description={generator.description}
>
<div class="max-w-2xl mx-auto px-4 py-12 sm:py-20">
<nav aria-label="Breadcrumb" class="mb-10">
<a
href="/"
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"
aria-label="Back to all generators"
>
<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" aria-hidden="true">
<path d="m15 18-6-6 6-6"/>
</svg>
All generators
</a>
</nav>
<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]" aria-hidden="true"></span>
<span class="text-sm font-medium text-zinc-400 uppercase tracking-widest">randify</span>
</div>
<h1 class="text-3xl sm:text-4xl font-bold text-zinc-100">{generator.title}</h1>
<p class="mt-2 text-base text-zinc-400">{generator.description}</p>
</header>
<main><UuidGenerator /></main>
<div class="mt-12 grid grid-cols-3 gap-3">
<AdBanner size="tile" /><AdBanner size="tile" /><AdBanner size="tile" />
</div>
</div>
</BaseLayout>