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
This commit is contained in:
+54
-2
@@ -8,7 +8,7 @@
|
||||
"plan_name": "critical-refactor",
|
||||
"status": "active",
|
||||
"started_at": "2026-05-13T16:25:19.266Z",
|
||||
"updated_at": "2026-05-13T17:16:10.363Z",
|
||||
"updated_at": "2026-05-13T17:25:32.173Z",
|
||||
"session_ids": [
|
||||
"ses_1de6ede59ffexUwt5C0sddEWwR"
|
||||
],
|
||||
@@ -68,6 +68,32 @@
|
||||
"status": "completed",
|
||||
"ended_at": "2026-05-13T17:16:10.363Z",
|
||||
"elapsed_ms": 1586504
|
||||
},
|
||||
"todo:13": {
|
||||
"task_key": "todo:13",
|
||||
"task_label": "13",
|
||||
"task_title": "Refactor GradientGenerator.astro",
|
||||
"session_id": "ses_1dda85f21ffeEGeOWrbqmWAk02",
|
||||
"agent": "Sisyphus-Junior",
|
||||
"category": "unspecified-high",
|
||||
"updated_at": "2026-05-13T17:22:16.123Z",
|
||||
"started_at": "2026-05-13T17:19:45.020Z",
|
||||
"status": "completed",
|
||||
"ended_at": "2026-05-13T17:22:16.123Z",
|
||||
"elapsed_ms": 151103
|
||||
},
|
||||
"todo:17": {
|
||||
"task_key": "todo:17",
|
||||
"task_label": "17",
|
||||
"task_title": "Refactor LoremGenerator.astro",
|
||||
"session_id": "ses_1dda37bf2ffexATCZ3W4Z6EDu1",
|
||||
"agent": "Sisyphus-Junior",
|
||||
"category": "unspecified-high",
|
||||
"updated_at": "2026-05-13T17:25:32.173Z",
|
||||
"started_at": "2026-05-13T17:25:11.269Z",
|
||||
"status": "completed",
|
||||
"ended_at": "2026-05-13T17:25:32.173Z",
|
||||
"elapsed_ms": 20904
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,7 +101,7 @@
|
||||
"active_plan": "/home/emil/Desktop/Coding/AI/Randify.pro/.sisyphus/plans/critical-refactor.md",
|
||||
"started_at": "2026-05-13T16:25:19.266Z",
|
||||
"status": "active",
|
||||
"updated_at": "2026-05-13T17:16:10.363Z",
|
||||
"updated_at": "2026-05-13T17:25:32.173Z",
|
||||
"session_ids": [
|
||||
"ses_1de6ede59ffexUwt5C0sddEWwR"
|
||||
],
|
||||
@@ -135,6 +161,32 @@
|
||||
"status": "completed",
|
||||
"ended_at": "2026-05-13T17:16:10.363Z",
|
||||
"elapsed_ms": 1586504
|
||||
},
|
||||
"todo:13": {
|
||||
"task_key": "todo:13",
|
||||
"task_label": "13",
|
||||
"task_title": "Refactor GradientGenerator.astro",
|
||||
"session_id": "ses_1dda85f21ffeEGeOWrbqmWAk02",
|
||||
"agent": "Sisyphus-Junior",
|
||||
"category": "unspecified-high",
|
||||
"updated_at": "2026-05-13T17:22:16.123Z",
|
||||
"started_at": "2026-05-13T17:19:45.020Z",
|
||||
"status": "completed",
|
||||
"ended_at": "2026-05-13T17:22:16.123Z",
|
||||
"elapsed_ms": 151103
|
||||
},
|
||||
"todo:17": {
|
||||
"task_key": "todo:17",
|
||||
"task_label": "17",
|
||||
"task_title": "Refactor LoremGenerator.astro",
|
||||
"session_id": "ses_1dda37bf2ffexATCZ3W4Z6EDu1",
|
||||
"agent": "Sisyphus-Junior",
|
||||
"category": "unspecified-high",
|
||||
"updated_at": "2026-05-13T17:25:32.173Z",
|
||||
"started_at": "2026-05-13T17:25:11.269Z",
|
||||
"status": "completed",
|
||||
"ended_at": "2026-05-13T17:25:32.173Z",
|
||||
"elapsed_ms": 20904
|
||||
}
|
||||
},
|
||||
"agent": "atlas"
|
||||
|
||||
@@ -519,7 +519,7 @@ Max Concurrent: 8 (Wave 3)
|
||||
- Message: `refactor(generators): migrate batch 1 to crypto random (Card, Coin, Color, Country, Date, Emoji, FontPair)`
|
||||
- Files: `src/components/generators/{Card,Coin,Color,Country,Date,Emoji,FontPair}Generator.astro`
|
||||
|
||||
- [ ] 13. Refactor GradientGenerator.astro
|
||||
- [x] 13. Refactor GradientGenerator.astro
|
||||
**What to do**: Same pattern as Task 6.
|
||||
**Recommended Agent Profile**: `unspecified-high`
|
||||
**Parallelization**: YES (with Tasks 14-19, 20-26, 27-32)
|
||||
@@ -527,7 +527,7 @@ Max Concurrent: 8 (Wave 3)
|
||||
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
||||
**Commit**: NO
|
||||
|
||||
- [ ] 14. Refactor HashGenerator.astro
|
||||
- [x] 14. Refactor HashGenerator.astro
|
||||
**What to do**: Same pattern as Task 6.
|
||||
**Recommended Agent Profile**: `unspecified-high`
|
||||
**Parallelization**: YES (with Tasks 13, 15-19, 20-26, 27-32)
|
||||
@@ -535,7 +535,7 @@ Max Concurrent: 8 (Wave 3)
|
||||
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
||||
**Commit**: NO
|
||||
|
||||
- [ ] 15. Refactor LetterGenerator.astro
|
||||
- [x] 15. Refactor LetterGenerator.astro
|
||||
**What to do**: Same pattern as Task 6.
|
||||
**Recommended Agent Profile**: `unspecified-high`
|
||||
**Parallelization**: YES (with Tasks 13-14, 16-19, 20-26, 27-32)
|
||||
@@ -543,7 +543,7 @@ Max Concurrent: 8 (Wave 3)
|
||||
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
||||
**Commit**: NO
|
||||
|
||||
- [ ] 16. Refactor ListGenerator.astro
|
||||
- [x] 16. Refactor ListGenerator.astro
|
||||
**What to do**: Same pattern as Task 6.
|
||||
**Recommended Agent Profile**: `unspecified-high`
|
||||
**Parallelization**: YES (with Tasks 13-15, 17-19, 20-26, 27-32)
|
||||
@@ -551,7 +551,7 @@ Max Concurrent: 8 (Wave 3)
|
||||
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
||||
**Commit**: NO
|
||||
|
||||
- [ ] 17. Refactor LoremGenerator.astro
|
||||
- [x] 17. Refactor LoremGenerator.astro
|
||||
**What to do**: Same pattern as Task 6.
|
||||
**Recommended Agent Profile**: `unspecified-high`
|
||||
**Parallelization**: YES (with Tasks 13-16, 18-19, 20-26, 27-32)
|
||||
@@ -559,7 +559,7 @@ Max Concurrent: 8 (Wave 3)
|
||||
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
||||
**Commit**: NO
|
||||
|
||||
- [ ] 18. Refactor LotteryGenerator.astro
|
||||
- [x] 18. Refactor LotteryGenerator.astro
|
||||
**What to do**: Same pattern as Task 6.
|
||||
**Recommended Agent Profile**: `unspecified-high`
|
||||
**Parallelization**: YES (with Tasks 13-17, 19, 20-26, 27-32)
|
||||
@@ -567,7 +567,7 @@ Max Concurrent: 8 (Wave 3)
|
||||
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
||||
**Commit**: NO
|
||||
|
||||
- [ ] 19. Refactor Magic8BallGenerator.astro
|
||||
- [x] 19. Refactor Magic8BallGenerator.astro
|
||||
**What to do**: Same pattern as Task 6.
|
||||
**Recommended Agent Profile**: `unspecified-high`
|
||||
**Parallelization**: YES (with Tasks 13-18, 20-26, 27-32)
|
||||
|
||||
@@ -113,7 +113,11 @@ const i18n = {
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const isRu = document.documentElement.lang === "ru";
|
||||
import { randomInt } from "@/lib/client/random";
|
||||
import { getClientLang, getClientT } from "@/lib/client/i18n";
|
||||
|
||||
const lang = getClientLang();
|
||||
const T = getClientT();
|
||||
|
||||
const typeSelect = document.getElementById("gg-type") as HTMLSelectElement;
|
||||
const stopsSelect = document.getElementById("gg-stops") as HTMLSelectElement;
|
||||
@@ -130,10 +134,6 @@ const i18n = {
|
||||
const copyIconSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" 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>`;
|
||||
const checkIconSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="text-accent"><path d="M20 6 9 17l-5-5"/></svg>`;
|
||||
|
||||
function randomInt(min: number, max: number): number {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
function hslToHex(h: number, s: number, l: number): string {
|
||||
s /= 100;
|
||||
l /= 100;
|
||||
@@ -223,7 +223,7 @@ const i18n = {
|
||||
chip.type = "button";
|
||||
chip.className =
|
||||
"group flex items-center gap-2 px-3 py-2 bg-zinc-950 border border-zinc-800 rounded-xl hover:border-zinc-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-zinc-950 transition-all duration-100 cursor-pointer";
|
||||
chip.setAttribute("aria-label", isRu ? `Скопировать ${hex}` : `Copy ${hex}`);
|
||||
chip.setAttribute("aria-label", `${T.copy} ${hex}`);
|
||||
|
||||
const swatch = document.createElement("span");
|
||||
swatch.className = "w-5 h-5 rounded-full border border-zinc-700 shrink-0";
|
||||
@@ -276,12 +276,12 @@ const i18n = {
|
||||
|
||||
copyCssIcon.classList.add("hidden");
|
||||
checkCssIcon.classList.remove("hidden");
|
||||
copyCssLabel.textContent = isRu ? "Скопировано!" : "Copied!";
|
||||
copyCssLabel.textContent = T.copied;
|
||||
if (cssCopyTimer) clearTimeout(cssCopyTimer);
|
||||
cssCopyTimer = setTimeout(() => {
|
||||
checkCssIcon.classList.add("hidden");
|
||||
copyCssIcon.classList.remove("hidden");
|
||||
copyCssLabel.textContent = isRu ? "Копировать" : "Copy";
|
||||
copyCssLabel.textContent = T.copy;
|
||||
}, 1500);
|
||||
});
|
||||
|
||||
|
||||
@@ -213,6 +213,12 @@ const T = useT(isRu ? "ru" : "en");
|
||||
</div>
|
||||
|
||||
<script>
|
||||
import { randomInt } from "@/lib/client/random";
|
||||
import { getClientLang, getClientT } from "@/lib/client/i18n";
|
||||
|
||||
const isRu = getClientLang() === "ru";
|
||||
const T = getClientT();
|
||||
|
||||
// ===================== MD5 Implementation =====================
|
||||
function md5(str: string): string {
|
||||
const utf8 = new TextEncoder().encode(str);
|
||||
@@ -300,33 +306,13 @@ const T = useT(isRu ? "ru" : "en");
|
||||
function generateRandomString(length: number): string {
|
||||
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
let result = "";
|
||||
const randomValues = new Uint32Array(length);
|
||||
crypto.getRandomValues(randomValues);
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += chars[randomValues[i] % chars.length];
|
||||
result += chars[randomInt(0, chars.length - 1)];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ===================== DOM Elements =====================
|
||||
const isRu = document.documentElement.lang === "ru";
|
||||
const TEXTS = {
|
||||
generate: isRu ? "Сгенерировать" : "Generate",
|
||||
generated: isRu ? "Сгенерировано" : "Generated",
|
||||
copy: isRu ? "Копировать" : "Copy",
|
||||
copied: isRu ? "Скопировано" : "Copied",
|
||||
match: isRu ? "Совпадение" : "Match",
|
||||
noMatch: isRu ? "Нет совпадения" : "No match",
|
||||
enterText: isRu ? "Ввести текст" : "Enter text",
|
||||
generateRandom: isRu ? "Случайная строка" : "Generate random",
|
||||
uppercase: isRu ? "ВЕРХНИЙ РЕГИСТР" : "UPPERCASE",
|
||||
lowercase: isRu ? "нижний регистр" : "lowercase",
|
||||
compareHint: isRu ? "Вставьте хеш, чтобы проверить совпадение с результатами." : "Paste a hash to check if it matches any result.",
|
||||
refresh: isRu ? "Обновить" : "Refresh",
|
||||
inputMode: isRu ? "Режим ввода" : "Input mode",
|
||||
stringLength: isRu ? "Длина строки" : "String length",
|
||||
};
|
||||
|
||||
const modeTextBtn = document.getElementById("hash-mode-text") as HTMLButtonElement;
|
||||
const modeRandomBtn = document.getElementById("hash-mode-random") as HTMLButtonElement;
|
||||
const textSection = document.getElementById("hash-text-section") as HTMLDivElement;
|
||||
@@ -411,19 +397,19 @@ const T = useT(isRu ? "ru" : "en");
|
||||
return {
|
||||
matches,
|
||||
display: matches
|
||||
? `<span class="inline-flex items-center gap-1 text-green-400 text-xs font-medium"><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> ${TEXTS.match}</span>`
|
||||
: `<span class="inline-flex items-center gap-1 text-red-400 text-xs font-medium"><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="M18 6 6 18"/><path d="m6 6 12 12"/></svg> ${TEXTS.noMatch}</span>`,
|
||||
? `<span class="inline-flex items-center gap-1 text-green-400 text-xs font-medium"><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> ${isRu ? "Совпадение" : "Match"}</span>`
|
||||
: `<span class="inline-flex items-center gap-1 text-red-400 text-xs font-medium"><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="M18 6 6 18"/><path d="m6 6 12 12"/></svg> ${isRu ? "Нет совпадения" : "No match"}</span>`,
|
||||
};
|
||||
}
|
||||
|
||||
async function copyToClipboard(text: string, iconEl: HTMLElement, checkEl: HTMLElement, labelEl: HTMLElement) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
labelEl.textContent = TEXTS.copied;
|
||||
labelEl.textContent = T.copied;
|
||||
iconEl.classList.add("hidden");
|
||||
checkEl.classList.remove("hidden");
|
||||
setTimeout(() => {
|
||||
labelEl.textContent = TEXTS.copy;
|
||||
labelEl.textContent = T.copy;
|
||||
iconEl.classList.remove("hidden");
|
||||
checkEl.classList.add("hidden");
|
||||
}, 1500);
|
||||
@@ -488,9 +474,9 @@ const T = useT(isRu ? "ru" : "en");
|
||||
<button
|
||||
type="button"
|
||||
class="hash-copy-btn inline-flex items-center gap-1.5 px-3 py-1.5 bg-zinc-900/80 border border-zinc-700/50 rounded-lg text-xs font-medium text-zinc-400 hover:text-zinc-100 hover:bg-zinc-800 transition-colors focus:outline-none focus-visible:ring-2 ${colors.ring} focus-visible:ring-offset-2 focus-visible:ring-offset-zinc-950 flex-shrink-0 cursor-pointer"
|
||||
aria-label="${TEXTS.copy} ${algo.toUpperCase()}"
|
||||
aria-label="${T.copy} ${algo.toUpperCase()}"
|
||||
>
|
||||
<span class="hash-copy-label">${TEXTS.copy}</span>
|
||||
<span class="hash-copy-label">${T.copy}</span>
|
||||
<span class="hash-copy-icon" aria-hidden="true">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" 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>
|
||||
@@ -572,7 +558,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
|
||||
caseToggleBtn.addEventListener("click", () => {
|
||||
isUppercase = !isUppercase;
|
||||
caseLabel.textContent = isUppercase ? TEXTS.uppercase : TEXTS.lowercase;
|
||||
caseLabel.textContent = isUppercase ? (isRu ? "ВЕРХНИЙ РЕГИСТР" : "UPPERCASE") : (isRu ? "нижний регистр" : "lowercase");
|
||||
updateCase();
|
||||
});
|
||||
|
||||
|
||||
@@ -79,8 +79,12 @@ const T = useT(isRu ? "ru" : "en");
|
||||
<script>
|
||||
import { CopyFeedback } from "@/lib/client/clipboard";
|
||||
import { popElement } from "@/lib/client/animations";
|
||||
import { randomInt } from "@/lib/client/random";
|
||||
import { getClientLang, getClientT } from "@/lib/client/i18n";
|
||||
|
||||
const isRu = document.documentElement.lang === "ru";
|
||||
const lang = getClientLang();
|
||||
const isRu = lang === "ru";
|
||||
const T = getClientT();
|
||||
const ALPHABET = isRu
|
||||
? "АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ"
|
||||
: "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
@@ -97,7 +101,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
const clipboard = new CopyFeedback(copyIcon, checkIcon);
|
||||
|
||||
function generate() {
|
||||
const idx = Math.floor(Math.random() * ALPHABET.length);
|
||||
const idx = randomInt(0, ALPHABET.length - 1);
|
||||
const letter = ALPHABET[idx];
|
||||
resultEl.textContent = letter;
|
||||
copyBtn.classList.remove("invisible");
|
||||
|
||||
@@ -135,16 +135,15 @@ David"
|
||||
<script>
|
||||
import { CopyFeedback } from "@/lib/client/clipboard";
|
||||
import { createErrorDisplay } from "@/lib/client/validation";
|
||||
import { randomInt, shuffleArray } from "@/lib/client/random";
|
||||
import { getClientLang, getClientT } from "@/lib/client/i18n";
|
||||
|
||||
const isRu = document.documentElement.lang === "ru";
|
||||
const COPY_LABEL = isRu ? "Копировать" : "Copy";
|
||||
const COPIED_LABEL = isRu ? "Скопировано" : "Copied";
|
||||
const ERR_ADD_ONE = isRu
|
||||
? "Добавьте хотя бы один элемент в список."
|
||||
: "Add at least one item to the list.";
|
||||
const ERR_PICK_ONE = isRu
|
||||
? "Выберите хотя бы 1 элемент."
|
||||
: "Pick at least 1 item.";
|
||||
const isRu = getClientLang() === "ru";
|
||||
const T = getClientT();
|
||||
const COPY_LABEL = T.copy;
|
||||
const COPIED_LABEL = T.copied;
|
||||
const ERR_ADD_ONE = T.errAddAtLeast1;
|
||||
const ERR_PICK_ONE = T.errPickAtLeast1;
|
||||
|
||||
const itemsInput = document.getElementById(
|
||||
"lst-items",
|
||||
@@ -198,15 +197,10 @@ David"
|
||||
if (withReplacement) {
|
||||
lastPicked = Array.from(
|
||||
{ length: count },
|
||||
() => items[Math.floor(Math.random() * items.length)],
|
||||
() => items[randomInt(0, items.length - 1)],
|
||||
);
|
||||
} 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);
|
||||
lastPicked = shuffleArray(items).slice(0, count);
|
||||
}
|
||||
|
||||
resultEl.innerHTML = "";
|
||||
|
||||
@@ -138,6 +138,8 @@ const startLabel = isRu
|
||||
</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",
|
||||
@@ -159,26 +161,13 @@ const startLabel = isRu
|
||||
const copyIcon = document.getElementById("lg-copy-icon") as SVGElement;
|
||||
const checkIcon = document.getElementById("lg-check-icon") as SVGElement;
|
||||
|
||||
function randInt(min: number, max: number) {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
function shuffle<T>(arr: T[]): T[] {
|
||||
const a = arr.slice();
|
||||
for (let i = a.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[a[i], a[j]] = [a[j], a[i]];
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
function capitalize(word: string) {
|
||||
return word.charAt(0).toUpperCase() + word.slice(1);
|
||||
}
|
||||
|
||||
function generateSentence(): string {
|
||||
const length = randInt(8, 18);
|
||||
const words = shuffle(WORDS).slice(0, length);
|
||||
const length = randomInt(8, 18);
|
||||
const words = shuffleArray(WORDS).slice(0, length);
|
||||
words[0] = capitalize(words[0]);
|
||||
const sentence = words.join(" ") + ".";
|
||||
return sentence;
|
||||
@@ -218,7 +207,7 @@ const startLabel = isRu
|
||||
const paragraphs: string[] = [];
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const sentenceCount = randInt(minSentences, maxSentences);
|
||||
const sentenceCount = randomInt(minSentences, maxSentences);
|
||||
paragraphs.push(generateParagraph(sentenceCount, i === 0, startClassic));
|
||||
}
|
||||
|
||||
@@ -226,8 +215,8 @@ const startLabel = isRu
|
||||
resultWrap.style.opacity = "1";
|
||||
}
|
||||
|
||||
const isRu = document.documentElement.lang === "ru";
|
||||
const copyLabel = copyBtn.getAttribute("aria-label") || (isRu ? "Копировать" : "Copy");
|
||||
const T = getClientT();
|
||||
const copyLabel = copyBtn.getAttribute("aria-label") || T.copy;
|
||||
let copyTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
copyBtn.addEventListener("click", async () => {
|
||||
@@ -236,7 +225,7 @@ const startLabel = isRu
|
||||
await navigator.clipboard.writeText(text);
|
||||
copyIcon.classList.add("hidden");
|
||||
checkIcon.classList.remove("hidden");
|
||||
copyBtn.setAttribute("aria-label", isRu ? "Скопировано" : "Copied");
|
||||
copyBtn.setAttribute("aria-label", T.copied);
|
||||
if (copyTimer) clearTimeout(copyTimer);
|
||||
copyTimer = setTimeout(() => {
|
||||
checkIcon.classList.add("hidden");
|
||||
|
||||
@@ -111,19 +111,17 @@ const T = useT(isRu ? "ru" : "en");
|
||||
<script>
|
||||
import { CopyFeedback } from "@/lib/client/clipboard";
|
||||
import { createErrorDisplay, isInteger } from "@/lib/client/validation";
|
||||
import { shuffleArray } from "@/lib/client/random";
|
||||
import { getClientLang, getClientT } from "@/lib/client/i18n";
|
||||
|
||||
const isRu = document.documentElement.lang === "ru";
|
||||
const ERR_INTEGERS = isRu
|
||||
? "Все значения должны быть целыми числами."
|
||||
: "All values must be whole numbers.";
|
||||
const ERR_FROM_TO = isRu
|
||||
? "«От» должно быть меньше «До»."
|
||||
: '"From" must be less than "To".';
|
||||
const ERR_PICK_MIN = isRu
|
||||
? "«Выбрать» должно быть не менее 1."
|
||||
: '"Pick" must be at least 1.';
|
||||
const COPY_LABEL = isRu ? "Копировать" : "Copy";
|
||||
const COPIED_LABEL = isRu ? "Скопировано" : "Copied";
|
||||
const lang = getClientLang();
|
||||
const T = getClientT();
|
||||
const isRu = lang === "ru";
|
||||
const ERR_INTEGERS = T.errAllIntegers;
|
||||
const ERR_FROM_TO = T.errFromLessThanTo;
|
||||
const ERR_PICK_MIN = T.errPickAtLeastOne;
|
||||
const COPY_LABEL = T.copy;
|
||||
const COPIED_LABEL = T.copied;
|
||||
|
||||
const fromInput = document.getElementById("lg-from") as HTMLInputElement;
|
||||
const toInput = document.getElementById("lg-to") as HTMLInputElement;
|
||||
@@ -170,11 +168,7 @@ const T = useT(isRu ? "ru" : "en");
|
||||
}
|
||||
errors.clear();
|
||||
const nums = Array.from({ length: pool }, (_, i) => min + i);
|
||||
for (let i = 0; i < pick; i++) {
|
||||
const j = i + Math.floor(Math.random() * (pool - i));
|
||||
[nums[i], nums[j]] = [nums[j], nums[i]];
|
||||
}
|
||||
const picked = nums.slice(0, pick).sort((a, b) => a - b);
|
||||
const picked = shuffleArray(nums).slice(0, pick).sort((a, b) => a - b);
|
||||
resultEl.innerHTML = "";
|
||||
picked.forEach((n) => {
|
||||
const ball = document.createElement("span");
|
||||
|
||||
@@ -214,8 +214,12 @@ const copiedLabel = isRu ? "Скопировано" : "Copied";
|
||||
|
||||
<script>
|
||||
import { CopyFeedback } from "@/lib/client/clipboard";
|
||||
import { randomInt } from "@/lib/client/random";
|
||||
import { getClientLang, getClientT } from "@/lib/client/i18n";
|
||||
|
||||
const isRu = document.documentElement.lang === "ru";
|
||||
const lang = getClientLang();
|
||||
const T = getClientT();
|
||||
const isRu = lang === "ru";
|
||||
|
||||
// ─── Answer pools ───────────────────────────────────────────────────────────
|
||||
|
||||
@@ -322,7 +326,7 @@ const copiedLabel = isRu ? "Скопировано" : "Copied";
|
||||
// ─── Helpers ────────────────────────────────────────────────────────────────
|
||||
|
||||
function getRandomAnswer(): Answer {
|
||||
return ANSWERS[Math.floor(Math.random() * ANSWERS.length)];
|
||||
return ANSWERS[randomInt(0, ANSWERS.length - 1)];
|
||||
}
|
||||
|
||||
function loadHistory(): { question: string; answer: string; type: AnswerType; time: number }[] {
|
||||
@@ -430,11 +434,11 @@ const copiedLabel = isRu ? "Скопировано" : "Copied";
|
||||
if (!currentAnswer) return;
|
||||
await navigator.clipboard.writeText(currentAnswer.text);
|
||||
clipboard.showCopied();
|
||||
copyLabel.textContent = isRu ? "Скопировано" : "Copied";
|
||||
copyLabel.textContent = T.copied;
|
||||
copiedLabel.style.opacity = "1";
|
||||
setTimeout(() => {
|
||||
copiedLabel.style.opacity = "0";
|
||||
copyLabel.textContent = isRu ? "Копировать" : "Copy";
|
||||
copyLabel.textContent = T.copy;
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user