import { useEffect, useRef } from 'react'; export interface SEOMeta { title: string; description: string; url: string; ogImage?: string; /** Module-level constant: injected once on mount, removed on unmount. */ jsonLd?: object | object[]; /** If true, sets robots meta to "noindex, nofollow" to prevent indexing. */ noindex?: boolean; } function qs(selector: string): HTMLMetaElement | null { return document.querySelector(selector) as HTMLMetaElement | null; } /** * Canonical + og:url must carry a trailing slash to match the URL the server * actually serves (nginx 301-redirects the slash-less form), so the canonical * never points at a redirecting URL and project pages agree with their * sitemap entry. Query/hash are preserved. */ function withTrailingSlash(u: string): string { try { const parsed = new URL(u, 'https://velxio.dev'); if (!parsed.pathname.endsWith('/')) parsed.pathname += '/'; return parsed.toString(); } catch { return u.endsWith('/') ? u : `${u}/`; } } /** * Updates document.title, meta description, OG/Twitter tags, and canonical * to reflect the current page. Restores originals on unmount. * * jsonLd (if provided) is injected as a