diff --git a/bun.lockb b/bun.lockb
index 312593ef1..634ca645f 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/package.json b/package.json
index ea5120699..d053cefe2 100644
--- a/package.json
+++ b/package.json
@@ -41,6 +41,7 @@
},
"license": "MIT",
"dependencies": {
+ "cheerio": "^1.1.2",
"googleapis": "^144.0.0",
"lodash-es": "^4.17.21",
"protobufjs": "^7.5.2",
diff --git a/packages/i18n/strings/en.json b/packages/i18n/strings/en.json
index 539136d13..371b6e0da 100644
--- a/packages/i18n/strings/en.json
+++ b/packages/i18n/strings/en.json
@@ -1026,7 +1026,6 @@
"review": {
"title": "Review videos",
- "disclaimer": "BlitzKit does not endorse any review videos.",
"join": "Apply to %s1 YouTuber program"
},
diff --git a/packages/website/src/components/Tankopedia/GuideSection/components/GuidesBlitz.tsx b/packages/website/src/components/Tankopedia/GuideSection/components/GuidesBlitz.tsx
new file mode 100644
index 000000000..4d40c39d6
--- /dev/null
+++ b/packages/website/src/components/Tankopedia/GuideSection/components/GuidesBlitz.tsx
@@ -0,0 +1,51 @@
+import { CaretRightIcon } from '@radix-ui/react-icons';
+import { Box, Flex, Link, Text } from '@radix-ui/themes';
+import { Var } from '../../../../core/radix/var';
+import type { TankGuide } from '../../../../pages/[...locale]/tanks/[slug]/index.astro';
+
+interface Props {
+ guide: TankGuide;
+}
+
+export function GuidesBlitz({ guide }: Props) {
+ return (
+ <>
+
+
+
+
+ GuidesBlitz
+
+
+
+ {guide.description}
+
+
+
+
+
+
+
+ Read the full guide
+
+
+
+ >
+ );
+}
diff --git a/packages/website/src/components/Tankopedia/GuideSection/components/YouTube.tsx b/packages/website/src/components/Tankopedia/GuideSection/components/YouTube.tsx
new file mode 100644
index 000000000..55c74e66f
--- /dev/null
+++ b/packages/website/src/components/Tankopedia/GuideSection/components/YouTube.tsx
@@ -0,0 +1,118 @@
+import { youtubers } from '@blitzkit/core';
+import { CaretRightIcon } from '@radix-ui/react-icons';
+import { Box, Flex, Link, Skeleton, Text } from '@radix-ui/themes';
+import { awaitableReviews } from '../../../../core/awaitables/reviews';
+import { Var } from '../../../../core/radix/var';
+import { Duel } from '../../../../stores/duel';
+import type { MaybeSkeletonComponentProps } from '../../../../types/maybeSkeletonComponentProps';
+import { VerifiedIcon } from '../../../VerifiedIcon';
+
+const reviews = await awaitableReviews;
+
+export function YouTube({ skeleton }: MaybeSkeletonComponentProps) {
+ const tank = Duel.use((state) => state.protagonist.tank);
+ const videos = reviews.reviews[tank.id]?.videos;
+
+ if (!videos) return null;
+
+ return (
+ <>
+
+
+
+
+ YouTube
+
+
+
+
+ {videos.map((video) => {
+ const image = `https://i.ytimg.com/vi/${video.id}/hqdefault.jpg`;
+ const youtuber = youtubers.find(
+ (youtuber) => youtuber.id === video.author,
+ );
+
+ if (!youtuber) return null;
+
+ return (
+
+
+
+
+
+ {!skeleton && youtuber.name}
+ {skeleton && }
+
+ {!skeleton && }
+
+
+
+ );
+ })}
+
+
+
+
+
+
+
+ Read the full guide
+
+
+
+ >
+ );
+}
diff --git a/packages/website/src/components/Tankopedia/GuideSection/index.tsx b/packages/website/src/components/Tankopedia/GuideSection/index.tsx
new file mode 100644
index 000000000..d3660a1b6
--- /dev/null
+++ b/packages/website/src/components/Tankopedia/GuideSection/index.tsx
@@ -0,0 +1,17 @@
+import { Flex } from '@radix-ui/themes';
+import type { TankGuide } from '../../../pages/[...locale]/tanks/[slug]/index.astro';
+import { GuidesBlitz } from './components/GuidesBlitz';
+
+interface Props {
+ guide: TankGuide;
+}
+
+export function GuideSection({ guide }: Props) {
+ return (
+
+
+
+
+
+ );
+}
diff --git a/packages/website/src/components/Tankopedia/VideoSection.tsx b/packages/website/src/components/Tankopedia/VideoSection.tsx
index aa2353a52..19293ed1b 100644
--- a/packages/website/src/components/Tankopedia/VideoSection.tsx
+++ b/packages/website/src/components/Tankopedia/VideoSection.tsx
@@ -24,10 +24,6 @@ export function VideoSection({ skeleton }: MaybeSkeletonComponentProps) {
{strings.website.tools.tankopedia.review.title}
-
-
- {strings.website.tools.tankopedia.review.disclaimer}
-
diff --git a/packages/website/src/pages/[...locale]/tanks/[slug]/_index.tsx b/packages/website/src/pages/[...locale]/tanks/[slug]/_index.tsx
index 933d53c8d..b7961104a 100644
--- a/packages/website/src/pages/[...locale]/tanks/[slug]/_index.tsx
+++ b/packages/website/src/pages/[...locale]/tanks/[slug]/_index.tsx
@@ -3,6 +3,7 @@ import { PageWrapper } from '../../../../components/PageWrapper';
import { CalloutsSection } from '../../../../components/Tankopedia/CalloutsSection';
import { CharacteristicsSection } from '../../../../components/Tankopedia/CharacteristicsSection';
import { GameModeSection } from '../../../../components/Tankopedia/GameModeSection';
+import { GuideSection } from '../../../../components/Tankopedia/GuideSection';
import { HeroSection } from '../../../../components/Tankopedia/HeroSection';
import { MetaSection } from '../../../../components/Tankopedia/MetaSection';
import { TechTreeSection } from '../../../../components/Tankopedia/TechTreeSection';
@@ -19,10 +20,12 @@ import { Duel } from '../../../../stores/duel';
import { TankopediaEphemeral } from '../../../../stores/tankopediaEphemeral';
import { TankopediaPersistent } from '../../../../stores/tankopediaPersistent';
import type { MaybeSkeletonComponentProps } from '../../../../types/maybeSkeletonComponentProps';
+import type { TankGuide } from './index.astro';
type PageProps = MaybeSkeletonComponentProps &
LocaleAcceptorProps & {
id: number;
+ guide?: TankGuide;
};
const [tankDefinitions, provisionDefinitions, modelDefinitions] =
@@ -32,7 +35,7 @@ const [tankDefinitions, provisionDefinitions, modelDefinitions] =
awaitableModelDefinitions,
]);
-export function Page({ id, skeleton, locale }: PageProps) {
+export function Page({ id, skeleton, locale, guide }: PageProps) {
const tank = tankDefinitions.tanks[id];
const model = modelDefinitions.models[id];
@@ -53,6 +56,7 @@ export function Page({ id, skeleton, locale }: PageProps) {
)}
+ {guide && }
diff --git a/packages/website/src/pages/[...locale]/tanks/[slug]/index.astro b/packages/website/src/pages/[...locale]/tanks/[slug]/index.astro
index 5db83f641..dead21e65 100644
--- a/packages/website/src/pages/[...locale]/tanks/[slug]/index.astro
+++ b/packages/website/src/pages/[...locale]/tanks/[slug]/index.astro
@@ -1,6 +1,7 @@
---
import { AstroSeo } from '@astrolib/seo';
import {
+ assertSecret,
asset,
createDefaultProvisions,
createDefaultSkills,
@@ -14,6 +15,7 @@ import {
import { DEFAULT_LOCALE, unwrapper } from '@blitzkit/i18n';
import { literals } from '@blitzkit/i18n/src/literals';
import type { GetStaticPaths } from 'astro';
+import { load } from 'cheerio';
import { tankCharacteristics } from '../../../../core/blitzkit/tankCharacteristics';
import { getStrings } from '../../../../core/i18n/getStrings';
import BaseLayout from '../../../../layouts/BaseLayout.astro';
@@ -43,6 +45,7 @@ const skillDefinitions = await fetchSkillDefinitions();
const provisionDefinitions = await fetchProvisionDefinitions();
const equipmentDefinitions = await fetchEquipmentDefinitions();
const tankModelDefinitions = await fetchModelDefinitions();
+
const tank = tankDefinitions.tanks[id];
const tankModelDefinition = tankModelDefinitions.models[tank.id];
const unwrap = unwrapper(locale);
@@ -57,6 +60,7 @@ const title = literals(strings.website.tools.tankopedia.tank_seo_title, [
strings.common.nations[tank.nation as keyof typeof strings.common.nations],
]);
const url = import.meta.env.SITE + Astro.url.pathname;
+
const icon = asset(`icons/tanks/big/${id}.webp`);
const turret = tank.turrets.at(-1)!;
const gun = turret.guns.at(-1)!;
@@ -96,6 +100,28 @@ const description = literals(
Math.round(characteristics.health).toLocaleString(locale),
],
);
+
+export interface TankGuide {
+ url: string;
+ description?: string;
+ image?: string;
+}
+
+const guideUrl = `${assertSecret(import.meta.env.PUBLIC_GUIDE_WEBSITE)}/${tank.slug}`;
+let guide: TankGuide | undefined = undefined;
+const guideResponse = await fetch(guideUrl);
+
+if (guideResponse.ok) {
+ const $ = await guideResponse.text().then(load);
+ const description = $('.entry-content p').first().text();
+ const image = $('.entry-content img').first().attr('src')?.toString();
+
+ guide = {
+ url: guideUrl,
+ description,
+ image,
+ };
+}
---
-
-
+
+