Files
jiang13-bbs/frontend/components/BrandLockup.tsx
freefire ff2ab286fb feat: 书库导入导出/图片变体/书籍搜索/小组件运行时等
新增:
- 书库导入导出(library_import/library_export)及测试
- 图片变体生成(image_variants)与响应式图片(responsiveImage)
- 书籍搜索(bookSearch)+ BookSearch/LibrarySearchGrid 组件
- 小组件运行时(widgetRuntime)与静态检查(widgetLint)
- 上传缓存中间件(upload_cache)与字体 CSS 提取脚本

其它:
- 后端 handlers/services 全量调整
- 前端页面、组件、库函数与配置更新
2026-10-01 03:06:05 +08:00

136 lines
4.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { brandLogoBox, type BrandLogoFit, type BrandLogoSize, type BrandMark } from "@/lib/brand";
import { densitySrcSet, isOwnImage, pickVariantWidth, variantURL } from "@/lib/responsiveImage";
/** 页眉品牌锁:图加文字 / 纯图 / 纯文字。图槽按画幅占位,铺法决定图怎么填进框。 */
export default function BrandLockup({
mode,
size = "sq",
fit = "contain",
name,
wordmark = "",
slogan = "",
logoLight = "",
logoDark = "",
frame = false,
}: {
mode: BrandMark;
size?: BrandLogoSize;
fit?: BrandLogoFit;
name: string;
wordmark?: string;
slogan?: string;
logoLight?: string;
logoDark?: string;
/** 后台预览画出画幅框,方便看非正方形占位 */
frame?: boolean;
}) {
const label = wordmark.trim() || name;
const line = slogan.trim();
const showImage = mode !== "text";
const showText = mode !== "image";
const box = brandLogoBox(size);
const src = logoLight || logoDark;
const both = !!(logoLight && logoDark && logoLight !== logoDark);
const useBox = showImage && (!!src || frame);
return (
<span className="j13-brand-lockup inline-flex items-center gap-2.5 min-w-0">
{showImage ? (
useBox ? (
<span
className="j13-brand-slot"
data-frame={frame ? "1" : undefined}
data-fit={src ? fit : undefined}
style={{ width: box.w, height: box.h }}
>
{src ? (
both ? (
<>
<Logo src={logoLight} fit={fit} alt={label} decorative={showText} extra="j13-brand-logo-light" cssPx={Math.max(box.w, box.h)} />
<Logo src={logoDark} fit={fit} alt={label} decorative={showText} extra="j13-brand-logo-dark" cssPx={Math.max(box.w, box.h)} />
</>
) : (
<Logo src={src} fit={fit} alt={label} decorative={showText} cssPx={Math.max(box.w, box.h)} />
)
) : (
<Bubble h={box.h} label={showText ? undefined : label} />
)}
</span>
) : (
<Bubble h={36} label={showText ? undefined : label} />
)
) : null}
{showText ? (
<span className="leading-none min-w-0">
<span className="block font-extrabold text-[17px] tracking-tight truncate" style={{ color: "var(--ink)" }}>
{label}
</span>
{line ? (
<span className="block text-[10.5px] mt-0.5 truncate" style={{ color: "var(--ink-3)" }}>
{line}
</span>
) : null}
</span>
) : null}
</span>
);
}
function Logo({
src,
fit,
alt,
decorative = false,
extra = "",
cssPx,
}: {
src: string;
fit: BrandLogoFit;
alt: string;
/** 旁边已有站名时,读屏跳过,HTML 仍保留 alt 供检索 */
decorative?: boolean;
extra?: string;
/** 图槽最大边 CSS px,用于密度变体 */
cssPx: number;
}) {
if (fit === "repeat") {
return (
<span
role={decorative ? undefined : "img"}
aria-hidden={decorative || undefined}
aria-label={decorative ? undefined : alt}
className={`j13-brand-logo j13-brand-logo-tile ${extra}`}
style={{ backgroundImage: `url("${src.replace(/["\\\n\r]/g, "")}")` }}
/>
);
}
// 本站光栅图走 1x/2x 变体(SVG/ICO 后端透传);外链图保持原地址
const own = isOwnImage(src);
return (
/* eslint-disable-next-line @next/next/no-img-element */
<img
src={own ? variantURL(src, pickVariantWidth(cssPx)) : src}
srcSet={own ? densitySrcSet(src, cssPx) : undefined}
alt={alt}
aria-hidden={decorative || undefined}
className={`j13-brand-logo ${extra}`}
/>
);
}
function Bubble({ h, label }: { h: number; label?: string }) {
return (
<span
role={label ? "img" : undefined}
aria-label={label}
aria-hidden={label ? undefined : true}
className="j13-brand-bubble rounded-2xl flex items-center justify-center shrink-0 transition-transform duration-300 group-hover:rotate-[-6deg]"
style={{ width: h, height: h, background: "var(--accent)", color: "var(--accent-on)" }}
>
<svg aria-hidden="true" focusable="false" width="50%" height="50%" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" />
</svg>
</span>
);
}