Wave 6: refactor generators batch 4 to crypto random
- refactor(generators): migrate Teams, Time, Uuid, Weighted to crypto random - refactor(generators): migrate WheelSpinner, YesNo to crypto random All 28 generators now use cryptographically secure random via src/lib/client/random.ts
This commit is contained in:
+28
-2
@@ -8,7 +8,7 @@
|
|||||||
"plan_name": "critical-refactor",
|
"plan_name": "critical-refactor",
|
||||||
"status": "active",
|
"status": "active",
|
||||||
"started_at": "2026-05-13T16:25:19.266Z",
|
"started_at": "2026-05-13T16:25:19.266Z",
|
||||||
"updated_at": "2026-05-13T17:34:08.166Z",
|
"updated_at": "2026-05-13T17:41:13.262Z",
|
||||||
"session_ids": [
|
"session_ids": [
|
||||||
"ses_1de6ede59ffexUwt5C0sddEWwR"
|
"ses_1de6ede59ffexUwt5C0sddEWwR"
|
||||||
],
|
],
|
||||||
@@ -120,6 +120,19 @@
|
|||||||
"status": "completed",
|
"status": "completed",
|
||||||
"ended_at": "2026-05-13T17:34:08.166Z",
|
"ended_at": "2026-05-13T17:34:08.166Z",
|
||||||
"elapsed_ms": 93514
|
"elapsed_ms": 93514
|
||||||
|
},
|
||||||
|
"todo:27": {
|
||||||
|
"task_key": "todo:27",
|
||||||
|
"task_label": "27",
|
||||||
|
"task_title": "Refactor TeamsGenerator.astro",
|
||||||
|
"session_id": "ses_1dd94b493ffemX2z2dEFdh8W7R",
|
||||||
|
"agent": "Sisyphus-Junior",
|
||||||
|
"category": "unspecified-high",
|
||||||
|
"started_at": "2026-05-13T17:37:23.881Z",
|
||||||
|
"status": "completed",
|
||||||
|
"updated_at": "2026-05-13T17:41:13.262Z",
|
||||||
|
"ended_at": "2026-05-13T17:41:13.262Z",
|
||||||
|
"elapsed_ms": 229381
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,7 +140,7 @@
|
|||||||
"active_plan": "/home/emil/Desktop/Coding/AI/Randify.pro/.sisyphus/plans/critical-refactor.md",
|
"active_plan": "/home/emil/Desktop/Coding/AI/Randify.pro/.sisyphus/plans/critical-refactor.md",
|
||||||
"started_at": "2026-05-13T16:25:19.266Z",
|
"started_at": "2026-05-13T16:25:19.266Z",
|
||||||
"status": "active",
|
"status": "active",
|
||||||
"updated_at": "2026-05-13T17:34:08.166Z",
|
"updated_at": "2026-05-13T17:41:13.262Z",
|
||||||
"session_ids": [
|
"session_ids": [
|
||||||
"ses_1de6ede59ffexUwt5C0sddEWwR"
|
"ses_1de6ede59ffexUwt5C0sddEWwR"
|
||||||
],
|
],
|
||||||
@@ -239,6 +252,19 @@
|
|||||||
"status": "completed",
|
"status": "completed",
|
||||||
"ended_at": "2026-05-13T17:34:08.166Z",
|
"ended_at": "2026-05-13T17:34:08.166Z",
|
||||||
"elapsed_ms": 93514
|
"elapsed_ms": 93514
|
||||||
|
},
|
||||||
|
"todo:27": {
|
||||||
|
"task_key": "todo:27",
|
||||||
|
"task_label": "27",
|
||||||
|
"task_title": "Refactor TeamsGenerator.astro",
|
||||||
|
"session_id": "ses_1dd94b493ffemX2z2dEFdh8W7R",
|
||||||
|
"agent": "Sisyphus-Junior",
|
||||||
|
"category": "unspecified-high",
|
||||||
|
"started_at": "2026-05-13T17:37:23.881Z",
|
||||||
|
"status": "completed",
|
||||||
|
"updated_at": "2026-05-13T17:41:13.262Z",
|
||||||
|
"ended_at": "2026-05-13T17:41:13.262Z",
|
||||||
|
"elapsed_ms": 229381
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"agent": "atlas"
|
"agent": "atlas"
|
||||||
|
|||||||
@@ -635,7 +635,7 @@ Max Concurrent: 8 (Wave 3)
|
|||||||
- Message: `refactor(generators): migrate batch 3 to crypto random (Meal, Names, Number, Palette, Password, Rps, Shuffler)`
|
- Message: `refactor(generators): migrate batch 3 to crypto random (Meal, Names, Number, Palette, Password, Rps, Shuffler)`
|
||||||
- Files: `src/components/generators/{Meal,Names,Number,Palette,Password,Rps,Shuffler}Generator.astro`
|
- Files: `src/components/generators/{Meal,Names,Number,Palette,Password,Rps,Shuffler}Generator.astro`
|
||||||
|
|
||||||
- [ ] 27. Refactor TeamsGenerator.astro
|
- [x] 27. Refactor TeamsGenerator.astro
|
||||||
**What to do**: Same pattern as Task 6.
|
**What to do**: Same pattern as Task 6.
|
||||||
**Recommended Agent Profile**: `unspecified-high`
|
**Recommended Agent Profile**: `unspecified-high`
|
||||||
**Parallelization**: YES (with Tasks 13-19, 20-26, 28-32)
|
**Parallelization**: YES (with Tasks 13-19, 20-26, 28-32)
|
||||||
@@ -643,7 +643,7 @@ Max Concurrent: 8 (Wave 3)
|
|||||||
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
||||||
**Commit**: NO
|
**Commit**: NO
|
||||||
|
|
||||||
- [ ] 28. Refactor TimeGenerator.astro
|
- [x] 28. Refactor TimeGenerator.astro
|
||||||
**What to do**: Same pattern as Task 6.
|
**What to do**: Same pattern as Task 6.
|
||||||
**Recommended Agent Profile**: `unspecified-high`
|
**Recommended Agent Profile**: `unspecified-high`
|
||||||
**Parallelization**: YES (with Tasks 13-19, 20-26, 27, 29-32)
|
**Parallelization**: YES (with Tasks 13-19, 20-26, 27, 29-32)
|
||||||
@@ -651,7 +651,7 @@ Max Concurrent: 8 (Wave 3)
|
|||||||
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
||||||
**Commit**: NO
|
**Commit**: NO
|
||||||
|
|
||||||
- [ ] 29. Refactor UuidGenerator.astro
|
- [x] 29. Refactor UuidGenerator.astro
|
||||||
**What to do**: Same pattern as Task 6.
|
**What to do**: Same pattern as Task 6.
|
||||||
**Recommended Agent Profile**: `unspecified-high`
|
**Recommended Agent Profile**: `unspecified-high`
|
||||||
**Parallelization**: YES (with Tasks 13-19, 20-26, 27-28, 30-32)
|
**Parallelization**: YES (with Tasks 13-19, 20-26, 27-28, 30-32)
|
||||||
@@ -659,7 +659,7 @@ Max Concurrent: 8 (Wave 3)
|
|||||||
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
||||||
**Commit**: NO
|
**Commit**: NO
|
||||||
|
|
||||||
- [ ] 30. Refactor WeightedGenerator.astro
|
- [x] 30. Refactor WeightedGenerator.astro
|
||||||
**What to do**: Same pattern as Task 6.
|
**What to do**: Same pattern as Task 6.
|
||||||
**Recommended Agent Profile**: `unspecified-high`
|
**Recommended Agent Profile**: `unspecified-high`
|
||||||
**Parallelization**: YES (with Tasks 13-19, 20-26, 27-29, 31-32)
|
**Parallelization**: YES (with Tasks 13-19, 20-26, 27-29, 31-32)
|
||||||
@@ -667,7 +667,7 @@ Max Concurrent: 8 (Wave 3)
|
|||||||
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
||||||
**Commit**: NO
|
**Commit**: NO
|
||||||
|
|
||||||
- [ ] 31. Refactor WheelSpinner.astro
|
- [x] 31. Refactor WheelSpinner.astro
|
||||||
**What to do**: Same pattern as Task 6. Note: file is `WheelSpinner.astro`, not `WheelGenerator.astro`.
|
**What to do**: Same pattern as Task 6. Note: file is `WheelSpinner.astro`, not `WheelGenerator.astro`.
|
||||||
**Recommended Agent Profile**: `unspecified-high`
|
**Recommended Agent Profile**: `unspecified-high`
|
||||||
**Parallelization**: YES (with Tasks 13-19, 20-26, 27-30, 32)
|
**Parallelization**: YES (with Tasks 13-19, 20-26, 27-30, 32)
|
||||||
@@ -675,7 +675,7 @@ Max Concurrent: 8 (Wave 3)
|
|||||||
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
**Acceptance Criteria**: No Math.random, uses random.ts and i18n.ts
|
||||||
**Commit**: NO
|
**Commit**: NO
|
||||||
|
|
||||||
- [ ] 32. Refactor YesNoGenerator.astro
|
- [x] 32. Refactor YesNoGenerator.astro
|
||||||
**What to do**: Same pattern as Task 6.
|
**What to do**: Same pattern as Task 6.
|
||||||
**Recommended Agent Profile**: `unspecified-high`
|
**Recommended Agent Profile**: `unspecified-high`
|
||||||
**Parallelization**: YES (with Tasks 13-19, 20-26, 27-31)
|
**Parallelization**: YES (with Tasks 13-19, 20-26, 27-31)
|
||||||
|
|||||||
@@ -139,21 +139,22 @@ const errMorePlayers = isRu
|
|||||||
<script>
|
<script>
|
||||||
import { CopyFeedback } from "@/lib/client/clipboard";
|
import { CopyFeedback } from "@/lib/client/clipboard";
|
||||||
import { createErrorDisplay } from "@/lib/client/validation";
|
import { createErrorDisplay } from "@/lib/client/validation";
|
||||||
|
import { shuffleArray } from "@/lib/client/random";
|
||||||
|
import { getClientLang, getClientT } from "@/lib/client/i18n";
|
||||||
|
|
||||||
const isRu = document.documentElement.lang === "ru";
|
const lang = getClientLang();
|
||||||
const COPY_LABEL = isRu ? "Копировать" : "Copy";
|
const T = getClientT();
|
||||||
const COPIED_LABEL = isRu ? "Скопировано" : "Copied";
|
const TEAM_PREFIX = lang === "ru" ? "Команда" : "Team";
|
||||||
const TEAM_PREFIX = isRu ? "Команда" : "Team";
|
const ERR_ADD_TWO = lang === "ru"
|
||||||
const ERR_ADD_TWO = isRu
|
|
||||||
? "Добавьте хотя бы 2 участника."
|
? "Добавьте хотя бы 2 участника."
|
||||||
: "Add at least 2 participants.";
|
: "Add at least 2 participants.";
|
||||||
const ERR_TEAMS_MIN = isRu
|
const ERR_TEAMS_MIN = lang === "ru"
|
||||||
? "Минимум 2 команды."
|
? "Минимум 2 команды."
|
||||||
: "Minimum 2 teams.";
|
: "Minimum 2 teams.";
|
||||||
const ERR_TEAMS_MAX = isRu
|
const ERR_TEAMS_MAX = lang === "ru"
|
||||||
? "Максимум 20 команд."
|
? "Максимум 20 команд."
|
||||||
: "Maximum 20 teams.";
|
: "Maximum 20 teams.";
|
||||||
const ERR_MORE_PLAYERS = isRu
|
const ERR_MORE_PLAYERS = lang === "ru"
|
||||||
? "Участников должно быть больше, чем команд."
|
? "Участников должно быть больше, чем команд."
|
||||||
: "Participants must outnumber teams.";
|
: "Participants must outnumber teams.";
|
||||||
|
|
||||||
@@ -201,17 +202,8 @@ const errMorePlayers = isRu
|
|||||||
const errors = createErrorDisplay(errorEl);
|
const errors = createErrorDisplay(errorEl);
|
||||||
const clipboard = new CopyFeedback(copyIcon, checkIcon);
|
const clipboard = new CopyFeedback(copyIcon, checkIcon);
|
||||||
|
|
||||||
function shuffle<T>(arr: T[]): T[] {
|
|
||||||
const a = [...arr];
|
|
||||||
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 splitIntoTeams(items: string[], teamCount: number): string[][] {
|
function splitIntoTeams(items: string[], teamCount: number): string[][] {
|
||||||
const shuffled = shuffle(items);
|
const shuffled = shuffleArray(items);
|
||||||
const teams: string[][] = Array.from({ length: teamCount }, () => []);
|
const teams: string[][] = Array.from({ length: teamCount }, () => []);
|
||||||
shuffled.forEach((item, i) => {
|
shuffled.forEach((item, i) => {
|
||||||
teams[i % teamCount].push(item);
|
teams[i % teamCount].push(item);
|
||||||
@@ -281,7 +273,7 @@ const errMorePlayers = isRu
|
|||||||
});
|
});
|
||||||
|
|
||||||
copyBtn.classList.remove("invisible");
|
copyBtn.classList.remove("invisible");
|
||||||
copyLabel.textContent = COPY_LABEL;
|
copyLabel.textContent = T.copy;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function copyResults() {
|
async function copyResults() {
|
||||||
@@ -295,10 +287,10 @@ const errMorePlayers = isRu
|
|||||||
await navigator.clipboard.writeText(text);
|
await navigator.clipboard.writeText(text);
|
||||||
|
|
||||||
clipboard.showCopied();
|
clipboard.showCopied();
|
||||||
copyLabel.textContent = COPIED_LABEL;
|
copyLabel.textContent = T.copied;
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
copyLabel.textContent = COPY_LABEL;
|
copyLabel.textContent = T.copy;
|
||||||
}, 1500);
|
}, 1500);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -174,8 +174,12 @@ const T = useT(isRu ? "ru" : "en");
|
|||||||
import { CopyFeedback } from "@/lib/client/clipboard";
|
import { CopyFeedback } from "@/lib/client/clipboard";
|
||||||
import { createErrorDisplay } from "@/lib/client/validation";
|
import { createErrorDisplay } from "@/lib/client/validation";
|
||||||
import { popElement } from "@/lib/client/animations";
|
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 ERR_FROM_BEFORE_TO = isRu
|
const ERR_FROM_BEFORE_TO = isRu
|
||||||
? "«От» должно быть раньше «До»."
|
? "«От» должно быть раньше «До»."
|
||||||
@@ -369,11 +373,11 @@ const T = useT(isRu ? "ru" : "en");
|
|||||||
// Generate random time within range, respecting interval
|
// Generate random time within range, respecting interval
|
||||||
const rangeMinutes = toMins - fromMins;
|
const rangeMinutes = toMins - fromMins;
|
||||||
const maxSteps = Math.floor(rangeMinutes / interval);
|
const maxSteps = Math.floor(rangeMinutes / interval);
|
||||||
const randomStep = Math.floor(Math.random() * (maxSteps + 1));
|
const randomStep = randomInt(0, maxSteps);
|
||||||
const randomTotalMinutes = fromMins + randomStep * interval;
|
const randomTotalMinutes = fromMins + randomStep * interval;
|
||||||
|
|
||||||
const { h, m } = minutesToTime(randomTotalMinutes);
|
const { h, m } = minutesToTime(randomTotalMinutes);
|
||||||
const s = Math.floor(Math.random() * 60);
|
const s = randomInt(0, 59);
|
||||||
|
|
||||||
displayResult(h, m, s);
|
displayResult(h, m, s);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,10 +133,10 @@ const T = useT(isRu ? "ru" : "en");
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { CopyFeedback } from "@/lib/client/clipboard";
|
import { CopyFeedback } from "@/lib/client/clipboard";
|
||||||
|
import { getClientT } from "@/lib/client/i18n";
|
||||||
|
import { randomInt, randomBytes } from "@/lib/client/random";
|
||||||
|
|
||||||
const isRu = document.documentElement.lang === "ru";
|
const T = getClientT();
|
||||||
const COPY_ALL_LABEL = isRu ? "Копировать всё" : "Copy all";
|
|
||||||
const COPIED_LABEL = isRu ? "Скопировано" : "Copied";
|
|
||||||
|
|
||||||
const lengthWrap = document.getElementById(
|
const lengthWrap = document.getElementById(
|
||||||
"ug-length-wrap",
|
"ug-length-wrap",
|
||||||
@@ -173,17 +173,11 @@ const T = useT(isRu ? "ru" : "en");
|
|||||||
|
|
||||||
function uuidV4(): string {
|
function uuidV4(): string {
|
||||||
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
||||||
const r = crypto.getRandomValues(new Uint8Array(1))[0] & 0xf;
|
const r = randomInt(0, 15);
|
||||||
return (c === "x" ? r : (r & 0x3) | 0x8).toString(16);
|
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 {
|
function toHex(bytes: Uint8Array): string {
|
||||||
return Array.from(bytes)
|
return Array.from(bytes)
|
||||||
.map((b) => b.toString(16).padStart(2, "0"))
|
.map((b) => b.toString(16).padStart(2, "0"))
|
||||||
@@ -223,7 +217,7 @@ const T = useT(isRu ? "ru" : "en");
|
|||||||
});
|
});
|
||||||
|
|
||||||
copyBtn.classList.remove("invisible");
|
copyBtn.classList.remove("invisible");
|
||||||
copyLabel.textContent = COPY_ALL_LABEL;
|
copyLabel.textContent = T.copyAll;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function copyAll() {
|
async function copyAll() {
|
||||||
@@ -231,10 +225,10 @@ const T = useT(isRu ? "ru" : "en");
|
|||||||
await navigator.clipboard.writeText(lastTokens.join("\n"));
|
await navigator.clipboard.writeText(lastTokens.join("\n"));
|
||||||
|
|
||||||
clipboard.showCopied();
|
clipboard.showCopied();
|
||||||
copyLabel.textContent = COPIED_LABEL;
|
copyLabel.textContent = T.copied;
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
copyLabel.textContent = COPY_ALL_LABEL;
|
copyLabel.textContent = T.copyAll;
|
||||||
}, 1500);
|
}, 1500);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -126,13 +126,17 @@ const T = useT(isRu ? "ru" : "en");
|
|||||||
<script>
|
<script>
|
||||||
import { CopyFeedback } from "@/lib/client/clipboard";
|
import { CopyFeedback } from "@/lib/client/clipboard";
|
||||||
import { createErrorDisplay } from "@/lib/client/validation";
|
import { createErrorDisplay } from "@/lib/client/validation";
|
||||||
|
import { randomFloat } from "@/lib/client/random";
|
||||||
|
import { getClientLang, getClientT } from "@/lib/client/i18n";
|
||||||
|
|
||||||
const isRu = document.documentElement.lang === "ru";
|
const lang = getClientLang();
|
||||||
const COPY_LABEL = isRu ? "Копировать" : "Copy";
|
const isRu = lang === "ru";
|
||||||
const COPIED_LABEL = isRu ? "Скопировано" : "Copied";
|
const T = getClientT();
|
||||||
|
const COPY_LABEL = T.copy;
|
||||||
|
const COPIED_LABEL = T.copied;
|
||||||
const ERR_ITEM_EMPTY = isRu ? "Имя элемента не может быть пустым." : "Item name cannot be empty.";
|
const ERR_ITEM_EMPTY = isRu ? "Имя элемента не может быть пустым." : "Item name cannot be empty.";
|
||||||
const ERR_WEIGHT_POSITIVE = isRu ? "Вес должен быть положительным числом." : "Weight must be a positive number.";
|
const ERR_WEIGHT_POSITIVE = isRu ? "Вес должен быть положительным числом." : "Weight must be a positive number.";
|
||||||
const ERR_ADD_AT_LEAST_ONE = isRu ? "Добавьте хотя бы один элемент с весом." : "Add at least one item with a weight.";
|
const ERR_ADD_AT_LEAST_ONE = T.errAddAtLeast1;
|
||||||
const LABEL_WEIGHT = isRu ? "Вес" : "Weight";
|
const LABEL_WEIGHT = isRu ? "Вес" : "Weight";
|
||||||
const LABEL_ITEM = isRu ? "Элемент" : "Item";
|
const LABEL_ITEM = isRu ? "Элемент" : "Item";
|
||||||
const LABEL_REMOVE = isRu ? "Удалить" : "Remove";
|
const LABEL_REMOVE = isRu ? "Удалить" : "Remove";
|
||||||
@@ -252,7 +256,7 @@ const T = useT(isRu ? "ru" : "en");
|
|||||||
|
|
||||||
function weightedPick(items: { name: string; weight: number }[]): { name: string; weight: number } {
|
function weightedPick(items: { name: string; weight: number }[]): { name: string; weight: number } {
|
||||||
const totalWeight = items.reduce((sum, item) => sum + item.weight, 0);
|
const totalWeight = items.reduce((sum, item) => sum + item.weight, 0);
|
||||||
let random = Math.random() * totalWeight;
|
let random = randomFloat() * totalWeight;
|
||||||
|
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
random -= item.weight;
|
random -= item.weight;
|
||||||
|
|||||||
@@ -139,14 +139,14 @@ Frank</textarea
|
|||||||
<script>
|
<script>
|
||||||
import { CopyFeedback } from "@/lib/client/clipboard";
|
import { CopyFeedback } from "@/lib/client/clipboard";
|
||||||
import { createErrorDisplay } from "@/lib/client/validation";
|
import { createErrorDisplay } from "@/lib/client/validation";
|
||||||
|
import { randomInt, randomFloat } from "@/lib/client/random";
|
||||||
|
import { getClientT } from "@/lib/client/i18n";
|
||||||
|
|
||||||
const isRu = document.documentElement.lang === "ru";
|
const T = getClientT();
|
||||||
const COPY_LABEL = isRu ? "Копировать" : "Copy";
|
const COPY_LABEL = T.copy;
|
||||||
const COPIED_LABEL = isRu ? "Скопировано" : "Copied";
|
const COPIED_LABEL = T.copied;
|
||||||
const ERR_MIN2 = isRu
|
const ERR_MIN2 = T.errAddAtLeast2;
|
||||||
? "Добавьте хотя бы 2 элемента."
|
const ERR_MAX24 = T.errMax24;
|
||||||
: "Add at least 2 items.";
|
|
||||||
const ERR_MAX24 = isRu ? "Максимум 24 элемента." : "Maximum 24 items.";
|
|
||||||
|
|
||||||
const COLORS = [
|
const COLORS = [
|
||||||
"#534AB7",
|
"#534AB7",
|
||||||
@@ -299,19 +299,19 @@ Frank</textarea
|
|||||||
|
|
||||||
const n = items.length;
|
const n = items.length;
|
||||||
const seg = TAU / n;
|
const seg = TAU / n;
|
||||||
const winnerIdx = Math.floor(Math.random() * n);
|
const winnerIdx = randomInt(0, n - 1);
|
||||||
|
|
||||||
const desiredPhase = (((-(winnerIdx + 0.5) * seg) % TAU) + TAU) % TAU;
|
const desiredPhase = (((-(winnerIdx + 0.5) * seg) % TAU) + TAU) % TAU;
|
||||||
const currentPhase = ((currentRotation % TAU) + TAU) % TAU;
|
const currentPhase = ((currentRotation % TAU) + TAU) % TAU;
|
||||||
let delta = (desiredPhase - currentPhase + TAU) % TAU;
|
let delta = (desiredPhase - currentPhase + TAU) % TAU;
|
||||||
if (delta < 0.5) delta += TAU;
|
if (delta < 0.5) delta += TAU;
|
||||||
|
|
||||||
const jitter = (Math.random() - 0.5) * seg * 0.55;
|
const jitter = (randomFloat() - 0.5) * seg * 0.55;
|
||||||
const fullTurns = 5 + Math.floor(Math.random() * 3);
|
const fullTurns = randomInt(5, 7);
|
||||||
const totalDelta = fullTurns * TAU + delta + jitter;
|
const totalDelta = fullTurns * TAU + delta + jitter;
|
||||||
|
|
||||||
const startRot = currentRotation;
|
const startRot = currentRotation;
|
||||||
const duration = 4000 + Math.random() * 2000;
|
const duration = 4000 + randomFloat() * 2000;
|
||||||
const t0 = performance.now();
|
const t0 = performance.now();
|
||||||
|
|
||||||
isSpinning = true;
|
isSpinning = true;
|
||||||
@@ -378,22 +378,22 @@ Frank</textarea
|
|||||||
|
|
||||||
for (let i = 0; i < 60; i++) {
|
for (let i = 0; i < 60; i++) {
|
||||||
const el = document.createElement("div");
|
const el = document.createElement("div");
|
||||||
const w = 4 + Math.random() * 6;
|
const w = 4 + randomFloat() * 6;
|
||||||
const h = 4 + Math.random() * 6;
|
const h = 4 + randomFloat() * 6;
|
||||||
el.style.cssText =
|
el.style.cssText =
|
||||||
`position:fixed;width:${w}px;height:${h}px;` +
|
`position:fixed;width:${w}px;height:${h}px;` +
|
||||||
`background:${COLORS[i % COLORS.length]};border-radius:1px;` +
|
`background:${COLORS[i % COLORS.length]};border-radius:1px;` +
|
||||||
`pointer-events:none;left:${cx}px;top:${cy}px;will-change:transform,opacity;z-index:9999;`;
|
`pointer-events:none;left:${cx}px;top:${cy}px;will-change:transform,opacity;z-index:9999;`;
|
||||||
document.body.appendChild(el);
|
document.body.appendChild(el);
|
||||||
|
|
||||||
const angle = Math.random() * TAU;
|
const angle = randomFloat() * TAU;
|
||||||
const speed = 4 + Math.random() * 7;
|
const speed = 4 + randomFloat() * 7;
|
||||||
particles.push({
|
particles.push({
|
||||||
el,
|
el,
|
||||||
x: cx,
|
x: cx,
|
||||||
y: cy,
|
y: cy,
|
||||||
vx: Math.cos(angle) * speed,
|
vx: Math.cos(angle) * speed,
|
||||||
vy: -(Math.abs(Math.sin(angle)) * speed + 2 + Math.random() * 4),
|
vy: -(Math.abs(Math.sin(angle)) * speed + 2 + randomFloat() * 4),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -111,8 +111,11 @@ const confidenceLabel = isRu ? "Уверенность" : "Confidence";
|
|||||||
<script>
|
<script>
|
||||||
import { CopyFeedback } from "@/lib/client/clipboard";
|
import { CopyFeedback } from "@/lib/client/clipboard";
|
||||||
import { popElement } from "@/lib/client/animations";
|
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 T = getClientT();
|
||||||
const ANSWERS = isRu
|
const ANSWERS = isRu
|
||||||
? ["ДА", "НЕТ", "ВОЗМОЖНО"]
|
? ["ДА", "НЕТ", "ВОЗМОЖНО"]
|
||||||
: ["YES", "NO", "MAYBE"];
|
: ["YES", "NO", "MAYBE"];
|
||||||
@@ -149,8 +152,8 @@ const confidenceLabel = isRu ? "Уверенность" : "Confidence";
|
|||||||
}
|
}
|
||||||
|
|
||||||
function generate() {
|
function generate() {
|
||||||
const answer = ANSWERS[Math.floor(Math.random() * ANSWERS.length)];
|
const answer = ANSWERS[randomInt(0, ANSWERS.length - 1)];
|
||||||
const confidence = Math.floor(Math.random() * 51) + 50;
|
const confidence = randomInt(50, 100);
|
||||||
|
|
||||||
resultEl.textContent = answer;
|
resultEl.textContent = answer;
|
||||||
copyBtn.classList.remove("invisible");
|
copyBtn.classList.remove("invisible");
|
||||||
|
|||||||
Reference in New Issue
Block a user