mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 03:59:50 +02:00
feat: homepage
This commit is contained in:
1 parent
1d44ecab18
commit
d9688f6051
18 files changed
+303
-153
No files matched your search
+138
-1
@@ -1,5 +1,18 @@
|
|||||||
{
|
{
|
||||||
|
"test": {
|
||||||
|
"$": "test",
|
||||||
|
"long": "This is a long test string. You MUST NEVER see this in the UI. My favorite fruit is %s1 because it has the color of %s2. I like it so much that I will say its name 3 times! %s1! %s1! %s1! Once again, it's %s2, not %s3.",
|
||||||
|
"fox": "The quick brown fox jumps over the lazy dog."
|
||||||
|
},
|
||||||
|
|
||||||
"common": {
|
"common": {
|
||||||
|
"branches": {
|
||||||
|
"local": "dev",
|
||||||
|
"dev": "beta",
|
||||||
|
"opentest": "opentest",
|
||||||
|
"preview": "preview"
|
||||||
|
},
|
||||||
|
|
||||||
"locales": {
|
"locales": {
|
||||||
"en": "English",
|
"en": "English",
|
||||||
"es": "Spanish",
|
"es": "Spanish",
|
||||||
@@ -689,5 +702,129 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"website": {}
|
"website": {
|
||||||
|
"home": {
|
||||||
|
"hero": {
|
||||||
|
"credit": "By %s1 ❤️"
|
||||||
|
},
|
||||||
|
"hot_tanks": {
|
||||||
|
"title": "Popular tanks",
|
||||||
|
"subtitle": "Last %s1 days"
|
||||||
|
},
|
||||||
|
"other_projects": {
|
||||||
|
"title": "Other cool projects"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"tools": {
|
||||||
|
"tankopedia": {
|
||||||
|
"name": "Tankopedia",
|
||||||
|
"button": "Find tanks",
|
||||||
|
"description": "Blitz tank encyclopedia",
|
||||||
|
"seo_description": "Statistics, armor, and more for all tanks in World of Tanks Blitz"
|
||||||
|
},
|
||||||
|
"compare": {
|
||||||
|
"name": "Compare",
|
||||||
|
"button": "Compare tanks",
|
||||||
|
"description": "Compare tank statistics",
|
||||||
|
"seo_description": "Compare tanks statistics and loadouts in World of Tanks Blitz"
|
||||||
|
},
|
||||||
|
"performance": {
|
||||||
|
"name": "Performance",
|
||||||
|
"button": "View tanks",
|
||||||
|
"description": "Tank performance statistics",
|
||||||
|
"seo_description": "Best tanks and their performances in World of Tanks Blitz"
|
||||||
|
},
|
||||||
|
"gallery": {
|
||||||
|
"name": "Gallery",
|
||||||
|
"button": "Browse all",
|
||||||
|
"description": "Blitz player avatars",
|
||||||
|
"seo_description": "All World of Tanks Blitz player avatars"
|
||||||
|
},
|
||||||
|
"session": {
|
||||||
|
"name": "Session",
|
||||||
|
"button": "Start tracking",
|
||||||
|
"description": "Live session stats",
|
||||||
|
"seo_description": "Track your stats in real time as you play World of Tanks Blitz"
|
||||||
|
},
|
||||||
|
"tier_list": {
|
||||||
|
"name": "Tier list",
|
||||||
|
"button": "Rank tanks",
|
||||||
|
"description": "Rank Blitz tanks",
|
||||||
|
"seo_description": "Explore tier lists ranking tanks from best to worst, based on player ratings and statistical performance in World of Tanks Blitz"
|
||||||
|
},
|
||||||
|
"embed": {
|
||||||
|
"name": "Embed",
|
||||||
|
"button": "Create now",
|
||||||
|
"description": "Embeds for streaming",
|
||||||
|
"seo_description": "Create embeds for streaming World of Tanks Blitz with extreme customization"
|
||||||
|
},
|
||||||
|
"charts": {
|
||||||
|
"name": "Charts",
|
||||||
|
"button": "Nerd out",
|
||||||
|
"description": "Lots of graphs",
|
||||||
|
"seo_description": "Charts and graphs on anything and everything about tanks, players, and events in World of Tanks Blitz"
|
||||||
|
},
|
||||||
|
"rating": {
|
||||||
|
"name": "Rating",
|
||||||
|
"button": "View leaderboard",
|
||||||
|
"description": "Full rating leaderboard",
|
||||||
|
"seo_description": "Live and archived full rating leaderboard for World of Tanks Blitz"
|
||||||
|
},
|
||||||
|
"discord": {
|
||||||
|
"name": "Discord bot",
|
||||||
|
"button": "Install now",
|
||||||
|
"description": "Stats right in Discord",
|
||||||
|
"seo_description": "Get stats right in Discord with BlitzKit's Discord bot"
|
||||||
|
},
|
||||||
|
"more": {
|
||||||
|
"name": "More coming soon",
|
||||||
|
"button": "Join Discord",
|
||||||
|
"description": "What we're making",
|
||||||
|
"seo_description": "What we're making with BlitzKit"
|
||||||
|
},
|
||||||
|
"players": {
|
||||||
|
"name": "Players",
|
||||||
|
"button": "Lookup player",
|
||||||
|
"description": "Periodical player statistics",
|
||||||
|
"seo_description": "Periodical player statistics in World of Tanks Blitz"
|
||||||
|
},
|
||||||
|
"home": {
|
||||||
|
"name": "Home",
|
||||||
|
"button": "Get started",
|
||||||
|
"description": "Get started with BlitzKit",
|
||||||
|
"seo_description": "Get started with BlitzKit"
|
||||||
|
},
|
||||||
|
"blitz_analysis": {
|
||||||
|
"name": "BlitzAnalysis[]",
|
||||||
|
"button": "Analyze now",
|
||||||
|
"description": "Rich historic average stats",
|
||||||
|
"seo_description": "Rich historic average stats in World of Tanks Blitz"
|
||||||
|
},
|
||||||
|
"blitz_insider": {
|
||||||
|
"name": "Blitz Insider",
|
||||||
|
"button": "Discover today",
|
||||||
|
"description": "In-game session stats",
|
||||||
|
"seo_description": "In-game session stats in World of Tanks Blitz"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"footer": {
|
||||||
|
"motto": "Everything World of Tanks Blitz",
|
||||||
|
"copyright": "Unaffiliated with Wargaming or any service",
|
||||||
|
|
||||||
|
"about": {
|
||||||
|
"title": "About",
|
||||||
|
"credits": "Credits",
|
||||||
|
"discord": "Discord",
|
||||||
|
"github": "GitHub",
|
||||||
|
"patches": "Patches"
|
||||||
|
},
|
||||||
|
"legal": {
|
||||||
|
"title": "Legal",
|
||||||
|
"privacy": "Privacy policy",
|
||||||
|
"terms": "Terms of service"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,2 @@
|
|||||||
export const SUPPORTED_LOCALES = [
|
export const SUPPORTED_LOCALES = ['en', 'es', 'hr', 'pt', 'ru', 'uk', 'zh'];
|
||||||
'en',
|
|
||||||
'es',
|
|
||||||
'hr',
|
|
||||||
'pt',
|
|
||||||
'ru',
|
|
||||||
'uk',
|
|
||||||
'zh',
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export const DEFAULT_LOCALE = SUPPORTED_LOCALES[0];
|
export const DEFAULT_LOCALE = SUPPORTED_LOCALES[0];
|
||||||
|
|
||||||
export type SupportedLocale = (typeof SUPPORTED_LOCALES)[number];
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './strings';
|
||||||
|
export * from './translator';
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { merge } from 'lodash-es';
|
||||||
|
import type en from '../../lang/en.json';
|
||||||
|
import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from '../blitzkit';
|
||||||
|
import { TranslationTree } from './translator';
|
||||||
|
|
||||||
|
const DEBUG_MISSING_I18N = import.meta.env.DEBUG_MISSING_I18N;
|
||||||
|
const files = import.meta.glob('../../lang/*.json', { eager: true });
|
||||||
|
export const localizedStrings: Record<string, typeof en> = {};
|
||||||
|
|
||||||
|
function nuke(object: TranslationTree) {
|
||||||
|
for (const key in object) {
|
||||||
|
const typedKey = key as keyof typeof object;
|
||||||
|
|
||||||
|
if (typeof object[typedKey] === 'string') {
|
||||||
|
const value = object[typedKey];
|
||||||
|
object[typedKey] = redact(value as string);
|
||||||
|
} else nuke(object[typedKey] as TranslationTree);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SUPPORTED_LOCALES.forEach((locale) => {
|
||||||
|
const defaultStrings = files[`../../lang/${DEFAULT_LOCALE}.json`];
|
||||||
|
const strings = files[`../../lang/${locale}.json`];
|
||||||
|
const mergedStrings = merge({}, defaultStrings, strings);
|
||||||
|
|
||||||
|
if (DEBUG_MISSING_I18N === 'true') nuke(mergedStrings);
|
||||||
|
|
||||||
|
localizedStrings[locale] = mergedStrings as typeof en;
|
||||||
|
});
|
||||||
|
|
||||||
|
function redact(string: string) {
|
||||||
|
const matches = [...string.matchAll(/%s\d+/g)];
|
||||||
|
const characters = string
|
||||||
|
.split('')
|
||||||
|
.map((char) => (char === ' ' ? ' ' : '█')) as string[];
|
||||||
|
|
||||||
|
matches.forEach((match) => {
|
||||||
|
const start = match.index;
|
||||||
|
const matchText = match[0];
|
||||||
|
for (let i = 0; i < matchText.length; i++) {
|
||||||
|
characters[start + i] = matchText[i];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return characters.join('');
|
||||||
|
}
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
import en from '@blitzkit/core/lang/en.json';
|
export type TranslationTree = { [key: string]: TranslationNode };
|
||||||
import ru from '@blitzkit/core/lang/ru.json';
|
|
||||||
|
|
||||||
type TranslationTree = { [key: string]: Translation };
|
export type TranslationNode = string | TranslationTree;
|
||||||
|
|
||||||
type Translation = string | TranslationTree;
|
|
||||||
|
|
||||||
type NestedKeys<Type> = Type extends object
|
type NestedKeys<Type> = Type extends object
|
||||||
? {
|
? {
|
||||||
@@ -17,7 +14,7 @@ type NestedKeys<Type> = Type extends object
|
|||||||
}[keyof Type]
|
}[keyof Type]
|
||||||
: never;
|
: never;
|
||||||
|
|
||||||
class Prate<
|
export class Translator<
|
||||||
Locale extends string,
|
Locale extends string,
|
||||||
DefaultLocale extends Locale,
|
DefaultLocale extends Locale,
|
||||||
Strings extends TranslationTree,
|
Strings extends TranslationTree,
|
||||||
@@ -26,8 +23,8 @@ class Prate<
|
|||||||
private readonly defaultStrings: Map<string, string>;
|
private readonly defaultStrings: Map<string, string>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public strings: Record<Locale, Strings>,
|
private strings: Record<Locale, Strings>,
|
||||||
public defaultLocale: DefaultLocale,
|
private defaultLocale: DefaultLocale,
|
||||||
) {
|
) {
|
||||||
this.defaultStrings = this.flatten(strings[defaultLocale]);
|
this.defaultStrings = this.flatten(strings[defaultLocale]);
|
||||||
}
|
}
|
||||||
@@ -35,7 +32,7 @@ class Prate<
|
|||||||
private flatten(strings: TranslationTree) {
|
private flatten(strings: TranslationTree) {
|
||||||
const translations = new Map<string, string>();
|
const translations = new Map<string, string>();
|
||||||
|
|
||||||
function traverse(parent: string, tree: Record<string, Translation>) {
|
function traverse(parent: string, tree: Record<string, TranslationNode>) {
|
||||||
for (const key in tree) {
|
for (const key in tree) {
|
||||||
const path = `${parent}${key}`;
|
const path = `${parent}${key}`;
|
||||||
|
|
||||||
@@ -91,5 +88,3 @@ class Prate<
|
|||||||
return { translate, t: translate };
|
return { translate, t: translate };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const prate = new Prate({ en, ru }, 'en');
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
export * from './blitz';
|
export * from './blitz';
|
||||||
export * from './blitzkit';
|
export * from './blitzkit';
|
||||||
export * from './databases';
|
export * from './databases';
|
||||||
|
export * from './i18n';
|
||||||
export * from './imgur';
|
export * from './imgur';
|
||||||
export * from './math';
|
export * from './math';
|
||||||
export * from './patreon';
|
export * from './patreon';
|
||||||
|
|||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
@@ -2,10 +2,12 @@
|
|||||||
import { assertSecret, fetchGameDefinitions } from '@blitzkit/core';
|
import { assertSecret, fetchGameDefinitions } from '@blitzkit/core';
|
||||||
import { Code, Flex, Heading, Link, Text } from '@radix-ui/themes';
|
import { Code, Flex, Heading, Link, Text } from '@radix-ui/themes';
|
||||||
import packageJson from '../../../../package.json';
|
import packageJson from '../../../../package.json';
|
||||||
|
import { getStrings } from '../core/i18n/getStrings';
|
||||||
import { LinkI18n } from './LinkI18n';
|
import { LinkI18n } from './LinkI18n';
|
||||||
|
|
||||||
const gameDefinitions = await fetchGameDefinitions();
|
const gameDefinitions = await fetchGameDefinitions();
|
||||||
const { locale } = Astro.params;
|
const { locale } = Astro.params;
|
||||||
|
const strings = getStrings(locale);
|
||||||
---
|
---
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -15,7 +17,6 @@ const { locale } = Astro.params;
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<Flex className="footer" gap="6" p="6" direction="column" align="center">
|
<Flex className="footer" gap="6" p="6" direction="column" align="center">
|
||||||
{locale}
|
|
||||||
<Flex
|
<Flex
|
||||||
align="center"
|
align="center"
|
||||||
justify="between"
|
justify="between"
|
||||||
@@ -34,9 +35,9 @@ const { locale } = Astro.params;
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Heading>BlitzKit</Heading>
|
<Heading>BlitzKit</Heading>
|
||||||
<Text color="gray">Everything World of Tanks Blitz</Text>
|
<Text color="gray">{strings.website.footer.motto}</Text>
|
||||||
<Text size="1" color="gray">
|
<Text size="1" color="gray">
|
||||||
Unaffiliated with Wargaming or any service
|
{strings.website.footer.copyright}
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
@@ -63,7 +64,7 @@ const { locale } = Astro.params;
|
|||||||
sm: 'auto',
|
sm: 'auto',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Heading size="3">About</Heading>
|
<Heading size="3">{strings.website.footer.about.title}</Heading>
|
||||||
<Flex
|
<Flex
|
||||||
direction="column"
|
direction="column"
|
||||||
align={{
|
align={{
|
||||||
@@ -71,25 +72,26 @@ const { locale } = Astro.params;
|
|||||||
sm: 'start',
|
sm: 'start',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<LinkI18n {locale} href="/docs/about/credits" color="gray"
|
<LinkI18n {locale} href="/docs/about/credits" color="gray">
|
||||||
>Credits</LinkI18n
|
{strings.website.footer.about.credits}
|
||||||
>
|
</LinkI18n>
|
||||||
<Link
|
<Link
|
||||||
href="https://discord.gg/nDt7AjGJQH"
|
href="https://discord.gg/nDt7AjGJQH"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
color="gray"
|
color="gray"
|
||||||
>
|
>
|
||||||
Discord
|
{strings.website.footer.about.discord}
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href="https://github.com/tresabhi/blitzkit"
|
href="https://github.com/tresabhi/blitzkit"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
color="gray"
|
color="gray"
|
||||||
>
|
>
|
||||||
GitHub
|
{strings.website.footer.about.github}
|
||||||
</Link>
|
</Link>
|
||||||
<LinkI18n {locale} href="/docs/patches" color="gray">Patches</LinkI18n
|
<LinkI18n {locale} href="/docs/patches" color="gray">
|
||||||
>
|
{strings.website.footer.about.patches}
|
||||||
|
</LinkI18n>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
@@ -105,13 +107,13 @@ const { locale } = Astro.params;
|
|||||||
sm: 'auto',
|
sm: 'auto',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Heading size="3">Legal</Heading>
|
<Heading size="3">{strings.website.footer.legal.title}</Heading>
|
||||||
<Flex direction="column">
|
<Flex direction="column">
|
||||||
<LinkI18n {locale} href="/docs/legal/privacy-policy" color="gray">
|
<LinkI18n {locale} href="/docs/legal/privacy-policy" color="gray">
|
||||||
Privacy policy
|
{strings.website.footer.legal.privacy}
|
||||||
</LinkI18n>
|
</LinkI18n>
|
||||||
<LinkI18n {locale} href="/docs/legal/terms-of-service" color="gray">
|
<LinkI18n {locale} href="/docs/legal/terms-of-service" color="gray">
|
||||||
Terms of service
|
{strings.website.footer.legal.terms}
|
||||||
</LinkI18n>
|
</LinkI18n>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
---
|
---
|
||||||
import { Code, Flex, Heading, Text } from '@radix-ui/themes';
|
import { Code, Flex, Heading, Text } from '@radix-ui/themes';
|
||||||
import { resolveBranchName } from '../core/blitzkit/branches';
|
import { resolveBranchName } from '../core/blitzkit/branches';
|
||||||
|
import { DEFAULT_LOCALE } from '@blitzkit/core';
|
||||||
|
import { getStrings } from '../core/i18n/getStrings';
|
||||||
|
import { literals } from '../core/i18n/literals';
|
||||||
|
|
||||||
const branchName = resolveBranchName();
|
const { locale } = Astro.params;
|
||||||
|
const branchName = resolveBranchName(locale);
|
||||||
const isBranchNamed = branchName !== undefined;
|
const isBranchNamed = branchName !== undefined;
|
||||||
|
const strings = getStrings(locale);
|
||||||
---
|
---
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -141,7 +146,9 @@ const isBranchNamed = branchName !== undefined;
|
|||||||
</Flex>
|
</Flex>
|
||||||
</Heading>
|
</Heading>
|
||||||
|
|
||||||
<Text color="gray" align="center" size="4">By TrèsAbhi ❤️</Text>
|
<Text color="gray" align="center" size="4"
|
||||||
|
>{literals(strings.website.home.hero.credit, ['TrèsAbhi'])}</Text
|
||||||
|
>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
@@ -3,7 +3,8 @@ import { EyeOpenIcon } from '@radix-ui/react-icons';
|
|||||||
import { Box, Flex, Heading, Text } from '@radix-ui/themes';
|
import { Box, Flex, Heading, Text } from '@radix-ui/themes';
|
||||||
import { google } from 'googleapis';
|
import { google } from 'googleapis';
|
||||||
import { awaitableTankDefinitions } from '../core/awaitables/tankDefinitions';
|
import { awaitableTankDefinitions } from '../core/awaitables/tankDefinitions';
|
||||||
import { LocaleProvider } from '../hooks/useLocale';
|
import { literals } from '../core/i18n/literals';
|
||||||
|
import { LocaleProvider, useLocale } from '../hooks/useLocale';
|
||||||
import { TankopediaPersistent } from '../stores/tankopediaPersistent';
|
import { TankopediaPersistent } from '../stores/tankopediaPersistent';
|
||||||
import { TankCard } from './TankCard';
|
import { TankCard } from './TankCard';
|
||||||
|
|
||||||
@@ -79,13 +80,15 @@ export function HomePageHotTanks({ locale }: HomePageHotTanksProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function Content() {
|
function Content() {
|
||||||
|
const { locale, strings } = useLocale();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex direction="column" pt="4" pb="8">
|
<Flex direction="column" pt="4" pb="8">
|
||||||
<Heading align="center" size="5">
|
<Heading align="center" size="5">
|
||||||
Popular tanks
|
{strings.website.home.hot_tanks.title}
|
||||||
</Heading>
|
</Heading>
|
||||||
<Text color="gray" align="center" mb="5" size="2">
|
<Text color="gray" align="center" mb="5" size="2">
|
||||||
Last 7 days
|
{literals(strings.website.home.hot_tanks.subtitle, ['7'])}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Flex justify="center" gap="4" wrap="wrap">
|
<Flex justify="center" gap="4" wrap="wrap">
|
||||||
@@ -96,7 +99,7 @@ function Content() {
|
|||||||
discriminator={
|
discriminator={
|
||||||
<Flex align="center" gap="1" justify="center">
|
<Flex align="center" gap="1" justify="center">
|
||||||
<EyeOpenIcon />
|
<EyeOpenIcon />
|
||||||
{Math.round(row.views * (30 / 7)).toLocaleString('en-US')}
|
{Math.round(row.views * (30 / 7)).toLocaleString(locale)}
|
||||||
</Flex>
|
</Flex>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -19,14 +19,15 @@ import {
|
|||||||
} from '@radix-ui/themes';
|
} from '@radix-ui/themes';
|
||||||
import { resolveBranchName } from '../core/blitzkit/branches';
|
import { resolveBranchName } from '../core/blitzkit/branches';
|
||||||
import { homeTool, TOOLS } from '../constants/tools';
|
import { homeTool, TOOLS } from '../constants/tools';
|
||||||
import { NavbarAccount } from './NavbarAccount';
|
|
||||||
import { LinkI18n } from './LinkI18n';
|
import { LinkI18n } from './LinkI18n';
|
||||||
|
import { getStrings } from '../core/i18n/getStrings';
|
||||||
|
|
||||||
const toolsFiltered = TOOLS.filter((tool) => !('href' in tool));
|
const toolsFiltered = TOOLS.filter((tool) => !('href' in tool));
|
||||||
const tools = [homeTool, ...toolsFiltered];
|
const tools = [homeTool, ...toolsFiltered];
|
||||||
const branchName = resolveBranchName();
|
|
||||||
const isBranchNamed = branchName !== undefined;
|
|
||||||
const { locale } = Astro.params;
|
const { locale } = Astro.params;
|
||||||
|
const branchName = resolveBranchName(locale);
|
||||||
|
const isBranchNamed = branchName !== undefined;
|
||||||
|
const strings = getStrings(locale);
|
||||||
---
|
---
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -139,14 +140,17 @@ const { locale } = Astro.params;
|
|||||||
(branch) =>
|
(branch) =>
|
||||||
branch !== assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH),
|
branch !== assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH),
|
||||||
);
|
);
|
||||||
const selected = Astro.url.pathname.startsWith(
|
const selected = Astro.url.pathname.includes(`/tools/${tool.id}`);
|
||||||
`/tools/${tool.id}`,
|
const toolStrings =
|
||||||
);
|
strings.website.tools[
|
||||||
|
(tool.stringsId ??
|
||||||
|
tool.id) as keyof typeof strings.website.tools
|
||||||
|
];
|
||||||
|
|
||||||
if (unavailableOnBranch) return null;
|
if (unavailableOnBranch) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment key={tool.title}>
|
<Fragment key={tool.id}>
|
||||||
{index > 0 && <Separator orientation="vertical" size="1" />}
|
{index > 0 && <Separator orientation="vertical" size="1" />}
|
||||||
<LinkI18n
|
<LinkI18n
|
||||||
{locale}
|
{locale}
|
||||||
@@ -156,7 +160,7 @@ const { locale } = Astro.params;
|
|||||||
href={`/tools/${tool.id}`}
|
href={`/tools/${tool.id}`}
|
||||||
underline={selected ? 'always' : 'hover'}
|
underline={selected ? 'always' : 'hover'}
|
||||||
>
|
>
|
||||||
{tool.title}
|
{toolStrings.name}
|
||||||
</LinkI18n>
|
</LinkI18n>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
@@ -212,6 +216,11 @@ const { locale } = Astro.params;
|
|||||||
(branch) =>
|
(branch) =>
|
||||||
branch !== assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH),
|
branch !== assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH),
|
||||||
);
|
);
|
||||||
|
const toolStrings =
|
||||||
|
strings.website.tools[
|
||||||
|
(tool.stringsId ??
|
||||||
|
tool.id) as keyof typeof strings.website.tools
|
||||||
|
];
|
||||||
|
|
||||||
if (unavailableOnBranch) return null;
|
if (unavailableOnBranch) return null;
|
||||||
|
|
||||||
@@ -265,7 +274,7 @@ const { locale } = Astro.params;
|
|||||||
WebkitBackdropFilter: 'blur(4rem)',
|
WebkitBackdropFilter: 'blur(4rem)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text>{tool.title}</Text>
|
<Text>{toolStrings.name}</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
</LinkI18n>
|
</LinkI18n>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { assertSecret, imgur, ImgurSize } from '@blitzkit/core';
|
|||||||
import { CaretRightIcon } from '@radix-ui/react-icons';
|
import { CaretRightIcon } from '@radix-ui/react-icons';
|
||||||
import { Box, Button, Code, Flex, Heading, Text } from '@radix-ui/themes';
|
import { Box, Button, Code, Flex, Heading, Text } from '@radix-ui/themes';
|
||||||
import type { Tool } from '../constants/tools';
|
import type { Tool } from '../constants/tools';
|
||||||
|
import { getStrings } from '../core/i18n/getStrings';
|
||||||
import { LinkI18n } from './LinkI18n';
|
import { LinkI18n } from './LinkI18n';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -14,6 +15,11 @@ const disabled = tool.branches?.every(
|
|||||||
(branch) => branch !== assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH),
|
(branch) => branch !== assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH),
|
||||||
);
|
);
|
||||||
const { locale } = Astro.params;
|
const { locale } = Astro.params;
|
||||||
|
const strings = getStrings(locale);
|
||||||
|
const toolStrings =
|
||||||
|
strings.website.tools[
|
||||||
|
(tool.stringsId??tool.id ) as keyof typeof strings.website.tools
|
||||||
|
];
|
||||||
---
|
---
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -101,7 +107,7 @@ const { locale } = Astro.params;
|
|||||||
size={{ initial: '6', sm: tool.significant ? '7' : '5' }}
|
size={{ initial: '6', sm: tool.significant ? '7' : '5' }}
|
||||||
weight="medium"
|
weight="medium"
|
||||||
>
|
>
|
||||||
{tool.title}
|
{toolStrings.name}
|
||||||
</Heading>
|
</Heading>
|
||||||
<Text
|
<Text
|
||||||
align={{ initial: 'center', sm: 'left' }}
|
align={{ initial: 'center', sm: 'left' }}
|
||||||
@@ -115,7 +121,7 @@ const { locale } = Astro.params;
|
|||||||
<Code>{assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH)}</Code>
|
<Code>{assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH)}</Code>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
tool.description
|
toolStrings.description
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -132,7 +138,7 @@ const { locale } = Astro.params;
|
|||||||
color={tool.button.color}
|
color={tool.button.color}
|
||||||
style={{ cursor: 'inherit' }}
|
style={{ cursor: 'inherit' }}
|
||||||
>
|
>
|
||||||
{tool.button.text}
|
{toolStrings.button}
|
||||||
<CaretRightIcon />
|
<CaretRightIcon />
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,15 +3,12 @@ import type { ComponentProps } from 'react';
|
|||||||
|
|
||||||
export interface Tool {
|
export interface Tool {
|
||||||
id: string;
|
id: string;
|
||||||
|
stringsId?: string;
|
||||||
image: string;
|
image: string;
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
href?: string;
|
href?: string;
|
||||||
pageDescription?: string;
|
|
||||||
button: {
|
button: {
|
||||||
highContrast?: boolean;
|
highContrast?: boolean;
|
||||||
text: string;
|
|
||||||
color: ComponentProps<typeof Button>['color'];
|
color: ComponentProps<typeof Button>['color'];
|
||||||
};
|
};
|
||||||
significant?: boolean;
|
significant?: boolean;
|
||||||
@@ -20,13 +17,8 @@ export interface Tool {
|
|||||||
|
|
||||||
export const tankopediaTool: Tool = {
|
export const tankopediaTool: Tool = {
|
||||||
id: 'tankopedia',
|
id: 'tankopedia',
|
||||||
title: 'Tankopedia',
|
|
||||||
description: 'Blitz tank encyclopedia',
|
|
||||||
pageDescription:
|
|
||||||
'Statistics, armor, and more for all tanks in World of Tanks Blitz',
|
|
||||||
image: 'D8j6GZx',
|
image: 'D8j6GZx',
|
||||||
button: {
|
button: {
|
||||||
text: 'Find tanks',
|
|
||||||
color: 'purple',
|
color: 'purple',
|
||||||
},
|
},
|
||||||
significant: true,
|
significant: true,
|
||||||
@@ -35,13 +27,8 @@ export const tankopediaTool: Tool = {
|
|||||||
|
|
||||||
// export const ratingTool: Tool = {
|
// export const ratingTool: Tool = {
|
||||||
// id: 'rating',
|
// id: 'rating',
|
||||||
// title: 'Rating',
|
|
||||||
// description: 'Full rating leaderboard',
|
|
||||||
// pageDescription:
|
|
||||||
// 'Live and archived full rating leaderboard for World of Tanks Blitz',
|
|
||||||
// image: 'WEyMZH3',
|
// image: 'WEyMZH3',
|
||||||
// button: {
|
// button: {
|
||||||
// text: 'View leaderboard',
|
|
||||||
// color: 'orange',
|
// color: 'orange',
|
||||||
// },
|
// },
|
||||||
// branches: ['main', 'dev'],
|
// branches: ['main', 'dev'],
|
||||||
@@ -49,13 +36,8 @@ export const tankopediaTool: Tool = {
|
|||||||
|
|
||||||
export const compareTool: Tool = {
|
export const compareTool: Tool = {
|
||||||
id: 'compare',
|
id: 'compare',
|
||||||
title: 'Compare',
|
|
||||||
description: 'Compare tank statistics',
|
|
||||||
pageDescription:
|
|
||||||
'Compare tanks statistics and loadouts in World of Tanks Blitz',
|
|
||||||
image: 'O6VNl6e',
|
image: 'O6VNl6e',
|
||||||
button: {
|
button: {
|
||||||
text: 'Compare tanks',
|
|
||||||
color: 'crimson',
|
color: 'crimson',
|
||||||
},
|
},
|
||||||
branches: ['main', 'dev', 'opentest', 'preview'],
|
branches: ['main', 'dev', 'opentest', 'preview'],
|
||||||
@@ -64,13 +46,8 @@ export const compareTool: Tool = {
|
|||||||
export const sessionTool: Tool = {
|
export const sessionTool: Tool = {
|
||||||
// TODO: rename to tracker
|
// TODO: rename to tracker
|
||||||
id: 'session',
|
id: 'session',
|
||||||
title: 'Session',
|
|
||||||
description: 'Live session stats',
|
|
||||||
pageDescription:
|
|
||||||
'Track your stats in real time as you play World of Tanks Blitz',
|
|
||||||
image: 'HdG9sTf',
|
image: 'HdG9sTf',
|
||||||
button: {
|
button: {
|
||||||
text: 'Start tracking',
|
|
||||||
color: 'blue',
|
color: 'blue',
|
||||||
},
|
},
|
||||||
branches: ['main', 'dev'],
|
branches: ['main', 'dev'],
|
||||||
@@ -78,35 +55,25 @@ export const sessionTool: Tool = {
|
|||||||
|
|
||||||
export const discordTool: Tool = {
|
export const discordTool: Tool = {
|
||||||
id: 'discord',
|
id: 'discord',
|
||||||
title: 'Discord bot',
|
|
||||||
description: 'Stats right in Discord',
|
|
||||||
href: 'https://discord.com/application-directory/1097673957865443370',
|
href: 'https://discord.com/application-directory/1097673957865443370',
|
||||||
image: '0bWE5hC',
|
image: '0bWE5hC',
|
||||||
button: {
|
button: {
|
||||||
text: 'Install now',
|
|
||||||
color: 'indigo',
|
color: 'indigo',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const moreTool: Tool = {
|
export const moreTool: Tool = {
|
||||||
id: 'more',
|
id: 'more',
|
||||||
title: 'More coming soon',
|
|
||||||
description: "What we're making",
|
|
||||||
href: 'https://discord.gg/nDt7AjGJQH',
|
href: 'https://discord.gg/nDt7AjGJQH',
|
||||||
image: '1nPm6VI',
|
image: '1nPm6VI',
|
||||||
button: {
|
button: {
|
||||||
text: 'Join Discord',
|
|
||||||
color: 'plum',
|
color: 'plum',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const playerStatsTool: Tool = {
|
export const playersTool: Tool = {
|
||||||
id: 'player-stats',
|
id: 'players',
|
||||||
title: 'Player stats',
|
|
||||||
description: 'Periodical player statistics',
|
|
||||||
pageDescription: 'Periodical player statistics in World of Tanks Blitz',
|
|
||||||
button: {
|
button: {
|
||||||
text: 'Lookup player',
|
|
||||||
color: 'blue',
|
color: 'blue',
|
||||||
},
|
},
|
||||||
image: 'YelSOfT',
|
image: 'YelSOfT',
|
||||||
@@ -116,11 +83,7 @@ export const playerStatsTool: Tool = {
|
|||||||
|
|
||||||
export const performanceTool: Tool = {
|
export const performanceTool: Tool = {
|
||||||
id: 'performance',
|
id: 'performance',
|
||||||
title: 'Performance',
|
|
||||||
description: 'Tank performance statistics',
|
|
||||||
pageDescription: 'Best tanks and their performances in World of Tanks Blitz',
|
|
||||||
button: {
|
button: {
|
||||||
text: 'View tanks',
|
|
||||||
color: 'jade',
|
color: 'jade',
|
||||||
},
|
},
|
||||||
image: 'vOKFB03',
|
image: 'vOKFB03',
|
||||||
@@ -129,12 +92,7 @@ export const performanceTool: Tool = {
|
|||||||
|
|
||||||
export const embedTool: Tool = {
|
export const embedTool: Tool = {
|
||||||
id: 'embed',
|
id: 'embed',
|
||||||
title: 'Embed',
|
|
||||||
description: 'Embeds for streaming',
|
|
||||||
pageDescription:
|
|
||||||
'Create embeds for streaming World of Tanks Blitz with extreme customization',
|
|
||||||
button: {
|
button: {
|
||||||
text: 'Create now',
|
|
||||||
color: 'red',
|
color: 'red',
|
||||||
},
|
},
|
||||||
image: 'Q0faRYg',
|
image: 'Q0faRYg',
|
||||||
@@ -142,20 +100,14 @@ export const embedTool: Tool = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const homeTool: Tool = {
|
export const homeTool: Tool = {
|
||||||
id: '',
|
id: 'home',
|
||||||
title: 'Home',
|
button: { color: 'blue' },
|
||||||
button: { color: 'blue', text: '' },
|
|
||||||
description: '',
|
|
||||||
image: 'rUPie9G',
|
image: 'rUPie9G',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const galleryTool: Tool = {
|
export const galleryTool: Tool = {
|
||||||
id: 'gallery',
|
id: 'gallery',
|
||||||
title: 'Gallery',
|
|
||||||
description: 'Blitz player avatars',
|
|
||||||
pageDescription: 'All World of Tanks Blitz player avatars',
|
|
||||||
button: {
|
button: {
|
||||||
text: 'Browse all',
|
|
||||||
color: 'gold',
|
color: 'gold',
|
||||||
},
|
},
|
||||||
image: 'VhKlknk',
|
image: 'VhKlknk',
|
||||||
@@ -164,12 +116,8 @@ export const galleryTool: Tool = {
|
|||||||
|
|
||||||
export const tierListTool: Tool = {
|
export const tierListTool: Tool = {
|
||||||
id: 'tier-list',
|
id: 'tier-list',
|
||||||
title: 'Tier list',
|
stringsId: 'tier_list',
|
||||||
description: 'Rank Blitz tanks',
|
|
||||||
pageDescription:
|
|
||||||
'Explore tier lists ranking tanks from best to worst, based on player ratings and statistical performance in World of Tanks Blitz',
|
|
||||||
button: {
|
button: {
|
||||||
text: 'Rank tanks',
|
|
||||||
color: 'orange',
|
color: 'orange',
|
||||||
},
|
},
|
||||||
image: 'oKKc8Xv',
|
image: 'oKKc8Xv',
|
||||||
@@ -178,12 +126,7 @@ export const tierListTool: Tool = {
|
|||||||
|
|
||||||
export const chartsTool: Tool = {
|
export const chartsTool: Tool = {
|
||||||
id: 'charts',
|
id: 'charts',
|
||||||
title: 'Charts',
|
|
||||||
description: 'Lots of graphs',
|
|
||||||
pageDescription:
|
|
||||||
'Charts and graphs on anything and everything about tanks, players, and events in World of Tanks Blitz',
|
|
||||||
button: {
|
button: {
|
||||||
text: 'Nerd out',
|
|
||||||
color: 'bronze',
|
color: 'bronze',
|
||||||
},
|
},
|
||||||
image: '6fhkljA',
|
image: '6fhkljA',
|
||||||
|
|||||||
@@ -1,19 +1,17 @@
|
|||||||
import { assertSecret } from '@blitzkit/core';
|
import { assertSecret, DEFAULT_LOCALE, localizedStrings } from '@blitzkit/core';
|
||||||
|
|
||||||
const BRANCH_NAMES: Record<string, string> = {
|
export function resolveBranchName(locale = DEFAULT_LOCALE) {
|
||||||
dev: 'beta',
|
const strings = localizedStrings[locale];
|
||||||
opentest: 'opentest',
|
|
||||||
preview: 'preview',
|
|
||||||
};
|
|
||||||
|
|
||||||
export function resolveBranchName() {
|
|
||||||
const secret = assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH);
|
const secret = assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
assertSecret(import.meta.env.MODE) === 'development' &&
|
assertSecret(import.meta.env.MODE) === 'development' &&
|
||||||
secret === 'dev'
|
secret === 'dev'
|
||||||
) {
|
) {
|
||||||
return 'dev';
|
return strings.common.branches.local;
|
||||||
} else {
|
} else {
|
||||||
return BRANCH_NAMES[secret] as string | undefined;
|
return (strings.common.branches as Record<string, string>)[secret] as
|
||||||
|
| string
|
||||||
|
| undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { DEFAULT_LOCALE, localizedStrings } from '@blitzkit/core';
|
||||||
|
|
||||||
|
export function getStrings(locale: string = DEFAULT_LOCALE) {
|
||||||
|
return localizedStrings[locale];
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export function literals(string: string, literals: string[]) {
|
||||||
|
return literals.reduce((accumulator, literal, index) => {
|
||||||
|
return accumulator.replace(`%s${index + 1}`, literal);
|
||||||
|
}, string);
|
||||||
|
}
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
|
import { DEFAULT_LOCALE } from '@blitzkit/core';
|
||||||
import { createContext, useContext, type ReactNode } from 'react';
|
import { createContext, useContext, type ReactNode } from 'react';
|
||||||
|
import { getStrings } from '../core/i18n/getStrings';
|
||||||
|
|
||||||
const LocaleContext = createContext<{ locale: string | undefined } | null>(
|
const LocaleContext = createContext<{
|
||||||
null,
|
locale: string;
|
||||||
);
|
localeRaw: string | undefined;
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
interface LocaleProviderProps {
|
interface LocaleProviderProps {
|
||||||
locale: string | undefined;
|
locale: string | undefined;
|
||||||
@@ -11,7 +14,9 @@ interface LocaleProviderProps {
|
|||||||
|
|
||||||
export function LocaleProvider({ locale, children }: LocaleProviderProps) {
|
export function LocaleProvider({ locale, children }: LocaleProviderProps) {
|
||||||
return (
|
return (
|
||||||
<LocaleContext.Provider value={{ locale }}>
|
<LocaleContext.Provider
|
||||||
|
value={{ locale: locale ?? DEFAULT_LOCALE, localeRaw: locale }}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</LocaleContext.Provider>
|
</LocaleContext.Provider>
|
||||||
);
|
);
|
||||||
@@ -24,5 +29,7 @@ export function useLocale() {
|
|||||||
throw new Error('useLocale must be used within a LocaleProvider');
|
throw new Error('useLocale must be used within a LocaleProvider');
|
||||||
}
|
}
|
||||||
|
|
||||||
return { locale: context.locale };
|
const strings = getStrings(context.locale);
|
||||||
|
|
||||||
|
return { locale: context.locale, strings };
|
||||||
}
|
}
|
||||||
@@ -2,9 +2,9 @@
|
|||||||
import {
|
import {
|
||||||
assertSecret,
|
assertSecret,
|
||||||
DEFAULT_LOCALE,
|
DEFAULT_LOCALE,
|
||||||
|
localizedStrings,
|
||||||
SUPPORTED_LOCALES,
|
SUPPORTED_LOCALES,
|
||||||
} from '@blitzkit/core';
|
} from '@blitzkit/core';
|
||||||
import strings from '@blitzkit/core/lang/en.json';
|
|
||||||
import { Box, Flex, Heading, Link, Text } from '@radix-ui/themes';
|
import { Box, Flex, Heading, Link, Text } from '@radix-ui/themes';
|
||||||
import type { GetStaticPaths } from 'astro';
|
import type { GetStaticPaths } from 'astro';
|
||||||
import { AesonPlug } from '../../components/AesonPlug';
|
import { AesonPlug } from '../../components/AesonPlug';
|
||||||
@@ -23,10 +23,12 @@ import {
|
|||||||
tankopediaTool,
|
tankopediaTool,
|
||||||
tierListTool,
|
tierListTool,
|
||||||
} from '../../constants/tools';
|
} from '../../constants/tools';
|
||||||
|
import { getStrings } from '../../core/i18n/getStrings';
|
||||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||||
|
|
||||||
const branch = assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH);
|
const branch = assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH);
|
||||||
const { locale } = Astro.params;
|
const locale = Astro.params.locale ?? DEFAULT_LOCALE;
|
||||||
|
const strings = getStrings(Astro.params.locale);
|
||||||
|
|
||||||
export const getStaticPaths = (() => {
|
export const getStaticPaths = (() => {
|
||||||
return [
|
return [
|
||||||
@@ -45,6 +47,7 @@ export const getStaticPaths = (() => {
|
|||||||
const selected =
|
const selected =
|
||||||
thisLocale === locale ||
|
thisLocale === locale ||
|
||||||
(locale === undefined && thisLocale === DEFAULT_LOCALE);
|
(locale === undefined && thisLocale === DEFAULT_LOCALE);
|
||||||
|
const localeNames = localizedStrings[thisLocale].common.locales;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -55,7 +58,7 @@ export const getStaticPaths = (() => {
|
|||||||
highContrast={selected}
|
highContrast={selected}
|
||||||
href={`/${thisLocale === DEFAULT_LOCALE ? '' : thisLocale}`}
|
href={`/${thisLocale === DEFAULT_LOCALE ? '' : thisLocale}`}
|
||||||
>
|
>
|
||||||
{strings.common.locales[thisLocale]}
|
{localeNames[thisLocale as keyof typeof localeNames]}
|
||||||
</Link>
|
</Link>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@@ -105,33 +108,24 @@ export const getStaticPaths = (() => {
|
|||||||
<Box flexGrow="1" />
|
<Box flexGrow="1" />
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Heading align="center" mt="6">Other cool projects</Heading>
|
<Heading align="center" mt="6"
|
||||||
|
>{strings.website.home.other_projects.title}</Heading
|
||||||
|
>
|
||||||
|
|
||||||
<Flex gap="4" mb="6" direction={{ initial: 'column', sm: 'row' }}>
|
<Flex gap="4" mb="6" direction={{ initial: 'column', sm: 'row' }}>
|
||||||
<ToolCard
|
<ToolCard
|
||||||
tool={{
|
tool={{
|
||||||
button: {
|
button: { color: 'blue' },
|
||||||
text: 'Analyze',
|
id: 'blitz_analysis',
|
||||||
color: 'blue',
|
|
||||||
},
|
|
||||||
description: 'Rich historic average stats',
|
|
||||||
id: 'blitz-analysis',
|
|
||||||
image: 'j76YXGl',
|
image: 'j76YXGl',
|
||||||
title: 'BlitzAnalysis[]',
|
|
||||||
href: 'https://blitzanalysiz.com/',
|
href: 'https://blitzanalysiz.com/',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ToolCard
|
<ToolCard
|
||||||
tool={{
|
tool={{
|
||||||
button: {
|
button: { color: 'purple', highContrast: true },
|
||||||
color: 'purple',
|
|
||||||
text: 'Install',
|
|
||||||
highContrast: true,
|
|
||||||
},
|
|
||||||
image: 'zYrrYbR',
|
image: 'zYrrYbR',
|
||||||
description: 'In-game session stats',
|
id: 'blitz_insider',
|
||||||
id: 'blitz-insider',
|
|
||||||
title: 'Blitz Insider',
|
|
||||||
href: 'https://discord.gg/UjWf9eGgtR',
|
href: 'https://discord.gg/UjWf9eGgtR',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in new issue
Block a user