mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-11-03 16:00:36 +01:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea412086d4 | ||
|
|
455679a981 | ||
|
|
a21bc6174d | ||
|
|
fae1a89402 | ||
|
|
5a1a0c59f1 | ||
|
|
c98271f783 |
No files matched your search
@@ -0,0 +1,19 @@
|
||||
.avatar-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.avatar-image {
|
||||
width: 100%;
|
||||
aspect-ratio: 7 / 8;
|
||||
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.avatar-label {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import type { Avatar } from "../../protos/avatar";
|
||||
import { Text } from "../Text";
|
||||
import "./index.css";
|
||||
|
||||
interface Props {
|
||||
avatar: Avatar;
|
||||
}
|
||||
|
||||
export function AvatarCard({ avatar }: Props) {
|
||||
return (
|
||||
<div className="avatar-card">
|
||||
<div
|
||||
className="avatar-image"
|
||||
style={{ backgroundImage: `url(/api/avatars/${avatar.name}.webp)` }}
|
||||
/>
|
||||
<Text className="avatar-label">{avatar.name}</Text>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -20,10 +20,14 @@ import "@fontsource-variable/roboto-flex";
|
||||
|
||||
--font-size-regular: 1rem;
|
||||
--font-size-minor: 0.75rem;
|
||||
--font-size-major: 1.25rem;
|
||||
|
||||
--font-family-regular: "Roboto Flex Variable", sans-serif;
|
||||
--font-family-mono: "JetBrains Mono Variable", monospace;
|
||||
|
||||
--color-regular: var(--gray-12);
|
||||
--color-low-contrast: var(--gray-11);
|
||||
|
||||
--weight-light: 300;
|
||||
--weight-regular: 400;
|
||||
--weight-medium: 500;
|
||||
@@ -43,5 +47,7 @@ import "@fontsource-variable/roboto-flex";
|
||||
var(--space-1) var(--white-a3),
|
||||
inset calc(var(--space-1) * sin(45deg) * -1)
|
||||
calc(var(--space-1) * sin(45deg) * -1) var(--space-1) var(--black-a3);
|
||||
|
||||
--blur-glass: blur(4rem);
|
||||
}
|
||||
</style>
|
||||
@@ -1,12 +1,13 @@
|
||||
---
|
||||
import type { Color } from "../pages/styles/colors/[accent].css";
|
||||
import type { Color } from "../core/ui/color";
|
||||
|
||||
interface Props {
|
||||
color: Color;
|
||||
alias: string;
|
||||
alpha?: boolean;
|
||||
}
|
||||
|
||||
const { color, alias } = Astro.props;
|
||||
const { color, alias, alpha } = Astro.props;
|
||||
|
||||
let styles = "";
|
||||
|
||||
@@ -14,7 +15,7 @@ styles += ":root{";
|
||||
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const n = i + 1;
|
||||
styles += `--${alias}-${n}:var(--${color}-${n});`;
|
||||
styles += `--${alias}-${alpha ? "a" : ""}${n}:var(--${color}-${alpha ? "a" : ""}${n});`;
|
||||
}
|
||||
|
||||
styles += "}";
|
||||
|
||||
@@ -1,20 +1,75 @@
|
||||
---
|
||||
import packageJson from "../../../../package.json";
|
||||
import { api } from "../core/api/dynamic";
|
||||
import { BlitzKitIcon } from "../icons/BlitzKit";
|
||||
import { Code } from "./Code";
|
||||
import { Text } from "./Text";
|
||||
|
||||
interface Props {
|
||||
locale: string;
|
||||
}
|
||||
|
||||
const { locale } = Astro.props;
|
||||
|
||||
const strings = await api.strings(locale);
|
||||
---
|
||||
|
||||
<style>
|
||||
.footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
margin-top: calc(var(--space-4) * -1);
|
||||
padding: var(--space-6) var(--space-4);
|
||||
padding: var(--space-5) var(--space-5);
|
||||
|
||||
background-color: var(--gray-3);
|
||||
color: var(--color-low-contrast);
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
max-width: 52rem;
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.icon {
|
||||
width: var(--space-6);
|
||||
height: var(--space-6);
|
||||
}
|
||||
|
||||
.info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: end;
|
||||
gap: var(--space-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer-spacer {
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="footer-spacer"></div>
|
||||
|
||||
<div class="footer">
|
||||
<div class="content">poopy</div>
|
||||
<div class="content">
|
||||
<div class="logo">
|
||||
<BlitzKitIcon className="icon" />
|
||||
|
||||
<div class="info">
|
||||
<Text lowContrast weight="medium" size="major">
|
||||
{strings.common.name}
|
||||
</Text>
|
||||
|
||||
<Code>
|
||||
{packageJson.version}
|
||||
</Code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
import { HamburgerMenuIcon, MagnifyingGlassIcon } from "@radix-ui/react-icons";
|
||||
import { BlitzKitIcon } from "../icons/BlitzKit";
|
||||
---
|
||||
|
||||
<style>
|
||||
.navbar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
margin-bottom: calc(var(--space-5) * -1);
|
||||
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
z-index: 1;
|
||||
backdrop-filter: var(--blur-glass);
|
||||
background-color: var(--black-a11);
|
||||
|
||||
.side {
|
||||
display: flex;
|
||||
|
||||
.action {
|
||||
width: calc(var(--space-5) + var(--space-4));
|
||||
height: calc(var(--space-5) + var(--space-4));
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.icon {
|
||||
width: 1.25em;
|
||||
height: 1.25em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="navbar">
|
||||
<div class="side">
|
||||
<div class="action">
|
||||
<BlitzKitIcon className="icon" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="side">
|
||||
<div class="action">
|
||||
<MagnifyingGlassIcon className="icon" />
|
||||
</div>
|
||||
|
||||
<div class="action">
|
||||
<HamburgerMenuIcon className="icon" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -5,6 +5,10 @@
|
||||
font-size: var(--font-size-minor);
|
||||
}
|
||||
|
||||
&[data-size="major"] {
|
||||
font-size: var(--font-size-major);
|
||||
}
|
||||
|
||||
&[data-weight="light"] {
|
||||
font-weight: var(--weight-light);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ export interface TextProps extends ComponentProps<"span"> {
|
||||
color?: Color;
|
||||
weight?: "light" | "regular" | "medium" | "bold" | "black";
|
||||
lowContrast?: boolean;
|
||||
size?: "regular" | "minor";
|
||||
size?: "regular" | "minor" | "major";
|
||||
}
|
||||
|
||||
export function Text({
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
border-radius: var(--max-size);
|
||||
|
||||
background-color: var(--gray-3);
|
||||
background-color: var(--gray-1);
|
||||
outline: var(--outline-interactive);
|
||||
box-shadow: var(--shadow-cavity);
|
||||
|
||||
|
||||
@@ -1,19 +1,9 @@
|
||||
import { fetchPB } from "@blitzkit/core";
|
||||
import { Tank } from "../../protos/tank";
|
||||
import { TankList } from "../../protos/tank_list";
|
||||
import { Tanks } from "../../protos/tanks";
|
||||
import { Avatars } from "../../protos/avatars";
|
||||
import { AbstractAPI } from "./abstract";
|
||||
|
||||
export class ClientAPI extends AbstractAPI {
|
||||
tanks() {
|
||||
return fetchPB("/api/tanks/all.pb", Tanks);
|
||||
}
|
||||
|
||||
tankList() {
|
||||
return fetchPB("/api/tanks/list.pb", TankList);
|
||||
}
|
||||
|
||||
tank(id: string) {
|
||||
return fetchPB(`/api/tanks/${id}.pb`, Tank);
|
||||
protected _avatars() {
|
||||
return fetchPB("/api/avatars/all.pb", Avatars);
|
||||
}
|
||||
}
|
||||
@@ -8,11 +8,12 @@ import Navbar from "../components/Navbar.astro";
|
||||
import type { Color } from "../core/ui/color";
|
||||
|
||||
interface Props {
|
||||
locale: string;
|
||||
accent: Color;
|
||||
noNavbar?: boolean;
|
||||
}
|
||||
|
||||
const { accent, noNavbar } = Astro.props;
|
||||
const { accent, noNavbar, locale } = Astro.props;
|
||||
---
|
||||
|
||||
<html>
|
||||
@@ -20,8 +21,12 @@ const { accent, noNavbar } = Astro.props;
|
||||
<BaseMeta />
|
||||
|
||||
<ColorScale color="mauve" />
|
||||
<ColorScale color={accent} />
|
||||
<ColorAlias color="mauve" alias="gray" />
|
||||
|
||||
<ColorScale color="black" alpha />
|
||||
<ColorScale color="white" alpha />
|
||||
|
||||
<ColorScale color={accent} />
|
||||
<ColorAlias color={accent} alias="accent" />
|
||||
<BaseStyles />
|
||||
|
||||
@@ -31,6 +36,7 @@ const { accent, noNavbar } = Astro.props;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-5);
|
||||
min-height: 100dvh;
|
||||
|
||||
background-color: var(--gray-1);
|
||||
color: var(--gray-12);
|
||||
@@ -46,6 +52,6 @@ const { accent, noNavbar } = Astro.props;
|
||||
|
||||
<slot />
|
||||
|
||||
{(<Footer />)}
|
||||
<Footer {locale} />
|
||||
</body>
|
||||
</html>
|
||||
@@ -11,9 +11,10 @@ import BaseLayout from "../../../layouts/BaseLayout.astro";
|
||||
export { getStaticPaths } from "../index.astro";
|
||||
|
||||
const root = `/src/pages/api`;
|
||||
const globbed = import.meta.glob("/src/pages/api/**/*.ts", {
|
||||
const globbed = import.meta.glob("/src/pages/api/**/*", {
|
||||
eager: true,
|
||||
query: "?raw",
|
||||
import: "default",
|
||||
});
|
||||
const paths: {
|
||||
path: string;
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { AvatarCard } from "../../../components/AvatarCard";
|
||||
import { api } from "../../../core/api/dynamic";
|
||||
import { useAwait } from "../../../hooks/useAwait";
|
||||
|
||||
interface Props {
|
||||
skeleton?: boolean;
|
||||
}
|
||||
|
||||
export function Page({ skeleton }: Props) {
|
||||
const { avatars } = useAwait(() => api.avatars(), "avatars");
|
||||
|
||||
return (
|
||||
<div className="avatars">
|
||||
{avatars.map((avatar) => (
|
||||
<AvatarCard key={avatar.name} avatar={avatar} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,41 +1,22 @@
|
||||
---
|
||||
import { ProfileAvatarComponent_Category } from "@protos/blitz_static_profile_avatar_component";
|
||||
import { Grade } from "@protos/blitz_static_standard_grades_enum";
|
||||
import { api } from "../../../core/api/dynamic";
|
||||
import BaseLayout from "../../../layouts/BaseLayout.astro";
|
||||
import { Page } from "./_index";
|
||||
|
||||
export { getStaticPaths } from "../index.astro";
|
||||
|
||||
const { avatars } = await api.avatars();
|
||||
const { locale } = Astro.props;
|
||||
---
|
||||
|
||||
<BaseLayout accent="gold">
|
||||
{
|
||||
avatars.map((avatar) => {
|
||||
return (
|
||||
<>
|
||||
<h3>
|
||||
{avatar.stuff_ui!.display_name} {Grade[avatar.stuff_ui!.grade]}{" "}
|
||||
{ProfileAvatarComponent_Category[avatar.profile_avatar!.category]}
|
||||
</h3>
|
||||
|
||||
{avatar.stuff_ui!.description && (
|
||||
<p>{avatar.stuff_ui!.description}</p>
|
||||
)}
|
||||
{avatar.stuff_ui!.obtaining_methods && (
|
||||
<p>{avatar.stuff_ui!.obtaining_methods}</p>
|
||||
)}
|
||||
{avatar.stuff_ui!.features && <p>{avatar.stuff_ui!.features}</p>}
|
||||
|
||||
<img width="128" src={`/api/avatars/${avatar.name}.webp`} />
|
||||
|
||||
{avatar.sellable && (
|
||||
<pre>{JSON.stringify(avatar.sellable, null, 2)}</pre>
|
||||
)}
|
||||
|
||||
<br />
|
||||
</>
|
||||
);
|
||||
})
|
||||
<style is:global>
|
||||
.avatars {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(var(--space-7), 1fr));
|
||||
gap: var(--space-4);
|
||||
}
|
||||
</style>
|
||||
|
||||
<BaseLayout {locale} accent="gold">
|
||||
<Page client:only="react">
|
||||
<Page skeleton slot="fallback" />
|
||||
</Page>
|
||||
</BaseLayout>
|
||||
@@ -21,12 +21,10 @@ export const getStaticPaths = (() => {
|
||||
const { locale } = Astro.props;
|
||||
---
|
||||
|
||||
<BaseLayout accent="amber">
|
||||
<BaseLayout accent="amber" noNavbar>
|
||||
<ColorScale color="purple" slot="head" />
|
||||
<ColorScale color="gold" slot="head" />
|
||||
<ColorScale color="lime" slot="head" />
|
||||
<ColorScale color="black" alpha slot="head" />
|
||||
<ColorScale color="white" alpha slot="head" />
|
||||
|
||||
<HomeHero {locale} />
|
||||
|
||||
|
||||
@@ -5,12 +5,9 @@ import { TankopediaToy } from "../../../../components/TankopediaToy";
|
||||
import { api } from "../../../../core/api/dynamic";
|
||||
import { characteristicsOrder } from "../../../../core/tankopedia/characteristicsOrder";
|
||||
import { computeCharacteristics } from "../../../../core/tankopedia/computeCharacteristics";
|
||||
import type { TerrainHardness } from "../../../../core/tankopedia/tankState";
|
||||
import { TerrainHardness } from "../../../../core/tankopedia/tankState";
|
||||
import { useAwait } from "../../../../hooks/useAwait";
|
||||
import {
|
||||
Tankopedia,
|
||||
type TankopediaCompare,
|
||||
} from "../../../../stores/tankopedia";
|
||||
import { Tankopedia, TankopediaCompare } from "../../../../stores/tankopedia";
|
||||
|
||||
export function Page({ id }: { id: string }) {
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import type { APIContext, GetStaticPaths } from "astro";
|
||||
import { api } from "../../../core/api/dynamic";
|
||||
|
||||
export const getStaticPaths = (async () => {
|
||||
const { avatars } = await api.avatars();
|
||||
|
||||
return avatars.map((avatar) => ({
|
||||
params: { id: avatar.name },
|
||||
}));
|
||||
}) satisfies GetStaticPaths;
|
||||
|
||||
export async function GET({ params }: APIContext<never, { id: string }>) {
|
||||
const avatar = await api.avatar(params.id);
|
||||
return Response.json(avatar);
|
||||
}
|
||||
@@ -1,18 +1,9 @@
|
||||
import type { APIContext, GetStaticPaths } from "astro";
|
||||
import type { APIContext } from "astro";
|
||||
import { api } from "../../../core/api/dynamic";
|
||||
import { imageProxy } from "../../../core/api/imageProxy";
|
||||
|
||||
export const getStaticPaths = (async () => {
|
||||
const { avatars } = await api.avatars();
|
||||
export { getStaticPaths } from "./[id].json";
|
||||
|
||||
return avatars.map((avatar) => ({
|
||||
params: { id: avatar.name },
|
||||
}));
|
||||
}) satisfies GetStaticPaths;
|
||||
|
||||
/**
|
||||
* One day I will write documentation for this
|
||||
*/
|
||||
export async function GET({ params }: APIContext<never, { id: string }>) {
|
||||
const avatar = await api.avatar(params.id);
|
||||
return await imageProxy(avatar.profile_avatar!.avatar);
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { api } from "../../../core/api/dynamic";
|
||||
|
||||
export async function GET() {
|
||||
const avatars = await api.avatars();
|
||||
return Response.json(avatars);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { api } from "../../../core/api/dynamic";
|
||||
import { Avatars } from "../../../protos/avatars";
|
||||
|
||||
export async function GET() {
|
||||
const avatars = await api.avatars();
|
||||
return new Response(Avatars.encode(avatars).finish());
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { api } from "../../../core/api/dynamic";
|
||||
|
||||
export async function GET() {
|
||||
const { avatars } = await api.avatars();
|
||||
const list = avatars.map((avatar) => avatar.name);
|
||||
return Response.json(list);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import type { APIContext, GetStaticPaths } from "astro";
|
||||
import { api } from "../../../core/api/dynamic";
|
||||
|
||||
export const getStaticPaths = (async () => {
|
||||
const { backgrounds } = await api.backgrounds();
|
||||
|
||||
return backgrounds.map((background) => ({
|
||||
params: { id: background.name },
|
||||
}));
|
||||
}) satisfies GetStaticPaths;
|
||||
|
||||
export async function GET({ params }: APIContext<never, { id: string }>) {
|
||||
const background = await api.background(params.id);
|
||||
return Response.json(background);
|
||||
}
|
||||
@@ -1,18 +1,9 @@
|
||||
import type { APIContext, GetStaticPaths } from "astro";
|
||||
import type { APIContext } from "astro";
|
||||
import { api } from "../../../core/api/dynamic";
|
||||
import { imageProxy } from "../../../core/api/imageProxy";
|
||||
|
||||
export const getStaticPaths = (async () => {
|
||||
const { backgrounds } = await api.backgrounds();
|
||||
export { getStaticPaths } from "./[id].json";
|
||||
|
||||
return backgrounds.map((background) => ({
|
||||
params: { id: background.name },
|
||||
}));
|
||||
}) satisfies GetStaticPaths;
|
||||
|
||||
/**
|
||||
* One day I will write documentation for this
|
||||
*/
|
||||
export async function GET({ params }: APIContext<never, { id: string }>) {
|
||||
const background = await api.background(params.id);
|
||||
return await imageProxy(background.profile_background!.background);
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { api } from "../../../core/api/dynamic";
|
||||
|
||||
export async function GET() {
|
||||
const { backgrounds } = await api.backgrounds();
|
||||
return Response.json(backgrounds);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { api } from "../../../core/api/dynamic";
|
||||
|
||||
export async function GET() {
|
||||
const { backgrounds } = await api.backgrounds();
|
||||
const list = backgrounds.map((background) => background.name);
|
||||
return Response.json(list);
|
||||
}
|
||||
@@ -2,8 +2,9 @@ import { TankClass, TankType } from "@blitzkit/core";
|
||||
import { Nation } from "@protos/blitz_static_tank_component";
|
||||
import type { APIContext, GetStaticPaths } from "astro";
|
||||
import { GunType } from "../../../core/tankopedia/characteristics";
|
||||
import { GET as _GET } from "./all.json";
|
||||
|
||||
const enums = {
|
||||
export const ENUMS = {
|
||||
nation: Nation,
|
||||
"tank-class": TankClass,
|
||||
"tank-type": TankType,
|
||||
@@ -11,29 +12,12 @@ const enums = {
|
||||
};
|
||||
|
||||
export const getStaticPaths = (() => {
|
||||
return Object.keys(enums).map((name) => ({ params: { enum: name } }));
|
||||
return Object.keys(ENUMS).map((name) => ({ params: { enum: name } }));
|
||||
}) satisfies GetStaticPaths;
|
||||
|
||||
/**
|
||||
* Enumerations used within BlitzKit, for nominal development reference only.
|
||||
*
|
||||
* DO NOT programmatically use this API as it is designed solely for human
|
||||
* reference.
|
||||
*
|
||||
* DO NOT blindly copy entries as naming conventions vary. Most enums come
|
||||
* directly from the game which uses a SCREAMING_SNAKE_CASE convention while
|
||||
* BlitzKit uses PascalCase.
|
||||
*/
|
||||
export function GET({
|
||||
export async function GET({
|
||||
params,
|
||||
}: APIContext<never, { enum: keyof typeof enums }>) {
|
||||
const map: Record<number, string> = {};
|
||||
|
||||
for (const key in enums[params.enum]) {
|
||||
const value = enums[params.enum][key];
|
||||
if (typeof value !== "string") continue;
|
||||
map[key] = value;
|
||||
}
|
||||
|
||||
return Response.json(map);
|
||||
}: APIContext<never, { enum: keyof typeof ENUMS }>) {
|
||||
const enums = (await _GET().json()) as Record<string, Record<number, string>>;
|
||||
return Response.json(enums[params.enum]);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { ENUMS } from "./[enum].json";
|
||||
|
||||
export function GET() {
|
||||
const enums: Record<string, Record<number, string>> = {};
|
||||
|
||||
for (const name in ENUMS) {
|
||||
const _enum = ENUMS[name as keyof typeof ENUMS];
|
||||
const map: Record<number, string> = {};
|
||||
|
||||
for (const key in _enum) {
|
||||
const value = _enum[key];
|
||||
if (typeof value !== "string") continue;
|
||||
map[key] = value;
|
||||
}
|
||||
|
||||
enums[name] = map;
|
||||
}
|
||||
|
||||
return Response.json(enums);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { ENUMS } from "./[enum].json";
|
||||
|
||||
export function GET() {
|
||||
const list = Object.keys(ENUMS);
|
||||
return Response.json(list);
|
||||
}
|
||||
Reference in new issue
Block a user