feat: home page
No files matched your search
@@ -76,7 +76,6 @@
|
||||
"@types/three": "^0.160.0",
|
||||
"astro-compressor": "^1.0.0",
|
||||
"@astrojs/partytown": "^2.1.4",
|
||||
"@astrolib/seo": "^1.0.0-beta.8",
|
||||
"@fontsource/inter": "^5.2.5",
|
||||
"@nanostores/persistent": "^0.10.2",
|
||||
"@nanostores/react": "^0.7.3",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"@radix-ui/colors": "^3.0.0",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/themes": "^3.1.4",
|
||||
"@astrolib/seo": "^1.0.0-beta.8",
|
||||
"@blitzkit/core": "workspace:*",
|
||||
"@blitzkit/closed": "workspace:*",
|
||||
"@astrojs/react": "^4.3.0",
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_741_237)">
|
||||
<rect width="60" height="60" fill="#FDD1EA"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M54.6274 0L55.4558 0.828427L54.0416 2.24264L51.799 0L54.6274 0ZM5.37258 0L4.54416 0.828427L5.95837 2.24264L8.20101 0L5.37258 0ZM48.9706 0L52.6274 3.65685L51.2132 5.07107L46.1421 0L48.9706 0ZM11.0294 0L7.37258 3.65685L8.7868 5.07107L13.8579 0L11.0294 0ZM43.3137 0L49.799 6.48528L48.3848 7.8995L40.4853 0H43.3137ZM16.6863 0L10.201 6.48528L11.6152 7.8995L19.5147 0L16.6863 0ZM37.6569 0L46.9706 9.31371L45.5564 10.7279L34.8284 0L37.6569 0ZM22.3431 0L13.0294 9.31371L14.4437 10.7279L25.1716 0L22.3431 0ZM32 0L44.1421 12.1421L42.7279 13.5563L30 0.828427L17.2721 13.5563L15.8579 12.1421L28 0H32ZM0.284271 0L28.2843 28L26.8701 29.4142L0 2.54416V0L0.284271 0ZM0 5.37258L25.4558 30.8284L24.0416 32.2426L0 8.20101L0 5.37258ZM0 11.0294L22.6274 33.6569L21.2132 35.0711L0 13.8579L0 11.0294ZM0 16.6863L19.799 36.4853L18.3848 37.8995L0 19.5147L0 16.6863ZM0 22.3431L16.9706 39.3137L15.5563 40.7279L0 25.1716L0 22.3431ZM0 28L14.1421 42.1421L12.7279 43.5564L0 30.8284V28ZM0 33.6569L11.3137 44.9706L9.8995 46.3848L0 36.4853L0 33.6569ZM0 39.3137L8.48528 47.799L7.07107 49.2132L0 42.1421L0 39.3137ZM0 44.9706L5.65685 50.6274L4.24264 52.0416L0 47.799L0 44.9706ZM0 50.6274L2.82843 53.4558L1.41421 54.8701L0 53.4558L0 50.6274ZM54.6274 60L30 35.3726L5.37258 60H8.20101L30 38.201L51.799 60H54.6274ZM48.9706 60L30 41.0294L11.0294 60H13.8579L30 43.8579L46.1421 60H48.9706ZM43.3137 60L30 46.6863L16.6863 60H19.5147L30 49.5147L40.4853 60H43.3137ZM37.6569 60L30 52.3431L22.3431 60H25.1716L30 55.1716L34.8284 60H37.6569ZM32 60L30 58L28 60H32ZM59.7157 0L31.7157 28L33.1299 29.4142L60 2.54416V0L59.7157 0ZM60 5.37258L34.5442 30.8284L35.9584 32.2426L60 8.20101V5.37258ZM60 11.0294L37.3726 33.6569L38.7868 35.0711L60 13.8579V11.0294ZM60 16.6863L40.201 36.4853L41.6152 37.8995L60 19.5147V16.6863ZM60 22.3431L43.0294 39.3137L44.4436 40.7279L60 25.1716V22.3431ZM60 28L45.8579 42.1421L47.2721 43.5564L60 30.8284V28ZM60 33.6569L48.6863 44.9706L50.1005 46.3848L60 36.4853V33.6569ZM60 39.3137L51.5147 47.799L52.9289 49.2132L60 42.1421V39.3137ZM60 44.9706L54.3431 50.6274L55.7574 52.0416L60 47.799V44.9706ZM60 50.6274L57.1716 53.4558L58.5858 54.8701L60 53.4558V50.6274ZM39.8995 16.3848L41.3137 14.9706L30 3.65685L18.6863 14.9706L20.1005 16.3848L30 6.48528L39.8995 16.3848ZM37.0711 19.2132L38.4853 17.799L30 9.31371L21.5147 17.799L22.9289 19.2132L30 12.1421L37.0711 19.2132ZM34.2426 22.0416L35.6569 20.6274L30 14.9706L24.3431 20.6274L25.7574 22.0416L30 17.799L34.2426 22.0416ZM31.4142 24.8701L32.8284 23.4558L30 20.6274L27.1716 23.4558L28.5858 24.8701L30 23.4558L31.4142 24.8701ZM56.8701 59.4142L58.2843 58L30 29.7157L1.71573 58L3.12994 59.4142L30 32.5442L56.8701 59.4142Z" fill="#FFBECE"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_741_237">
|
||||
<rect width="60" height="60" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 443 KiB |
|
After Width: | Height: | Size: 330 KiB |
|
After Width: | Height: | Size: 715 KiB |
|
After Width: | Height: | Size: 144 KiB |
|
After Width: | Height: | Size: 676 KiB |
|
After Width: | Height: | Size: 188 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 274 KiB |
|
After Width: | Height: | Size: 346 KiB |
|
After Width: | Height: | Size: 406 KiB |
|
After Width: | Height: | Size: 360 KiB |
|
After Width: | Height: | Size: 193 KiB |
|
After Width: | Height: | Size: 200 KiB |
@@ -0,0 +1,156 @@
|
||||
---
|
||||
import { Code, Flex, Heading, Text } from '@radix-ui/themes';
|
||||
import { resolveBranchName } from '../core/blitzkit/branches';
|
||||
import { getStrings } from '../core/i18n/getStrings';
|
||||
import { literals } from '@blitzkit/i18n/src/literals';
|
||||
|
||||
interface Props {
|
||||
locale: string;
|
||||
}
|
||||
|
||||
const { locale } = Astro.props;
|
||||
const strings = await getStrings(locale);
|
||||
const branchName = resolveBranchName();
|
||||
const isBranchNamed = branchName !== undefined;
|
||||
---
|
||||
|
||||
<style>
|
||||
#home-hero {
|
||||
--p-1: 0%;
|
||||
--p-2: 0%;
|
||||
--p-3: 0%;
|
||||
--p-4: 0%;
|
||||
--p-7: 0%;
|
||||
|
||||
--g-1-dark: var(--blue-3);
|
||||
--g-1-light: var(--blue-7);
|
||||
--g-2-dark: var(--violet-3);
|
||||
--g-2-light: var(--violet-7);
|
||||
--g-3-dark: var(--purple-3);
|
||||
--g-3-light: var(--purple-7);
|
||||
--g-4-dark: var(--crimson-3);
|
||||
--g-4-light: var(--crimson-7);
|
||||
--g-7-dark: var(--tomato-3);
|
||||
--g-7-light: var(--tomato-7);
|
||||
|
||||
--g-1: color-mix(in srgb, var(--g-1-dark), var(--g-1-light) var(--p-1));
|
||||
--g-2: color-mix(in srgb, var(--g-2-dark), var(--g-2-light) var(--p-2));
|
||||
--g-3: color-mix(in srgb, var(--g-3-dark), var(--g-3-light) var(--p-3));
|
||||
--g-4: color-mix(in srgb, var(--g-4-dark), var(--g-4-light) var(--p-4));
|
||||
--g-7: color-mix(in srgb, var(--g-7-dark), var(--g-7-light) var(--p-7));
|
||||
|
||||
background: linear-gradient(
|
||||
45deg,
|
||||
var(--g-1),
|
||||
var(--g-2),
|
||||
var(--g-3),
|
||||
var(--g-4),
|
||||
var(--g-7)
|
||||
);
|
||||
}
|
||||
|
||||
#home-hero[data-variant='pride'] {
|
||||
--g-1-dark: var(--crimson-3);
|
||||
--g-1-light: var(--crimson-7);
|
||||
--g-2-dark: var(--orange-3);
|
||||
--g-2-light: var(--orange-7);
|
||||
--g-3-dark: var(--yellow-3);
|
||||
--g-3-light: var(--yellow-7);
|
||||
--g-4-dark: var(--blue-3);
|
||||
--g-4-light: var(--blue-7);
|
||||
--g-7-dark: var(--purple-3);
|
||||
--g-7-light: var(--purple-7);
|
||||
}
|
||||
|
||||
#home-hero[data-variant='spooky'] {
|
||||
--g-1-dark: var(--orange-3);
|
||||
--g-1-light: var(--orange-7);
|
||||
--g-2-dark: var(--tomato-3);
|
||||
--g-2-light: var(--tomato-7);
|
||||
--g-3-dark: var(--orange-3);
|
||||
--g-3-light: var(--orange-7);
|
||||
--g-4-dark: var(--tomato-3);
|
||||
--g-4-light: var(--tomato-7);
|
||||
--g-7-dark: var(--orange-3);
|
||||
--g-7-light: var(--orange-7);
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { clamp, times } from 'lodash-es';
|
||||
|
||||
const n = 5;
|
||||
const homeHero = document.getElementById('home-hero');
|
||||
|
||||
if (homeHero) {
|
||||
switch (new Date().getMonth()) {
|
||||
case 5:
|
||||
homeHero.dataset.variant = 'pride';
|
||||
break;
|
||||
|
||||
case 9:
|
||||
homeHero.dataset.variant = 'spooky';
|
||||
break;
|
||||
}
|
||||
|
||||
window.addEventListener('pointermove', (event) => {
|
||||
const x = event.clientX / window.innerWidth;
|
||||
const c = clamp(
|
||||
(1 -
|
||||
(event.clientY + homeHero.getBoundingClientRect().top) /
|
||||
window.innerHeight) **
|
||||
2,
|
||||
0,
|
||||
1,
|
||||
);
|
||||
|
||||
// https://www.desmos.com/calculator/ndz2poxk32
|
||||
times(n, (i) => {
|
||||
const p = c * clamp(-n * Math.abs(x - i / (n - 1)) + 1, 0, 3);
|
||||
homeHero.style.setProperty(`--p-${i + 1}`, `${p * 100}%`);
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<Flex
|
||||
id="home-hero"
|
||||
align="center"
|
||||
justify="center"
|
||||
position="relative"
|
||||
px="4"
|
||||
py="9"
|
||||
overflow="hidden"
|
||||
>
|
||||
<Flex
|
||||
direction="column"
|
||||
gap="6"
|
||||
align="center"
|
||||
justify="center"
|
||||
position="relative"
|
||||
width="100%"
|
||||
>
|
||||
<Flex direction="column" align="center" gap="1">
|
||||
<Heading size={{ initial: '8', xs: '9' }} weight="bold" align="center">
|
||||
<Flex align="center" gap={{ initial: '2', xs: '4' }}>
|
||||
BlitzKit
|
||||
{
|
||||
isBranchNamed && (
|
||||
<Code
|
||||
size={{ initial: '7', xs: '8' }}
|
||||
color="gray"
|
||||
variant="outline"
|
||||
>
|
||||
{branchName}
|
||||
</Code>
|
||||
)
|
||||
}
|
||||
</Flex>
|
||||
</Heading>
|
||||
|
||||
<Text color="gray" align="center" size="4"
|
||||
>{literals(strings.website.home.hero.credit, ['TrèsAbhi'])}</Text
|
||||
>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
@@ -0,0 +1,136 @@
|
||||
---
|
||||
import { assertSecret } from '@blitzkit/core';
|
||||
import { CaretRightIcon } from '@radix-ui/react-icons';
|
||||
import { Box, Button, Code, Flex, Heading, Text } from '@radix-ui/themes';
|
||||
import type { Tool } from '../constants/tools';
|
||||
import { getStrings } from '../core/i18n/getStrings';
|
||||
import { LinkI18n } from './LinkI18n';
|
||||
|
||||
interface Props {
|
||||
tool: Tool;
|
||||
locale: string;
|
||||
}
|
||||
|
||||
const { tool, locale } = Astro.props;
|
||||
const strings = await getStrings(locale);
|
||||
const disabled = false;
|
||||
const toolStrings =
|
||||
strings.website.tools[
|
||||
(tool.strings ?? tool.id) as keyof typeof strings.website.tools
|
||||
];
|
||||
---
|
||||
|
||||
<style>
|
||||
.container {
|
||||
border-radius: var(--radius-2);
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
height: 16rem;
|
||||
|
||||
&[data-disabled='true'] {
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
&[data-significant='true'] {
|
||||
height: min(20rem, 50vh);
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.link {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
color: inherit;
|
||||
}
|
||||
</style>
|
||||
|
||||
<Flex
|
||||
style={{ backgroundImage: `url(/assets/tool-backgrounds/${tool.id}.webp)` }}
|
||||
data-disabled={disabled}
|
||||
data-significant={tool.significant}
|
||||
className="container"
|
||||
position="relative"
|
||||
overflow="hidden"
|
||||
flexGrow={tool.significant ? undefined : '1'}
|
||||
flexBasis={tool.significant ? undefined : { initial: undefined, sm: '0' }}
|
||||
>
|
||||
<LinkI18n
|
||||
{locale}
|
||||
className="link"
|
||||
underline="none"
|
||||
href={disabled ? '#' : `/${tool.id}`}
|
||||
>
|
||||
<Box
|
||||
flexGrow="1"
|
||||
style={{
|
||||
backgroundImage: `url(/assets/tool-backgrounds/${tool.id}.webp)`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
}}
|
||||
/>
|
||||
|
||||
<Flex
|
||||
px={{ initial: '6', md: tool.significant ? '8' : '6' }}
|
||||
py={{ initial: '4', sm: tool.significant ? '6' : '4' }}
|
||||
gap="4"
|
||||
align="center"
|
||||
justify="between"
|
||||
width="100%"
|
||||
direction={{ initial: 'column', xs: 'row' }}
|
||||
style={{
|
||||
backgroundColor: 'var(--color-panel-translucent)',
|
||||
backdropFilter: 'blur(64px)',
|
||||
WebkitBackdropFilter: 'blur(64px)',
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
direction="column"
|
||||
justify="center"
|
||||
align={{ initial: 'center', xs: 'start' }}
|
||||
>
|
||||
<Heading
|
||||
align={{ initial: 'center', sm: 'left' }}
|
||||
size={{ initial: '6', sm: tool.significant ? '7' : '5' }}
|
||||
weight="medium"
|
||||
>
|
||||
{toolStrings.name}
|
||||
</Heading>
|
||||
<Text
|
||||
align={{ initial: 'center', sm: 'left' }}
|
||||
size={{ initial: '3', sm: tool.significant ? '4' : '3' }}
|
||||
color="gray"
|
||||
>
|
||||
{
|
||||
disabled ? (
|
||||
<>
|
||||
Unavailable on{' '}
|
||||
<Code>{assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH)}</Code>
|
||||
</>
|
||||
) : (
|
||||
toolStrings.description
|
||||
)
|
||||
}
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
{
|
||||
!disabled && (
|
||||
<Button
|
||||
highContrast={tool.button.contrast}
|
||||
size={{
|
||||
initial: undefined,
|
||||
sm: tool.significant ? '3' : undefined,
|
||||
}}
|
||||
color={tool.button.color}
|
||||
style={{ cursor: 'inherit' }}
|
||||
>
|
||||
{toolStrings.button}
|
||||
<CaretRightIcon />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
</Flex>
|
||||
</LinkI18n>
|
||||
</Flex>
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { ButtonProps } from '@radix-ui/themes';
|
||||
|
||||
interface Tool {
|
||||
export interface Tool {
|
||||
id: string;
|
||||
enabled: boolean;
|
||||
strings?: string;
|
||||
@@ -8,7 +8,7 @@ interface Tool {
|
||||
button: { color: ButtonProps['color']; contrast?: boolean };
|
||||
}
|
||||
|
||||
export const tanks: Tool = {
|
||||
export const tanksTool: Tool = {
|
||||
id: 'tanks',
|
||||
enabled: true,
|
||||
strings: 'tankopedia',
|
||||
@@ -16,70 +16,70 @@ export const tanks: Tool = {
|
||||
button: { color: 'purple' },
|
||||
};
|
||||
|
||||
export const compare: Tool = {
|
||||
export const compareTool: Tool = {
|
||||
id: 'compare',
|
||||
enabled: true,
|
||||
button: { color: 'crimson' },
|
||||
};
|
||||
|
||||
export const performance: Tool = {
|
||||
export const performanceTool: Tool = {
|
||||
id: 'performance',
|
||||
enabled: true,
|
||||
button: { color: 'jade' },
|
||||
};
|
||||
|
||||
export const guess: Tool = {
|
||||
export const guessTool: Tool = {
|
||||
id: 'guess',
|
||||
enabled: false,
|
||||
button: { color: 'bronze' },
|
||||
};
|
||||
|
||||
export const charts: Tool = {
|
||||
export const chartsTool: Tool = {
|
||||
id: 'charts',
|
||||
enabled: false,
|
||||
button: { color: 'bronze' },
|
||||
};
|
||||
|
||||
export const gallery: Tool = {
|
||||
export const galleryTool: Tool = {
|
||||
id: 'gallery',
|
||||
enabled: true,
|
||||
button: { color: 'gold' },
|
||||
};
|
||||
|
||||
export const session: Tool = {
|
||||
export const sessionTool: Tool = {
|
||||
id: 'session',
|
||||
enabled: true,
|
||||
button: { color: 'blue' },
|
||||
};
|
||||
|
||||
export const tierList: Tool = {
|
||||
export const tierListTool: Tool = {
|
||||
id: 'tier-list',
|
||||
enabled: true,
|
||||
strings: 'tier_list',
|
||||
button: { color: 'orange' },
|
||||
};
|
||||
|
||||
export const embed: Tool = {
|
||||
export const embedTool: Tool = {
|
||||
id: 'embed',
|
||||
enabled: true,
|
||||
button: { color: 'cyan' },
|
||||
};
|
||||
|
||||
export const rating: Tool = {
|
||||
export const ratingTool: Tool = {
|
||||
id: 'rating',
|
||||
enabled: false,
|
||||
button: { color: 'orange' },
|
||||
};
|
||||
|
||||
export const tools: Tool[] = [
|
||||
tanks,
|
||||
compare,
|
||||
performance,
|
||||
guess,
|
||||
charts,
|
||||
gallery,
|
||||
session,
|
||||
tierList,
|
||||
embed,
|
||||
rating,
|
||||
tanksTool,
|
||||
compareTool,
|
||||
performanceTool,
|
||||
guessTool,
|
||||
chartsTool,
|
||||
galleryTool,
|
||||
sessionTool,
|
||||
tierListTool,
|
||||
embedTool,
|
||||
ratingTool,
|
||||
];
|
||||
@@ -1,6 +1,6 @@
|
||||
import { assertSecret } from 'packages/core/src';
|
||||
|
||||
export function resolveBranchName(locale: string) {
|
||||
export function resolveBranchName() {
|
||||
if (assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH) === 'main') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
---
|
||||
import { AstroSeo } from '@astrolib/seo';
|
||||
import locales from '@blitzkit/i18n/locales.json';
|
||||
import { ExternalLinkIcon } from '@radix-ui/react-icons';
|
||||
import { Flex, Link } from '@radix-ui/themes';
|
||||
import type { GetStaticPaths } from 'astro';
|
||||
import { literals } from 'packages/i18n/src';
|
||||
import HomeHero from '../../components/HomeHero.astro';
|
||||
import ToolCard from '../../components/ToolCard.astro';
|
||||
import { type Tool, tools } from '../../constants/tools';
|
||||
import { getStrings } from '../../core/i18n/getStrings';
|
||||
import { Var } from '../../core/radix/var';
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
|
||||
const MAX_TOOLS_PER_ROW = 2;
|
||||
|
||||
export const getStaticPaths = (() => {
|
||||
return locales.supported.map(({ locale, blitz }) => ({
|
||||
@@ -10,6 +22,92 @@ export const getStaticPaths = (() => {
|
||||
}) satisfies GetStaticPaths;
|
||||
|
||||
const { locale } = Astro.props;
|
||||
const strings = await getStrings(locale);
|
||||
const rows: Tool[][] = [];
|
||||
|
||||
toolLoop: for (const tool of tools.filter((tool) => tool.enabled)) {
|
||||
if (tool.significant) {
|
||||
rows.push([tool]);
|
||||
} else {
|
||||
for (const row of rows) {
|
||||
if (!row[0]?.significant && row.length < MAX_TOOLS_PER_ROW) {
|
||||
row.push(tool);
|
||||
continue toolLoop;
|
||||
}
|
||||
}
|
||||
|
||||
rows.push([tool]);
|
||||
}
|
||||
}
|
||||
---
|
||||
|
||||
Hello in {locale}!
|
||||
<AstroSeo
|
||||
title="BlitzKit"
|
||||
description={strings.website.home.seo_description}
|
||||
canonical={Astro.url.href}
|
||||
openGraph={{
|
||||
url: Astro.url.href,
|
||||
title: 'BlitzKit',
|
||||
description: strings.website.home.seo_description,
|
||||
site_name: 'BlitzKit',
|
||||
}}
|
||||
twitter={{ cardType: 'summary_large_image' }}
|
||||
/>
|
||||
|
||||
<BaseLayout {locale}>
|
||||
<Flex direction="column" gap="8" pb="9">
|
||||
<HomeHero {locale} />
|
||||
|
||||
{
|
||||
locale !== locales.default && (
|
||||
<Flex justify="center">
|
||||
<Link
|
||||
href="https://crowdin.com/project/blitzkrieg"
|
||||
target="_blank"
|
||||
underline="hover"
|
||||
color="gray"
|
||||
highContrast
|
||||
style={{
|
||||
color: Var('gray-3'),
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
align="center"
|
||||
px="5"
|
||||
py="4"
|
||||
gap="3"
|
||||
style={{
|
||||
backgroundImage: 'url(/assets/morphing-diamonds.svg)',
|
||||
borderRadius: Var('radius-full'),
|
||||
boxShadow: Var('shadow-2'),
|
||||
}}
|
||||
>
|
||||
{literals(strings.website.home.help_translate, [
|
||||
'BlitzKit',
|
||||
strings.common.locales[
|
||||
locale as keyof typeof strings.common.locales
|
||||
],
|
||||
])}
|
||||
|
||||
<ExternalLinkIcon />
|
||||
</Flex>
|
||||
</Link>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
<Flex px="4" justify="center" gap="4">
|
||||
<Flex maxWidth="80rem" flexGrow="1" gap="4" direction="column">
|
||||
{
|
||||
rows.map((row) => (
|
||||
<Flex gap="4" direction={{ initial: 'column', sm: 'row' }}>
|
||||
{row.map((tool) => (
|
||||
<ToolCard {locale} tool={tool} />
|
||||
))}
|
||||
</Flex>
|
||||
))
|
||||
}
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</BaseLayout>
|
||||