"use client"; import { useEffect, useState } from "react"; import { Button } from "@/components/ui/button"; import { adminFetch } from "@/lib/admin-fetch"; import type { CompressionReport, NitroQuality, ScaleReport, } from "@/lib/furni/nitro-quality"; function kb(bytes: number): string { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; return `${(bytes / 1024 / 1024).toFixed(2)} MB`; } function CompressionPanel({ report }: { report: CompressionReport }) { const { texture, json } = report, // Differences that a renderer can actually see. visibleDiffs = texture.mismatchedPixels - texture.invisibleDifferences, // `webpEstimate.savingBytes` measures a PNG texture against a WebP // re-encode, so it reads ~0 for a bundle that already *is* WebP. The // honest WebP-vs-PNG comparison is the stored bytes against the PNG // encoding of the same artwork. pngSaving = texture.rawBytes - texture.pngEstimate.bytes; return (

Compression · {texture.format.toUpperCase()} {texture.format === "webp" ? texture.lossless ? " Lossless (VP8L)" : " Lossy (VP8)" : ""}

Bundle
{kb(report.bundleBytes)}
Texture
{kb(texture.compressedBytes)} {" "} ({Math.round(report.textureShare * 100)}%)
Metadata
{kb(json.compressedBytes)} · ×{json.ratio}
Container
{kb(report.overheadBytes)}

Texture {kb(texture.rawBytes)} decoded · deflate ×{texture.ratio} · sheet {texture.width}×{texture.height} · {texture.spriteCount} frames

{texture.format === "png" ? (

Legacy PNG texture. Re-importing as WebP Lossless would store{" "} {kb(texture.webpEstimate.bytes)} {texture.webpEstimate.savingBytes > 0 ? ( <> , saving {kb(texture.webpEstimate.savingBytes)} ( {texture.webpEstimate.bytes > 0 ? Math.round( (1 - texture.webpEstimate.bytes / texture.rawBytes) * 100, ) : 0} % smaller) ) : ( " (no gain on this bundle)" )}

) : (

WebP Lossless stores {kb(texture.rawBytes)} against{" "} {kb(texture.pngEstimate.bytes)} for the same artwork as PNG {pngSaving > 0 ? ( <> , saving {kb(pngSaving)} ( {texture.pngEstimate.bytes > 0 ? Math.round((pngSaving / texture.pngEstimate.bytes) * 100) : 0} % smaller) ) : ( " (PNG is not larger on this bundle)" )}

)}

{visibleDiffs === 0 ? ( <> Pixel-exact — every visible texel round-trips unchanged. {texture.invisibleDifferences > 0 && ( {" "} {texture.invisibleDifferences.toLocaleString()} fully transparent texels carry no RGB, which VP8L discards and no renderer can see. )} ) : ( {visibleDiffs.toLocaleString()} texel {visibleDiffs === 1 ? "" : "s"} differ after a lossless round-trip — the furniture may not render identically. )}

); } function ScalePanel({ report, classname, revision, }: { report: ScaleReport; classname: string; revision: number; }) { const [asset, setAsset] = useState(report.assets[0] ?? ""), [failed, setFailed] = useState(false); return (

Scale {report.size} ·{" "} {report.state === "missing" ? "Not included" : report.state === "issues" ? "Needs review" : report.derived ? "Derived from 64" : "Present"}

{report.assets.length} sprites · {report.animations.length} animation definitions · directions {report.directions.join(", ") || "—"}

{report.state === "missing" && (

This bundle contains no sprites at this scale. Missing scale 32 does not by itself mean the furniture is unusable.

)} {report.assets.length > 0 && ( <>
{failed ? (

This sprite could not be rendered.

) : ( {asset} setFailed(true)} /> )}
)} {report.issues.length > 0 && ( )}
); } export function NitroQualityPanel({ classname }: { classname: string }) { const [report, setReport] = useState(null), [error, setError] = useState(""), [revision, setRevision] = useState(0); useEffect(() => { const controller = new AbortController(); setReport(null); setError(""); void (async () => { try { const response = await adminFetch( `/api/admin/studio/nitro-quality?classname=${encodeURIComponent(classname)}&v=${revision}`, { signal: controller.signal }, ); const data = await response.json(); if (!response.ok) throw Error(data.error || "Inspection failed"); if (!controller.signal.aborted) setReport(data.report); } catch (error) { if (!controller.signal.aborted) setError( error instanceof Error ? error.message : "Inspection failed", ); } })(); return () => controller.abort(); }, [classname, revision]); return (

Inspect the saved bundle at both scales. Previews show individual sprites, not the assembled furniture or animation playback.

{error ? (

{error}

) : !report ? (

Checking scales and texture references…

) : (
{report.compression && ( )}
{report.scales.map((scale) => ( ))}
)}
); }