Compare commits

...
3 Commits
Author SHA1 Message Date
TrèsAbhi 4a6f5845f9 feat: shadows for outlines 2026-01-13 20:49:40 -06:00
TrèsAbhi e9684ce033 feat: navbar rewrite 2026-01-13 20:33:14 -06:00
TrèsAbhi 27abf1b0de feat: string hooks 2026-01-13 20:15:55 -06:00
13 changed files with 149 additions and 46 deletions

No files matched your search

@@ -12,13 +12,13 @@
background-size: cover;
background-position: center;
outline: var(--outline-interactive);
box-shadow: var(--shadow-subtle);
box-shadow: var(--shadow-interactive), var(--shadow-subtle);
}
.avatar-label {
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
@@ -1,5 +1,4 @@
import { api } from "../../core/api/dynamic";
import { useAwait } from "../../hooks/useAwait";
import { useGameStrings } from "../../hooks/useGameStrings";
import type { Avatar } from "../../protos/avatar";
import { Text } from "../Text";
import "./index.css";
@@ -9,12 +8,7 @@ interface Props {
}
export function AvatarCard({ avatar }: Props) {
// const { locale } = useLocale();
const { locale } = { locale: "en" };
const profileAvatarEntityStrings = useAwait(
() => api.groupedGameStrings(locale, "ProfileAvatarEntity"),
"profile-avatar-entity-strings"
);
const profileAvatarEntityStrings = useGameStrings("ProfileAvatarEntity");
return (
<div className="avatar-card">
@@ -16,6 +16,8 @@ import "@fontsource-variable/roboto-flex";
--space-8: 16rem;
--space-9: 32rem;
--page-width: 52rem;
--max-size: max(100vw, 100vh);
--font-size-regular: 1rem;
@@ -34,7 +36,6 @@ import "@fontsource-variable/roboto-flex";
--weight-bold: 700;
--weight-black: 900;
--outline-interactive: solid var(--space-1) var(--gray-5);
--outline-focus: var(--space-1) solid var(--accent-6);
--shadow-cavity:
@@ -42,11 +43,27 @@ import "@fontsource-variable/roboto-flex";
var(--space-1) var(--black-a3),
inset calc(var(--space-1) * sin(45deg) * -1)
calc(var(--space-1) * sin(45deg) * -1) var(--space-1) var(--white-a1);
--shadow-bump:
inset calc(var(--space-1) * sin(45deg)) calc(var(--space-1) * sin(45deg))
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);
--shadow-outline-width: 1.5px;
--shadow-outline-width-negative: calc(var(--shadow-outline-width) * -1);
--shadow-interactive-background: 0 0 0 var(--shadow-outline-width)
var(--gray-6);
--shadow-interactive-shine: var(--shadow-outline-width-negative)
var(--shadow-outline-width-negative) var(--shadow-outline-width)
var(--white-a1);
--shadow-interactive-shadow: var(--shadow-outline-width)
var(--shadow-outline-width) var(--shadow-outline-width) var(--black-a4);
--shadow-interactive:
var(--shadow-interactive-shine), var(--shadow-interactive-shadow),
var(--shadow-interactive-background);
--shadow-subtle: var(--space-1) var(--space-1) var(--space-4)
var(--black-a11);
@@ -27,7 +27,7 @@ const strings = await api.strings(locale);
.content {
flex: 1;
max-width: 52rem;
max-width: var(--page-width);
.logo {
display: flex;
+45 -25
View File
@@ -1,36 +1,54 @@
---
import { HamburgerMenuIcon, MagnifyingGlassIcon } from "@radix-ui/react-icons";
import { BlitzKitIcon } from "../icons/BlitzKit";
import { LinkI18n } from "./LinkI18n";
interface Props {
locale: string;
}
const { locale } = Astro.props;
---
<style>
.navbar {
position: sticky;
top: 0;
width: 100%;
margin-bottom: calc(var(--space-4) * -1);
left: 0;
display: flex;
justify-content: space-between;
justify-content: center;
z-index: 1;
backdrop-filter: var(--blur-glass);
background-color: var(--black-a11);
background-color: var(--black-a5);
.content {
flex: 1;
max-width: calc(var(--page-width) + 2 * var(--space-4));
.side {
display: flex;
align-items: center;
justify-content: space-between;
.action {
width: calc(var(--space-5) + var(--space-4));
height: calc(var(--space-5) + var(--space-4));
.side {
display: flex;
align-items: center;
justify-content: center;
.icon {
width: 1.25em;
height: 1.25em;
.action {
padding: var(--space-4);
cursor: pointer;
color: var(--color-regular);
&:focus {
outline: var(--outline-focus);
}
.icon {
width: 1.25em;
height: 1.25em;
vertical-align: top;
}
}
}
}
@@ -38,19 +56,21 @@ import { BlitzKitIcon } from "../icons/BlitzKit";
</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 class="content">
<div class="side">
<LinkI18n {locale} href="/" className="action" tabIndex={0}>
<BlitzKitIcon className="icon" />
</LinkI18n>
</div>
<div class="action">
<HamburgerMenuIcon className="icon" />
<div class="side">
<div class="action" tabindex="0">
<MagnifyingGlassIcon className="icon" />
</div>
<div class="action" tabindex="0">
<HamburgerMenuIcon className="icon" />
</div>
</div>
</div>
</div>
@@ -6,7 +6,7 @@
.content {
flex: 1;
max-width: 52rem;
max-width: var(--page-width);
display: flex;
flex-direction: column;
@@ -9,8 +9,7 @@
border-radius: var(--max-size);
background-color: var(--gray-1);
outline: var(--outline-interactive);
box-shadow: var(--shadow-cavity);
box-shadow: var(--shadow-interactive), var(--shadow-cavity);
font-size: var(--font-size-regular);
color: var(--gray-11);
@@ -1,4 +1,5 @@
import { fetchPB } from "@blitzkit/core";
import type { Strings } from "@blitzkit/i18n";
import { Avatars } from "../../protos/avatars";
import { AbstractAPI } from "./abstract";
@@ -29,4 +30,11 @@ export class ClientAPI extends AbstractAPI {
return strings;
}
protected async _strings(locale: string) {
const response = await fetch(`/api/strings/${locale}.json`);
const json = (await response.json()) as Strings;
return json;
}
}
@@ -0,0 +1,13 @@
import { api } from "../core/api/dynamic";
import { useAwait } from "./useAwait";
import { useLocale } from "./useLocale";
export function useGameStrings(group: string) {
const { locale } = useLocale();
const strings = useAwait(
() => api.groupedGameStrings(locale, group),
`game-strings-${locale}-${group}`
);
return strings;
}
@@ -0,0 +1,39 @@
import locales from "@blitzkit/i18n/locales.json";
import { createContext, useContext, type ReactNode } from "react";
import { api } from "../core/api/dynamic";
import { useAwait } from "./useAwait";
const LocaleContext = createContext<{
locale: string;
} | null>(null);
interface LocaleProviderProps extends LocaleAcceptorProps {
children: ReactNode;
}
export function LocaleProvider({ locale, children }: LocaleProviderProps) {
return (
<LocaleContext.Provider value={{ locale: locale ?? locales.default }}>
{children}
</LocaleContext.Provider>
);
}
export function useLocale() {
const context = useContext(LocaleContext);
if (!context) {
throw new Error("useLocale must be used within a LocaleProvider");
}
const strings = useAwait(
() => api.strings(context.locale),
`strings-${context.locale}`
);
return { locale: context.locale, strings };
}
export interface LocaleAcceptorProps {
locale: string | undefined;
}
@@ -48,7 +48,7 @@ const { accent, noNavbar, locale } = Astro.props;
</head>
<body class="dark">
{!noNavbar && <Navbar />}
{!noNavbar && <Navbar {locale} />}
<slot />
@@ -1,17 +1,30 @@
import { AvatarCard } from "../../../components/AvatarCard";
import { api } from "../../../core/api/dynamic";
import { useAwait } from "../../../hooks/useAwait";
import { LocaleProvider } from "../../../hooks/useLocale";
interface Props {
interface PageProps extends ContentProps {
locale: string;
}
export function Page({ locale, ...props }: PageProps) {
return (
<LocaleProvider locale={locale}>
<Content {...props} />
</LocaleProvider>
);
}
interface ContentProps {
skeleton?: boolean;
}
export function Page({ skeleton }: Props) {
function Content({ skeleton }: ContentProps) {
const { avatars } = useAwait(() => api.avatars(), "avatars");
return (
<div className="avatars">
{avatars.map((avatar) => (
{avatars.slice(0, 12).map((avatar) => (
<AvatarCard key={avatar.name} avatar={avatar} />
))}
</div>
@@ -18,8 +18,8 @@ const { locale } = Astro.props;
<BaseLayout {locale} accent="gold">
<Section>
<Page client:only="react">
<Page skeleton slot="fallback" />
<Page {locale} client:only="react">
<Page {locale} skeleton slot="fallback" />
</Page>
</Section>
</BaseLayout>