refactor: i18n routing, shared GeneratorLayout, analytics component, content collections
- Add Astro native i18n routing (prefixDefaultLocale: false) - Introduce GeneratorLayout to deduplicate all generator pages - Enrich generator data with pageTitle, ruPageTitle, howTo/whenTo arrays - Make EN and RU generator pages identical using @/ path aliases - Extract analytics (Yandex Metrika + Top.Mail.Ru) into Analytics.astro - Move tracker IDs to src/data/config.ts, inject via define:vars - Migrate generator metadata to Content Collections (src/content/generators/*.json) - Add Zod schema with .strict() validation in src/lib/generator-schema.ts - Update CLAUDE.md with new conventions
This commit is contained in:
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"_variables": {
|
|
||||||
"lastUpdateCheck": 1778278468140
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Vendored
-1
@@ -1 +0,0 @@
|
|||||||
/// <reference types="astro/client" />
|
|
||||||
@@ -2,3 +2,4 @@ node_modules/
|
|||||||
dist/
|
dist/
|
||||||
.env
|
.env
|
||||||
*.log
|
*.log
|
||||||
|
.astro/
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|||||||
|
|
||||||
Randify is a bilingual (EN/RU) static site (Astro 4 + Tailwind CSS 4) hosting random-value generators. All 10 generators are live. Deployed to randify.pro on reg.ru via GitHub Actions + rsync on push to `main`.
|
Randify is a bilingual (EN/RU) static site (Astro 4 + Tailwind CSS 4) hosting random-value generators. All 10 generators are live. Deployed to randify.pro on reg.ru via GitHub Actions + rsync on push to `main`.
|
||||||
|
|
||||||
|
**i18n is handled via Astro's built-in i18n routing.** English pages live at `src/pages/`, Russian pages at `src/pages/ru/`. Both share identical templates because `BaseLayout`, `GeneratorLayout`, and components derive the active locale from `Astro.currentLocale`.
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -27,16 +29,29 @@ No test runner or linter is configured.
|
|||||||
|
|
||||||
Every new generator must be created in **both English and Russian simultaneously**.
|
Every new generator must be created in **both English and Russian simultaneously**.
|
||||||
|
|
||||||
1. **`src/data/generators.ts`** — add an entry with all fields: `slug`, `title`, `description`, `icon`, `status: 'live'`, `seoTitle`, `seoDescription`, `ruTitle`, `ruDescription`, `ruSeoTitle`, `ruSeoDescription`.
|
1. **`src/content/generators/<slug>.json`** — create a JSON file with all fields: `slug`, `title`, `description`, `icon`, `status`, `seoTitle`, `seoDescription`, `ruTitle`, `ruDescription`, `ruSeoTitle`, `ruSeoDescription`, `pageTitle`, `ruPageTitle`, `howTo`, `whenTo`, `ruHowTo`, `ruWhenTo`. The schema is defined in `src/lib/generator-schema.ts` and validated automatically at build time via Zod (`.strict()` — unknown fields will fail the build).
|
||||||
|
|
||||||
2. **`src/components/generators/<Name>Generator.astro`** — the interactive component. Language detection pattern:
|
2. **`src/components/generators/<Name>Generator.astro`** — the interactive component. Language detection pattern:
|
||||||
- Frontmatter: `const isRu = Astro.url.pathname.startsWith('/ru'); const T = useT(isRu ? 'ru' : 'en');`
|
- Frontmatter: `const isRu = Astro.url.pathname.startsWith('/ru'); const T = useT(isRu ? 'ru' : 'en');`
|
||||||
- Client script: `const isRu = document.documentElement.lang === 'ru';`
|
- Client script: `const isRu = document.documentElement.lang === 'ru';`
|
||||||
- Use `T.*` keys for all user-visible strings in the template; use `isRu` ternaries in the `<script>` block.
|
- Use `T.*` keys for all user-visible strings in the template; use `isRu` ternaries in the `<script>` block.
|
||||||
|
|
||||||
3. **`src/pages/generators/<slug>.astro`** — English page with `<BaseLayout lang="en">`.
|
3. **`src/pages/generators/<slug>.astro`** — create the page using `GeneratorLayout`:
|
||||||
|
```astro
|
||||||
|
---
|
||||||
|
import GeneratorLayout from '@/layouts/GeneratorLayout.astro';
|
||||||
|
import <Name>Generator from '@/components/generators/<Name>Generator.astro';
|
||||||
|
import { generators } from '@/data/generators';
|
||||||
|
|
||||||
4. **`src/pages/ru/generators/<slug>.astro`** — Russian page with `<BaseLayout lang="ru">`, `ruSeoTitle`/`ruSeoDescription`, breadcrumb back to `/ru/`, Russian `<SeoBlock>` content.
|
const generator = generators.find((g) => g.slug === '<slug>')!;
|
||||||
|
---
|
||||||
|
|
||||||
|
<GeneratorLayout generator={generator}>
|
||||||
|
<<Name>Generator />
|
||||||
|
</GeneratorLayout>
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **`src/pages/ru/generators/<slug>.astro`** — **copy the English file exactly**. Because both files use `@/` path aliases and `GeneratorLayout` derives the locale from `Astro.currentLocale`, the file content is identical for both languages.
|
||||||
|
|
||||||
**Critical:** Never use a shared dynamic `[slug].astro` for Russian pages. Astro bundles scripts from all imported components — using a single file that imports all 10 generators causes every Russian page to run all 10 scripts, breaking them. Each page must be its own file importing only its generator.
|
**Critical:** Never use a shared dynamic `[slug].astro` for Russian pages. Astro bundles scripts from all imported components — using a single file that imports all 10 generators causes every Russian page to run all 10 scripts, breaking them. Each page must be its own file importing only its generator.
|
||||||
|
|
||||||
@@ -46,13 +61,15 @@ Every new generator must be created in **both English and Russian simultaneously
|
|||||||
- **`src/components/LanguageSwitcher.astro`** — fixed top-right EN/RU toggle; persists choice in `localStorage('lang-pref')`.
|
- **`src/components/LanguageSwitcher.astro`** — fixed top-right EN/RU toggle; persists choice in `localStorage('lang-pref')`.
|
||||||
- **`src/layouts/BaseLayout.astro`** — accepts `lang` prop (`'en' | 'ru'`), sets `<html lang>`, injects `hreflang` alternates, and includes auto-redirect script (first visit, Russian browser → `/ru/`).
|
- **`src/layouts/BaseLayout.astro`** — accepts `lang` prop (`'en' | 'ru'`), sets `<html lang>`, injects `hreflang` alternates, and includes auto-redirect script (first visit, Russian browser → `/ru/`).
|
||||||
- English pages: `/generators/<slug>/` — Russian pages: `/ru/generators/<slug>/`
|
- English pages: `/generators/<slug>/` — Russian pages: `/ru/generators/<slug>/`
|
||||||
|
- Path aliases `@/*` resolve to `src/*` and are used in all page files so EN and RU templates can be identical.
|
||||||
|
- `GeneratorLayout.astro` wraps every generator page: breadcrumb, header, AdBanner, SeoBlock, and slot for the interactive component.
|
||||||
|
|
||||||
## Key conventions
|
## Key conventions
|
||||||
|
|
||||||
- Accent color: `#534AB7` (CSS var `--accent` in BaseLayout).
|
- Accent color: `#534AB7` (CSS var `--accent` in BaseLayout).
|
||||||
- All SVG icons are inlined strings in the `icons` map inside `GeneratorCard.astro`; add new ones there.
|
- All SVG icons are inlined strings in the `icons` map inside `GeneratorCard.astro`; add new ones there.
|
||||||
- `GeneratorCard.astro` accepts `lang` prop — pass `lang="ru"` on Russian pages to get `ruTitle`/`ruDescription` and `/ru/` links.
|
- `GeneratorCard.astro` auto-detects locale from `Astro.currentLocale` when no `lang` prop is passed.
|
||||||
- `SeoBlock.astro` accepts `lang` prop for translated "How to use" / "When to use" headers.
|
- `SeoBlock.astro` accepts `lang` prop for translated "How to use" / "When to use" headers.
|
||||||
- `AdBanner.astro` randomises between two Yandex referral links; three size variants: `leaderboard`, `rectangle`, `tile`.
|
- `AdBanner.astro` randomises between two Yandex referral links; three size variants: `leaderboard`, `rectangle`, `tile`.
|
||||||
- Yandex Metrika counter (ID 109130319) is in `BaseLayout.astro`.
|
- Yandex Metrika counter (ID `109130319`) and Top.Mail.Ru counter (ID `3765043`) live in `src/components/Analytics.astro`. IDs are configured in `src/data/config.ts` and injected via `define:vars`.
|
||||||
- No client-side router — each generator is a separate static HTML page.
|
- No client-side router — each generator is a separate static HTML page.
|
||||||
|
|||||||
+11
-4
@@ -1,11 +1,18 @@
|
|||||||
import { defineConfig } from 'astro/config';
|
import { defineConfig } from "astro/config";
|
||||||
import tailwindcss from '@tailwindcss/vite';
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
import sitemap from '@astrojs/sitemap';
|
import sitemap from "@astrojs/sitemap";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
site: 'https://randify.pro',
|
site: "https://randify.pro",
|
||||||
integrations: [sitemap()],
|
integrations: [sitemap()],
|
||||||
vite: {
|
vite: {
|
||||||
plugins: [tailwindcss()],
|
plugins: [tailwindcss()],
|
||||||
},
|
},
|
||||||
|
i18n: {
|
||||||
|
defaultLocale: "en",
|
||||||
|
locales: ["en", "ru"],
|
||||||
|
routing: {
|
||||||
|
prefixDefaultLocale: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
---
|
||||||
|
import { analytics } from "@/data/config";
|
||||||
|
|
||||||
|
const yandexId = analytics.yandexMetrikaId;
|
||||||
|
const mailRuId = analytics.topMailRuId;
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- Yandex.Metrika counter -->
|
||||||
|
<script type="text/javascript" define:vars={{ yandexId }}>
|
||||||
|
(function (m, e, t, r, i, k, a) {
|
||||||
|
m[i] =
|
||||||
|
m[i] ||
|
||||||
|
function () {
|
||||||
|
(m[i].a = m[i].a || []).push(arguments);
|
||||||
|
};
|
||||||
|
m[i].l = 1 * new Date();
|
||||||
|
for (var j = 0; j < document.scripts.length; j++) {
|
||||||
|
if (document.scripts[j].src === r) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
((k = e.createElement(t)),
|
||||||
|
(a = e.getElementsByTagName(t)[0]),
|
||||||
|
(k.async = 1),
|
||||||
|
(k.src = r),
|
||||||
|
a.parentNode.insertBefore(k, a));
|
||||||
|
})(
|
||||||
|
window,
|
||||||
|
document,
|
||||||
|
"script",
|
||||||
|
"https://mc.yandex.ru/metrika/tag.js?id=" + String(yandexId),
|
||||||
|
"ym",
|
||||||
|
);
|
||||||
|
ym(Number(yandexId), "init", {
|
||||||
|
ssr: true,
|
||||||
|
webvisor: true,
|
||||||
|
clickmap: true,
|
||||||
|
ecommerce: "dataLayer",
|
||||||
|
referrer: document.referrer,
|
||||||
|
url: location.href,
|
||||||
|
accurateTrackBounce: true,
|
||||||
|
trackLinks: true,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<noscript
|
||||||
|
><div>
|
||||||
|
<img
|
||||||
|
src={`https://mc.yandex.ru/watch/${yandexId}`}
|
||||||
|
style="position:absolute; left:-9999px;"
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
</div></noscript
|
||||||
|
>
|
||||||
|
<!-- /Yandex.Metrika counter -->
|
||||||
|
|
||||||
|
<!-- Top.Mail.Ru counter -->
|
||||||
|
<script type="text/javascript" define:vars={{ mailRuId }}>
|
||||||
|
var _tmr = window._tmr || (window._tmr = []);
|
||||||
|
_tmr.push({
|
||||||
|
id: String(mailRuId),
|
||||||
|
type: "pageView",
|
||||||
|
start: new Date().getTime(),
|
||||||
|
});
|
||||||
|
(function (d, w, id) {
|
||||||
|
if (d.getElementById(id)) return;
|
||||||
|
var ts = d.createElement("script");
|
||||||
|
ts.type = "text/javascript";
|
||||||
|
ts.async = true;
|
||||||
|
ts.id = id;
|
||||||
|
ts.src = "https://top-fwz1.mail.ru/js/code.js";
|
||||||
|
var f = function () {
|
||||||
|
var s = d.getElementsByTagName("script")[0];
|
||||||
|
s.parentNode.insertBefore(ts, s);
|
||||||
|
};
|
||||||
|
if (w.opera == "[object Opera]") {
|
||||||
|
d.addEventListener("DOMContentLoaded", f, false);
|
||||||
|
} else {
|
||||||
|
f();
|
||||||
|
}
|
||||||
|
})(document, window, "tmr-code");
|
||||||
|
</script>
|
||||||
|
<noscript
|
||||||
|
><div>
|
||||||
|
<img
|
||||||
|
src={`https://top-fwz1.mail.ru/counter?id=${mailRuId};js=na`}
|
||||||
|
style="position:absolute;left:-9999px;"
|
||||||
|
alt="Top.Mail.Ru"
|
||||||
|
/>
|
||||||
|
</div></noscript
|
||||||
|
>
|
||||||
|
<!-- /Top.Mail.Ru counter -->
|
||||||
@@ -8,7 +8,8 @@ interface Props {
|
|||||||
lang?: Lang;
|
lang?: Lang;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { generator, lang = 'en' } = Astro.props;
|
const { generator, lang: langProp } = Astro.props;
|
||||||
|
const lang = (langProp || (Astro.currentLocale as Lang) || 'en');
|
||||||
const T = useT(lang);
|
const T = useT(lang);
|
||||||
const { slug, icon, status } = generator;
|
const { slug, icon, status } = generator;
|
||||||
const isLive = status === 'live';
|
const isLive = status === 'live';
|
||||||
|
|||||||
@@ -1,29 +1,27 @@
|
|||||||
---
|
---
|
||||||
interface Props {
|
const currentPath = Astro.url.pathname;
|
||||||
lang: 'en' | 'ru';
|
const isRu = currentPath.startsWith('/ru');
|
||||||
alternatePath: string;
|
const alternatePath = isRu
|
||||||
}
|
? (currentPath.replace(/^\/ru/, '') || '/')
|
||||||
|
: ('/ru' + currentPath);
|
||||||
const { lang, alternatePath } = Astro.props;
|
|
||||||
const targetLang = lang === 'en' ? 'ru' : 'en';
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="fixed top-3 right-3 z-50 flex items-center gap-1 bg-zinc-900/80 border border-zinc-800 rounded-lg px-1 py-1 backdrop-blur-sm text-xs font-semibold">
|
<div class="fixed top-3 right-3 z-50 flex items-center gap-1 bg-zinc-900/80 border border-zinc-800 rounded-lg px-1 py-1 backdrop-blur-sm text-xs font-semibold">
|
||||||
<a
|
<a
|
||||||
href={lang === 'ru' ? alternatePath : '#'}
|
href={isRu ? alternatePath : '#'}
|
||||||
id="lang-en"
|
id="lang-en"
|
||||||
class={`px-2 py-1 rounded-md transition-colors ${lang === 'en' ? 'bg-[#534AB7] text-white' : 'text-zinc-400 hover:text-zinc-200'}`}
|
class={`px-2 py-1 rounded-md transition-colors ${isRu ? 'text-zinc-400 hover:text-zinc-200' : 'bg-[#534AB7] text-white'}`}
|
||||||
aria-current={lang === 'en' ? 'true' : undefined}
|
aria-current={isRu ? undefined : 'true'}
|
||||||
data-target-lang="en"
|
data-target-lang="en"
|
||||||
data-alternate={alternatePath}
|
data-alternate={isRu ? alternatePath : ''}
|
||||||
>EN</a>
|
>EN</a>
|
||||||
<a
|
<a
|
||||||
href={lang === 'en' ? alternatePath : '#'}
|
href={isRu ? '#' : alternatePath}
|
||||||
id="lang-ru"
|
id="lang-ru"
|
||||||
class={`px-2 py-1 rounded-md transition-colors ${lang === 'ru' ? 'bg-[#534AB7] text-white' : 'text-zinc-400 hover:text-zinc-200'}`}
|
class={`px-2 py-1 rounded-md transition-colors ${isRu ? 'bg-[#534AB7] text-white' : 'text-zinc-400 hover:text-zinc-200'}`}
|
||||||
aria-current={lang === 'ru' ? 'true' : undefined}
|
aria-current={isRu ? 'true' : undefined}
|
||||||
data-target-lang="ru"
|
data-target-lang="ru"
|
||||||
data-alternate={alternatePath}
|
data-alternate={isRu ? '' : alternatePath}
|
||||||
>RU</a>
|
>RU</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { defineCollection } from "astro:content";
|
||||||
|
import { generatorSchema } from "@/lib/generator-schema";
|
||||||
|
|
||||||
|
const generators = defineCollection({
|
||||||
|
type: "data",
|
||||||
|
schema: generatorSchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const collections = {
|
||||||
|
generators,
|
||||||
|
};
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"slug": "cards",
|
||||||
|
"title": "Card",
|
||||||
|
"description": "Draw a random playing card from a standard deck.",
|
||||||
|
"icon": "square-stack",
|
||||||
|
"status": "live",
|
||||||
|
"seoTitle": "Random Card Generator — Draw from a Deck | Randify",
|
||||||
|
"seoDescription": "Draw random playing cards from a standard 52-card deck. Pick any number of cards, with or without replacement. Free online card picker.",
|
||||||
|
"ruTitle": "Карта",
|
||||||
|
"ruDescription": "Вытащите случайную карту из стандартной колоды.",
|
||||||
|
"ruSeoTitle": "Генератор случайных карт | Randify",
|
||||||
|
"ruSeoDescription": "Тяните случайные карты из колоды 52 карт. С повторениями или без. Бесплатный онлайн-генератор карт.",
|
||||||
|
"pageTitle": "Card Generator",
|
||||||
|
"ruPageTitle": "Генератор карт",
|
||||||
|
"howTo": [
|
||||||
|
"Set how many cards to draw (1–52).",
|
||||||
|
"Toggle Allow duplicates if you want cards to repeat.",
|
||||||
|
"Click Draw — cards are shown as mini card tiles.",
|
||||||
|
"Click Copy to copy the result (e.g. A♠, K♥) to your clipboard."
|
||||||
|
],
|
||||||
|
"whenTo": [
|
||||||
|
"Drawing a random hand for card games.",
|
||||||
|
"Practising card probability and statistics.",
|
||||||
|
"Simulating a random card pull for magic tricks or puzzles.",
|
||||||
|
"Running fair draws without a physical deck."
|
||||||
|
],
|
||||||
|
"ruHowTo": [
|
||||||
|
"Укажите количество карт для розыгрыша.",
|
||||||
|
"Включите «Разрешить повторения», если нужны дубли.",
|
||||||
|
"Нажмите «Тянуть» — карты появятся на экране.",
|
||||||
|
"Нажмите «Копировать», чтобы скопировать результат."
|
||||||
|
],
|
||||||
|
"ruWhenTo": [
|
||||||
|
"Случайная раздача карт для карточных игр.",
|
||||||
|
"Выбор случайных карт для гаданий.",
|
||||||
|
"Генерация случайных комбинаций карт для тестирования."
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"slug": "coin",
|
||||||
|
"title": "Coin Flip",
|
||||||
|
"description": "Flip one or more coins and see heads or tails.",
|
||||||
|
"icon": "circle-dollar-sign",
|
||||||
|
"status": "live",
|
||||||
|
"seoTitle": "Coin Flip Online — Heads or Tails | Randify",
|
||||||
|
"seoDescription": "Flip a virtual coin online. Flip multiple coins at once and see how many heads and tails you get. Free random coin toss simulator.",
|
||||||
|
"ruTitle": "Монетка",
|
||||||
|
"ruDescription": "Подбросьте одну или несколько монет — орёл или решка.",
|
||||||
|
"ruSeoTitle": "Подбросить монетку онлайн — орёл или решка | Randify",
|
||||||
|
"ruSeoDescription": "Подбрасывайте виртуальную монету онлайн. Одну или несколько сразу. Бесплатный симулятор подбрасывания монеты.",
|
||||||
|
"pageTitle": "Coin Flip",
|
||||||
|
"ruPageTitle": "Подбросить монетку",
|
||||||
|
"howTo": [
|
||||||
|
"Drag the slider to choose how many coins to flip (1–20).",
|
||||||
|
"Click Flip — each coin shows H for heads or T for tails.",
|
||||||
|
"When flipping more than one coin, the heads/tails count is shown below.",
|
||||||
|
"Click Copy to copy all results to the clipboard."
|
||||||
|
],
|
||||||
|
"whenTo": [
|
||||||
|
"Making a quick heads-or-tails decision.",
|
||||||
|
"Simulating a coin toss for a sports match or game.",
|
||||||
|
"Demonstrating probability and the law of large numbers.",
|
||||||
|
"Settling a fair 50/50 choice between two options."
|
||||||
|
],
|
||||||
|
"ruHowTo": [
|
||||||
|
"Выберите количество монет с помощью ползунка.",
|
||||||
|
"Нажмите «Подбросить» — орёл или решка.",
|
||||||
|
"При нескольких монетах отображается итоговый счёт.",
|
||||||
|
"Нажмите «Копировать», чтобы сохранить результат."
|
||||||
|
],
|
||||||
|
"ruWhenTo": [
|
||||||
|
"Принятие решений по принципу «орёл или решка».",
|
||||||
|
"Определение очерёдности в игре.",
|
||||||
|
"Симуляция случайных событий с равной вероятностью."
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"slug": "colors",
|
||||||
|
"title": "Color",
|
||||||
|
"description": "Generate a random color in HEX, RGB, or HSL format.",
|
||||||
|
"icon": "palette",
|
||||||
|
"status": "live",
|
||||||
|
"seoTitle": "Random Color Generator — HEX, RGB, HSL | Randify",
|
||||||
|
"seoDescription": "Get a random color in HEX, RGB or HSL format with one click. Free online color randomizer for designers and developers.",
|
||||||
|
"ruTitle": "Цвет",
|
||||||
|
"ruDescription": "Генерируйте случайный цвет в форматах HEX, RGB или HSL.",
|
||||||
|
"ruSeoTitle": "Генератор случайных цветов — HEX, RGB, HSL | Randify",
|
||||||
|
"ruSeoDescription": "Получайте случайные цвета в форматах HEX, RGB и HSL одним нажатием. Бесплатный рандомайзер цветов для дизайнеров.",
|
||||||
|
"pageTitle": "Color Generator",
|
||||||
|
"ruPageTitle": "Генератор цветов",
|
||||||
|
"howTo": [
|
||||||
|
"Click Generate to produce a random color.",
|
||||||
|
"Switch between HEX, RGB, and HSL tabs to see the value in your preferred format.",
|
||||||
|
"Click the color value or the copy icon to copy it to the clipboard."
|
||||||
|
],
|
||||||
|
"whenTo": [
|
||||||
|
"Finding inspiration for a new design palette.",
|
||||||
|
"Picking a placeholder color while prototyping.",
|
||||||
|
"Selecting a random theme color for a project or presentation.",
|
||||||
|
"Teaching or exploring color theory and formats."
|
||||||
|
],
|
||||||
|
"ruHowTo": [
|
||||||
|
"Нажмите «Сгенерировать», чтобы получить случайный цвет.",
|
||||||
|
"Цвет отображается в форматах HEX, RGB и HSL одновременно.",
|
||||||
|
"Нажмите на иконку копирования рядом с нужным форматом."
|
||||||
|
],
|
||||||
|
"ruWhenTo": [
|
||||||
|
"Поиск вдохновения для цветовой палитры.",
|
||||||
|
"Случайный выбор цвета для дизайна или иллюстрации.",
|
||||||
|
"Генерация тестовых данных с цветовыми значениями."
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"slug": "dice",
|
||||||
|
"title": "Dice",
|
||||||
|
"description": "Roll one or more dice with any number of sides.",
|
||||||
|
"icon": "dice-6",
|
||||||
|
"status": "live",
|
||||||
|
"seoTitle": "Dice Roller — d4 d6 d20 + Full Notation | D&D Dice | Randify",
|
||||||
|
"seoDescription": "Free online dice roller with full XdY+Z notation, keep/drop (4d6dl1), exploding dice (3d6!), reroll, advantage/disadvantage, and roll history. Perfect for D&D, Pathfinder, and Savage Worlds.",
|
||||||
|
"ruTitle": "Кубик",
|
||||||
|
"ruDescription": "Бросьте один или несколько кубиков с любым числом граней.",
|
||||||
|
"ruSeoTitle": "Бросить кубик онлайн — d4 d6 d8 d10 d12 d20 | D&D | Randify",
|
||||||
|
"ruSeoDescription": "Бесплатный онлайн-бросок кубиков с полной нотацией XdY+Z, keep/drop (4d6dl1), взрывающимися кубиками (3d6!), перебросом, преимуществом/помехой и историей. Для D&D, Pathfinder и Savage Worlds.",
|
||||||
|
"pageTitle": "Dice Generator",
|
||||||
|
"ruPageTitle": "Бросить кубик онлайн",
|
||||||
|
"howTo": [
|
||||||
|
"Type a notation like 2d6+3 or 1d20 — or use the die buttons and count.",
|
||||||
|
"Pick a mode: Normal, Advantage, or Disadvantage (single die only).",
|
||||||
|
"Click Roll — each die result appears with the total sum.",
|
||||||
|
"Maximum rolls are highlighted in purple; ones are highlighted in red."
|
||||||
|
],
|
||||||
|
"whenTo": [
|
||||||
|
"Rolling dice for D&D, Pathfinder, or any tabletop RPG.",
|
||||||
|
"Using dice notation shortcuts like 2d6+3 or 1d20 for fast rolling.",
|
||||||
|
"Rolling with advantage or disadvantage for D&D 5e mechanics.",
|
||||||
|
"Generating character stats with 4d6 drop-lowest method.",
|
||||||
|
"Rolling exploding dice for Savage Worlds or house rules (e.g. 3d6!).",
|
||||||
|
"Using keep/drop modifiers like 4d6kh3 to keep highest rolls."
|
||||||
|
],
|
||||||
|
"ruHowTo": [
|
||||||
|
"Введите нотацию, например 2d6+3 или 1d20 — или настройте кубики вручную.",
|
||||||
|
"Выберите режим: Обычный, Преимущество или Помеха (только для одного кубика).",
|
||||||
|
"Нажмите «Бросить» — результаты отобразятся мгновенно.",
|
||||||
|
"Максимумы подсвечены фиолетовым, единицы — красным."
|
||||||
|
],
|
||||||
|
"ruWhenTo": [
|
||||||
|
"Броски в настольных ролевых играх (D&D и другие).",
|
||||||
|
"Быстрые броски через нотацию: 2d6+3, 1d20 и любые другие.",
|
||||||
|
"Преимущество и помеха для механик D&D 5e.",
|
||||||
|
"Бросок характеристик: 4d6, отбросить наименьший (4d6dl1).",
|
||||||
|
"Взрывающиеся кубики для Savage Worlds или хаус-рулов (например, 3d6!).",
|
||||||
|
"Использование keep/drop модификаторов типа 4d6kh3 (оставить три лучших)."
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"slug": "list",
|
||||||
|
"title": "List Picker",
|
||||||
|
"description": "Paste a list of items and pick random winners.",
|
||||||
|
"icon": "list",
|
||||||
|
"status": "live",
|
||||||
|
"seoTitle": "Random List Picker — Pick a Random Winner | Randify",
|
||||||
|
"seoDescription": "Paste any list of names or items and pick random winners instantly. Great for giveaways, choosing who goes first, or any random selection.",
|
||||||
|
"ruTitle": "Список",
|
||||||
|
"ruDescription": "Вставьте список элементов и выберите случайных победителей.",
|
||||||
|
"ruSeoTitle": "Случайный выбор из списка | Randify",
|
||||||
|
"ruSeoDescription": "Вставьте список имён или вариантов и выберите случайных победителей мгновенно. Идеально для розыгрышей и жеребьёвок.",
|
||||||
|
"pageTitle": "List Picker",
|
||||||
|
"ruPageTitle": "Случайный выбор из списка",
|
||||||
|
"howTo": [
|
||||||
|
"Type or paste your items into the text area — one item per line.",
|
||||||
|
"Set how many items to pick in the Pick field.",
|
||||||
|
"Toggle Allow duplicates if the same item can be chosen more than once.",
|
||||||
|
"Click Pick (or press Ctrl+Enter) — the selected items appear as chips."
|
||||||
|
],
|
||||||
|
"whenTo": [
|
||||||
|
"Choosing a random winner for a giveaway or contest.",
|
||||||
|
"Picking who presents first in a meeting or class.",
|
||||||
|
"Organising a Secret Santa or gift exchange draw.",
|
||||||
|
"Selecting a random movie, restaurant, or activity from a list."
|
||||||
|
],
|
||||||
|
"ruHowTo": [
|
||||||
|
"Введите список элементов — по одному на строку.",
|
||||||
|
"Укажите, сколько элементов выбрать.",
|
||||||
|
"При необходимости разрешите повторения.",
|
||||||
|
"Нажмите «Выбрать» — результаты появятся на экране."
|
||||||
|
],
|
||||||
|
"ruWhenTo": [
|
||||||
|
"Розыгрыш победителей конкурса.",
|
||||||
|
"Случайное распределение задач между участниками команды.",
|
||||||
|
"Выбор случайного варианта из списка."
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"slug": "lottery",
|
||||||
|
"title": "Lottery",
|
||||||
|
"description": "Draw a set of unique numbers for lottery-style picks.",
|
||||||
|
"icon": "ticket",
|
||||||
|
"status": "live",
|
||||||
|
"seoTitle": "Lottery Number Generator — Random Pick | Randify",
|
||||||
|
"seoDescription": "Draw a set of unique random lottery numbers from any range. Perfect for lotteries, raffles, and lucky draws. Free online lottery picker.",
|
||||||
|
"ruTitle": "Лотерея",
|
||||||
|
"ruDescription": "Вытащите набор уникальных чисел в стиле лотереи.",
|
||||||
|
"ruSeoTitle": "Генератор лотерейных номеров | Randify",
|
||||||
|
"ruSeoDescription": "Тяните уникальные случайные числа для лотереи, розыгрыша или жеребьёвки. Бесплатный онлайн-генератор лотерейных номеров.",
|
||||||
|
"pageTitle": "Lottery Generator",
|
||||||
|
"ruPageTitle": "Генератор лотереи",
|
||||||
|
"howTo": [
|
||||||
|
"Set the From and To fields to define your number range (e.g. 1 to 49).",
|
||||||
|
"Enter how many numbers to pick in the Pick field.",
|
||||||
|
"Click Draw — a sorted set of unique numbers appears.",
|
||||||
|
"Click Copy to copy all numbers to the clipboard."
|
||||||
|
],
|
||||||
|
"whenTo": [
|
||||||
|
"Generating lottery or lotto ticket numbers.",
|
||||||
|
"Running a fair raffle draw with unique entries.",
|
||||||
|
"Selecting a random sample from a numbered list.",
|
||||||
|
"Choosing lottery numbers for Powerball, EuroMillions, or local draws."
|
||||||
|
],
|
||||||
|
"ruHowTo": [
|
||||||
|
"Укажите диапазон чисел в полях «От» и «До».",
|
||||||
|
"Задайте количество чисел для розыгрыша.",
|
||||||
|
"Нажмите «Тянуть» — выпавшие числа появятся на экране.",
|
||||||
|
"Нажмите «Копировать», чтобы сохранить результат."
|
||||||
|
],
|
||||||
|
"ruWhenTo": [
|
||||||
|
"Розыгрыш лотерейных номеров.",
|
||||||
|
"Жеребьёвка участников конкурса.",
|
||||||
|
"Случайный выбор нескольких уникальных чисел."
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"slug": "numbers",
|
||||||
|
"title": "Number",
|
||||||
|
"description": "Pick a random number within any range you choose.",
|
||||||
|
"icon": "hash",
|
||||||
|
"status": "live",
|
||||||
|
"seoTitle": "Random Number Generator | Randify",
|
||||||
|
"seoDescription": "Generate a random number between any two values instantly. Free online random number picker — perfect for giveaways, games, and decisions.",
|
||||||
|
"ruTitle": "Число",
|
||||||
|
"ruDescription": "Выберите случайное число в любом диапазоне.",
|
||||||
|
"ruSeoTitle": "Генератор случайных чисел | Randify",
|
||||||
|
"ruSeoDescription": "Генерируйте случайное число в любом диапазоне мгновенно. Идеально для розыгрышей, игр и принятия решений.",
|
||||||
|
"pageTitle": "Number Generator",
|
||||||
|
"ruPageTitle": "Генератор чисел",
|
||||||
|
"howTo": [
|
||||||
|
"Enter the minimum value in the From field.",
|
||||||
|
"Enter the maximum value in the To field.",
|
||||||
|
"Click Generate — a random number within your range appears instantly.",
|
||||||
|
"Click the number to copy it to the clipboard."
|
||||||
|
],
|
||||||
|
"whenTo": [
|
||||||
|
"Picking a winner in a giveaway or raffle.",
|
||||||
|
"Choosing a random starting player in a board game.",
|
||||||
|
"Making a quick decision between numbered options.",
|
||||||
|
"Generating test data with values in a specific range."
|
||||||
|
],
|
||||||
|
"ruHowTo": [
|
||||||
|
"Введите минимальное значение в поле «От».",
|
||||||
|
"Введите максимальное значение в поле «До».",
|
||||||
|
"Нажмите «Сгенерировать» — случайное число появится мгновенно.",
|
||||||
|
"Нажмите на число, чтобы скопировать его в буфер обмена."
|
||||||
|
],
|
||||||
|
"ruWhenTo": [
|
||||||
|
"Выбор победителя в розыгрыше или конкурсе.",
|
||||||
|
"Определение случайного первого игрока в настольной игре.",
|
||||||
|
"Быстрое принятие решения между пронумерованными вариантами.",
|
||||||
|
"Генерация тестовых данных с числами в заданном диапазоне."
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"slug": "password",
|
||||||
|
"title": "Password",
|
||||||
|
"description": "Create a strong, random password with custom rules.",
|
||||||
|
"icon": "lock",
|
||||||
|
"status": "live",
|
||||||
|
"seoTitle": "Random Password Generator | Randify",
|
||||||
|
"seoDescription": "Create a strong random password with custom length, uppercase, lowercase, digits and symbols. Free and secure — runs entirely in your browser.",
|
||||||
|
"ruTitle": "Пароль",
|
||||||
|
"ruDescription": "Создайте надёжный случайный пароль с настраиваемыми правилами.",
|
||||||
|
"ruSeoTitle": "Генератор случайных паролей | Randify",
|
||||||
|
"ruSeoDescription": "Создавайте надёжные пароли с заглавными буквами, цифрами и символами. Бесплатно и безопасно — работает прямо в браузере.",
|
||||||
|
"pageTitle": "Password Generator",
|
||||||
|
"ruPageTitle": "Генератор паролей",
|
||||||
|
"howTo": [
|
||||||
|
"Drag the length slider to set how many characters you need (4–64).",
|
||||||
|
"Toggle uppercase, lowercase, digits, and symbols on or off.",
|
||||||
|
"Click Generate — a new password appears instantly.",
|
||||||
|
"Click the password to copy it to your clipboard."
|
||||||
|
],
|
||||||
|
"whenTo": [
|
||||||
|
"Creating a strong password for a new account.",
|
||||||
|
"Generating a temporary access credential.",
|
||||||
|
"Producing a random secret key for development or testing.",
|
||||||
|
"Replacing a weak or reused password."
|
||||||
|
],
|
||||||
|
"ruHowTo": [
|
||||||
|
"Задайте длину пароля с помощью ползунка.",
|
||||||
|
"Выберите типы символов: заглавные, строчные, цифры, спецсимволы.",
|
||||||
|
"Нажмите «Сгенерировать» — готовый пароль появится на экране.",
|
||||||
|
"Нажмите на пароль, чтобы скопировать его."
|
||||||
|
],
|
||||||
|
"ruWhenTo": [
|
||||||
|
"Создание надёжного пароля для нового аккаунта.",
|
||||||
|
"Генерация секретного ключа или токена.",
|
||||||
|
"Регулярное обновление паролей для повышения безопасности."
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"slug": "uuid",
|
||||||
|
"title": "UUID / Token",
|
||||||
|
"description": "Generate UUIDs, hex tokens, or random base64 strings.",
|
||||||
|
"icon": "fingerprint",
|
||||||
|
"status": "live",
|
||||||
|
"seoTitle": "UUID Generator — UUID v4, Hex & Base64 Tokens | Randify",
|
||||||
|
"seoDescription": "Generate random UUID v4, hex tokens, or base64 strings online. Cryptographically secure, runs in your browser. Free UUID and token generator.",
|
||||||
|
"ruTitle": "UUID / Токен",
|
||||||
|
"ruDescription": "Генерируйте UUID, hex-токены или случайные Base64-строки.",
|
||||||
|
"ruSeoTitle": "Генератор UUID и токенов | Randify",
|
||||||
|
"ruSeoDescription": "Генерируйте UUID v4, hex-токены и Base64-строки онлайн. Криптографически безопасно, работает в браузере.",
|
||||||
|
"pageTitle": "UUID / Token",
|
||||||
|
"ruPageTitle": "Генератор UUID и токенов",
|
||||||
|
"howTo": [
|
||||||
|
"Choose a token type: UUID v4, Hex, or Base64.",
|
||||||
|
"For Hex and Base64, set the byte length (4–64 bytes).",
|
||||||
|
"Enter how many tokens to generate (1–20).",
|
||||||
|
"Click Generate, then Copy all to copy every token to the clipboard."
|
||||||
|
],
|
||||||
|
"whenTo": [
|
||||||
|
"Generating unique IDs for database records or API resources.",
|
||||||
|
"Creating random session tokens or API keys during development.",
|
||||||
|
"Producing test data with realistic-looking identifiers.",
|
||||||
|
"Generating a one-time secret for environment variables or config files."
|
||||||
|
],
|
||||||
|
"ruHowTo": [
|
||||||
|
"Выберите тип: UUID v4, Hex или Base64.",
|
||||||
|
"Для Hex и Base64 задайте длину в байтах.",
|
||||||
|
"Укажите количество токенов.",
|
||||||
|
"Нажмите «Сгенерировать» и скопируйте результат."
|
||||||
|
],
|
||||||
|
"ruWhenTo": [
|
||||||
|
"Генерация уникальных идентификаторов для баз данных.",
|
||||||
|
"Создание токенов для API и сессий.",
|
||||||
|
"Генерация случайных ключей шифрования."
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"slug": "wheel",
|
||||||
|
"title": "Spin the Wheel",
|
||||||
|
"description": "Add your items, spin the wheel, and let chance decide.",
|
||||||
|
"icon": "pie-chart",
|
||||||
|
"status": "live",
|
||||||
|
"seoTitle": "Spin the Wheel — Random Picker | Randify",
|
||||||
|
"seoDescription": "Spin a customizable wheel of fortune to pick a random winner, choice, or option. Add your own items and let the wheel decide.",
|
||||||
|
"ruTitle": "Колесо фортуны",
|
||||||
|
"ruDescription": "Добавьте элементы, крутите колесо и пусть случай решит.",
|
||||||
|
"ruSeoTitle": "Колесо фортуны онлайн — случайный выбор | Randify",
|
||||||
|
"ruSeoDescription": "Крутите колесо фортуны онлайн. Добавьте свои варианты и пусть случай решит победителя.",
|
||||||
|
"pageTitle": "Spin the Wheel",
|
||||||
|
"ruPageTitle": "Колесо фортуны онлайн",
|
||||||
|
"howTo": [
|
||||||
|
"Type your items into the text box — one per line.",
|
||||||
|
"Click \"Update wheel\" to apply changes.",
|
||||||
|
"Hit \"Spin\" and watch the wheel decide.",
|
||||||
|
"Copy the result or spin again."
|
||||||
|
],
|
||||||
|
"whenTo": [
|
||||||
|
"Picking a winner for a giveaway or raffle.",
|
||||||
|
"Deciding whose turn it is in a game or meeting.",
|
||||||
|
"Choosing a random activity, movie, or restaurant.",
|
||||||
|
"Any decision you want to leave to chance."
|
||||||
|
],
|
||||||
|
"ruHowTo": [
|
||||||
|
"Введите варианты в поле — по одному на строку (от 2 до 24).",
|
||||||
|
"Колесо обновляется автоматически.",
|
||||||
|
"Нажмите «Крутить» и дождитесь результата.",
|
||||||
|
"Победитель выделяется — скопируйте его кнопкой «Копировать»."
|
||||||
|
],
|
||||||
|
"ruWhenTo": [
|
||||||
|
"Случайный выбор победителя розыгрыша.",
|
||||||
|
"Принятие решений в команде.",
|
||||||
|
"Распределение ролей или задач между участниками."
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export const siteUrl = 'https://randify.pro';
|
||||||
|
|
||||||
|
export const analytics = {
|
||||||
|
yandexMetrikaId: '109130319',
|
||||||
|
topMailRuId: '3765043',
|
||||||
|
} as const;
|
||||||
+19
-144
@@ -1,146 +1,21 @@
|
|||||||
export interface Generator {
|
import { generatorSchema } from "@/lib/generator-schema";
|
||||||
slug: string;
|
import type { Generator } from "@/lib/generator-schema";
|
||||||
title: string;
|
|
||||||
description: string;
|
const modules = import.meta.glob<{ default: unknown }>(
|
||||||
icon: string;
|
"../content/generators/*.json",
|
||||||
status: 'live' | 'coming-soon';
|
{
|
||||||
seoTitle: string;
|
eager: true,
|
||||||
seoDescription: string;
|
},
|
||||||
ruTitle: string;
|
);
|
||||||
ruDescription: string;
|
|
||||||
ruSeoTitle: string;
|
export const generators: Generator[] = Object.values(modules).map((mod) =>
|
||||||
ruSeoDescription: string;
|
generatorSchema.parse(mod.default),
|
||||||
|
);
|
||||||
|
|
||||||
|
export function findGenerator(slug: string): Generator {
|
||||||
|
const g = generators.find((gen) => gen.slug === slug);
|
||||||
|
if (!g) throw new Error(`Generator not found: ${slug}`);
|
||||||
|
return g;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const generators: Generator[] = [
|
export type { Generator };
|
||||||
{
|
|
||||||
slug: 'numbers',
|
|
||||||
title: 'Number',
|
|
||||||
description: 'Pick a random number within any range you choose.',
|
|
||||||
icon: 'hash',
|
|
||||||
status: 'live',
|
|
||||||
seoTitle: 'Random Number Generator | Randify',
|
|
||||||
seoDescription: 'Generate a random number between any two values instantly. Free online random number picker — perfect for giveaways, games, and decisions.',
|
|
||||||
ruTitle: 'Число',
|
|
||||||
ruDescription: 'Выберите случайное число в любом диапазоне.',
|
|
||||||
ruSeoTitle: 'Генератор случайных чисел | Randify',
|
|
||||||
ruSeoDescription: 'Генерируйте случайное число в любом диапазоне мгновенно. Идеально для розыгрышей, игр и принятия решений.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
slug: 'dice',
|
|
||||||
title: 'Dice',
|
|
||||||
description: 'Roll one or more dice with any number of sides.',
|
|
||||||
icon: 'dice-6',
|
|
||||||
status: 'live',
|
|
||||||
seoTitle: 'Dice Roller — d4 d6 d20 + Full Notation | D&D Dice | Randify',
|
|
||||||
seoDescription: 'Free online dice roller with full XdY+Z notation, keep/drop (4d6dl1), exploding dice (3d6!), reroll, advantage/disadvantage, and roll history. Perfect for D&D, Pathfinder, and Savage Worlds.',
|
|
||||||
ruTitle: 'Кубик',
|
|
||||||
ruDescription: 'Бросьте один или несколько кубиков с любым числом граней.',
|
|
||||||
ruSeoTitle: 'Бросить кубик онлайн — d4 d6 d8 d10 d12 d20 | D&D | Randify',
|
|
||||||
ruSeoDescription: 'Бесплатный онлайн-бросок кубиков с полной нотацией XdY+Z, keep/drop (4d6dl1), взрывающимися кубиками (3d6!), перебросом, преимуществом/помехой и историей. Для D&D, Pathfinder и Savage Worlds.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
slug: 'wheel',
|
|
||||||
title: 'Spin the Wheel',
|
|
||||||
description: 'Add your items, spin the wheel, and let chance decide.',
|
|
||||||
icon: 'pie-chart',
|
|
||||||
status: 'live',
|
|
||||||
seoTitle: 'Spin the Wheel — Random Picker | Randify',
|
|
||||||
seoDescription: 'Spin a customizable wheel of fortune to pick a random winner, choice, or option. Add your own items and let the wheel decide.',
|
|
||||||
ruTitle: 'Колесо фортуны',
|
|
||||||
ruDescription: 'Добавьте элементы, крутите колесо и пусть случай решит.',
|
|
||||||
ruSeoTitle: 'Колесо фортуны онлайн — случайный выбор | Randify',
|
|
||||||
ruSeoDescription: 'Крутите колесо фортуны онлайн. Добавьте свои варианты и пусть случай решит победителя.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
slug: 'colors',
|
|
||||||
title: 'Color',
|
|
||||||
description: 'Generate a random color in HEX, RGB, or HSL format.',
|
|
||||||
icon: 'palette',
|
|
||||||
status: 'live',
|
|
||||||
seoTitle: 'Random Color Generator — HEX, RGB, HSL | Randify',
|
|
||||||
seoDescription: 'Get a random color in HEX, RGB or HSL format with one click. Free online color randomizer for designers and developers.',
|
|
||||||
ruTitle: 'Цвет',
|
|
||||||
ruDescription: 'Генерируйте случайный цвет в форматах HEX, RGB или HSL.',
|
|
||||||
ruSeoTitle: 'Генератор случайных цветов — HEX, RGB, HSL | Randify',
|
|
||||||
ruSeoDescription: 'Получайте случайные цвета в форматах HEX, RGB и HSL одним нажатием. Бесплатный рандомайзер цветов для дизайнеров.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
slug: 'password',
|
|
||||||
title: 'Password',
|
|
||||||
description: 'Create a strong, random password with custom rules.',
|
|
||||||
icon: 'lock',
|
|
||||||
status: 'live',
|
|
||||||
seoTitle: 'Random Password Generator | Randify',
|
|
||||||
seoDescription: 'Create a strong random password with custom length, uppercase, lowercase, digits and symbols. Free and secure — runs entirely in your browser.',
|
|
||||||
ruTitle: 'Пароль',
|
|
||||||
ruDescription: 'Создайте надёжный случайный пароль с настраиваемыми правилами.',
|
|
||||||
ruSeoTitle: 'Генератор случайных паролей | Randify',
|
|
||||||
ruSeoDescription: 'Создавайте надёжные пароли с заглавными буквами, цифрами и символами. Бесплатно и безопасно — работает прямо в браузере.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
slug: 'lottery',
|
|
||||||
title: 'Lottery',
|
|
||||||
description: 'Draw a set of unique numbers for lottery-style picks.',
|
|
||||||
icon: 'ticket',
|
|
||||||
status: 'live',
|
|
||||||
seoTitle: 'Lottery Number Generator — Random Pick | Randify',
|
|
||||||
seoDescription: 'Draw a set of unique random lottery numbers from any range. Perfect for lotteries, raffles, and lucky draws. Free online lottery picker.',
|
|
||||||
ruTitle: 'Лотерея',
|
|
||||||
ruDescription: 'Вытащите набор уникальных чисел в стиле лотереи.',
|
|
||||||
ruSeoTitle: 'Генератор лотерейных номеров | Randify',
|
|
||||||
ruSeoDescription: 'Тяните уникальные случайные числа для лотереи, розыгрыша или жеребьёвки. Бесплатный онлайн-генератор лотерейных номеров.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
slug: 'cards',
|
|
||||||
title: 'Card',
|
|
||||||
description: 'Draw a random playing card from a standard deck.',
|
|
||||||
icon: 'square-stack',
|
|
||||||
status: 'live',
|
|
||||||
seoTitle: 'Random Card Generator — Draw from a Deck | Randify',
|
|
||||||
seoDescription: 'Draw random playing cards from a standard 52-card deck. Pick any number of cards, with or without replacement. Free online card picker.',
|
|
||||||
ruTitle: 'Карта',
|
|
||||||
ruDescription: 'Вытащите случайную карту из стандартной колоды.',
|
|
||||||
ruSeoTitle: 'Генератор случайных карт | Randify',
|
|
||||||
ruSeoDescription: 'Тяните случайные карты из колоды 52 карт. С повторениями или без. Бесплатный онлайн-генератор карт.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
slug: 'coin',
|
|
||||||
title: 'Coin Flip',
|
|
||||||
description: 'Flip one or more coins and see heads or tails.',
|
|
||||||
icon: 'circle-dollar-sign',
|
|
||||||
status: 'live',
|
|
||||||
seoTitle: 'Coin Flip Online — Heads or Tails | Randify',
|
|
||||||
seoDescription: 'Flip a virtual coin online. Flip multiple coins at once and see how many heads and tails you get. Free random coin toss simulator.',
|
|
||||||
ruTitle: 'Монетка',
|
|
||||||
ruDescription: 'Подбросьте одну или несколько монет — орёл или решка.',
|
|
||||||
ruSeoTitle: 'Подбросить монетку онлайн — орёл или решка | Randify',
|
|
||||||
ruSeoDescription: 'Подбрасывайте виртуальную монету онлайн. Одну или несколько сразу. Бесплатный симулятор подбрасывания монеты.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
slug: 'list',
|
|
||||||
title: 'List Picker',
|
|
||||||
description: 'Paste a list of items and pick random winners.',
|
|
||||||
icon: 'list',
|
|
||||||
status: 'live',
|
|
||||||
seoTitle: 'Random List Picker — Pick a Random Winner | Randify',
|
|
||||||
seoDescription: 'Paste any list of names or items and pick random winners instantly. Great for giveaways, choosing who goes first, or any random selection.',
|
|
||||||
ruTitle: 'Список',
|
|
||||||
ruDescription: 'Вставьте список элементов и выберите случайных победителей.',
|
|
||||||
ruSeoTitle: 'Случайный выбор из списка | Randify',
|
|
||||||
ruSeoDescription: 'Вставьте список имён или вариантов и выберите случайных победителей мгновенно. Идеально для розыгрышей и жеребьёвок.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
slug: 'uuid',
|
|
||||||
title: 'UUID / Token',
|
|
||||||
description: 'Generate UUIDs, hex tokens, or random base64 strings.',
|
|
||||||
icon: 'fingerprint',
|
|
||||||
status: 'live',
|
|
||||||
seoTitle: 'UUID Generator — UUID v4, Hex & Base64 Tokens | Randify',
|
|
||||||
seoDescription: 'Generate random UUID v4, hex tokens, or base64 strings online. Cryptographically secure, runs in your browser. Free UUID and token generator.',
|
|
||||||
ruTitle: 'UUID / Токен',
|
|
||||||
ruDescription: 'Генерируйте UUID, hex-токены или случайные Base64-строки.',
|
|
||||||
ruSeoTitle: 'Генератор UUID и токенов | Randify',
|
|
||||||
ruSeoDescription: 'Генерируйте UUID v4, hex-токены и Base64-строки онлайн. Криптографически безопасно, работает в браузере.',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|||||||
@@ -1,107 +1,93 @@
|
|||||||
---
|
---
|
||||||
import LanguageSwitcher from '../components/LanguageSwitcher.astro';
|
import { getRelativeLocaleUrl } from "astro:i18n";
|
||||||
import { useT } from '../i18n/translations';
|
import LanguageSwitcher from "../components/LanguageSwitcher.astro";
|
||||||
import type { Lang } from '../i18n/translations';
|
import Analytics from "../components/Analytics.astro";
|
||||||
|
import { useT } from "../i18n/translations";
|
||||||
|
import type { Lang } from "../i18n/translations";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title?: string;
|
title?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
lang?: Lang;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { lang = 'en' } = Astro.props;
|
const lang = (Astro.currentLocale as Lang) || "en";
|
||||||
const T = useT(lang);
|
const T = useT(lang);
|
||||||
|
|
||||||
const {
|
const { title = T.defaultTitle, description = T.defaultDesc } = Astro.props;
|
||||||
title = T.defaultTitle,
|
|
||||||
description = T.defaultDesc,
|
|
||||||
} = Astro.props;
|
|
||||||
|
|
||||||
const currentPath = Astro.url.pathname;
|
const pathWithoutLocale = Astro.url.pathname.replace(/^\/ru/, "") || "/";
|
||||||
const alternatePath = lang === 'ru'
|
const enUrl = `https://randify.pro${getRelativeLocaleUrl("en", pathWithoutLocale)}`;
|
||||||
? (currentPath.replace(/^\/ru/, '') || '/')
|
const ruUrl = `https://randify.pro${getRelativeLocaleUrl("ru", pathWithoutLocale)}`;
|
||||||
: ('/ru' + currentPath);
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang={lang}>
|
<html lang={lang}>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="description" content={description} />
|
<meta name="description" content={description} />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300..700&display=swap" rel="stylesheet" />
|
<link
|
||||||
<link rel="alternate" hreflang="en" href={`https://randify.pro${lang === 'ru' ? alternatePath : currentPath}`} />
|
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300..700&display=swap"
|
||||||
<link rel="alternate" hreflang="ru" href={`https://randify.pro${lang === 'ru' ? currentPath : alternatePath}`} />
|
rel="stylesheet"
|
||||||
<meta name="verification" content="er9ndnv9ih7agmh8" />
|
/>
|
||||||
<title>{title}</title>
|
<link rel="alternate" hreflang="en" href={enUrl} />
|
||||||
<!-- Yandex.Metrika counter -->
|
<link rel="alternate" hreflang="ru" href={ruUrl} />
|
||||||
<script type="text/javascript">
|
<meta name="verification" content="er9ndnv9ih7agmh8" />
|
||||||
(function(m,e,t,r,i,k,a){
|
<title>{title}</title>
|
||||||
m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
|
<Analytics />
|
||||||
m[i].l=1*new Date();
|
</head>
|
||||||
for (var j = 0; j < document.scripts.length; j++) {if (document.scripts[j].src === r) { return; }}
|
<body class="bg-zinc-950 text-zinc-100 min-h-screen font-sans antialiased">
|
||||||
k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)
|
<LanguageSwitcher />
|
||||||
})(window, document,'script','https://mc.yandex.ru/metrika/tag.js?id=109130319', 'ym');
|
<slot />
|
||||||
ym(109130319, 'init', {ssr:true, webvisor:true, clickmap:true, ecommerce:"dataLayer", referrer: document.referrer, url: location.href, accurateTrackBounce:true, trackLinks:true});
|
</body>
|
||||||
</script>
|
|
||||||
<noscript><div><img src="https://mc.yandex.ru/watch/109130319" style="position:absolute; left:-9999px;" alt="" /></div></noscript>
|
|
||||||
<!-- /Yandex.Metrika counter -->
|
|
||||||
<!-- Top.Mail.Ru counter -->
|
|
||||||
<script type="text/javascript">
|
|
||||||
var _tmr = window._tmr || (window._tmr = []);
|
|
||||||
_tmr.push({id: "3765043", type: "pageView", start: (new Date()).getTime()});
|
|
||||||
(function (d, w, id) {
|
|
||||||
if (d.getElementById(id)) return;
|
|
||||||
var ts = d.createElement("script"); ts.type = "text/javascript"; ts.async = true; ts.id = id;
|
|
||||||
ts.src = "https://top-fwz1.mail.ru/js/code.js";
|
|
||||||
var f = function () {var s = d.getElementsByTagName("script")[0]; s.parentNode.insertBefore(ts, s);};
|
|
||||||
if (w.opera == "[object Opera]") { d.addEventListener("DOMContentLoaded", f, false); } else { f(); }
|
|
||||||
})(document, window, "tmr-code");
|
|
||||||
</script>
|
|
||||||
<noscript><div><img src="https://top-fwz1.mail.ru/counter?id=3765043;js=na" style="position:absolute;left:-9999px;" alt="Top.Mail.Ru" /></div></noscript>
|
|
||||||
<!-- /Top.Mail.Ru counter -->
|
|
||||||
</head>
|
|
||||||
<body class="bg-zinc-950 text-zinc-100 min-h-screen font-sans antialiased">
|
|
||||||
<LanguageSwitcher lang={lang} alternatePath={alternatePath} />
|
|
||||||
<slot />
|
|
||||||
</body>
|
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
<!-- Auto-detect language on first visit -->
|
<!-- Auto-detect language on first visit -->
|
||||||
<script>
|
<script>
|
||||||
if (!localStorage.getItem('lang-pref')) {
|
if (!localStorage.getItem("lang-pref")) {
|
||||||
const browserLang = (navigator.language || '').toLowerCase();
|
const browserLang = (navigator.language || "").toLowerCase();
|
||||||
if (browserLang.startsWith('ru') && !location.pathname.startsWith('/ru')) {
|
if (
|
||||||
location.replace('/ru' + location.pathname);
|
browserLang.startsWith("ru") &&
|
||||||
|
!location.pathname.startsWith("/ru")
|
||||||
|
) {
|
||||||
|
location.replace("/ru" + location.pathname);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style is:global>
|
<style is:global>
|
||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
@theme {
|
@theme {
|
||||||
--font-sans: 'Space Grotesk', sans-serif;
|
--font-sans: "Space Grotesk", sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--accent: #534AB7;
|
--accent: #534ab7;
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background-image:
|
background-image:
|
||||||
radial-gradient(ellipse 90% 45% at 50% -5%, rgba(83, 74, 183, 0.2) 0%, transparent 65%),
|
radial-gradient(
|
||||||
radial-gradient(ellipse 90% 45% at 50% 105%, rgba(83, 74, 183, 0.13) 0%, transparent 65%);
|
ellipse 90% 45% at 50% -5%,
|
||||||
}
|
rgba(83, 74, 183, 0.2) 0%,
|
||||||
|
transparent 65%
|
||||||
|
),
|
||||||
|
radial-gradient(
|
||||||
|
ellipse 90% 45% at 50% 105%,
|
||||||
|
rgba(83, 74, 183, 0.13) 0%,
|
||||||
|
transparent 65%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
input[type="number"] {
|
input[type="number"] {
|
||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
---
|
||||||
|
import BaseLayout from './BaseLayout.astro';
|
||||||
|
import AdBanner from '../components/AdBanner.astro';
|
||||||
|
import SeoBlock from '../components/SeoBlock.astro';
|
||||||
|
import { useT } from '../i18n/translations';
|
||||||
|
import type { Lang } from '../i18n/translations';
|
||||||
|
import type { Generator } from '../data/generators';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
generator: Generator;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { generator } = Astro.props;
|
||||||
|
const lang = (Astro.currentLocale as Lang) || 'en';
|
||||||
|
const T = useT(lang);
|
||||||
|
const isRu = lang === 'ru';
|
||||||
|
---
|
||||||
|
|
||||||
|
<BaseLayout
|
||||||
|
title={isRu ? generator.ruSeoTitle : generator.seoTitle}
|
||||||
|
description={isRu ? generator.ruSeoDescription : generator.seoDescription}
|
||||||
|
>
|
||||||
|
<div class="max-w-2xl mx-auto px-4 py-12 sm:py-20">
|
||||||
|
<nav aria-label="Breadcrumb" class="mb-10">
|
||||||
|
<a
|
||||||
|
href={isRu ? '/ru/' : '/'}
|
||||||
|
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={T.backToAll}
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
{T.backToAll}
|
||||||
|
</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">
|
||||||
|
{isRu ? generator.ruPageTitle : generator.pageTitle}
|
||||||
|
</h1>
|
||||||
|
<p class="mt-2 text-base text-zinc-400">{isRu ? generator.ruDescription : generator.description}</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<slot />
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<div class="mt-12 flex justify-center">
|
||||||
|
<AdBanner size="tile" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<SeoBlock
|
||||||
|
lang={lang}
|
||||||
|
howTo={isRu ? generator.ruHowTo : generator.howTo}
|
||||||
|
whenTo={isRu ? generator.ruWhenTo : generator.whenTo}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</BaseLayout>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const generatorSchema = z
|
||||||
|
.object({
|
||||||
|
slug: z.string(),
|
||||||
|
title: z.string(),
|
||||||
|
description: z.string(),
|
||||||
|
icon: z.string(),
|
||||||
|
status: z.enum(["live", "coming-soon"]),
|
||||||
|
seoTitle: z.string(),
|
||||||
|
seoDescription: z.string(),
|
||||||
|
ruTitle: z.string(),
|
||||||
|
ruDescription: z.string(),
|
||||||
|
ruSeoTitle: z.string(),
|
||||||
|
ruSeoDescription: z.string(),
|
||||||
|
pageTitle: z.string(),
|
||||||
|
ruPageTitle: z.string(),
|
||||||
|
howTo: z.array(z.string()),
|
||||||
|
whenTo: z.array(z.string()),
|
||||||
|
ruHowTo: z.array(z.string()),
|
||||||
|
ruWhenTo: z.array(z.string()),
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
|
export type Generator = z.infer<typeof generatorSchema>;
|
||||||
@@ -1,77 +1,11 @@
|
|||||||
---
|
---
|
||||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
import GeneratorLayout from "@/layouts/GeneratorLayout.astro";
|
||||||
import CardGenerator from '../../components/generators/CardGenerator.astro';
|
import CardGenerator from "@/components/generators/CardGenerator.astro";
|
||||||
import AdBanner from '../../components/AdBanner.astro';
|
import { generators } from "@/data/generators";
|
||||||
import { generators } from '../../data/generators';
|
|
||||||
import SeoBlock from '../../components/SeoBlock.astro';
|
|
||||||
|
|
||||||
const generator = generators.find((g) => g.slug === 'cards')!;
|
const generator = generators.find((g) => g.slug === "cards")!;
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout lang="en"
|
<GeneratorLayout generator={generator}>
|
||||||
title={generator.seoTitle}
|
<CardGenerator />
|
||||||
description={generator.seoDescription}
|
</GeneratorLayout>
|
||||||
>
|
|
||||||
<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} Generator
|
|
||||||
</h1>
|
|
||||||
<p class="mt-2 text-base text-zinc-400">{generator.description}</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
<CardGenerator />
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<div class="mt-12 flex justify-center">
|
|
||||||
<AdBanner size="tile" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<SeoBlock
|
|
||||||
howTo={[
|
|
||||||
'Set how many cards to draw (1–52).',
|
|
||||||
'Toggle Allow duplicates if you want cards to repeat.',
|
|
||||||
'Click Draw — cards are shown as mini card tiles.',
|
|
||||||
'Click Copy to copy the result (e.g. A♠, K♥) to your clipboard.',
|
|
||||||
]}
|
|
||||||
whenTo={[
|
|
||||||
'Drawing a random hand for card games.',
|
|
||||||
'Practising card probability and statistics.',
|
|
||||||
'Simulating a random card pull for magic tricks or puzzles.',
|
|
||||||
'Running fair draws without a physical deck.',
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</BaseLayout>
|
|
||||||
|
|||||||
@@ -1,56 +1,11 @@
|
|||||||
---
|
---
|
||||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
import GeneratorLayout from "@/layouts/GeneratorLayout.astro";
|
||||||
import CoinGenerator from '../../components/generators/CoinGenerator.astro';
|
import CoinGenerator from "@/components/generators/CoinGenerator.astro";
|
||||||
import AdBanner from '../../components/AdBanner.astro';
|
import { generators } from "@/data/generators";
|
||||||
import { generators } from '../../data/generators';
|
|
||||||
import SeoBlock from '../../components/SeoBlock.astro';
|
|
||||||
|
|
||||||
const generator = generators.find((g) => g.slug === 'coin')!;
|
const generator = generators.find((g) => g.slug === "coin")!;
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout lang="en"
|
<GeneratorLayout generator={generator}>
|
||||||
title={generator.seoTitle}
|
<CoinGenerator />
|
||||||
description={generator.seoDescription}
|
</GeneratorLayout>
|
||||||
>
|
|
||||||
<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 flex justify-center">
|
|
||||||
<AdBanner size="tile" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<SeoBlock
|
|
||||||
howTo={[
|
|
||||||
'Drag the slider to choose how many coins to flip (1–20).',
|
|
||||||
'Click Flip — each coin shows H for heads or T for tails.',
|
|
||||||
'When flipping more than one coin, the heads/tails count is shown below.',
|
|
||||||
'Click Copy to copy all results to the clipboard.',
|
|
||||||
]}
|
|
||||||
whenTo={[
|
|
||||||
'Making a quick heads-or-tails decision.',
|
|
||||||
'Simulating a coin toss for a sports match or game.',
|
|
||||||
'Demonstrating probability and the law of large numbers.',
|
|
||||||
'Settling a fair 50/50 choice between two options.',
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</BaseLayout>
|
|
||||||
|
|||||||
@@ -1,76 +1,11 @@
|
|||||||
---
|
---
|
||||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
import GeneratorLayout from "@/layouts/GeneratorLayout.astro";
|
||||||
import ColorGenerator from '../../components/generators/ColorGenerator.astro';
|
import ColorGenerator from "@/components/generators/ColorGenerator.astro";
|
||||||
import AdBanner from '../../components/AdBanner.astro';
|
import { generators } from "@/data/generators";
|
||||||
import { generators } from '../../data/generators';
|
|
||||||
import SeoBlock from '../../components/SeoBlock.astro';
|
|
||||||
|
|
||||||
const generator = generators.find((g) => g.slug === 'colors')!;
|
const generator = generators.find((g) => g.slug === "colors")!;
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout lang="en"
|
<GeneratorLayout generator={generator}>
|
||||||
title={generator.seoTitle}
|
<ColorGenerator />
|
||||||
description={generator.seoDescription}
|
</GeneratorLayout>
|
||||||
>
|
|
||||||
<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} Generator
|
|
||||||
</h1>
|
|
||||||
<p class="mt-2 text-base text-zinc-400">{generator.description}</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
<ColorGenerator />
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<div class="mt-12 flex justify-center">
|
|
||||||
<AdBanner size="tile" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<SeoBlock
|
|
||||||
howTo={[
|
|
||||||
'Click Generate to produce a random color.',
|
|
||||||
'Switch between HEX, RGB, and HSL tabs to see the value in your preferred format.',
|
|
||||||
'Click the color value or the copy icon to copy it to the clipboard.',
|
|
||||||
]}
|
|
||||||
whenTo={[
|
|
||||||
'Finding inspiration for a new design palette.',
|
|
||||||
'Picking a placeholder color while prototyping.',
|
|
||||||
'Selecting a random theme color for a project or presentation.',
|
|
||||||
'Teaching or exploring color theory and formats.',
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</BaseLayout>
|
|
||||||
|
|||||||
@@ -1,79 +1,11 @@
|
|||||||
---
|
---
|
||||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
import GeneratorLayout from "@/layouts/GeneratorLayout.astro";
|
||||||
import DiceGenerator from '../../components/generators/DiceGenerator.astro';
|
import DiceGenerator from "@/components/generators/DiceGenerator.astro";
|
||||||
import AdBanner from '../../components/AdBanner.astro';
|
import { generators } from "@/data/generators";
|
||||||
import { generators } from '../../data/generators';
|
|
||||||
import SeoBlock from '../../components/SeoBlock.astro';
|
|
||||||
|
|
||||||
const generator = generators.find((g) => g.slug === 'dice')!;
|
const generator = generators.find((g) => g.slug === "dice")!;
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout lang="en"
|
<GeneratorLayout generator={generator}>
|
||||||
title={generator.seoTitle}
|
<DiceGenerator />
|
||||||
description={generator.seoDescription}
|
</GeneratorLayout>
|
||||||
>
|
|
||||||
<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} Generator
|
|
||||||
</h1>
|
|
||||||
<p class="mt-2 text-base text-zinc-400">{generator.description}</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
<DiceGenerator />
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<div class="mt-12 flex justify-center">
|
|
||||||
<AdBanner size="tile" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<SeoBlock
|
|
||||||
howTo={[
|
|
||||||
'Type a notation like 2d6+3 or 1d20 — or use the die buttons and count.',
|
|
||||||
'Pick a mode: Normal, Advantage, or Disadvantage (single die only).',
|
|
||||||
'Click Roll — each die result appears with the total sum.',
|
|
||||||
'Maximum rolls are highlighted in purple; ones are highlighted in red.',
|
|
||||||
]}
|
|
||||||
whenTo={[
|
|
||||||
'Rolling dice for D&D, Pathfinder, or any tabletop RPG.',
|
|
||||||
'Using dice notation shortcuts like 2d6+3 or 1d20 for fast rolling.',
|
|
||||||
'Rolling with advantage or disadvantage for D&D 5e mechanics.',
|
|
||||||
'Generating character stats with 4d6 drop-lowest method.',
|
|
||||||
'Rolling exploding dice for Savage Worlds or house rules (e.g., 3d6!).',
|
|
||||||
'Using keep/drop modifiers like 4d6kh3 to keep highest rolls.',
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</BaseLayout>
|
|
||||||
|
|||||||
@@ -1,56 +1,11 @@
|
|||||||
---
|
---
|
||||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
import GeneratorLayout from "@/layouts/GeneratorLayout.astro";
|
||||||
import ListGenerator from '../../components/generators/ListGenerator.astro';
|
import ListGenerator from "@/components/generators/ListGenerator.astro";
|
||||||
import AdBanner from '../../components/AdBanner.astro';
|
import { generators } from "@/data/generators";
|
||||||
import { generators } from '../../data/generators';
|
|
||||||
import SeoBlock from '../../components/SeoBlock.astro';
|
|
||||||
|
|
||||||
const generator = generators.find((g) => g.slug === 'list')!;
|
const generator = generators.find((g) => g.slug === "list")!;
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout lang="en"
|
<GeneratorLayout generator={generator}>
|
||||||
title={generator.seoTitle}
|
<ListGenerator />
|
||||||
description={generator.seoDescription}
|
</GeneratorLayout>
|
||||||
>
|
|
||||||
<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 flex justify-center">
|
|
||||||
<AdBanner size="tile" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<SeoBlock
|
|
||||||
howTo={[
|
|
||||||
'Type or paste your items into the text area — one item per line.',
|
|
||||||
'Set how many items to pick in the Pick field.',
|
|
||||||
'Toggle Allow duplicates if the same item can be chosen more than once.',
|
|
||||||
'Click Pick (or press Ctrl+Enter) — the selected items appear as chips.',
|
|
||||||
]}
|
|
||||||
whenTo={[
|
|
||||||
'Choosing a random winner for a giveaway or contest.',
|
|
||||||
'Picking who presents first in a meeting or class.',
|
|
||||||
'Organising a Secret Santa or gift exchange draw.',
|
|
||||||
'Selecting a random movie, restaurant, or activity from a list.',
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</BaseLayout>
|
|
||||||
|
|||||||
@@ -1,77 +1,11 @@
|
|||||||
---
|
---
|
||||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
import GeneratorLayout from "@/layouts/GeneratorLayout.astro";
|
||||||
import LotteryGenerator from '../../components/generators/LotteryGenerator.astro';
|
import LotteryGenerator from "@/components/generators/LotteryGenerator.astro";
|
||||||
import AdBanner from '../../components/AdBanner.astro';
|
import { generators } from "@/data/generators";
|
||||||
import { generators } from '../../data/generators';
|
|
||||||
import SeoBlock from '../../components/SeoBlock.astro';
|
|
||||||
|
|
||||||
const generator = generators.find((g) => g.slug === 'lottery')!;
|
const generator = generators.find((g) => g.slug === "lottery")!;
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout lang="en"
|
<GeneratorLayout generator={generator}>
|
||||||
title={generator.seoTitle}
|
<LotteryGenerator />
|
||||||
description={generator.seoDescription}
|
</GeneratorLayout>
|
||||||
>
|
|
||||||
<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} Generator
|
|
||||||
</h1>
|
|
||||||
<p class="mt-2 text-base text-zinc-400">{generator.description}</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
<LotteryGenerator />
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<div class="mt-12 flex justify-center">
|
|
||||||
<AdBanner size="tile" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<SeoBlock
|
|
||||||
howTo={[
|
|
||||||
'Set the From and To fields to define your number range (e.g. 1 to 49).',
|
|
||||||
'Enter how many numbers to pick in the Pick field.',
|
|
||||||
'Click Draw — a sorted set of unique numbers appears.',
|
|
||||||
'Click Copy to copy all numbers to the clipboard.',
|
|
||||||
]}
|
|
||||||
whenTo={[
|
|
||||||
'Generating lottery or lotto ticket numbers.',
|
|
||||||
'Running a fair raffle draw with unique entries.',
|
|
||||||
'Selecting a random sample from a numbered list.',
|
|
||||||
'Choosing lottery numbers for Powerball, EuroMillions, or local draws.',
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</BaseLayout>
|
|
||||||
|
|||||||
@@ -1,77 +1,11 @@
|
|||||||
---
|
---
|
||||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
import GeneratorLayout from "@/layouts/GeneratorLayout.astro";
|
||||||
import NumberGenerator from '../../components/generators/NumberGenerator.astro';
|
import NumberGenerator from "@/components/generators/NumberGenerator.astro";
|
||||||
import AdBanner from '../../components/AdBanner.astro';
|
import { generators } from "@/data/generators";
|
||||||
import { generators } from '../../data/generators';
|
|
||||||
import SeoBlock from '../../components/SeoBlock.astro';
|
|
||||||
|
|
||||||
const generator = generators.find((g) => g.slug === 'numbers')!;
|
const generator = generators.find((g) => g.slug === "numbers")!;
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout lang="en"
|
<GeneratorLayout generator={generator}>
|
||||||
title={generator.seoTitle}
|
<NumberGenerator />
|
||||||
description={generator.seoDescription}
|
</GeneratorLayout>
|
||||||
>
|
|
||||||
<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} Generator
|
|
||||||
</h1>
|
|
||||||
<p class="mt-2 text-base text-zinc-400">{generator.description}</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
<NumberGenerator />
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<div class="mt-12 flex justify-center">
|
|
||||||
<AdBanner size="tile" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<SeoBlock
|
|
||||||
howTo={[
|
|
||||||
'Enter the minimum value in the From field.',
|
|
||||||
'Enter the maximum value in the To field.',
|
|
||||||
'Click Generate — a random number within your range appears instantly.',
|
|
||||||
'Click the number to copy it to the clipboard.',
|
|
||||||
]}
|
|
||||||
whenTo={[
|
|
||||||
'Picking a winner in a giveaway or raffle.',
|
|
||||||
'Choosing a random starting player in a board game.',
|
|
||||||
'Making a quick decision between numbered options.',
|
|
||||||
'Generating test data with values in a specific range.',
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</BaseLayout>
|
|
||||||
|
|||||||
@@ -1,77 +1,11 @@
|
|||||||
---
|
---
|
||||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
import GeneratorLayout from "@/layouts/GeneratorLayout.astro";
|
||||||
import PasswordGenerator from '../../components/generators/PasswordGenerator.astro';
|
import PasswordGenerator from "@/components/generators/PasswordGenerator.astro";
|
||||||
import AdBanner from '../../components/AdBanner.astro';
|
import { generators } from "@/data/generators";
|
||||||
import { generators } from '../../data/generators';
|
|
||||||
import SeoBlock from '../../components/SeoBlock.astro';
|
|
||||||
|
|
||||||
const generator = generators.find((g) => g.slug === 'password')!;
|
const generator = generators.find((g) => g.slug === "password")!;
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout lang="en"
|
<GeneratorLayout generator={generator}>
|
||||||
title={generator.seoTitle}
|
<PasswordGenerator />
|
||||||
description={generator.seoDescription}
|
</GeneratorLayout>
|
||||||
>
|
|
||||||
<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} Generator
|
|
||||||
</h1>
|
|
||||||
<p class="mt-2 text-base text-zinc-400">{generator.description}</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
<PasswordGenerator />
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<div class="mt-12 flex justify-center">
|
|
||||||
<AdBanner size="tile" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<SeoBlock
|
|
||||||
howTo={[
|
|
||||||
'Drag the length slider to set how many characters you need (4–64).',
|
|
||||||
'Toggle uppercase, lowercase, digits, and symbols on or off.',
|
|
||||||
'Click Generate — a new password appears instantly.',
|
|
||||||
'Click the password to copy it to your clipboard.',
|
|
||||||
]}
|
|
||||||
whenTo={[
|
|
||||||
'Creating a strong password for a new account.',
|
|
||||||
'Generating a temporary access credential.',
|
|
||||||
'Producing a random secret key for development or testing.',
|
|
||||||
'Replacing a weak or reused password.',
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</BaseLayout>
|
|
||||||
|
|||||||
@@ -1,56 +1,11 @@
|
|||||||
---
|
---
|
||||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
import GeneratorLayout from "@/layouts/GeneratorLayout.astro";
|
||||||
import UuidGenerator from '../../components/generators/UuidGenerator.astro';
|
import UuidGenerator from "@/components/generators/UuidGenerator.astro";
|
||||||
import AdBanner from '../../components/AdBanner.astro';
|
import { generators } from "@/data/generators";
|
||||||
import { generators } from '../../data/generators';
|
|
||||||
import SeoBlock from '../../components/SeoBlock.astro';
|
|
||||||
|
|
||||||
const generator = generators.find((g) => g.slug === 'uuid')!;
|
const generator = generators.find((g) => g.slug === "uuid")!;
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout lang="en"
|
<GeneratorLayout generator={generator}>
|
||||||
title={generator.seoTitle}
|
<UuidGenerator />
|
||||||
description={generator.seoDescription}
|
</GeneratorLayout>
|
||||||
>
|
|
||||||
<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 flex justify-center">
|
|
||||||
<AdBanner size="tile" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<SeoBlock
|
|
||||||
howTo={[
|
|
||||||
'Choose a token type: UUID v4, Hex, or Base64.',
|
|
||||||
'For Hex and Base64, set the byte length (4–64 bytes).',
|
|
||||||
'Enter how many tokens to generate (1–20).',
|
|
||||||
'Click Generate, then Copy all to copy every token to the clipboard.',
|
|
||||||
]}
|
|
||||||
whenTo={[
|
|
||||||
'Generating unique IDs for database records or API resources.',
|
|
||||||
'Creating random session tokens or API keys during development.',
|
|
||||||
'Producing test data with realistic-looking identifiers.',
|
|
||||||
'Generating a one-time secret for environment variables or config files.',
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</BaseLayout>
|
|
||||||
|
|||||||
@@ -1,77 +1,11 @@
|
|||||||
---
|
---
|
||||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
import GeneratorLayout from "@/layouts/GeneratorLayout.astro";
|
||||||
import WheelSpinner from '../../components/generators/WheelSpinner.astro';
|
import WheelSpinner from "@/components/generators/WheelSpinner.astro";
|
||||||
import AdBanner from '../../components/AdBanner.astro';
|
import { generators } from "@/data/generators";
|
||||||
import SeoBlock from '../../components/SeoBlock.astro';
|
|
||||||
import { generators } from '../../data/generators';
|
|
||||||
|
|
||||||
const generator = generators.find((g) => g.slug === 'wheel')!;
|
const generator = generators.find((g) => g.slug === "wheel")!;
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout lang="en"
|
<GeneratorLayout generator={generator}>
|
||||||
title={generator.seoTitle}
|
<WheelSpinner />
|
||||||
description={generator.seoDescription}
|
</GeneratorLayout>
|
||||||
>
|
|
||||||
<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>
|
|
||||||
<WheelSpinner />
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<div class="mt-12 flex justify-center">
|
|
||||||
<AdBanner size="tile" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<SeoBlock
|
|
||||||
howTo={[
|
|
||||||
'Type your items into the text box — one per line.',
|
|
||||||
'Click "Update wheel" to apply changes.',
|
|
||||||
'Hit "Spin" and watch the wheel decide.',
|
|
||||||
'Copy the result or spin again.',
|
|
||||||
]}
|
|
||||||
whenTo={[
|
|
||||||
'Picking a winner for a giveaway or raffle.',
|
|
||||||
'Deciding whose turn it is in a game or meeting.',
|
|
||||||
'Choosing a random activity, movie, or restaurant.',
|
|
||||||
'Any decision you want to leave to chance.',
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</BaseLayout>
|
|
||||||
|
|||||||
+46
-39
@@ -1,46 +1,53 @@
|
|||||||
---
|
---
|
||||||
import BaseLayout from '../layouts/BaseLayout.astro';
|
import BaseLayout from "@/layouts/BaseLayout.astro";
|
||||||
import GeneratorCard from '../components/GeneratorCard.astro';
|
import GeneratorCard from "@/components/GeneratorCard.astro";
|
||||||
import AdBanner from '../components/AdBanner.astro';
|
import AdBanner from "@/components/AdBanner.astro";
|
||||||
import { generators } from '../data/generators';
|
import { generators } from "@/data/generators";
|
||||||
import { useT } from '../i18n/translations';
|
import { useT } from "@/i18n/translations";
|
||||||
|
import type { Lang } from "@/i18n/translations";
|
||||||
|
|
||||||
const T = useT('en');
|
const lang = (Astro.currentLocale as Lang) || "en";
|
||||||
|
const T = useT(lang);
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout lang="en">
|
<BaseLayout>
|
||||||
<div class="max-w-4xl mx-auto px-4 py-12 sm:py-20">
|
<div class="max-w-4xl mx-auto px-4 py-12 sm:py-20">
|
||||||
<header class="mb-12">
|
<header class="mb-12">
|
||||||
<div class="flex items-center gap-2 mb-6">
|
<div class="flex items-center gap-2 mb-6">
|
||||||
<span
|
<span
|
||||||
class="inline-block w-3 h-3 rounded-full bg-[#534AB7]"
|
class="inline-block w-3 h-3 rounded-full bg-[#534AB7]"
|
||||||
aria-hidden="true"
|
aria-hidden="true"></span>
|
||||||
></span>
|
<span class="text-xl font-bold tracking-tight text-zinc-100"
|
||||||
<span class="text-xl font-bold tracking-tight text-zinc-100">randify</span>
|
>{T.brandLabel}</span
|
||||||
</div>
|
>
|
||||||
<h1 class="text-3xl sm:text-4xl font-bold text-zinc-100 leading-tight">
|
</div>
|
||||||
{T.homeTitle}
|
<h1
|
||||||
</h1>
|
class="text-3xl sm:text-4xl font-bold text-zinc-100 leading-tight"
|
||||||
<p class="mt-3 text-base text-zinc-400 max-w-md">
|
>
|
||||||
{T.homeSubtitle}
|
{T.homeTitle}
|
||||||
</p>
|
</h1>
|
||||||
</header>
|
<p class="mt-3 text-base text-zinc-400 max-w-md">
|
||||||
|
{T.homeSubtitle}
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
<AdBanner size="leaderboard" />
|
<AdBanner size="leaderboard" />
|
||||||
|
|
||||||
<main class="mt-8">
|
<main class="mt-8">
|
||||||
<div
|
<div
|
||||||
class="grid gap-3"
|
class="grid gap-3"
|
||||||
style="grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));"
|
style="grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));"
|
||||||
role="list"
|
role="list"
|
||||||
aria-label="Generator catalog"
|
aria-label="Generator catalog"
|
||||||
>
|
>
|
||||||
{generators.map((generator) => (
|
{
|
||||||
<div role="listitem">
|
generators.map((generator) => (
|
||||||
<GeneratorCard generator={generator} />
|
<div role="listitem">
|
||||||
</div>
|
<GeneratorCard generator={generator} />
|
||||||
))}
|
</div>
|
||||||
</div>
|
))
|
||||||
</main>
|
}
|
||||||
</div>
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
|||||||
@@ -1,40 +1,11 @@
|
|||||||
---
|
---
|
||||||
import BaseLayout from '../../../layouts/BaseLayout.astro';
|
import GeneratorLayout from "@/layouts/GeneratorLayout.astro";
|
||||||
import CardGenerator from '../../../components/generators/CardGenerator.astro';
|
import CardGenerator from "@/components/generators/CardGenerator.astro";
|
||||||
import AdBanner from '../../../components/AdBanner.astro';
|
import { generators } from "@/data/generators";
|
||||||
import SeoBlock from '../../../components/SeoBlock.astro';
|
|
||||||
import { generators } from '../../../data/generators';
|
|
||||||
|
|
||||||
const generator = generators.find((g) => g.slug === 'cards')!;
|
const generator = generators.find((g) => g.slug === "cards")!;
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout title={generator.ruSeoTitle} description={generator.ruSeoDescription} lang="ru">
|
<GeneratorLayout generator={generator}>
|
||||||
<div class="max-w-2xl mx-auto px-4 py-12 sm:py-20">
|
<CardGenerator />
|
||||||
<nav aria-label="Breadcrumb" class="mb-10">
|
</GeneratorLayout>
|
||||||
<a href="/ru/" 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="Все генераторы">
|
|
||||||
<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>
|
|
||||||
Все генераторы
|
|
||||||
</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">Генератор карт</h1>
|
|
||||||
<p class="mt-2 text-base text-zinc-400">{generator.ruDescription}</p>
|
|
||||||
</header>
|
|
||||||
<main><CardGenerator /></main>
|
|
||||||
<div class="mt-12 flex justify-center"><AdBanner size="tile" /></div>
|
|
||||||
<SeoBlock lang="ru" howTo={[
|
|
||||||
'Укажите количество карт для розыгрыша.',
|
|
||||||
'Включите «Разрешить повторения», если нужны дубли.',
|
|
||||||
'Нажмите «Тянуть» — карты появятся на экране.',
|
|
||||||
'Нажмите «Копировать», чтобы скопировать результат.',
|
|
||||||
]} whenTo={[
|
|
||||||
'Случайная раздача карт для карточных игр.',
|
|
||||||
'Выбор случайных карт для гаданий.',
|
|
||||||
'Генерация случайных комбинаций карт для тестирования.',
|
|
||||||
]} />
|
|
||||||
</div>
|
|
||||||
</BaseLayout>
|
|
||||||
|
|||||||
@@ -1,40 +1,11 @@
|
|||||||
---
|
---
|
||||||
import BaseLayout from '../../../layouts/BaseLayout.astro';
|
import GeneratorLayout from "@/layouts/GeneratorLayout.astro";
|
||||||
import CoinGenerator from '../../../components/generators/CoinGenerator.astro';
|
import CoinGenerator from "@/components/generators/CoinGenerator.astro";
|
||||||
import AdBanner from '../../../components/AdBanner.astro';
|
import { generators } from "@/data/generators";
|
||||||
import SeoBlock from '../../../components/SeoBlock.astro';
|
|
||||||
import { generators } from '../../../data/generators';
|
|
||||||
|
|
||||||
const generator = generators.find((g) => g.slug === 'coin')!;
|
const generator = generators.find((g) => g.slug === "coin")!;
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout title={generator.ruSeoTitle} description={generator.ruSeoDescription} lang="ru">
|
<GeneratorLayout generator={generator}>
|
||||||
<div class="max-w-2xl mx-auto px-4 py-12 sm:py-20">
|
<CoinGenerator />
|
||||||
<nav aria-label="Breadcrumb" class="mb-10">
|
</GeneratorLayout>
|
||||||
<a href="/ru/" 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="Все генераторы">
|
|
||||||
<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>
|
|
||||||
Все генераторы
|
|
||||||
</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">Подбросить монетку</h1>
|
|
||||||
<p class="mt-2 text-base text-zinc-400">{generator.ruDescription}</p>
|
|
||||||
</header>
|
|
||||||
<main><CoinGenerator /></main>
|
|
||||||
<div class="mt-12 flex justify-center"><AdBanner size="tile" /></div>
|
|
||||||
<SeoBlock lang="ru" howTo={[
|
|
||||||
'Выберите количество монет с помощью ползунка.',
|
|
||||||
'Нажмите «Подбросить» — орёл или решка.',
|
|
||||||
'При нескольких монетах отображается итоговый счёт.',
|
|
||||||
'Нажмите «Копировать», чтобы сохранить результат.',
|
|
||||||
]} whenTo={[
|
|
||||||
'Принятие решений по принципу «орёл или решка».',
|
|
||||||
'Определение очерёдности в игре.',
|
|
||||||
'Симуляция случайных событий с равной вероятностью.',
|
|
||||||
]} />
|
|
||||||
</div>
|
|
||||||
</BaseLayout>
|
|
||||||
|
|||||||
@@ -1,39 +1,11 @@
|
|||||||
---
|
---
|
||||||
import BaseLayout from '../../../layouts/BaseLayout.astro';
|
import GeneratorLayout from "@/layouts/GeneratorLayout.astro";
|
||||||
import ColorGenerator from '../../../components/generators/ColorGenerator.astro';
|
import ColorGenerator from "@/components/generators/ColorGenerator.astro";
|
||||||
import AdBanner from '../../../components/AdBanner.astro';
|
import { generators } from "@/data/generators";
|
||||||
import SeoBlock from '../../../components/SeoBlock.astro';
|
|
||||||
import { generators } from '../../../data/generators';
|
|
||||||
|
|
||||||
const generator = generators.find((g) => g.slug === 'colors')!;
|
const generator = generators.find((g) => g.slug === "colors")!;
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout title={generator.ruSeoTitle} description={generator.ruSeoDescription} lang="ru">
|
<GeneratorLayout generator={generator}>
|
||||||
<div class="max-w-2xl mx-auto px-4 py-12 sm:py-20">
|
<ColorGenerator />
|
||||||
<nav aria-label="Breadcrumb" class="mb-10">
|
</GeneratorLayout>
|
||||||
<a href="/ru/" 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="Все генераторы">
|
|
||||||
<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>
|
|
||||||
Все генераторы
|
|
||||||
</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">Генератор цветов</h1>
|
|
||||||
<p class="mt-2 text-base text-zinc-400">{generator.ruDescription}</p>
|
|
||||||
</header>
|
|
||||||
<main><ColorGenerator /></main>
|
|
||||||
<div class="mt-12 flex justify-center"><AdBanner size="tile" /></div>
|
|
||||||
<SeoBlock lang="ru" howTo={[
|
|
||||||
'Нажмите «Сгенерировать», чтобы получить случайный цвет.',
|
|
||||||
'Цвет отображается в форматах HEX, RGB и HSL одновременно.',
|
|
||||||
'Нажмите на иконку копирования рядом с нужным форматом.',
|
|
||||||
]} whenTo={[
|
|
||||||
'Поиск вдохновения для цветовой палитры.',
|
|
||||||
'Случайный выбор цвета для дизайна или иллюстрации.',
|
|
||||||
'Генерация тестовых данных с цветовыми значениями.',
|
|
||||||
]} />
|
|
||||||
</div>
|
|
||||||
</BaseLayout>
|
|
||||||
|
|||||||
@@ -1,43 +1,11 @@
|
|||||||
---
|
---
|
||||||
import BaseLayout from '../../../layouts/BaseLayout.astro';
|
import GeneratorLayout from "@/layouts/GeneratorLayout.astro";
|
||||||
import DiceGenerator from '../../../components/generators/DiceGenerator.astro';
|
import DiceGenerator from "@/components/generators/DiceGenerator.astro";
|
||||||
import AdBanner from '../../../components/AdBanner.astro';
|
import { generators } from "@/data/generators";
|
||||||
import SeoBlock from '../../../components/SeoBlock.astro';
|
|
||||||
import { generators } from '../../../data/generators';
|
|
||||||
|
|
||||||
const generator = generators.find((g) => g.slug === 'dice')!;
|
const generator = generators.find((g) => g.slug === "dice")!;
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout title={generator.ruSeoTitle} description={generator.ruSeoDescription} lang="ru">
|
<GeneratorLayout generator={generator}>
|
||||||
<div class="max-w-2xl mx-auto px-4 py-12 sm:py-20">
|
<DiceGenerator />
|
||||||
<nav aria-label="Breadcrumb" class="mb-10">
|
</GeneratorLayout>
|
||||||
<a href="/ru/" 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="Все генераторы">
|
|
||||||
<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>
|
|
||||||
Все генераторы
|
|
||||||
</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">Бросить кубик онлайн</h1>
|
|
||||||
<p class="mt-2 text-base text-zinc-400">{generator.ruDescription}</p>
|
|
||||||
</header>
|
|
||||||
<main><DiceGenerator /></main>
|
|
||||||
<div class="mt-12 flex justify-center"><AdBanner size="tile" /></div>
|
|
||||||
<SeoBlock lang="ru" howTo={[
|
|
||||||
'Введите нотацию, например 2d6+3 или 1d20 — или настройте кубики вручную.',
|
|
||||||
'Выберите режим: Обычный, Преимущество или Помеха (только для одного кубика).',
|
|
||||||
'Нажмите «Бросить» — результаты отобразятся мгновенно.',
|
|
||||||
'Максимумы подсвечены фиолетовым, единицы — красным.',
|
|
||||||
]} whenTo={[
|
|
||||||
'Броски в настольных ролевых играх (D&D и другие).',
|
|
||||||
'Быстрые броски через нотацию: 2d6+3, 1d20 и любые другие.',
|
|
||||||
'Преимущество и помеха для механик D&D 5e.',
|
|
||||||
'Бросок характеристик: 4d6, отбросить наименьший (4d6dl1).',
|
|
||||||
'Взрывающиеся кубики для Savage Worlds или хаус-рулов (например, 3d6!).',
|
|
||||||
'Использование keep/drop модификаторов типа 4d6kh3 (оставить три лучших).',
|
|
||||||
]} />
|
|
||||||
</div>
|
|
||||||
</BaseLayout>
|
|
||||||
|
|||||||
@@ -1,40 +1,11 @@
|
|||||||
---
|
---
|
||||||
import BaseLayout from '../../../layouts/BaseLayout.astro';
|
import GeneratorLayout from "@/layouts/GeneratorLayout.astro";
|
||||||
import ListGenerator from '../../../components/generators/ListGenerator.astro';
|
import ListGenerator from "@/components/generators/ListGenerator.astro";
|
||||||
import AdBanner from '../../../components/AdBanner.astro';
|
import { generators } from "@/data/generators";
|
||||||
import SeoBlock from '../../../components/SeoBlock.astro';
|
|
||||||
import { generators } from '../../../data/generators';
|
|
||||||
|
|
||||||
const generator = generators.find((g) => g.slug === 'list')!;
|
const generator = generators.find((g) => g.slug === "list")!;
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout title={generator.ruSeoTitle} description={generator.ruSeoDescription} lang="ru">
|
<GeneratorLayout generator={generator}>
|
||||||
<div class="max-w-2xl mx-auto px-4 py-12 sm:py-20">
|
<ListGenerator />
|
||||||
<nav aria-label="Breadcrumb" class="mb-10">
|
</GeneratorLayout>
|
||||||
<a href="/ru/" 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="Все генераторы">
|
|
||||||
<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>
|
|
||||||
Все генераторы
|
|
||||||
</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">Случайный выбор из списка</h1>
|
|
||||||
<p class="mt-2 text-base text-zinc-400">{generator.ruDescription}</p>
|
|
||||||
</header>
|
|
||||||
<main><ListGenerator /></main>
|
|
||||||
<div class="mt-12 flex justify-center"><AdBanner size="tile" /></div>
|
|
||||||
<SeoBlock lang="ru" howTo={[
|
|
||||||
'Введите список элементов — по одному на строку.',
|
|
||||||
'Укажите, сколько элементов выбрать.',
|
|
||||||
'При необходимости разрешите повторения.',
|
|
||||||
'Нажмите «Выбрать» — результаты появятся на экране.',
|
|
||||||
]} whenTo={[
|
|
||||||
'Розыгрыш победителей конкурса.',
|
|
||||||
'Случайное распределение задач между участниками команды.',
|
|
||||||
'Выбор случайного варианта из списка.',
|
|
||||||
]} />
|
|
||||||
</div>
|
|
||||||
</BaseLayout>
|
|
||||||
|
|||||||
@@ -1,40 +1,11 @@
|
|||||||
---
|
---
|
||||||
import BaseLayout from '../../../layouts/BaseLayout.astro';
|
import GeneratorLayout from "@/layouts/GeneratorLayout.astro";
|
||||||
import LotteryGenerator from '../../../components/generators/LotteryGenerator.astro';
|
import LotteryGenerator from "@/components/generators/LotteryGenerator.astro";
|
||||||
import AdBanner from '../../../components/AdBanner.astro';
|
import { generators } from "@/data/generators";
|
||||||
import SeoBlock from '../../../components/SeoBlock.astro';
|
|
||||||
import { generators } from '../../../data/generators';
|
|
||||||
|
|
||||||
const generator = generators.find((g) => g.slug === 'lottery')!;
|
const generator = generators.find((g) => g.slug === "lottery")!;
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout title={generator.ruSeoTitle} description={generator.ruSeoDescription} lang="ru">
|
<GeneratorLayout generator={generator}>
|
||||||
<div class="max-w-2xl mx-auto px-4 py-12 sm:py-20">
|
<LotteryGenerator />
|
||||||
<nav aria-label="Breadcrumb" class="mb-10">
|
</GeneratorLayout>
|
||||||
<a href="/ru/" 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="Все генераторы">
|
|
||||||
<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>
|
|
||||||
Все генераторы
|
|
||||||
</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">Генератор лотереи</h1>
|
|
||||||
<p class="mt-2 text-base text-zinc-400">{generator.ruDescription}</p>
|
|
||||||
</header>
|
|
||||||
<main><LotteryGenerator /></main>
|
|
||||||
<div class="mt-12 flex justify-center"><AdBanner size="tile" /></div>
|
|
||||||
<SeoBlock lang="ru" howTo={[
|
|
||||||
'Укажите диапазон чисел в полях «От» и «До».',
|
|
||||||
'Задайте количество чисел для розыгрыша.',
|
|
||||||
'Нажмите «Тянуть» — выпавшие числа появятся на экране.',
|
|
||||||
'Нажмите «Копировать», чтобы сохранить результат.',
|
|
||||||
]} whenTo={[
|
|
||||||
'Розыгрыш лотерейных номеров.',
|
|
||||||
'Жеребьёвка участников конкурса.',
|
|
||||||
'Случайный выбор нескольких уникальных чисел.',
|
|
||||||
]} />
|
|
||||||
</div>
|
|
||||||
</BaseLayout>
|
|
||||||
|
|||||||
@@ -1,41 +1,11 @@
|
|||||||
---
|
---
|
||||||
import BaseLayout from '../../../layouts/BaseLayout.astro';
|
import GeneratorLayout from "@/layouts/GeneratorLayout.astro";
|
||||||
import NumberGenerator from '../../../components/generators/NumberGenerator.astro';
|
import NumberGenerator from "@/components/generators/NumberGenerator.astro";
|
||||||
import AdBanner from '../../../components/AdBanner.astro';
|
import { generators } from "@/data/generators";
|
||||||
import SeoBlock from '../../../components/SeoBlock.astro';
|
|
||||||
import { generators } from '../../../data/generators';
|
|
||||||
|
|
||||||
const generator = generators.find((g) => g.slug === 'numbers')!;
|
const generator = generators.find((g) => g.slug === "numbers")!;
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout title={generator.ruSeoTitle} description={generator.ruSeoDescription} lang="ru">
|
<GeneratorLayout generator={generator}>
|
||||||
<div class="max-w-2xl mx-auto px-4 py-12 sm:py-20">
|
<NumberGenerator />
|
||||||
<nav aria-label="Breadcrumb" class="mb-10">
|
</GeneratorLayout>
|
||||||
<a href="/ru/" 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="Все генераторы">
|
|
||||||
<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>
|
|
||||||
Все генераторы
|
|
||||||
</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">Генератор чисел</h1>
|
|
||||||
<p class="mt-2 text-base text-zinc-400">{generator.ruDescription}</p>
|
|
||||||
</header>
|
|
||||||
<main><NumberGenerator /></main>
|
|
||||||
<div class="mt-12 flex justify-center"><AdBanner size="tile" /></div>
|
|
||||||
<SeoBlock lang="ru" howTo={[
|
|
||||||
'Введите минимальное значение в поле «От».',
|
|
||||||
'Введите максимальное значение в поле «До».',
|
|
||||||
'Нажмите «Сгенерировать» — случайное число появится мгновенно.',
|
|
||||||
'Нажмите на число, чтобы скопировать его в буфер обмена.',
|
|
||||||
]} whenTo={[
|
|
||||||
'Выбор победителя в розыгрыше или конкурсе.',
|
|
||||||
'Определение случайного первого игрока в настольной игре.',
|
|
||||||
'Быстрое принятие решения между пронумерованными вариантами.',
|
|
||||||
'Генерация тестовых данных с числами в заданном диапазоне.',
|
|
||||||
]} />
|
|
||||||
</div>
|
|
||||||
</BaseLayout>
|
|
||||||
|
|||||||
@@ -1,40 +1,11 @@
|
|||||||
---
|
---
|
||||||
import BaseLayout from '../../../layouts/BaseLayout.astro';
|
import GeneratorLayout from "@/layouts/GeneratorLayout.astro";
|
||||||
import PasswordGenerator from '../../../components/generators/PasswordGenerator.astro';
|
import PasswordGenerator from "@/components/generators/PasswordGenerator.astro";
|
||||||
import AdBanner from '../../../components/AdBanner.astro';
|
import { generators } from "@/data/generators";
|
||||||
import SeoBlock from '../../../components/SeoBlock.astro';
|
|
||||||
import { generators } from '../../../data/generators';
|
|
||||||
|
|
||||||
const generator = generators.find((g) => g.slug === 'password')!;
|
const generator = generators.find((g) => g.slug === "password")!;
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout title={generator.ruSeoTitle} description={generator.ruSeoDescription} lang="ru">
|
<GeneratorLayout generator={generator}>
|
||||||
<div class="max-w-2xl mx-auto px-4 py-12 sm:py-20">
|
<PasswordGenerator />
|
||||||
<nav aria-label="Breadcrumb" class="mb-10">
|
</GeneratorLayout>
|
||||||
<a href="/ru/" 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="Все генераторы">
|
|
||||||
<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>
|
|
||||||
Все генераторы
|
|
||||||
</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">Генератор паролей</h1>
|
|
||||||
<p class="mt-2 text-base text-zinc-400">{generator.ruDescription}</p>
|
|
||||||
</header>
|
|
||||||
<main><PasswordGenerator /></main>
|
|
||||||
<div class="mt-12 flex justify-center"><AdBanner size="tile" /></div>
|
|
||||||
<SeoBlock lang="ru" howTo={[
|
|
||||||
'Задайте длину пароля с помощью ползунка.',
|
|
||||||
'Выберите типы символов: заглавные, строчные, цифры, спецсимволы.',
|
|
||||||
'Нажмите «Сгенерировать» — готовый пароль появится на экране.',
|
|
||||||
'Нажмите на пароль, чтобы скопировать его.',
|
|
||||||
]} whenTo={[
|
|
||||||
'Создание надёжного пароля для нового аккаунта.',
|
|
||||||
'Генерация секретного ключа или токена.',
|
|
||||||
'Регулярное обновление паролей для повышения безопасности.',
|
|
||||||
]} />
|
|
||||||
</div>
|
|
||||||
</BaseLayout>
|
|
||||||
|
|||||||
@@ -1,40 +1,11 @@
|
|||||||
---
|
---
|
||||||
import BaseLayout from '../../../layouts/BaseLayout.astro';
|
import GeneratorLayout from "@/layouts/GeneratorLayout.astro";
|
||||||
import UuidGenerator from '../../../components/generators/UuidGenerator.astro';
|
import UuidGenerator from "@/components/generators/UuidGenerator.astro";
|
||||||
import AdBanner from '../../../components/AdBanner.astro';
|
import { generators } from "@/data/generators";
|
||||||
import SeoBlock from '../../../components/SeoBlock.astro';
|
|
||||||
import { generators } from '../../../data/generators';
|
|
||||||
|
|
||||||
const generator = generators.find((g) => g.slug === 'uuid')!;
|
const generator = generators.find((g) => g.slug === "uuid")!;
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout title={generator.ruSeoTitle} description={generator.ruSeoDescription} lang="ru">
|
<GeneratorLayout generator={generator}>
|
||||||
<div class="max-w-2xl mx-auto px-4 py-12 sm:py-20">
|
<UuidGenerator />
|
||||||
<nav aria-label="Breadcrumb" class="mb-10">
|
</GeneratorLayout>
|
||||||
<a href="/ru/" 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="Все генераторы">
|
|
||||||
<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>
|
|
||||||
Все генераторы
|
|
||||||
</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">Генератор UUID и токенов</h1>
|
|
||||||
<p class="mt-2 text-base text-zinc-400">{generator.ruDescription}</p>
|
|
||||||
</header>
|
|
||||||
<main><UuidGenerator /></main>
|
|
||||||
<div class="mt-12 flex justify-center"><AdBanner size="tile" /></div>
|
|
||||||
<SeoBlock lang="ru" howTo={[
|
|
||||||
'Выберите тип: UUID v4, Hex или Base64.',
|
|
||||||
'Для Hex и Base64 задайте длину в байтах.',
|
|
||||||
'Укажите количество токенов.',
|
|
||||||
'Нажмите «Сгенерировать» и скопируйте результат.',
|
|
||||||
]} whenTo={[
|
|
||||||
'Генерация уникальных идентификаторов для баз данных.',
|
|
||||||
'Создание токенов для API и сессий.',
|
|
||||||
'Генерация случайных ключей шифрования.',
|
|
||||||
]} />
|
|
||||||
</div>
|
|
||||||
</BaseLayout>
|
|
||||||
|
|||||||
@@ -1,40 +1,11 @@
|
|||||||
---
|
---
|
||||||
import BaseLayout from '../../../layouts/BaseLayout.astro';
|
import GeneratorLayout from "@/layouts/GeneratorLayout.astro";
|
||||||
import WheelSpinner from '../../../components/generators/WheelSpinner.astro';
|
import WheelSpinner from "@/components/generators/WheelSpinner.astro";
|
||||||
import AdBanner from '../../../components/AdBanner.astro';
|
import { generators } from "@/data/generators";
|
||||||
import SeoBlock from '../../../components/SeoBlock.astro';
|
|
||||||
import { generators } from '../../../data/generators';
|
|
||||||
|
|
||||||
const generator = generators.find((g) => g.slug === 'wheel')!;
|
const generator = generators.find((g) => g.slug === "wheel")!;
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout title={generator.ruSeoTitle} description={generator.ruSeoDescription} lang="ru">
|
<GeneratorLayout generator={generator}>
|
||||||
<div class="max-w-2xl mx-auto px-4 py-12 sm:py-20">
|
<WheelSpinner />
|
||||||
<nav aria-label="Breadcrumb" class="mb-10">
|
</GeneratorLayout>
|
||||||
<a href="/ru/" 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="Все генераторы">
|
|
||||||
<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>
|
|
||||||
Все генераторы
|
|
||||||
</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">Колесо фортуны онлайн</h1>
|
|
||||||
<p class="mt-2 text-base text-zinc-400">{generator.ruDescription}</p>
|
|
||||||
</header>
|
|
||||||
<main><WheelSpinner /></main>
|
|
||||||
<div class="mt-12 flex justify-center"><AdBanner size="tile" /></div>
|
|
||||||
<SeoBlock lang="ru" howTo={[
|
|
||||||
'Введите варианты в поле — по одному на строку (от 2 до 24).',
|
|
||||||
'Колесо обновляется автоматически.',
|
|
||||||
'Нажмите «Крутить» и дождитесь результата.',
|
|
||||||
'Победитель выделяется — скопируйте его кнопкой «Копировать».',
|
|
||||||
]} whenTo={[
|
|
||||||
'Случайный выбор победителя розыгрыша.',
|
|
||||||
'Принятие решений в команде.',
|
|
||||||
'Распределение ролей или задач между участниками.',
|
|
||||||
]} />
|
|
||||||
</div>
|
|
||||||
</BaseLayout>
|
|
||||||
|
|||||||
+46
-43
@@ -1,50 +1,53 @@
|
|||||||
---
|
---
|
||||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
import BaseLayout from "@/layouts/BaseLayout.astro";
|
||||||
import GeneratorCard from '../../components/GeneratorCard.astro';
|
import GeneratorCard from "@/components/GeneratorCard.astro";
|
||||||
import AdBanner from '../../components/AdBanner.astro';
|
import AdBanner from "@/components/AdBanner.astro";
|
||||||
import { generators } from '../../data/generators';
|
import { generators } from "@/data/generators";
|
||||||
import { useT } from '../../i18n/translations';
|
import { useT } from "@/i18n/translations";
|
||||||
|
import type { Lang } from "@/i18n/translations";
|
||||||
|
|
||||||
const T = useT('ru');
|
const lang = (Astro.currentLocale as Lang) || "en";
|
||||||
|
const T = useT(lang);
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout
|
<BaseLayout>
|
||||||
title={T.defaultTitle}
|
<div class="max-w-4xl mx-auto px-4 py-12 sm:py-20">
|
||||||
description={T.defaultDesc}
|
<header class="mb-12">
|
||||||
lang="ru"
|
<div class="flex items-center gap-2 mb-6">
|
||||||
>
|
<span
|
||||||
<div class="max-w-4xl mx-auto px-4 py-12 sm:py-20">
|
class="inline-block w-3 h-3 rounded-full bg-[#534AB7]"
|
||||||
<header class="mb-12">
|
aria-hidden="true"></span>
|
||||||
<div class="flex items-center gap-2 mb-6">
|
<span class="text-xl font-bold tracking-tight text-zinc-100"
|
||||||
<span
|
>{T.brandLabel}</span
|
||||||
class="inline-block w-3 h-3 rounded-full bg-[#534AB7]"
|
>
|
||||||
aria-hidden="true"
|
</div>
|
||||||
></span>
|
<h1
|
||||||
<span class="text-xl font-bold tracking-tight text-zinc-100">{T.brandLabel}</span>
|
class="text-3xl sm:text-4xl font-bold text-zinc-100 leading-tight"
|
||||||
</div>
|
>
|
||||||
<h1 class="text-3xl sm:text-4xl font-bold text-zinc-100 leading-tight">
|
{T.homeTitle}
|
||||||
{T.homeTitle}
|
</h1>
|
||||||
</h1>
|
<p class="mt-3 text-base text-zinc-400 max-w-md">
|
||||||
<p class="mt-3 text-base text-zinc-400 max-w-md">
|
{T.homeSubtitle}
|
||||||
{T.homeSubtitle}
|
</p>
|
||||||
</p>
|
</header>
|
||||||
</header>
|
|
||||||
|
|
||||||
<AdBanner size="leaderboard" />
|
<AdBanner size="leaderboard" />
|
||||||
|
|
||||||
<main class="mt-8">
|
<main class="mt-8">
|
||||||
<div
|
<div
|
||||||
class="grid gap-3"
|
class="grid gap-3"
|
||||||
style="grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));"
|
style="grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));"
|
||||||
role="list"
|
role="list"
|
||||||
aria-label="Generator catalog"
|
aria-label="Generator catalog"
|
||||||
>
|
>
|
||||||
{generators.map((generator) => (
|
{
|
||||||
<div role="listitem">
|
generators.map((generator) => (
|
||||||
<GeneratorCard generator={generator} lang="ru" />
|
<div role="listitem">
|
||||||
</div>
|
<GeneratorCard generator={generator} />
|
||||||
))}
|
</div>
|
||||||
</div>
|
))
|
||||||
</main>
|
}
|
||||||
</div>
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
|||||||
Reference in New Issue
Block a user