Files
dyolink/frontend/scripts/build-brand-components.mjs

118 lines
3.6 KiB
JavaScript
Raw Normal View History

import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const brand = path.join(root, 'src/assets/brand');
function stripBom(s) {
return s.replace(/^\uFEFF/, '');
}
function svgToInner(svg) {
return svg
.replace(/<\?xml[^>]*>/, '')
.replace(/<svg[^>]*>/, '')
.replace(/<\/svg>\s*$/, '')
.replace(/<title>[^<]*<\/title>\s*/, '')
.trim()
.replace(/fill-rule=/g, 'fillRule=');
}
const wordmark = stripBom(fs.readFileSync(path.join(brand, 'wordmark.svg'), 'utf8'));
const logo = stripBom(fs.readFileSync(path.join(brand, 'logo.svg'), 'utf8'));
const wmInner = svgToInner(wordmark);
const lgInner = svgToInner(logo);
const wordmarkTsx = `import type { CSSProperties } from 'react';
export const BRAND_NAME = 'Nudentic';
type BrandWordmarkProps = {
className?: string;
style?: CSSProperties;
decorative?: boolean;
};
/** Official wordmark from \`src/assets/brand/wordmark.svg\`. Re-run \`scripts/build-brand-components.mjs\` after editing the SVG. */
export function BrandWordmark({ className, style, decorative }: BrandWordmarkProps) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 345 70"
className={className}
style={{ display: 'inline-block', height: '1em', width: 'auto', overflow: 'visible', ...style }}
role={decorative ? undefined : 'img'}
aria-label={decorative ? undefined : BRAND_NAME}
aria-hidden={decorative || undefined}
>
${wmInner}
</svg>
);
}
`;
const logoTsx = `import type { CSSProperties } from 'react';
import { BRAND_NAME } from '@/components/ui/shared/BrandWordmark';
type BrandLogoProps = {
className?: string;
style?: CSSProperties;
decorative?: boolean;
};
/** Official mark from \`src/assets/brand/logo.svg\`. Re-run \`scripts/build-brand-components.mjs\` after editing the SVG. */
export function BrandLogo({ className, style, decorative }: BrandLogoProps) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 4294 3630"
className={className}
style={{ display: 'inline-block', height: '1.15em', width: 'auto', overflow: 'visible', ...style }}
role={decorative ? undefined : 'img'}
aria-label={decorative ? undefined : BRAND_NAME}
aria-hidden={decorative || undefined}
>
${lgInner}
</svg>
);
}
`;
const lockupTsx = `import type { CSSProperties } from 'react';
import { BrandLogo } from '@/components/ui/shared/BrandLogo';
import { BrandWordmark, BRAND_NAME } from '@/components/ui/shared/BrandWordmark';
type BrandLockupProps = {
className?: string;
style?: CSSProperties;
};
/** Logo + wordmark. Size via font-size / text-* on this wrapper (\`em\` on the SVGs). */
export function BrandLockup({ className, style }: BrandLockupProps) {
return (
<span
dir="ltr"
className={className}
style={{ display: 'inline-flex', alignItems: 'center', gap: '0.35em', ...style }}
role="img"
aria-label={BRAND_NAME}
>
<BrandLogo decorative />
<BrandWordmark decorative />
</span>
);
}
`;
fs.writeFileSync(path.join(root, 'src/components/ui/shared/BrandWordmark.tsx'), wordmarkTsx);
fs.writeFileSync(path.join(root, 'src/components/ui/shared/BrandLogo.tsx'), logoTsx);
fs.writeFileSync(path.join(root, 'src/components/ui/shared/BrandLockup.tsx'), lockupTsx);
const icon = logo
.replaceAll('fill="currentColor"', 'fill="#0f172a"')
.replace(/<title>[^<]*<\/title>\s*/, '');
fs.writeFileSync(path.join(root, 'src/app/icon.svg'), icon);
console.log('brand components + icon.svg written');