diff --git a/app/scripts/generate-semantic-pages.mjs b/app/scripts/generate-semantic-pages.mjs
new file mode 100644
index 0000000..dc32a0a
--- /dev/null
+++ b/app/scripts/generate-semantic-pages.mjs
@@ -0,0 +1,440 @@
+import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
+import { resolve } from 'node:path';
+import { TeknavData } from '../teknav-data.js';
+import { renderStaticFallback, replaceStaticFallback } from './static-fallback.mjs';
+
+const SITE_URL = (process.env.TEKNAV_SITE_URL || process.env.VITE_SITE_URL || 'https://www.teknav.ir').replace(/\/$/, '');
+const SITE_NAME = 'تکناو';
+const DEFAULT_IMAGE = `${SITE_URL}/images/og/default.jpg`;
+const DEFAULT_KEYWORDS = 'تکناو, فناوری, هوش مصنوعی, علم داده, امنیت سایبری, نرم افزار, سخت افزار, استارتاپ, آینده فناوری, تحلیل تکنولوژی';
+
+const template = readFileSync(resolve('dist/index.html'), 'utf8');
+
+function esc(value) {
+ return String(value ?? '')
+ .replace(/&/g, '&')
+ .replace(/"/g, '"')
+ .replace(//g, '>');
+}
+
+function clean(value) {
+ return String(value ?? '').replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
+}
+
+function truncate(value, max = 155) {
+ const text = clean(value);
+ return text.length > max ? `${text.slice(0, max - 1).trim()}…` : text;
+}
+
+const org = {
+ '@type': ['Organization', 'NewsMediaOrganization'],
+ '@id': `${SITE_URL}/#organization`,
+ name: 'تکناو',
+ alternateName: ['Teknav', 'تکنّاو'],
+ url: SITE_URL,
+ logo: {
+ '@type': 'ImageObject',
+ '@id': `${SITE_URL}/#logo`,
+ url: `${SITE_URL}/favicon.png`,
+ width: 512,
+ height: 512,
+ },
+ image: { '@id': `${SITE_URL}/#logo` },
+};
+
+const website = {
+ '@type': 'WebSite',
+ '@id': `${SITE_URL}/#website`,
+ url: SITE_URL,
+ name: 'تکناو',
+ alternateName: 'Teknav',
+ inLanguage: 'fa-IR',
+ publisher: { '@id': `${SITE_URL}/#organization` },
+};
+
+const GLOSSARY_SAMEAS_MAP = {
+ ai: [
+ 'https://www.wikidata.org/wiki/Q11660',
+ 'https://fa.wikipedia.org/wiki/%D9%87%D9%88%D8%B4_%D9%85%D8%B5%D9%86%D9%88%D8%B5%DB%8C'
+ ],
+ llm: [
+ 'https://www.wikidata.org/wiki/Q115305900',
+ 'https://fa.wikipedia.org/wiki/%D9%85%D8%AF%D9%84_%D8%B2%D8%A8%D8%A7%D9%86%DB%8C_%D8%A8%D8%B2%D8%B1%DA%AF'
+ ],
+ rag: [
+ 'https://www.wikidata.org/wiki/Q124378129',
+ 'https://en.wikipedia.org/wiki/Retrieval-augmented_generation'
+ ],
+ 'prompt-injection': [
+ 'https://www.wikidata.org/wiki/Q117409023',
+ 'https://en.wikipedia.org/wiki/Prompt_injection'
+ ],
+ 'vector-database': [
+ 'https://www.wikidata.org/wiki/Q119790697',
+ 'https://en.wikipedia.org/wiki/Vector_database'
+ ],
+ 'agentic-ai': [
+ 'https://www.wikidata.org/wiki/Q127415174'
+ ]
+};
+
+function buildBreadcrumb(items) {
+ return {
+ '@type': 'BreadcrumbList',
+ itemListElement: items.map((item, idx) => ({
+ '@type': 'ListItem',
+ position: idx + 1,
+ name: item.name,
+ ...(item.path ? { item: `${SITE_URL}${item.path}` } : {}),
+ })),
+ };
+}
+
+let count = 0;
+
+function writePage(routePath, title, description, keywords, jsonLdGraph) {
+ const canonical = `${SITE_URL}${routePath}`;
+ const jsonLd = JSON.stringify({
+ '@context': 'https://schema.org',
+ '@graph': [
+ org,
+ website,
+ ...jsonLdGraph,
+ ]
+ });
+
+ let html = template;
+ html = html.replace(//, ``);
+ html = html.replace(/
[^<]*<\/title>/, `${title}`);
+ html = html.replace(/(/, ``);
+ html = html.replace(/`);
+ html = replaceStaticFallback(html, renderStaticFallback({
+ eyebrow: 'تکناو',
+ title,
+ description,
+ facts: [
+ { label: 'زبان', value: 'فارسی' },
+ { label: 'نشانی canonical', value: canonical },
+ ],
+ links: [
+ { href: '/', label: SITE_NAME, text: 'صفحه اصلی' },
+ { href: '/articles', label: 'مقالههای فناوری', text: 'آرشیو تحلیلها' },
+ { href: '/authors', label: 'نویسندگان تکناو', text: 'پروفایل نویسندگان' },
+ ],
+ }));
+
+ // Normalize path format for directory creation
+ const relativeDir = routePath.startsWith('/') ? routePath.slice(1) : routePath;
+ const localDir = resolve(`dist/${relativeDir}`);
+ mkdirSync(localDir, { recursive: true });
+ writeFileSync(`${localDir}/index.html`, html, 'utf8');
+ count++;
+}
+
+// 1. Generate Categories
+for (const category of TeknavData.categories ?? []) {
+ const name = category.name;
+ const description = category.description || `مقالهها و تحلیلهای فارسی تکناو درباره ${name}.`;
+ const path = `/category/${category.slug}`;
+ const title = `${name} | مقالهها و تحلیلهای تکناو`;
+ const keywords = `${name}, مقاله ${name}, تحلیل ${name}, فناوری, تکناو`;
+
+ const jsonLdGraph = [
+ {
+ '@type': 'CollectionPage',
+ name,
+ description,
+ url: `${SITE_URL}${path}`,
+ inLanguage: 'fa-IR',
+ },
+ buildBreadcrumb([
+ { name: SITE_NAME, path: '/' },
+ { name }
+ ]),
+ ];
+
+ writePage(path, title, description, keywords, jsonLdGraph);
+}
+
+// 2. Generate Tags
+for (const tag of TeknavData.tags ?? []) {
+ const name = tag.name;
+ const path = `/tag/${encodeURIComponent(name)}`;
+ const title = `برچسب ${name} | مقالههای تکنـاو`;
+ const description = `همه مقالههای تکنـاو درباره ${name}، همراه با تحلیل فارسی، داده و مسیرهای مرتبط برای مطالعه بیشتر.`;
+ const keywords = `${name}, تکنـاو, مقاله فناوری, تحلیل فناوری`;
+
+ const jsonLdGraph = [
+ {
+ '@type': 'CollectionPage',
+ name: `برچسب ${name}`,
+ url: `${SITE_URL}${path}`,
+ inLanguage: 'fa-IR',
+ },
+ buildBreadcrumb([
+ { name: SITE_NAME, path: '/' },
+ { name: 'برچسبها', path: '/articles' },
+ { name }
+ ]),
+ ];
+
+ writePage(path, title, description, keywords, jsonLdGraph);
+}
+
+// 3. Generate Glossary
+for (const term of TeknavData.glossary ?? []) {
+ const slug = term.slug;
+ const path = `/glossary/${slug}`;
+ const title = `${term.term} چیست؟ | فرهنگ واژگان تکنّاو`;
+ const description = truncate(term.summary, 155);
+ const keywords = `${term.term}, ${term.english || ''}, فرهنگ واژگان فناوری, تکنّاو`;
+ const sameAs = GLOSSARY_SAMEAS_MAP[slug] || [];
+
+ const jsonLdGraph = [
+ {
+ '@type': 'DefinedTerm',
+ name: term.term,
+ alternateName: term.english,
+ description,
+ inDefinedTermSet: `${SITE_URL}/glossary`,
+ url: `${SITE_URL}${path}`,
+ sameAs,
+ },
+ term.faqs?.length ? {
+ '@type': 'FAQPage',
+ mainEntity: term.faqs.map(([q, a]) => ({
+ '@type': 'Question',
+ name: q,
+ acceptedAnswer: { '@type': 'Answer', text: a },
+ })),
+ } : null,
+ buildBreadcrumb([
+ { name: SITE_NAME, path: '/' },
+ { name: 'فرهنگ واژگان', path: '/articles' },
+ { name: term.term }
+ ]),
+ ].filter(Boolean);
+
+ writePage(path, title, description, keywords, jsonLdGraph);
+}
+
+// 4. Generate Topics
+for (const topic of TeknavData.topicHubs ?? []) {
+ const slug = topic.slug;
+ const path = `/topics/${slug}`;
+ const name = topic.title;
+ const description = topic.description || `مقالهها و تحلیلهای تکناو درباره ${name}.`;
+ const title = topic.seoTitle || `${name} | خوشه موضوعی تکناو`;
+ const keywords = (topic.keywords ?? [name, 'تکناو', 'تحلیل فناوری']).join(', ');
+
+ const jsonLdGraph = [
+ {
+ '@type': 'CollectionPage',
+ name,
+ description,
+ url: `${SITE_URL}${path}`,
+ inLanguage: 'fa-IR',
+ isPartOf: { '@id': `${SITE_URL}/#website` },
+ },
+ topic.faqs?.length ? {
+ '@type': 'FAQPage',
+ mainEntity: topic.faqs.map(([q, a]) => ({
+ '@type': 'Question',
+ name: q,
+ acceptedAnswer: { '@type': 'Answer', text: a },
+ })),
+ } : null,
+ buildBreadcrumb([
+ { name: SITE_NAME, path: '/' },
+ { name: 'موضوعات', path: '/articles' },
+ { name }
+ ]),
+ ].filter(Boolean);
+
+ writePage(path, title, description, keywords, jsonLdGraph);
+}
+
+// 5. Generate Series Pages
+for (const series of TeknavData.articleSeries ?? []) {
+ const slug = series.slug;
+ const path = `/series/${slug}`;
+ const name = series.title;
+ const description = truncate(series.description || 'سری مقالههای تکناو برای دنبال کردن یک موضوع فناوری بهترتیب تحریریه.', 155);
+ const title = `${name} | سری مقالههای تکناو`;
+ const keywords = `${name}, سری مقاله, تکناو, تحلیل فناوری`;
+
+ const jsonLdGraph = [
+ {
+ '@type': 'CollectionPage',
+ name,
+ description,
+ url: `${SITE_URL}${path}`,
+ inLanguage: 'fa-IR',
+ },
+ buildBreadcrumb([
+ { name: SITE_NAME, path: '/' },
+ { name: 'سری مقالهها', path: '/articles' },
+ { name }
+ ]),
+ ];
+
+ writePage(path, title, description, keywords, jsonLdGraph);
+}
+
+// 6. Generate Static / Core Pages
+const staticPages = [
+ {
+ path: '/articles',
+ title: 'مقالههای فناوری | تکناو',
+ description: 'آرشیو مقالههای فارسی تکناو درباره هوش مصنوعی، علم داده، امنیت سایبری، نرمافزار، سختافزار و آینده فناوری.',
+ keywords: DEFAULT_KEYWORDS,
+ jsonLdGraph: [{ '@type': 'CollectionPage', name: 'مقالههای فناوری تکناو', url: `${SITE_URL}/articles`, inLanguage: 'fa-IR' }]
+ },
+ {
+ path: '/authors',
+ title: 'نویسندگان تکناو | تحلیلگران فناوری',
+ description: 'آشنایی با نویسندگان و تحلیلگران تکناو در حوزه هوش مصنوعی، امنیت سایبری، نرمافزار، سختافزار و استارتاپ.',
+ keywords: 'نویسندگان تکناو, تحلیلگر فناوری, نویسنده هوش مصنوعی, نویسنده امنیت سایبری',
+ jsonLdGraph: [{ '@type': 'CollectionPage', name: 'نویسندگان تکناو', url: `${SITE_URL}/authors`, inLanguage: 'fa-IR' }]
+ },
+ {
+ path: '/newsletter',
+ title: 'آرشیو خبرنامه تکنـاو | تحلیل هفتگی فناوری',
+ description: 'آرشیو خبرنامههای ارسالشده تکنـاو درباره هوش مصنوعی، امنیت سایبری، نرمافزار، سختافزار، استارتاپ و داده.',
+ keywords: 'خبرنامه تکنـاو, آرشیو خبرنامه فناوری, تحلیل هفتگی فناوری, هوش مصنوعی, امنیت سایبری',
+ jsonLdGraph: [
+ { '@type': 'CollectionPage', name: 'آرشیو خبرنامه تکنـاو', url: `${SITE_URL}/newsletter`, inLanguage: 'fa-IR' },
+ buildBreadcrumb([{ name: SITE_NAME, path: '/' }, { name: 'خبرنامه' }])
+ ]
+ },
+ {
+ path: '/jobs',
+ title: 'فرصتهای شغلی فناوری | تکناو',
+ description: 'آگهیهای شغلی منتخب فناوری و امکان ثبت موقعیت شغلی شرکتها در تکناو.',
+ keywords: 'شغل فناوری, استخدام برنامه نویس, استخدام هوش مصنوعی, تکناو جابز',
+ jsonLdGraph: [
+ { '@type': 'CollectionPage', name: 'فرصتهای شغلی فناوری | تکناو', url: `${SITE_URL}/jobs`, inLanguage: 'fa-IR', description: 'آگهیهای شغلی منتخب فناوری و امکان ثبت موقعیت شغلی شرکتها در تکناو.' },
+ buildBreadcrumb([{ name: SITE_NAME, path: '/' }, { name: 'فرصتهای شغلی فناوری' }])
+ ]
+ },
+ {
+ path: '/courses',
+ title: 'دورههای تخصصی فناوری | تکناو',
+ description: 'دورههای فارسی تکناو برای هوش مصنوعی، امنیت، نرمافزار و مهارتهای فنی محصولمحور.',
+ keywords: 'دوره فناوری, آموزش هوش مصنوعی, آموزش امنیت, دوره فارسی برنامه نویسی',
+ jsonLdGraph: [
+ { '@type': 'CollectionPage', name: 'دورههای تخصصی فناوری | تکناو', url: `${SITE_URL}/courses`, inLanguage: 'fa-IR', description: 'دورههای فارسی تکناو برای هوش مصنوعی، امنیت، نرمافزار و مهارتهای فنی محصولمحور.' },
+ buildBreadcrumb([{ name: SITE_NAME, path: '/' }, { name: 'دورههای تخصصی فناوری' }])
+ ]
+ },
+ {
+ path: '/membership',
+ title: 'عضویت پریمیوم تکناو | محتوای اختصاصی فناوری',
+ description: 'عضویت پریمیوم تکناو برای تجربه بدون تبلیغ، محتوای اختصاصی و دسترسی بهتر به دورهها و گزارشهای تخصصی.',
+ keywords: 'عضویت تکناو, محتوای پریمیوم فناوری, تحلیل اختصاصی فناوری',
+ jsonLdGraph: [
+ { '@type': 'CollectionPage', name: 'عضویت پریمیوم تکناو | محتوای اختصاصی فناوری', url: `${SITE_URL}/membership`, inLanguage: 'fa-IR', description: 'عضویت پریمیوم تکناو برای تجربه بدون تبلیغ، محتوای اختصاصی و دسترسی بهتر به دورهها و گزارشهای تخصصی.' },
+ buildBreadcrumb([{ name: SITE_NAME, path: '/' }, { name: 'عضویت پریمیوم تکناو' }])
+ ]
+ },
+ {
+ path: '/about',
+ title: 'درباره ما | تکناو',
+ description: 'درباره رسانه فارسی تکناو و ماموریت ما در تحلیل فناوری، هوش مصنوعی و امنیت سایبری.',
+ keywords: 'درباره تکناو, ماموریت تکناو, رسانه فناوری, تحلیل هوش مصنوعی',
+ jsonLdGraph: [
+ { '@type': 'WebPage', '@id': `${SITE_URL}/about#webpage`, name: 'درباره ما | تکناو', description: 'درباره رسانه فارسی تکناو و ماموریت ما در تحلیل فناوری، هوش مصنوعی و امنیت سایبری.', url: `${SITE_URL}/about`, inLanguage: 'fa-IR' },
+ buildBreadcrumb([{ name: SITE_NAME, path: '/' }, { name: 'درباره ما' }])
+ ]
+ },
+ {
+ path: '/contact',
+ title: 'تماس با ما | تکناو',
+ description: 'ارتباط با تیم تحریریه و پشتیبانی تکناو برای نظرات، پیشنهادات و همکاری.',
+ keywords: 'تماس با تکناو, پشتیبانی تکناو, همکاری با تکناو, تبلیغات تکناو',
+ jsonLdGraph: [
+ { '@type': 'WebPage', '@id': `${SITE_URL}/contact#webpage`, name: 'تماس با ما | تکناو', description: 'ارتباط با تیم تحریریه و پشتیبانی تکناو برای نظرات، پیشنهادات و همکاری.', url: `${SITE_URL}/contact`, inLanguage: 'fa-IR' },
+ buildBreadcrumb([{ name: SITE_NAME, path: '/' }, { name: 'تماس با ما' }])
+ ]
+ },
+ {
+ path: '/terms',
+ title: 'قوانین و مقررات | تکناو',
+ description: 'شرایط استفاده و قوانین و مقررات استفاده از محتوا و خدمات رسانه تکناو.',
+ keywords: 'قوانین تکناو, مقررات تکناو, شرایط استفاده تکناو',
+ jsonLdGraph: [
+ { '@type': 'WebPage', '@id': `${SITE_URL}/terms#webpage`, name: 'قوانین و مقررات | تکناو', description: 'شرایط استفاده و قوانین و مقررات استفاده از محتوا و خدمات رسانه تکناو.', url: `${SITE_URL}/terms`, inLanguage: 'fa-IR' },
+ buildBreadcrumb([{ name: SITE_NAME, path: '/' }, { name: 'قوانین و مقررات' }])
+ ]
+ },
+ {
+ path: '/privacy',
+ title: 'حریم خصوصی | تکناو',
+ description: 'سیاست حریم خصوصی و نحوه حفاظت از اطلاعات کاربران در رسانه تکناو.',
+ keywords: 'حریم خصوصی تکناو, سیاست حریم خصوصی, امنیت اطلاعات کاربران',
+ jsonLdGraph: [
+ { '@type': 'WebPage', '@id': `${SITE_URL}/privacy#webpage`, name: 'حریم خصوصی | تکناو', description: 'سیاست حریم خصوصی و نحوه حفاظت از اطلاعات کاربران در رسانه تکناو.', url: `${SITE_URL}/privacy`, inLanguage: 'fa-IR' },
+ buildBreadcrumb([{ name: SITE_NAME, path: '/' }, { name: 'حریم خصوصی' }])
+ ]
+ },
+ {
+ path: '/tools',
+ title: 'ابزارهای تخصصی فناوری | تکناو',
+ description: 'جعبهابزار تخصصی تکناو برای توسعهدهندگان، مهندسان داده و تحلیلگران؛ شامل نرمالساز متن و ماشینحساب RAG.',
+ keywords: 'ابزار توسعه دهنده, نرمال ساز متن, ماشین حساب RAG, جعبه ابزار تکناو',
+ jsonLdGraph: [
+ { '@type': 'CollectionPage', name: 'ابزارهای تخصصی فناوری | تکناو', url: `${SITE_URL}/tools`, inLanguage: 'fa-IR', description: 'جعبهابزار تخصصی تکناو برای توسعهدهندگان، مهندسان داده و تحلیلگران؛ شامل نرمالساز متن و ماشینحساب RAG.' },
+ buildBreadcrumb([{ name: SITE_NAME, path: '/' }, { name: 'ابزارها' }])
+ ]
+ },
+ {
+ path: '/tools/nlp-normalizer',
+ title: 'نرمالساز متن فارسی (NLP) | ابزارهای تکناو',
+ description: 'ابزار تخصصی پاکسازی، نیمفاصلهگذاری و پیشپردازش متن فارسی برای تسکهای پردازش زبان طبیعی و مدلهای زبانی.',
+ keywords: 'نرمال ساز متن فارسی, نیم فاصله گذاری, پیش پردازش متن فارسی, NLP فارسی',
+ jsonLdGraph: [
+ { '@type': 'WebPage', '@id': `${SITE_URL}/tools/nlp-normalizer#webpage`, name: 'نرمالساز متن فارسی (NLP) | ابزارهای تکناو', description: 'ابزار تخصصی پاکسازی، نیمفاصلهگذاری و پیشپردازش متن فارسی برای تسکهای پردازش زبان طبیعی و مدلهای زبانی.', url: `${SITE_URL}/tools/nlp-normalizer`, inLanguage: 'fa-IR' },
+ buildBreadcrumb([{ name: SITE_NAME, path: '/' }, { name: 'ابزارها', path: '/tools' }, { name: 'نرمالساز متن فارسی' }])
+ ]
+ },
+ {
+ path: '/tools/rag-calculator',
+ title: 'ماشینحساب ظرفیت و هزینه RAG | ابزارهای تکناو',
+ description: 'ماشینحساب تخصصی محاسبه تعداد توکن، فضای پایگاهداده برداری، هزینههای استنتاج LLM و پهنای باند معماری RAG.',
+ keywords: 'هزینه RAG, ماشین حساب RAG, پهنای باند RAG, پایگاه داده برداری',
+ jsonLdGraph: [
+ { '@type': 'WebPage', '@id': `${SITE_URL}/tools/rag-calculator#webpage`, name: 'ماشینحساب ظرفیت و هزینه RAG | ابزارهای تکناو', description: 'ماشینحساب تخصصی محاسبه تعداد توکن، فضای پایگاهداده برداری، هزینههای استنتاج LLM و پهنای باند معماری RAG.', url: `${SITE_URL}/tools/rag-calculator`, inLanguage: 'fa-IR' },
+ buildBreadcrumb([{ name: SITE_NAME, path: '/' }, { name: 'ابزارها', path: '/tools' }, { name: 'ماشینحساب RAG' }])
+ ]
+ },
+ {
+ path: '/tekrag-persian',
+ title: 'بنچمارک TekRag | ارزیابی سامانههای RAG فارسی',
+ description: 'درگاه رسمی بنچمارک TekRag؛ اولین فریمورک استاندارد ارزیابی دقت، بازیابی و پاسخدهی سیستمهای RAG زبان فارسی.',
+ keywords: 'TekRag benchmark, بنچمارک RAG فارسی, ارزیابی سیستم RAG, هوش مصنوعی فارسی',
+ jsonLdGraph: [
+ { '@type': 'WebPage', '@id': `${SITE_URL}/tekrag-persian#webpage`, name: 'بنچمارک TekRag | ارزیابی سامانههای RAG فارسی', description: 'درگاه رسمی بنچمارک TekRag؛ اولین فریمورک استاندارد ارزیابی دقت، بازیابی و پاسخدهی سیستمهای RAG زبان فارسی.', url: `${SITE_URL}/tekrag-persian`, inLanguage: 'fa-IR' },
+ buildBreadcrumb([{ name: SITE_NAME, path: '/' }, { name: 'بنچمارک TekRag' }])
+ ]
+ }
+];
+
+for (const pg of staticPages) {
+ writePage(pg.path, pg.title, pg.description, pg.keywords, pg.jsonLdGraph);
+}
+
+console.log(`[semantic-pages] generated ${count} static semantic HTML pages`);