Revert "Add missing admin action files and navigation links"
This reverts commit 41be6835bf.
This commit is contained in:
1 parent
4a1e1115b3
commit
4d515bc400
338 files changed
+5949
-28144
No files matched your search
@@ -136,7 +136,9 @@ export const HABBO_FONT_GROUPS: HabboFontGroup[] = [
|
||||
},
|
||||
{
|
||||
groupName: "Custom creations",
|
||||
fonts: [{ name: "Jurrasic", styleName: "jurrasic" }],
|
||||
fonts: [
|
||||
{ name: "Jurrasic", styleName: "jurrasic" },
|
||||
],
|
||||
},
|
||||
{
|
||||
groupName: "Halloween fonts",
|
||||
@@ -209,8 +211,11 @@ export const HABBO_FONT_GROUPS: HabboFontGroup[] = [
|
||||
},
|
||||
{
|
||||
groupName: "Special fonts",
|
||||
fonts: [{ name: "Relax", styleName: "relax" }],
|
||||
fonts: [
|
||||
{ name: "Relax", styleName: "relax" },
|
||||
],
|
||||
},
|
||||
|
||||
];
|
||||
|
||||
export function buildFontUrl(styleName: string, text: string): string {
|
||||
|
||||
@@ -60,9 +60,7 @@ export default function LogoGenerator() {
|
||||
if (!cancelled) setFontError(true);
|
||||
});
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
return () => { cancelled = true; };
|
||||
}, [styleName, safeText]);
|
||||
|
||||
const handleDownload = useCallback(() => {
|
||||
@@ -83,10 +81,7 @@ export default function LogoGenerator() {
|
||||
if (!canvas) return;
|
||||
setSaveStatus("saving");
|
||||
canvas.toBlob((blob) => {
|
||||
if (!blob) {
|
||||
setSaveStatus("error");
|
||||
return;
|
||||
}
|
||||
if (!blob) { setSaveStatus("error"); return; }
|
||||
const file = new File([blob], "logo.png", { type: "image/png" });
|
||||
const fd = new FormData();
|
||||
fd.append("file", file);
|
||||
@@ -115,16 +110,11 @@ export default function LogoGenerator() {
|
||||
try {
|
||||
const info = await getFontInfo(font.styleName);
|
||||
const canvas = renderToCanvas(info.chars, info.h, info.top, info.sheet, safeText, info.avgW);
|
||||
if (!canvas) {
|
||||
fail++;
|
||||
continue;
|
||||
}
|
||||
if (!canvas) { fail++; continue; }
|
||||
const blob = await new Promise<Blob | null>((r) => canvas.toBlob((b) => r(b), "image/png"));
|
||||
if (blob) folder.file(`${font.styleName}.png`, blob);
|
||||
else fail++;
|
||||
} catch {
|
||||
fail++;
|
||||
}
|
||||
} catch { fail++; }
|
||||
}
|
||||
|
||||
const content = await zip.generateAsync({ type: "blob" });
|
||||
@@ -208,7 +198,12 @@ export default function LogoGenerator() {
|
||||
? "❌ Error"
|
||||
: "💾 Save as site logo"}
|
||||
</button>
|
||||
<button type="button" className="btn btn-secondary" onClick={handleDownloadAll} disabled={zipping}>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary"
|
||||
onClick={handleDownloadAll}
|
||||
disabled={zipping}
|
||||
>
|
||||
{zipping ? "⏳ Creating ZIP..." : "📦 Download all fonts"}
|
||||
</button>
|
||||
</div>
|
||||
@@ -224,8 +219,7 @@ export default function LogoGenerator() {
|
||||
borderRadius: "12px",
|
||||
border: "1px dashed color-mix(in srgb, var(--color-text-muted) 30%, transparent)",
|
||||
overflow: "auto",
|
||||
background:
|
||||
"repeating-conic-gradient(color-mix(in srgb, var(--color-text-muted) 14%, transparent) 0% 25%, transparent 0% 50%) 50% / 22px 22px",
|
||||
background: "repeating-conic-gradient(color-mix(in srgb, var(--color-text-muted) 14%, transparent) 0% 25%, transparent 0% 50%) 50% / 22px 22px",
|
||||
}}
|
||||
>
|
||||
{fontError ? (
|
||||
|
||||
@@ -66,7 +66,11 @@ export function PhotoLightbox({ photos }: { photos: LightboxPhoto[] }) {
|
||||
aria-label={p.alt}
|
||||
>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img src={p.url} alt={p.alt} style={{ width: "100%", height: "auto", display: "block" }} />
|
||||
<img
|
||||
src={p.url}
|
||||
alt={p.alt}
|
||||
style={{ width: "100%", height: "auto", display: "block" }}
|
||||
/>
|
||||
<div style={{ padding: "0.7rem 0.85rem" }}>
|
||||
<p className="muted" style={{ margin: 0, fontSize: "0.85rem" }}>
|
||||
{p.date}
|
||||
|
||||
@@ -80,7 +80,8 @@ function parseNowPlaying(data: unknown): string {
|
||||
if (np) return np;
|
||||
|
||||
// { song, artist } / { title, artist } at the top level.
|
||||
const top = joinSongArtist(data.song, data.artist) ?? joinSongArtist(data.title, data.artist);
|
||||
const top =
|
||||
joinSongArtist(data.song, data.artist) ?? joinSongArtist(data.title, data.artist);
|
||||
if (top) return top;
|
||||
|
||||
// AzureCast: { now_playing: { song: { title, artist, text } } }.
|
||||
@@ -277,7 +278,9 @@ export default function RadioPlayer() {
|
||||
height: "8px",
|
||||
borderRadius: "999px",
|
||||
background: playing ? "#16a34a" : muted,
|
||||
boxShadow: playing ? "0 0 0 3px color-mix(in srgb, #16a34a 22%, transparent)" : "none",
|
||||
boxShadow: playing
|
||||
? "0 0 0 3px color-mix(in srgb, #16a34a 22%, transparent)"
|
||||
: "none",
|
||||
}}
|
||||
/>
|
||||
<span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
|
||||
@@ -301,7 +304,9 @@ export default function RadioPlayer() {
|
||||
</div>
|
||||
|
||||
<span
|
||||
aria-label={listeners != null ? `${listeners.toLocaleString()} listeners` : "Listeners"}
|
||||
aria-label={
|
||||
listeners != null ? `${listeners.toLocaleString()} listeners` : "Listeners"
|
||||
}
|
||||
style={{
|
||||
flex: "none",
|
||||
display: "inline-flex",
|
||||
|
||||
@@ -6,10 +6,7 @@ interface CharInfo {
|
||||
w: number;
|
||||
}
|
||||
|
||||
const fontCache = new Map<
|
||||
string,
|
||||
{ chars: CharInfo[]; h: number; top: number; sheet: HTMLImageElement; avgW: number }
|
||||
>();
|
||||
const fontCache = new Map<string, { chars: CharInfo[]; h: number; top: number; sheet: HTMLImageElement; avgW: number }>();
|
||||
|
||||
function isTransparent(data: Uint8ClampedArray, x: number, y: number, w: number): boolean {
|
||||
return data[(y * w + x) * 4 + 3] === 0;
|
||||
@@ -48,9 +45,7 @@ function scanCharWidths(data: Uint8ClampedArray, sw: number, sh: number): CharIn
|
||||
return chars;
|
||||
}
|
||||
|
||||
export async function getFontInfo(
|
||||
font: string,
|
||||
): Promise<{ chars: CharInfo[]; h: number; top: number; sheet: HTMLImageElement; avgW: number }> {
|
||||
export async function getFontInfo(font: string): Promise<{ chars: CharInfo[]; h: number; top: number; sheet: HTMLImageElement; avgW: number }> {
|
||||
const cached = fontCache.get(font);
|
||||
if (cached) return cached;
|
||||
|
||||
@@ -73,8 +68,7 @@ export async function getFontInfo(
|
||||
const chars = scanCharWidths(imageData.data, canvas.width, canvas.height);
|
||||
|
||||
// measure total height by finding first/last non-empty row
|
||||
let top = canvas.height,
|
||||
bottom = 0;
|
||||
let top = canvas.height, bottom = 0;
|
||||
for (let y = 0; y < canvas.height; y++) {
|
||||
for (let x = 0; x < canvas.width; x++) {
|
||||
if (!isTransparent(imageData.data, x, y, canvas.width)) {
|
||||
|
||||
@@ -48,7 +48,15 @@ export function ContentCard({
|
||||
}
|
||||
|
||||
/** A value + label tile (profile stats, rare-value summaries, …). */
|
||||
export function StatBlock({ value, label, icon }: { value: ReactNode; label: string; icon?: string }) {
|
||||
export function StatBlock({
|
||||
value,
|
||||
label,
|
||||
icon,
|
||||
}: {
|
||||
value: ReactNode;
|
||||
label: string;
|
||||
icon?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="stat-block">
|
||||
<div className="stat-block-value">
|
||||
@@ -88,7 +96,9 @@ export function OnlineBadge({ online }: { online: boolean }) {
|
||||
export function RankBadge({ position }: { position: number }) {
|
||||
const medal = position === 1 ? "🥇" : position === 2 ? "🥈" : position === 3 ? "🥉" : null;
|
||||
return (
|
||||
<span className={`rank-badge rank-${position <= 3 ? position : "n"}`}>{medal ?? `#${position}`}</span>
|
||||
<span className={`rank-badge rank-${position <= 3 ? position : "n"}`}>
|
||||
{medal ?? `#${position}`}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -124,3 +134,5 @@ export function SkeletonCard({ lines = 3 }: { lines?: number }) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user