merge: dmgnr/image-optimization into dev [no ci]

This commit is contained in:
2026-07-04 20:28:10 +00:00 Unverified
21 changed files with 123 additions and 38 deletions
+9 -2
View File
@@ -39,6 +39,9 @@ jobs:
-t mts.dgnr.us:5000/${{ gitea.repository }}:frontend \
-t mts.dgnr.us:5000/${{ gitea.repository }}:frontend-$TAG \
.
docker build --target migration \
-t mts.dgnr.us:5000/${{ gitea.repository }}:migration \
.
build-backend:
runs-on: ubuntu-latest
@@ -83,6 +86,7 @@ jobs:
docker push mts.dgnr.us:5000/${{ gitea.repository }}:backend-$TAG &
docker push mts.dgnr.us:5000/${{ gitea.repository }}:frontend &
docker push mts.dgnr.us:5000/${{ gitea.repository }}:frontend-$TAG &
docker push mts.dgnr.us:5000/${{ gitea.repository }}:migration &
wait
- name: Install kubectl
@@ -106,9 +110,12 @@ jobs:
# DB migration
kubectl delete job db-migrate -n buzz --ignore-not-found
kubectl create job db-migrate \
--image=mts.dgnr.us:5000/${{ gitea.repository }}:frontend-$TAG \
--image=mts.dgnr.us:5000/${{ gitea.repository }}:migration \
-n buzz \
-- bun dr push
--dry-run=client -o json \
-- bun dr push | \
jq '.spec.template.spec.containers[0].envFrom = [{"secretRef": {"name": "buzz-env"}}]' | \
kubectl apply -f -
# Roll app & backend
kubectl set image deployment/app app=mts.dgnr.us:5000/${{ gitea.repository }}:frontend-$TAG -n buzz
+20 -25
View File
@@ -4,17 +4,6 @@
FROM oven/bun:canary-alpine AS deps
WORKDIR /home/container
# RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
# --mount=type=cache,target=/var/lib/apt,sharing=locked \
# apt-get update \
# && apt-get install -y \
# python3 \
# make \
# g++ \
# sqlite3 \
# libsqlite3-dev \
# && rm -rf /var/lib/apt/lists/*
COPY package*.json bun.lock ./
COPY patches ./patches
@@ -50,7 +39,22 @@ RUN --mount=type=cache,target=/home/container/.next/cache,uid=1001,gid=1001 \
# Fix nextjs caching(bind mount removes it after build process)
RUN mkdir -p .next/cache
### Stage 3: runner ###
### Stage 3: migration ###
FROM oven/bun:canary-alpine AS migration
WORKDIR /home/container
COPY --from=deps /home/container/node_modules ./node_modules
COPY --from=deps /home/container/package*.json ./
COPY --from=deps /home/container/bun.lock ./
COPY --from=builder /home/container/drizzle.config.ts ./drizzle.config.ts
COPY --from=builder /home/container/lib ./lib
COPY drizzle.config.ts ./
COPY lib/db/ ./lib/db/
CMD ["bun", "drizzle-kit"]
### Stage 4: runner ###
FROM oven/bun:canary-alpine AS runner
# Isolation
@@ -58,19 +62,10 @@ RUN adduser -Du 1001 container
USER container
WORKDIR /home/container
COPY --from=builder /home/container/node_modules ./node_modules
COPY --from=builder /home/container/next.config.ts ./
COPY --from=builder /home/container/bun.lock ./
COPY --from=builder /home/container/package*.json ./
COPY --from=builder /home/container/.next/standalone ./
COPY --from=builder /home/container/public ./public
COPY --from=builder /home/container/.env ./.env
COPY --from=builder /home/container/.next ./.next
COPY --from=builder /home/container/util ./util
COPY --from=builder /home/container/lib ./lib
COPY --from=builder /home/container/tsconfig.json ./tsconfig.json
COPY --from=builder /home/container/.next/static ./.next/static
COPY --from=builder /home/container/.version ./.version
COPY --from=builder /home/container/drizzle.config.ts ./drizzle.config.ts
CMD ["bun", "start"]
ENV HOSTNAME=0.0.0.0
CMD ["bun", "server.js"]
+72
View File
@@ -0,0 +1,72 @@
"use client";
import Image from "next/image";
import Background0 from "#/bg.webp";
import Background1 from "#/bgassets/buzzbg1-r2.webp";
import Background2 from "#/bgassets/buzzbg2-r1.webp";
import Background3 from "#/bgassets/buzzbg3-r1.webp";
import Background4 from "#/bgassets/buzzbg4-r1.webp";
import Background5 from "#/bgassets/buzzbg5-r1.webp";
import Background6 from "#/bgassets/buzzbg6-r1.webp";
import Background7 from "#/bgassets/buzzbg7-r1.webp";
import type { StaticImport } from "next/dist/shared/lib/get-img-props";
import { usePathname } from "next/navigation";
import { useMemo, useState } from "react";
import { cn } from "@/lib/utils";
import { StarsRenderer } from "@/components/stars";
type PrefixBackground = [`/${string}`, string | StaticImport, `/${string}`];
const backgrounds = [
// path prefix, background, stars mask
["/artifact", Background1, "/bgassets/buzzbg1-r2-mask.webp"],
["/rubgram", Background2, "/bgassets/buzzbg2-r1-mask.webp"],
["/donate", Background3, "/bgassets/buzzbg3-r1-mask.webp"],
["/tl", Background5, "/bgassets/buzzbg4-r1-mask.webp"],
["/guide", Background4, "/bgassets/buzzbg5-r1-mask.webp"],
["/admin", Background6, "/bgassets/buzzbg6-r1-mask.webp"],
// unused
["/donate", Background7, "/bgassets/buzzbg7-r1-mask.webp"],
// this should be last
["/", Background0, "/mask.webp"],
] satisfies PrefixBackground[];
export function BackgroundProvider() {
const pathname = usePathname();
const [, background, mask] = useMemo(
() => backgrounds.find((b) => pathname.startsWith(b[0]))!,
[pathname],
);
const [transitioning, setTransitioning] = useState(false);
const [current, setCurrent] = useState<string | StaticImport>(background);
return (
<>
<Image
src={background}
alt="Background Swap"
className={cn(
"fixed top-0 left-0 z-[-1] h-fit min-h-dvh w-full object-cover opacity-0",
transitioning && "opacity-40 transition-opacity duration-500",
)}
onLoad={() => {
if (background === current) return;
setTransitioning(true);
}}
onTransitionEnd={() => {
if (!transitioning) return;
setCurrent(background);
setTransitioning(false);
}}
/>
<Image
src={current}
alt="Background Current"
className={cn(
"fixed top-0 left-0 z-[-1] h-fit min-h-dvh w-full object-cover opacity-40",
transitioning && "opacity-0 transition-opacity duration-500",
)}
/>
{transitioning || <StarsRenderer mask={mask} />}
</>
);
}
+2 -9
View File
@@ -1,11 +1,9 @@
import { Anuphan } from "next/font/google";
import { ViewTransition } from "react";
import { Toaster } from "sonner";
import Background from "#/bg.webp";
import Image from "@/components/image";
import { StarsRenderer } from "@/components/stars";
import Providers, { VersionCheck } from "../client";
import { Navbar } from "./navbar";
import { BackgroundProvider } from "./background";
const anuphan = Anuphan({
variable: "--font-anuphan",
@@ -19,12 +17,7 @@ export default async function UiLayout({
}) {
return (
<>
<Image
src={Background}
alt="Background"
className="fixed top-0 left-0 z-[-1] h-fit min-h-dvh w-full object-cover opacity-40"
/>
<StarsRenderer />
<BackgroundProvider />
<Providers>
<ViewTransition>{children}</ViewTransition>
<VersionCheck />
+5 -2
View File
@@ -3,7 +3,7 @@
import { useEffect, useRef, useState } from "react";
import { cn } from "@/lib/utils";
export function StarsRenderer() {
export function StarsRenderer({ mask }: { mask: string }) {
const cv = useRef<HTMLCanvasElement | null>(null);
const [loaded, setLoaded] = useState(false);
@@ -122,9 +122,12 @@ export function StarsRenderer() {
<canvas
ref={cv}
className={cn(
"fixed top-0 left-0 z-[-1] h-fit min-h-dvh w-full mask-[url('/mask.webp')] mask-cover mask-center mask-no-repeat object-cover opacity-0 transition-opacity duration-5000",
"fixed top-0 left-0 z-[-1] h-fit min-h-dvh w-full mask-cover mask-center mask-no-repeat object-cover opacity-0 transition-opacity duration-5000",
loaded && "opacity-40",
)}
style={{
maskImage: `url(${JSON.stringify(mask)})`,
}}
></canvas>
);
}
+1
View File
@@ -52,4 +52,5 @@ export default {
// reactCompiler: true,
allowedDevOrigins: ["astral:3000", "dev3000.astrxl.dev"],
// basePath: "/beta"
output: "standalone",
} satisfies NextConfig;
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 839 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1016 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

+14
View File
@@ -0,0 +1,14 @@
import { file, Glob, write } from "bun";
import { rm } from "node:fs/promises";
const g = new Glob("public/bgassets/*.png");
for await (const img of g.scan()) {
file(img)
.image()
.webp()
.buffer()
.then((b) => write(img.replace(".png", ".webp"), b))
.then(() => rm(img))
.finally(() => console.log(img, "processed."));
}