diff --git a/packages/core/src/blitzkit/tankDefinitions/constants.ts b/packages/core/src/blitzkit/tankDefinitions/constants.ts index 9671b938a..a92e56743 100644 --- a/packages/core/src/blitzkit/tankDefinitions/constants.ts +++ b/packages/core/src/blitzkit/tankDefinitions/constants.ts @@ -1,5 +1,5 @@ import { times } from "lodash-es"; -import { CrewType, ShellType, TankClass, TankType } from "../../protos"; +import { CrewType, ShellType } from "../../protos"; export const SHELL_NAMES: Record = { [ShellType.SHELL_TYPE_AP]: "AP", @@ -15,33 +15,6 @@ export const CREW_MEMBER_NAMES = { [CrewType.CREW_TYPE_RADIOMAN]: "radioman", } as const; -/** - * @deprecated use svg - */ -export const TANK_ICONS: Record = { - [TankClass.TANK_CLASS_TANK_DESTROYER]: "https://i.imgur.com/BIHSEH0.png", - [TankClass.TANK_CLASS_LIGHT]: "https://i.imgur.com/CSNha5V.png", - [TankClass.TANK_CLASS_MEDIUM]: "https://i.imgur.com/wvf3ltm.png", - [TankClass.TANK_CLASS_HEAVY]: "https://i.imgur.com/ECeqlZa.png", -}; -/** - * @deprecated use svg - */ -export const TANK_ICONS_PREMIUM: Record = { - [TankClass.TANK_CLASS_TANK_DESTROYER]: "https://i.imgur.com/TCu3EdR.png", - [TankClass.TANK_CLASS_LIGHT]: "https://i.imgur.com/zdkpTRb.png", - [TankClass.TANK_CLASS_MEDIUM]: "https://i.imgur.com/3z7eHX6.png", - [TankClass.TANK_CLASS_HEAVY]: "https://i.imgur.com/P3vbmyA.png", -}; -/** - * @deprecated use svg - */ -export const TANK_ICONS_COLLECTOR: Record = { - [TankClass.TANK_CLASS_TANK_DESTROYER]: "https://i.imgur.com/WTjeirB.png", - [TankClass.TANK_CLASS_LIGHT]: "https://i.imgur.com/EwhtKkU.png", - [TankClass.TANK_CLASS_MEDIUM]: "https://i.imgur.com/u8YDMBh.png", - [TankClass.TANK_CLASS_HEAVY]: "https://i.imgur.com/8xRf3nc.png", -}; export const TIERS = times(10, (index) => index + 1); export const TIER_ROMAN_NUMERALS: Record = { 1: "I", @@ -68,8 +41,3 @@ export const flags: Record = { european: "🇪🇺", other: "<:other:1218421572243558482>", }; -export const TREE_TYPE_ICONS: Record> = { - [TankType.TANK_TYPE_RESEARCHABLE]: TANK_ICONS, - [TankType.TANK_TYPE_PREMIUM]: TANK_ICONS_PREMIUM, - [TankType.TANK_TYPE_COLLECTOR]: TANK_ICONS_COLLECTOR, -}; diff --git a/packages/core/src/imgur/imgur.ts b/packages/core/src/imgur/imgur.ts deleted file mode 100644 index 8135d678c..000000000 --- a/packages/core/src/imgur/imgur.ts +++ /dev/null @@ -1,50 +0,0 @@ -export enum ImgurSize { - /** - * The default size - */ - Default = '', - /** - * Unknown size but really really small - */ - UltraSmall = 'ss', - /** - * 90x90 - */ - SmallSquare = 's', - /** - * 160x160 - */ - BigSquare = 'b', - /** - * 160x160 - */ - SmallThumbnail = 't', - /** - * 320x320 - */ - MediumThumbnail = 'm', - /** - * 640x640 - */ - LargeThumbnail = 'l', - /** - * 1024x1024 - */ - HugeThumbnail = 'h', -} - -interface ImgurOptions { - format: 'jpeg' | 'png'; - size: ImgurSize; -} - -const defaultImgurOptions: ImgurOptions = { - format: 'png', - size: ImgurSize.Default, -}; - -export function imgur(id: string, imgurOptions?: Partial) { - const options = { ...defaultImgurOptions, ...imgurOptions }; - - return `https://i.imgur.com/${id}${options.size}.${options.format}`; -} diff --git a/packages/core/src/imgur/index.ts b/packages/core/src/imgur/index.ts deleted file mode 100644 index 6daa175fb..000000000 --- a/packages/core/src/imgur/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './imgur'; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 5550ecc1a..cb8712d9a 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,6 +1,5 @@ export * from "./blitz"; export * from "./blitzkit"; -export * from "./imgur"; export * from "./math"; export * from "./patreon"; export * from "./protobuf"; diff --git a/packages/website/src/components/ArticlePlug.astro b/packages/website/src/components/ArticlePlug.astro deleted file mode 100644 index 4ef838462..000000000 --- a/packages/website/src/components/ArticlePlug.astro +++ /dev/null @@ -1,38 +0,0 @@ ---- -import { tankIcon } from "@blitzkit/core"; -import { Flex, Heading, Link, Strong, Text } from "@radix-ui/themes"; ---- - - - - - - - - Christmas 2025 - - Win the GSOR the TANK and more! - - - - - diff --git a/packages/website/src/components/Giveaway.tsx b/packages/website/src/components/Giveaway.tsx deleted file mode 100644 index edd64c67a..000000000 --- a/packages/website/src/components/Giveaway.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { Code, Flex, Heading, Link, Text } from "@radix-ui/themes"; -import { useEffect, useState, type ReactNode } from "react"; -import type { MaybeSkeletonComponentProps } from "../types/maybeSkeletonComponentProps"; -import { InlineSkeleton } from "./InlineSkeleton"; - -const TIME = 1764898200 * 1000; - -export function Giveaway({ skeleton }: MaybeSkeletonComponentProps) { - const [timeLeft, setTimeLeft] = useState(TIME - Date.now()); - - useEffect(() => { - const interval = setInterval(() => { - setTimeLeft(TIME - Date.now()); - }, 1000); - - return () => { - clearInterval(interval); - }; - }, []); - - let string: ReactNode; - - if (timeLeft > 0) { - const hours = Math.floor(timeLeft / (1000 * 60 * 60)); - const minutes = Math.floor((timeLeft / (1000 * 60)) % 60) - .toString() - .padStart(2, "0"); - const seconds = Math.floor((timeLeft / 1000) % 60) - .toString() - .padStart(2, "0"); - - string = ( - <> - in{" "} - - {hours}h {minutes}m {seconds}s - - - ); - } else { - string = "has ended. Come again later!"; - } - - return ( - - - - - - - - Music Box giveaway {!skeleton && string}{" "} - {skeleton && } - - - Join the BlitzKit Discord server event - - - - - - ); -} diff --git a/packages/website/src/components/Markdown.tsx b/packages/website/src/components/Markdown.tsx index cc198c317..c4ba7685f 100644 --- a/packages/website/src/components/Markdown.tsx +++ b/packages/website/src/components/Markdown.tsx @@ -1,13 +1,20 @@ import { Link, Table } from '@radix-ui/themes'; import MarkdownToJSX from 'markdown-to-jsx'; import type { ComponentProps } from 'react'; +import { docImageUrl, isExternalImage } from '../core/blitzkit/docImages'; interface MarkdownProps { children: string; } -function Image(props: ComponentProps<'img'>) { - return ; +function Image({ src, ...props }: ComponentProps<'img'>) { + return ( + + ); } export function Markdown({ children }: MarkdownProps) { diff --git a/packages/website/src/components/StupidCat.tsx b/packages/website/src/components/StupidCat.tsx deleted file mode 100644 index 86c5be34d..000000000 --- a/packages/website/src/components/StupidCat.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export function StupidCat() { - return ; -} diff --git a/packages/website/src/core/blitzkit/docImages.ts b/packages/website/src/core/blitzkit/docImages.ts new file mode 100644 index 000000000..634bc1708 --- /dev/null +++ b/packages/website/src/core/blitzkit/docImages.ts @@ -0,0 +1,32 @@ +import { alias } from "@blitzkit/core"; + +const EXTERNAL_IMAGE = /^https?:\/\//; +const MARKDOWN_IMAGES = [ + /!\[[^\]]*\]\((\S+?)(?:\s+"[^"]*")?\)/g, + /]*\ssrc=["']([^"']+)["']/g, +]; + +export function isExternalImage(src: string) { + return EXTERNAL_IMAGE.test(src); +} + +export function docImageId(url: string) { + let hash = 0x811c9dc5; + + for (let index = 0; index < url.length; index++) { + hash ^= url.charCodeAt(index); + hash = Math.imul(hash, 0x01000193) >>> 0; + } + + return hash.toString(16).padStart(8, "0"); +} + +export function docImageUrl(url: string) { + return alias("api", `/docs/images/${docImageId(url)}.webp`); +} + +export function externalDocImages(markdown: string) { + return MARKDOWN_IMAGES.flatMap((pattern) => + Array.from(markdown.matchAll(pattern), (match) => match[1]), + ).filter(isExternalImage); +} diff --git a/packages/website/src/pages/[...chunk_api]/docs/images/[id].webp.ts b/packages/website/src/pages/[...chunk_api]/docs/images/[id].webp.ts new file mode 100644 index 000000000..0a87357f0 --- /dev/null +++ b/packages/website/src/pages/[...chunk_api]/docs/images/[id].webp.ts @@ -0,0 +1,39 @@ +import type { APIContext, GetStaticPathsItem } from "astro"; +import sharp from "sharp"; +import { + docImageId, + externalDocImages, +} from "../../../../core/blitzkit/docImages"; +import { mixStaticPaths } from "../../../../core/blitzkit/mixStaticPaths"; +import { getStaticPaths as _getStaticPaths } from "../../_index"; + +export const getStaticPaths = mixStaticPaths(_getStaticPaths, async () => { + const documents = import.meta.glob( + "../../../../../../../docs/**/*.md", + { eager: true, query: "?raw", import: "default" }, + ); + const urls = new Set(Object.values(documents).flatMap(externalDocImages)); + + return Array.from( + urls, + (url) => + ({ + params: { id: docImageId(url) }, + props: { url }, + }) satisfies GetStaticPathsItem, + ); +}); + +export async function GET({ props }: APIContext<{ url: string }>) { + const response = await fetch(props.url); + + if (!response.ok) { + throw new Error(`Failed to fetch ${props.url}: ${response.status}`); + } + + const image = await sharp(Buffer.from(await response.arrayBuffer())) + .webp() + .toBuffer(); + + return new Response(new Uint8Array(image)); +}