diff --git a/public/dna-social-card.svg b/public/dna-social-card.svg new file mode 100644 index 0000000..90a7041 --- /dev/null +++ b/public/dna-social-card.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + SUEDE / DNA + ARCHIVE RECORD 001 + + + + SIGNAL CHAINS, + ARCHIVED. + + + + + + RIG DIAGRAMS · SOURCED RECORDS · VERIFIED LINEAGE + dna.suedeai.ai + + diff --git a/public/dna-social-card.webp b/public/dna-social-card.webp new file mode 100644 index 0000000..30d5bbf Binary files /dev/null and b/public/dna-social-card.webp differ diff --git a/src/app/articles/building-a-tone-over-a-career/page.tsx b/src/app/articles/building-a-tone-over-a-career/page.tsx index 5d2c5cb..545d9ec 100644 --- a/src/app/articles/building-a-tone-over-a-career/page.tsx +++ b/src/app/articles/building-a-tone-over-a-career/page.tsx @@ -1,6 +1,7 @@ import type { Metadata } from 'next'; import { PageShell, DocSection, SuedeLink } from '@/components/docs/PageShell'; import { getArticle } from '@/lib/articles-content'; +import { DNA_SOCIAL_IMAGE } from '@/lib/seo/social-metadata'; const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? 'https://dna.suedeai.ai'; const SLUG = 'building-a-tone-over-a-career'; @@ -17,11 +18,13 @@ export const metadata: Metadata = { siteName: 'Suede DNA', type: 'article', publishedTime: article.date, + images: [DNA_SOCIAL_IMAGE], }, twitter: { card: 'summary_large_image', title: `${article.title} — Suede DNA`, description: article.description, + images: [DNA_SOCIAL_IMAGE.url], }, }; diff --git a/src/app/articles/page.tsx b/src/app/articles/page.tsx index d07bdb9..09dd9e1 100644 --- a/src/app/articles/page.tsx +++ b/src/app/articles/page.tsx @@ -1,6 +1,7 @@ import type { Metadata } from 'next'; import Link from 'next/link'; import { ARTICLES } from '@/lib/articles-content'; +import { DNA_SOCIAL_IMAGE } from '@/lib/seo/social-metadata'; const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? 'https://dna.suedeai.ai'; @@ -16,12 +17,14 @@ export const metadata: Metadata = { url: `${SITE_URL}/articles`, siteName: 'Suede DNA', type: 'website', + images: [DNA_SOCIAL_IMAGE], }, twitter: { card: 'summary_large_image', title: 'Articles — Suede DNA', description: 'Essays on rig history, tone genealogy, and the craft of the signal chain — from the Suede DNA archive.', + images: [DNA_SOCIAL_IMAGE.url], }, }; diff --git a/src/app/articles/rig-archaeology-and-the-ear-trained-player/page.tsx b/src/app/articles/rig-archaeology-and-the-ear-trained-player/page.tsx index d9220df..ffbe513 100644 --- a/src/app/articles/rig-archaeology-and-the-ear-trained-player/page.tsx +++ b/src/app/articles/rig-archaeology-and-the-ear-trained-player/page.tsx @@ -1,6 +1,7 @@ import type { Metadata } from 'next'; import { PageShell, DocSection, SuedeLink } from '@/components/docs/PageShell'; import { getArticle } from '@/lib/articles-content'; +import { DNA_SOCIAL_IMAGE } from '@/lib/seo/social-metadata'; const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? 'https://dna.suedeai.ai'; const SLUG = 'rig-archaeology-and-the-ear-trained-player'; @@ -17,11 +18,13 @@ export const metadata: Metadata = { siteName: 'Suede DNA', type: 'article', publishedTime: article.date, + images: [DNA_SOCIAL_IMAGE], }, twitter: { card: 'summary_large_image', title: `${article.title} — Suede DNA`, description: article.description, + images: [DNA_SOCIAL_IMAGE.url], }, }; diff --git a/src/app/articles/shred-signal-and-the-virtuoso-rig/page.tsx b/src/app/articles/shred-signal-and-the-virtuoso-rig/page.tsx index 4ed049b..bcea988 100644 --- a/src/app/articles/shred-signal-and-the-virtuoso-rig/page.tsx +++ b/src/app/articles/shred-signal-and-the-virtuoso-rig/page.tsx @@ -1,6 +1,7 @@ import type { Metadata } from 'next'; import { PageShell, DocSection, SuedeLink } from '@/components/docs/PageShell'; import { getArticle } from '@/lib/articles-content'; +import { DNA_SOCIAL_IMAGE } from '@/lib/seo/social-metadata'; const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? 'https://dna.suedeai.ai'; const SLUG = 'shred-signal-and-the-virtuoso-rig'; @@ -17,11 +18,13 @@ export const metadata: Metadata = { siteName: 'Suede DNA', type: 'article', publishedTime: article.date, + images: [DNA_SOCIAL_IMAGE], }, twitter: { card: 'summary_large_image', title: `${article.title} — Suede DNA`, description: article.description, + images: [DNA_SOCIAL_IMAGE.url], }, }; diff --git a/src/app/articles/the-guitar-rig-diagram-as-a-documentary-form/page.tsx b/src/app/articles/the-guitar-rig-diagram-as-a-documentary-form/page.tsx index bfa4bab..c21e221 100644 --- a/src/app/articles/the-guitar-rig-diagram-as-a-documentary-form/page.tsx +++ b/src/app/articles/the-guitar-rig-diagram-as-a-documentary-form/page.tsx @@ -1,6 +1,7 @@ import type { Metadata } from 'next'; import { PageShell, DocSection, SuedeLink } from '@/components/docs/PageShell'; import { getArticle } from '@/lib/articles-content'; +import { DNA_SOCIAL_IMAGE } from '@/lib/seo/social-metadata'; const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? 'https://dna.suedeai.ai'; const SLUG = 'the-guitar-rig-diagram-as-a-documentary-form'; @@ -17,11 +18,13 @@ export const metadata: Metadata = { siteName: 'Suede DNA', type: 'article', publishedTime: article.date, + images: [DNA_SOCIAL_IMAGE], }, twitter: { card: 'summary_large_image', title: `${article.title} — Suede DNA`, description: article.description, + images: [DNA_SOCIAL_IMAGE.url], }, }; diff --git a/src/app/articles/the-unsung-link/page.tsx b/src/app/articles/the-unsung-link/page.tsx index faf66d6..c721353 100644 --- a/src/app/articles/the-unsung-link/page.tsx +++ b/src/app/articles/the-unsung-link/page.tsx @@ -1,6 +1,7 @@ import type { Metadata } from 'next'; import { PageShell, DocSection, SuedeLink } from '@/components/docs/PageShell'; import { getArticle } from '@/lib/articles-content'; +import { DNA_SOCIAL_IMAGE } from '@/lib/seo/social-metadata'; const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? 'https://dna.suedeai.ai'; const SLUG = 'the-unsung-link'; @@ -17,11 +18,13 @@ export const metadata: Metadata = { siteName: 'Suede DNA', type: 'article', publishedTime: article.date, + images: [DNA_SOCIAL_IMAGE], }, twitter: { card: 'summary_large_image', title: `${article.title} — Suede DNA`, description: article.description, + images: [DNA_SOCIAL_IMAGE.url], }, }; diff --git a/src/app/docs/faq/page.tsx b/src/app/docs/faq/page.tsx index a4e2b6d..5b123bc 100644 --- a/src/app/docs/faq/page.tsx +++ b/src/app/docs/faq/page.tsx @@ -2,6 +2,7 @@ import type { Metadata } from 'next'; import { PageShell, DocSection, SuedeLink } from '@/components/docs/PageShell'; import { getStats } from '@/lib/manifest'; import { JsonLd } from '@/components/seo/JsonLd'; +import { DNA_SOCIAL_IMAGE } from '@/lib/seo/social-metadata'; const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? 'https://dna.suedeai.ai'; const PATH = '/docs/faq'; @@ -18,12 +19,14 @@ export const metadata: Metadata = { url: `${SITE_URL}${PATH}`, siteName: 'Suede DNA', type: 'article', + images: [DNA_SOCIAL_IMAGE], }, twitter: { card: 'summary_large_image', title: 'FAQ — Suede DNA Docs', description: 'Common questions about coverage, corrections, image rights, and how to reach the archive if something is wrong.', + images: [DNA_SOCIAL_IMAGE.url], }, }; diff --git a/src/app/docs/page.tsx b/src/app/docs/page.tsx index 0ae38a5..df011bf 100644 --- a/src/app/docs/page.tsx +++ b/src/app/docs/page.tsx @@ -2,6 +2,7 @@ import type { Metadata } from 'next'; import Link from 'next/link'; import { DOCS } from '@/lib/docs-content'; import { IndexCard } from '@/components/docs/IndexCard'; +import { DNA_SOCIAL_IMAGE } from '@/lib/seo/social-metadata'; const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? 'https://dna.suedeai.ai'; @@ -17,12 +18,14 @@ export const metadata: Metadata = { url: `${SITE_URL}/docs`, siteName: 'Suede DNA', type: 'website', + images: [DNA_SOCIAL_IMAGE], }, twitter: { card: 'summary_large_image', title: 'Docs — Suede DNA', description: 'How Suede DNA is built: sourcing, the manifest pipeline, search syntax, and answers to common questions about the archive.', + images: [DNA_SOCIAL_IMAGE.url], }, }; diff --git a/src/app/docs/search-and-filters/page.tsx b/src/app/docs/search-and-filters/page.tsx index 8e752d0..5ba93f4 100644 --- a/src/app/docs/search-and-filters/page.tsx +++ b/src/app/docs/search-and-filters/page.tsx @@ -1,5 +1,6 @@ import type { Metadata } from 'next'; import { PageShell, DocSection } from '@/components/docs/PageShell'; +import { DNA_SOCIAL_IMAGE } from '@/lib/seo/social-metadata'; const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? 'https://dna.suedeai.ai'; const PATH = '/docs/search-and-filters'; @@ -16,12 +17,14 @@ export const metadata: Metadata = { url: `${SITE_URL}${PATH}`, siteName: 'Suede DNA', type: 'article', + images: [DNA_SOCIAL_IMAGE], }, twitter: { card: 'summary_large_image', title: 'Search and Filters — Suede DNA Docs', description: 'How the search box parses names, years, and decades, how ranking works, and how decade filters and sort order combine.', + images: [DNA_SOCIAL_IMAGE.url], }, }; diff --git a/src/app/docs/sourcing-and-verification/page.tsx b/src/app/docs/sourcing-and-verification/page.tsx index db85240..ed1aa63 100644 --- a/src/app/docs/sourcing-and-verification/page.tsx +++ b/src/app/docs/sourcing-and-verification/page.tsx @@ -1,5 +1,6 @@ import type { Metadata } from 'next'; import { PageShell, DocSection, SuedeLink } from '@/components/docs/PageShell'; +import { DNA_SOCIAL_IMAGE } from '@/lib/seo/social-metadata'; const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? 'https://dna.suedeai.ai'; const PATH = '/docs/sourcing-and-verification'; @@ -16,12 +17,14 @@ export const metadata: Metadata = { url: `${SITE_URL}${PATH}`, siteName: 'Suede DNA', type: 'article', + images: [DNA_SOCIAL_IMAGE], }, twitter: { card: 'summary_large_image', title: 'Sourcing and Verification — Suede DNA Docs', description: 'Where the rig photos come from, how the manifest is built and validated, and exactly what Suede DNA does and does not verify.', + images: [DNA_SOCIAL_IMAGE.url], }, }; diff --git a/src/app/docs/what-is-suede-dna/page.tsx b/src/app/docs/what-is-suede-dna/page.tsx index 626922e..b98b48a 100644 --- a/src/app/docs/what-is-suede-dna/page.tsx +++ b/src/app/docs/what-is-suede-dna/page.tsx @@ -1,6 +1,7 @@ import type { Metadata } from 'next'; import { PageShell, DocSection, SuedeLink } from '@/components/docs/PageShell'; import { getStats } from '@/lib/manifest'; +import { DNA_SOCIAL_IMAGE } from '@/lib/seo/social-metadata'; const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? 'https://dna.suedeai.ai'; const PATH = '/docs/what-is-suede-dna'; @@ -17,12 +18,14 @@ export const metadata: Metadata = { url: `${SITE_URL}${PATH}`, siteName: 'Suede DNA', type: 'article', + images: [DNA_SOCIAL_IMAGE], }, twitter: { card: 'summary_large_image', title: 'What Suede DNA Is — Suede DNA Docs', description: 'The premise behind the archive: why a guitarist’s rigs form a signal chain across time, and how the site is structured around that idea.', + images: [DNA_SOCIAL_IMAGE.url], }, }; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index caebd1b..108fe5f 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -5,6 +5,7 @@ import { Header } from '@/components/header/Header'; import { Footer } from '@/components/footer/Footer'; import { GridMotif } from '@/components/chrome/GridMotif'; import { getStats } from '@/lib/manifest'; +import { DNA_SOCIAL_IMAGE } from '@/lib/seo/social-metadata'; import './globals.css'; const stats = getStats(); @@ -42,14 +43,7 @@ export const metadata: Metadata = { siteName: 'Suede DNA', title: 'Suede DNA — Signal Chains, Archived', description: defaultDescription, - images: [ - { - url: '/opengraph-image', - width: 1200, - height: 630, - alt: 'Suede DNA — a compilation archive of guitarists\' rigs and signal chains', - }, - ], + images: [DNA_SOCIAL_IMAGE], }, twitter: { card: 'summary_large_image', @@ -57,7 +51,7 @@ export const metadata: Metadata = { description: defaultDescription, site: '@AISUEDE', creator: '@johnnysuede', - images: ['/opengraph-image'], + images: [DNA_SOCIAL_IMAGE.url], }, robots: { index: true, diff --git a/src/lib/seo/social-metadata.ts b/src/lib/seo/social-metadata.ts new file mode 100644 index 0000000..4d4979b --- /dev/null +++ b/src/lib/seo/social-metadata.ts @@ -0,0 +1,6 @@ +export const DNA_SOCIAL_IMAGE = { + url: '/dna-social-card.webp', + width: 1200, + height: 630, + alt: 'Suede DNA — Signal Chains, Archived', +}; diff --git a/tests/lib/social-metadata.test.ts b/tests/lib/social-metadata.test.ts new file mode 100644 index 0000000..af493b8 --- /dev/null +++ b/tests/lib/social-metadata.test.ts @@ -0,0 +1,37 @@ +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { describe, expect, it } from 'vitest'; +import { DNA_SOCIAL_IMAGE } from '@/lib/seo/social-metadata'; + +const METADATA_PAGES = [ + 'src/app/docs/page.tsx', + 'src/app/docs/search-and-filters/page.tsx', + 'src/app/docs/what-is-suede-dna/page.tsx', + 'src/app/docs/sourcing-and-verification/page.tsx', + 'src/app/docs/faq/page.tsx', + 'src/app/articles/page.tsx', + 'src/app/articles/rig-archaeology-and-the-ear-trained-player/page.tsx', + 'src/app/articles/the-unsung-link/page.tsx', + 'src/app/articles/shred-signal-and-the-virtuoso-rig/page.tsx', + 'src/app/articles/building-a-tone-over-a-career/page.tsx', + 'src/app/articles/the-guitar-rig-diagram-as-a-documentary-form/page.tsx', +] as const; + +describe('Suede DNA social metadata', () => { + it('defines a share card at the required dimensions', () => { + expect(DNA_SOCIAL_IMAGE).toMatchObject({ + url: '/dna-social-card.webp', + width: 1200, + height: 630, + }); + }); + + it.each(METADATA_PAGES)('%s keeps the shared card when metadata is replaced', (path) => { + const source = readFileSync(resolve(process.cwd(), path), 'utf8'); + + expect(source).toContain("from '@/lib/seo/social-metadata'"); + expect(source).toContain('images: [DNA_SOCIAL_IMAGE]'); + expect(source).toContain("card: 'summary_large_image'"); + expect(source).toContain('images: [DNA_SOCIAL_IMAGE.url]'); + }); +});