Wave 5: refactor generators batch 3 to crypto random

- refactor(generators): migrate Meal, Names, Number, Palette to crypto random
- refactor(generators): migrate Password, Rps, Shuffler to crypto random
This commit is contained in:
emil
2026-05-13 20:34:16 +03:00
parent 49269f209d
commit 8e0485c215
9 changed files with 162 additions and 119 deletions
+54 -2
View File
@@ -8,7 +8,7 @@
"plan_name": "critical-refactor",
"status": "active",
"started_at": "2026-05-13T16:25:19.266Z",
"updated_at": "2026-05-13T17:25:32.173Z",
"updated_at": "2026-05-13T17:34:08.166Z",
"session_ids": [
"ses_1de6ede59ffexUwt5C0sddEWwR"
],
@@ -94,6 +94,32 @@
"status": "completed",
"ended_at": "2026-05-13T17:25:32.173Z",
"elapsed_ms": 20904
},
"todo:20": {
"task_key": "todo:20",
"task_label": "20",
"task_title": "Refactor MealGenerator.astro",
"session_id": "ses_1dda01e38ffeVc6AUwAlt7lQXl",
"agent": "Sisyphus-Junior",
"category": "unspecified-high",
"updated_at": "2026-05-13T17:31:02.031Z",
"started_at": "2026-05-13T17:30:26.005Z",
"status": "completed",
"ended_at": "2026-05-13T17:31:02.031Z",
"elapsed_ms": 36026
},
"todo:24": {
"task_key": "todo:24",
"task_label": "24",
"task_title": "Refactor PasswordGenerator.astro",
"session_id": "ses_1dd9b7bb1ffez3Q0aayg7PZUIF",
"agent": "Sisyphus-Junior",
"category": "unspecified-high",
"updated_at": "2026-05-13T17:34:08.166Z",
"started_at": "2026-05-13T17:32:34.652Z",
"status": "completed",
"ended_at": "2026-05-13T17:34:08.166Z",
"elapsed_ms": 93514
}
}
}
@@ -101,7 +127,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:25:32.173Z",
"updated_at": "2026-05-13T17:34:08.166Z",
"session_ids": [
"ses_1de6ede59ffexUwt5C0sddEWwR"
],
@@ -187,6 +213,32 @@
"status": "completed",
"ended_at": "2026-05-13T17:25:32.173Z",
"elapsed_ms": 20904
},
"todo:20": {
"task_key": "todo:20",
"task_label": "20",
"task_title": "Refactor MealGenerator.astro",
"session_id": "ses_1dda01e38ffeVc6AUwAlt7lQXl",
"agent": "Sisyphus-Junior",
"category": "unspecified-high",
"updated_at": "2026-05-13T17:31:02.031Z",
"started_at": "2026-05-13T17:30:26.005Z",
"status": "completed",
"ended_at": "2026-05-13T17:31:02.031Z",
"elapsed_ms": 36026
},
"todo:24": {
"task_key": "todo:24",
"task_label": "24",
"task_title": "Refactor PasswordGenerator.astro",
"session_id": "ses_1dd9b7bb1ffez3Q0aayg7PZUIF",
"agent": "Sisyphus-Junior",
"category": "unspecified-high",
"updated_at": "2026-05-13T17:34:08.166Z",
"started_at": "2026-05-13T17:32:34.652Z",
"status": "completed",
"ended_at": "2026-05-13T17:34:08.166Z",
"elapsed_ms": 93514
}
},
"agent": "atlas"
+7 -7
View File
@@ -577,7 +577,7 @@ Max Concurrent: 8 (Wave 3)
- Message: `refactor(generators): migrate batch 2 to crypto random (Gradient, Hash, Letter, List, Lorem, Lottery, Magic8Ball)`
- Files: `src/components/generators/{Gradient,Hash,Letter,List,Lorem,Lottery,Magic8Ball}Generator.astro`
- [ ] 20. Refactor MealGenerator.astro
- [x] 20. Refactor MealGenerator.astro
**What to do**: Same pattern as Task 6.
**Recommended Agent Profile**: `unspecified-high`
**Parallelization**: YES (with Tasks 13-19, 21-26, 27-32)
@@ -585,7 +585,7 @@ Max Concurrent: 8 (Wave 3)
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
**Commit**: NO
- [ ] 21. Refactor NamesGenerator.astro
- [x] 21. Refactor NamesGenerator.astro
**What to do**: Same pattern as Task 6.
**Recommended Agent Profile**: `unspecified-high`
**Parallelization**: YES (with Tasks 13-19, 20, 22-26, 27-32)
@@ -593,7 +593,7 @@ Max Concurrent: 8 (Wave 3)
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
**Commit**: NO
- [ ] 22. Refactor NumberGenerator.astro
- [x] 22. Refactor NumberGenerator.astro
**What to do**: Same pattern as Task 6.
**Recommended Agent Profile**: `unspecified-high`
**Parallelization**: YES (with Tasks 13-19, 20-21, 23-26, 27-32)
@@ -601,7 +601,7 @@ Max Concurrent: 8 (Wave 3)
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
**Commit**: NO
- [ ] 23. Refactor PaletteGenerator.astro
- [x] 23. Refactor PaletteGenerator.astro
**What to do**: Same pattern as Task 6.
**Recommended Agent Profile**: `unspecified-high`
**Parallelization**: YES (with Tasks 13-19, 20-22, 24-26, 27-32)
@@ -609,7 +609,7 @@ Max Concurrent: 8 (Wave 3)
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
**Commit**: NO
- [ ] 24. Refactor PasswordGenerator.astro
- [x] 24. Refactor PasswordGenerator.astro
**What to do**: Same pattern as Task 6.
**Recommended Agent Profile**: `unspecified-high`
**Parallelization**: YES (with Tasks 13-19, 20-23, 25-26, 27-32)
@@ -617,7 +617,7 @@ Max Concurrent: 8 (Wave 3)
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
**Commit**: NO
- [ ] 25. Refactor RpsGenerator.astro
- [x] 25. Refactor RpsGenerator.astro
**What to do**: Same pattern as Task 6.
**Recommended Agent Profile**: `unspecified-high`
**Parallelization**: YES (with Tasks 13-19, 20-24, 26, 27-32)
@@ -625,7 +625,7 @@ Max Concurrent: 8 (Wave 3)
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
**Commit**: NO
- [ ] 26. Refactor ShufflerGenerator.astro
- [x] 26. Refactor ShufflerGenerator.astro
**What to do**: Same pattern as Task 6.
**Recommended Agent Profile**: `unspecified-high`
**Parallelization**: YES (with Tasks 13-19, 20-25, 27-32)
+44 -39
View File
@@ -77,38 +77,7 @@ const timeFilters = isRu
{ value: "slow", label: "Slow cook" },
];
const cuisineLabelMap: Record<string, { en: string; ru: string }> = {
italian: { en: "Italian", ru: "Итальянская" },
asian: { en: "Asian", ru: "Азиатская" },
mexican: { en: "Mexican", ru: "Мексиканская" },
american: { en: "American", ru: "Американская" },
mediterranean: { en: "Mediterranean", ru: "Средиземноморская" },
french: { en: "French", ru: "Французская" },
indian: { en: "Indian", ru: "Индийская" },
russian: { en: "Russian", ru: "Русская" },
"middle-eastern": { en: "Middle Eastern", ru: "Ближневосточная" },
};
const mealTypeLabelMap: Record<string, { en: string; ru: string }> = {
breakfast: { en: "Breakfast", ru: "Завтрак" },
lunch: { en: "Lunch", ru: "Обед" },
dinner: { en: "Dinner", ru: "Ужин" },
snack: { en: "Snack", ru: "Перекус" },
dessert: { en: "Dessert", ru: "Десерт" },
};
const difficultyLabelMap: Record<string, { en: string; ru: string; color: string }> = {
easy: { en: "Easy", ru: "Легко", color: "bg-emerald-500/15 text-emerald-400 border-emerald-500/25" },
medium: { en: "Medium", ru: "Средне", color: "bg-amber-500/15 text-amber-400 border-amber-500/25" },
hard: { en: "Hard", ru: "Сложно", color: "bg-red-500/15 text-red-400 border-red-500/25" },
};
const dietaryLabelMap: Record<string, { en: string; ru: string; emoji: string }> = {
vegetarian: { en: "Vegetarian", ru: "Вегетарианское", emoji: "🌿" },
vegan: { en: "Vegan", ru: "Веганское", emoji: "🌱" },
"gluten-free": { en: "Gluten-Free", ru: "Без глютена", emoji: "🌾" },
spicy: { en: "Spicy", ru: "Острое", emoji: "🌶" },
};
---
<div id="meal-generator" class="mt-8">
@@ -273,9 +242,44 @@ const dietaryLabelMap: Record<string, { en: string; ru: string; emoji: string }>
</div>
</div>
<script define:vars={{ cuisineLabelMap, mealTypeLabelMap, difficultyLabelMap, dietaryLabelMap }}>
(function() {
// Meals data
<script>
import { randomInt } from "@/lib/client/random";
import { getClientLang, getClientT } from "@/lib/client/i18n";
const cuisineLabelMap = {
italian: { en: "Italian", ru: "Итальянская" },
asian: { en: "Asian", ru: "Азиатская" },
mexican: { en: "Mexican", ru: "Мексиканская" },
american: { en: "American", ru: "Американская" },
mediterranean: { en: "Mediterranean", ru: "Средиземноморская" },
french: { en: "French", ru: "Французская" },
indian: { en: "Indian", ru: "Индийская" },
russian: { en: "Russian", ru: "Русская" },
"middle-eastern": { en: "Middle Eastern", ru: "Ближневосточная" },
};
const mealTypeLabelMap = {
breakfast: { en: "Breakfast", ru: "Завтрак" },
lunch: { en: "Lunch", ru: "Обед" },
dinner: { en: "Dinner", ru: "Ужин" },
snack: { en: "Snack", ru: "Перекус" },
dessert: { en: "Dessert", ru: "Десерт" },
};
const difficultyLabelMap = {
easy: { en: "Easy", ru: "Легко", color: "bg-emerald-500/15 text-emerald-400 border-emerald-500/25" },
medium: { en: "Medium", ru: "Средне", color: "bg-amber-500/15 text-amber-400 border-amber-500/25" },
hard: { en: "Hard", ru: "Сложно", color: "bg-red-500/15 text-red-400 border-red-500/25" },
};
const dietaryLabelMap = {
vegetarian: { en: "Vegetarian", ru: "Вегетарианское", emoji: "🌿" },
vegan: { en: "Vegan", ru: "Веганское", emoji: "🌱" },
"gluten-free": { en: "Gluten-Free", ru: "Без глютена", emoji: "🌾" },
spicy: { en: "Spicy", ru: "Острое", emoji: "🌶" },
};
// Meals data
const meals = [
{ en: "Shakshuka", ru: "Шакшука", cuisine: "middle-eastern", type: "breakfast", difficulty: "easy", time: 25, desc: "Eggs poached in a simmering tomato sauce with spices.", ruDesc: "Яйца, приготовленные в томатном соусе со специями.", ingredients: ["eggs", "tomatoes", "bell pepper", "onion", "cumin", "paprika"], calories: 320, dietary: ["vegetarian", "gluten-free"] },
{ en: "Spaghetti Carbonara", ru: "Спагетти Карбонара", cuisine: "italian", type: "dinner", difficulty: "medium", time: 20, desc: "Classic Roman pasta with eggs, pecorino, guanciale and black pepper.", ruDesc: "Классическая римская паста с яйцами, пекорино, гуанчиале и черным перцем.", ingredients: ["spaghetti", "eggs", "pecorino", "guanciale", "black pepper"], calories: 650, dietary: [] },
@@ -400,7 +404,9 @@ const dietaryLabelMap: Record<string, { en: string; ru: string; emoji: string }>
}
}
const isRu = document.documentElement.lang === "ru";
const lang = getClientLang();
const isRu = lang === "ru";
const T = getClientT();
// DOM refs
const typeSelect = document.getElementById("mg-type");
@@ -529,7 +535,7 @@ const dietaryLabelMap: Record<string, { en: string; ru: string; emoji: string }>
return;
}
const meal = list[Math.floor(Math.random() * list.length)];
const meal = list[randomInt(0, list.length - 1)];
currentMeal = meal;
// Name
@@ -602,13 +608,13 @@ const dietaryLabelMap: Record<string, { en: string; ru: string; emoji: string }>
copyIcon.classList.add("hidden");
checkIcon.classList.remove("hidden");
copyText.textContent = isRu ? "Скопировано" : "Copied";
copyText.textContent = T.copied;
if (copyTimer) clearTimeout(copyTimer);
copyTimer = setTimeout(() => {
checkIcon.classList.add("hidden");
copyIcon.classList.remove("hidden");
copyText.textContent = isRu ? "Копировать" : "Copy";
copyText.textContent = T.copy;
}, 1500);
});
@@ -674,5 +680,4 @@ const dietaryLabelMap: Record<string, { en: string; ru: string; emoji: string }>
// Init
updateSavedCount();
generate();
})();
</script>
@@ -118,8 +118,10 @@ const genderOptions = [
<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 isRu = getClientLang() === "ru";
const EN_MALE = [
"James", "John", "Robert", "Michael", "William", "David", "Richard",
@@ -195,7 +197,7 @@ const genderOptions = [
function generate() {
const pool = getNamesPool(getSelectedGender());
const name = pool[Math.floor(Math.random() * pool.length)];
const name = pool[randomInt(0, pool.length - 1)];
resultEl.textContent = name;
copyBtn.classList.remove("invisible");
popElement(resultEl);
@@ -115,14 +115,10 @@ const T = useT(isRu ? "ru" : "en");
import { CopyFeedback } from "@/lib/client/clipboard";
import { createErrorDisplay, isInteger } from "@/lib/client/validation";
import { popElement } from "@/lib/client/animations";
import { randomInt } from "@/lib/client/random";
import { getClientT } from "@/lib/client/i18n";
const isRu = document.documentElement.lang === "ru";
const ERR_INTEGERS = isRu
? "Оба значения должны быть целыми числами."
: "Both values must be whole numbers.";
const ERR_FROM_TO = isRu
? "«От» должно быть меньше «До»."
: '"From" must be less than "To".';
const T = getClientT();
const fromInput = document.getElementById("ng-from") as HTMLInputElement;
const toInput = document.getElementById("ng-to") as HTMLInputElement;
@@ -141,17 +137,17 @@ const T = useT(isRu ? "ru" : "en");
function generate() {
if (!isInteger(fromInput.value) || !isInteger(toInput.value)) {
errors.show(ERR_INTEGERS);
errors.show(T.errBothIntegers);
return;
}
const min = parseInt(fromInput.value, 10);
const max = parseInt(toInput.value, 10);
if (min >= max) {
errors.show(ERR_FROM_TO);
errors.show(T.errFromLessThanTo);
return;
}
errors.clear();
const result = Math.floor(Math.random() * (max - min + 1)) + min;
const result = randomInt(min, max);
resultEl.textContent = String(result);
copyBtn.classList.remove("invisible");
popElement(resultEl);
@@ -99,8 +99,11 @@ const i18n = {
<script>
import { createErrorDisplay } from "@/lib/client/validation";
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 harmonySelect = document.getElementById("pg-harmony") as HTMLSelectElement;
const countSelect = document.getElementById("pg-count") as HTMLSelectElement;
@@ -115,10 +118,6 @@ const i18n = {
const copyIconSvg = `<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>`;
const checkIconSvg = `<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" class="text-accent"><path d="M20 6 9 17l-5-5"/></svg>`;
function randomInt(max: number): number {
return Math.floor(Math.random() * max);
}
function hslToHex(h: number, s: number, l: number): string {
s /= 100;
l /= 100;
@@ -136,18 +135,18 @@ const i18n = {
function generateRandomColors(count: number): string[] {
const colors: string[] = [];
for (let i = 0; i < count; i++) {
const h = randomInt(360);
const s = 50 + randomInt(51);
const l = 40 + randomInt(41);
const h = randomInt(0, 359);
const s = randomInt(50, 100);
const l = randomInt(40, 80);
colors.push(hslToHex(h, s, l));
}
return colors;
}
function generateAnalogousColors(count: number): string[] {
const baseH = randomInt(360);
const s = 55 + randomInt(41);
const l = 40 + randomInt(31);
const baseH = randomInt(0, 359);
const s = randomInt(55, 95);
const l = randomInt(40, 70);
const colors: string[] = [];
const step = 30;
const start = baseH - ((count - 1) * step) / 2;
@@ -159,9 +158,9 @@ const i18n = {
}
function generateComplementaryColors(count: number): string[] {
const baseH = randomInt(360);
const s = 55 + randomInt(41);
const l = 40 + randomInt(31);
const baseH = randomInt(0, 359);
const s = randomInt(55, 95);
const l = randomInt(40, 70);
const colors: string[] = [];
const compH = (baseH + 180) % 360;
if (count <= 2) {
@@ -178,9 +177,9 @@ const i18n = {
}
function generateTriadicColors(count: number): string[] {
const baseH = randomInt(360);
const s = 55 + randomInt(41);
const l = 40 + randomInt(31);
const baseH = randomInt(0, 359);
const s = randomInt(55, 95);
const l = randomInt(40, 70);
const colors: string[] = [];
const triad = [baseH, (baseH + 120) % 360, (baseH + 240) % 360];
for (let i = 0; i < count; i++) {
@@ -192,8 +191,8 @@ const i18n = {
}
function generateMonochromaticColors(count: number): string[] {
const baseH = randomInt(360);
const s = 55 + randomInt(41);
const baseH = randomInt(0, 359);
const s = randomInt(55, 95);
const colors: string[] = [];
const startL = 25;
const endL = 75;
@@ -205,9 +204,9 @@ const i18n = {
}
function generateSplitComplementaryColors(count: number): string[] {
const baseH = randomInt(360);
const s = 55 + randomInt(41);
const l = 40 + randomInt(31);
const baseH = randomInt(0, 359);
const s = randomInt(55, 95);
const l = randomInt(40, 70);
const colors: string[] = [];
const hues = [baseH, (baseH + 150) % 360, (baseH + 210) % 360];
for (let i = 0; i < count; i++) {
@@ -219,9 +218,9 @@ const i18n = {
}
function generateTetradicColors(count: number): string[] {
const baseH = randomInt(360);
const s = 55 + randomInt(41);
const l = 40 + randomInt(31);
const baseH = randomInt(0, 359);
const s = randomInt(55, 95);
const l = randomInt(40, 70);
const colors: string[] = [];
const rectHues = [baseH, (baseH + 60) % 360, (baseH + 180) % 360, (baseH + 240) % 360];
for (let i = 0; i < count; i++) {
@@ -268,7 +267,7 @@ const i18n = {
colorBox.className =
"group relative w-20 h-20 sm:w-24 sm:h-24 rounded-2xl border border-zinc-800 cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-zinc-950 transition-transform duration-150 hover:scale-105";
colorBox.style.backgroundColor = hex;
colorBox.setAttribute("aria-label", isRu ? `Скопировать ${hex}` : `Copy ${hex}`);
colorBox.setAttribute("aria-label", lang === "ru" ? `Скопировать ${hex}` : `Copy ${hex}`);
colorBox.dataset.hex = hex;
// Copy overlay icon
@@ -322,7 +321,7 @@ const i18n = {
await navigator.clipboard.writeText(text);
const originalText = copyAllBtn.textContent;
copyAllBtn.textContent = isRu ? "Скопировано!" : "Copied!";
copyAllBtn.textContent = T.copied + "!";
setTimeout(() => {
copyAllBtn.textContent = originalText;
}, 1500);
@@ -335,7 +334,7 @@ const i18n = {
const colors = generatePalette(mode, count);
renderSwatches(colors);
} catch {
errorDisplay.show(isRu ? "Не удалось сгенерировать палитру." : "Failed to generate palette.");
errorDisplay.show(lang === "ru" ? "Не удалось сгенерировать палитру." : "Failed to generate palette.");
}
}
@@ -132,9 +132,11 @@ const T = useT(isRu ? "ru" : "en");
<script>
import { CopyFeedback } from "@/lib/client/clipboard";
import { createErrorDisplay } from "@/lib/client/validation";
import { randomInt, shuffleArray } from "@/lib/client/random";
import { getClientLang } from "@/lib/client/i18n";
const isRu = document.documentElement.lang === "ru";
const ERR_SELECT_TYPE = isRu
const lang = getClientLang();
const ERR_SELECT_TYPE = lang === "ru"
? "Выберите хотя бы один тип символов."
: "Select at least one character type.";
@@ -178,17 +180,13 @@ const T = useT(isRu ? "ru" : "en");
return;
}
errors.clear();
const required = pools.map((p) => p[Math.floor(Math.random() * p.length)]);
const required = pools.map((p) => p[randomInt(0, p.length - 1)]);
const combined = pools.join("");
const rest = Array.from(
{ length: length - required.length },
() => combined[Math.floor(Math.random() * combined.length)],
() => combined[randomInt(0, combined.length - 1)],
);
const chars = [...required, ...rest];
for (let i = chars.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[chars[i], chars[j]] = [chars[j], chars[i]];
}
const chars = shuffleArray([...required, ...rest]);
resultEl.textContent = chars.join("");
resultEl.style.opacity = "0.5";
requestAnimationFrame(() =>
+5 -2
View File
@@ -67,7 +67,10 @@ const T = useT(isRu ? "ru" : "en");
</div>
<script>
const isRu = document.documentElement.lang === "ru";
import { getClientLang } from "@/lib/client/i18n";
import { randomInt } from "@/lib/client/random";
const isRu = getClientLang() === "ru";
const LABELS: Record<string, { name: string; emoji: string }> = isRu
? {
@@ -111,7 +114,7 @@ const T = useT(isRu ? "ru" : "en");
}
function play(playerMove: string) {
const botMove = MOVES[Math.floor(Math.random() * MOVES.length)];
const botMove = MOVES[randomInt(0, MOVES.length - 1)];
const result = getWinner(playerMove, botMove);
choices.forEach((btn) => {
@@ -112,13 +112,10 @@ const T = useT(isRu ? "ru" : "en");
<script>
import { CopyFeedback } from "@/lib/client/clipboard";
import { createErrorDisplay } from "@/lib/client/validation";
import { shuffleArray } from "@/lib/client/random";
import { getClientT } from "@/lib/client/i18n";
const isRu = document.documentElement.lang === "ru";
const COPY_LABEL = isRu ? "Копировать" : "Copy";
const COPIED_LABEL = isRu ? "Скопировано" : "Copied";
const ERR_MIN_TWO = isRu
? "Добавьте хотя бы 2 элемента."
: "Add at least 2 items.";
const T = getClientT();
const itemsInput = document.getElementById(
"shf-items",
@@ -143,15 +140,6 @@ const T = useT(isRu ? "ru" : "en");
const errors = createErrorDisplay(errorEl);
const clipboard = new CopyFeedback(copyIcon, checkIcon);
function fisherYatesShuffle<T>(array: T[]): T[] {
const arr = [...array];
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr;
}
function shuffle() {
const items = itemsInput.value
.split("\n")
@@ -159,12 +147,12 @@ const T = useT(isRu ? "ru" : "en");
.filter(Boolean);
if (items.length < 2) {
errors.show(ERR_MIN_TWO);
errors.show(T.errAddAtLeast2);
return;
}
errors.clear();
lastShuffled = fisherYatesShuffle(items);
lastShuffled = shuffleArray(items);
resultEl.innerHTML = "";
lastShuffled.forEach((item, index) => {
@@ -188,7 +176,7 @@ const T = useT(isRu ? "ru" : "en");
});
resultArea.classList.remove("hidden");
copyLabel.textContent = COPY_LABEL;
copyLabel.textContent = T.copy;
clipboard.revert();
}
@@ -197,10 +185,10 @@ const T = useT(isRu ? "ru" : "en");
await navigator.clipboard.writeText(lastShuffled.join("\n"));
clipboard.showCopied();
copyLabel.textContent = COPIED_LABEL;
copyLabel.textContent = T.copied;
setTimeout(() => {
copyLabel.textContent = COPY_LABEL;
copyLabel.textContent = T.copy;
}, 1500);
}