Files
Randify.pro/src/components/generators/LoremGenerator.astro
T
emil 49269f209d Wave 4: refactor generators batch 2 to crypto random
- refactor(generators): migrate Gradient, Hash, Letter, List to crypto random
- refactor(generators): migrate Lorem, Lottery, Magic8Ball to crypto random
2026-05-13 20:25:39 +03:00

242 lines
8.6 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
import { useT } from "../../i18n/translations";
const isRu = Astro.url.pathname.startsWith("/ru");
const T = useT(isRu ? "ru" : "en");
const lengthOptions = isRu
? [
{ value: "short", label: "Короткие" },
{ value: "medium", label: "Средние" },
{ value: "long", label: "Длинные" },
]
: [
{ value: "short", label: "Short" },
{ value: "medium", label: "Medium" },
{ value: "long", label: "Long" },
];
const startLabel = isRu
? "Начать с Lorem ipsum dolor sit amet"
: "Start with Lorem ipsum dolor sit amet";
---
<div id="lorem-generator" class="mt-8">
<!-- Controls -->
<div class="my-6 rounded-2xl bg-zinc-900/80 border border-zinc-800/80 p-8">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-6 mb-6">
<!-- Paragraph count -->
<div>
<label for="lg-count" class="block text-sm font-medium text-zinc-300 mb-2">
{isRu ? "Количество абзацев" : "Paragraphs"}
</label>
<input
id="lg-count"
type="number"
min="1"
max="20"
value="3"
class="w-full px-4 py-2.5 bg-zinc-950 border border-zinc-800 rounded-xl text-zinc-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-zinc-950"
/>
</div>
<!-- Length selector -->
<div>
<label for="lg-length" class="block text-sm font-medium text-zinc-300 mb-2">
{isRu ? "Длина абзаца" : "Paragraph length"}
</label>
<select
id="lg-length"
class="w-full px-4 py-2.5 bg-zinc-950 border border-zinc-800 rounded-xl text-zinc-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-zinc-950 appearance-none"
>
{lengthOptions.map((opt) => (
<option value={opt.value}>{opt.label}</option>
))}
</select>
</div>
</div>
<!-- Start with Lorem ipsum checkbox -->
<div class="flex items-center gap-3 mb-6">
<input
id="lg-start"
type="checkbox"
checked
class="w-4 h-4 rounded border-zinc-700 bg-zinc-950 text-accent focus:ring-accent focus:ring-offset-zinc-950"
/>
<label for="lg-start" class="text-sm text-zinc-300 select-none cursor-pointer">
{startLabel}
</label>
</div>
<!-- Generate button -->
<div class="flex justify-center">
<button
id="lg-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.generate}
</button>
</div>
</div>
<!-- Result -->
<div
id="lg-result-wrap"
class="rounded-2xl bg-zinc-900/80 border border-zinc-800/80 p-8 opacity-0"
style="transition: opacity 0.25s ease;"
aria-live="polite"
>
<div class="flex items-center justify-between gap-3 mb-4">
<span class="text-xs font-semibold uppercase tracking-widest text-zinc-500">
{isRu ? "Результат" : "Result"}
</span>
<button
id="lg-copy"
type="button"
aria-label={T.copy}
class="group shrink-0 p-1.5 rounded-lg 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="lg-copy-icon"
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
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>
<svg
id="lg-check-icon"
class="hidden text-accent"
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
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>
</button>
</div>
<div
id="lg-output"
class="text-zinc-100 text-sm leading-relaxed whitespace-pre-wrap font-serif"
/>
</div>
</div>
<script>
import { randomInt, shuffleArray } from "@/lib/client/random";
import { getClientLang, getClientT } from "@/lib/client/i18n";
const WORDS = [
"lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing", "elit",
"sed", "do", "eiusmod", "tempor", "incididunt", "ut", "labore", "et", "dolore",
"magna", "aliqua", "enim", "ad", "minim", "veniam", "quis", "nostrud",
"exercitation", "ullamco", "laboris", "nisi", "aliquip", "ex", "ea", "commodo",
"consequat", "duis", "aute", "irure", "in", "reprehenderit", "voluptate", "velit",
"esse", "cillum", "fugiat", "nulla", "pariatur", "excepteur", "sint", "occaecat",
"cupidatat", "non", "proident", "sunt", "culpa", "qui", "officia", "deserunt",
"mollit", "anim", "id", "est", "laborum",
];
const countInput = document.getElementById("lg-count") as HTMLInputElement;
const lengthSelect = document.getElementById("lg-length") as HTMLSelectElement;
const startCheckbox = document.getElementById("lg-start") as HTMLInputElement;
const btn = document.getElementById("lg-btn") as HTMLButtonElement;
const resultWrap = document.getElementById("lg-result-wrap") as HTMLDivElement;
const output = document.getElementById("lg-output") as HTMLDivElement;
const copyBtn = document.getElementById("lg-copy") as HTMLButtonElement;
const copyIcon = document.getElementById("lg-copy-icon") as SVGElement;
const checkIcon = document.getElementById("lg-check-icon") as SVGElement;
function capitalize(word: string) {
return word.charAt(0).toUpperCase() + word.slice(1);
}
function generateSentence(): string {
const length = randomInt(8, 18);
const words = shuffleArray(WORDS).slice(0, length);
words[0] = capitalize(words[0]);
const sentence = words.join(" ") + ".";
return sentence;
}
function generateParagraph(sentenceCount: number, isFirst: boolean, startClassic: boolean): string {
const sentences: string[] = [];
if (isFirst && startClassic) {
const classic = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.";
sentences.push(classic);
for (let i = 1; i < sentenceCount; i++) {
sentences.push(generateSentence());
}
} else {
for (let i = 0; i < sentenceCount; i++) {
sentences.push(generateSentence());
}
}
return sentences.join(" ");
}
function getSentenceRange(): [number, number] {
const val = lengthSelect.value;
if (val === "short") return [2, 4];
if (val === "long") return [6, 10];
return [4, 6];
}
function generate() {
let count = parseInt(countInput.value, 10);
if (isNaN(count) || count < 1) count = 1;
if (count > 20) count = 20;
countInput.value = String(count);
const [minSentences, maxSentences] = getSentenceRange();
const startClassic = startCheckbox.checked;
const paragraphs: string[] = [];
for (let i = 0; i < count; i++) {
const sentenceCount = randomInt(minSentences, maxSentences);
paragraphs.push(generateParagraph(sentenceCount, i === 0, startClassic));
}
output.textContent = paragraphs.join("\n\n");
resultWrap.style.opacity = "1";
}
const T = getClientT();
const copyLabel = copyBtn.getAttribute("aria-label") || T.copy;
let copyTimer: ReturnType<typeof setTimeout> | null = null;
copyBtn.addEventListener("click", async () => {
const text = output.textContent?.trim();
if (!text) return;
await navigator.clipboard.writeText(text);
copyIcon.classList.add("hidden");
checkIcon.classList.remove("hidden");
copyBtn.setAttribute("aria-label", T.copied);
if (copyTimer) clearTimeout(copyTimer);
copyTimer = setTimeout(() => {
checkIcon.classList.add("hidden");
copyIcon.classList.remove("hidden");
copyBtn.setAttribute("aria-label", copyLabel);
}, 1500);
});
btn.addEventListener("click", generate);
// Generate on load
generate();
</script>