118 lines
3.6 KiB
JavaScript
118 lines
3.6 KiB
JavaScript
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');
|