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(/]*>/, '') .replace(/<\/svg>\s*$/, '') .replace(/[^<]*<\/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');