diff --git a/frontend/src/lib/components/RichtlinienRuleCard.svelte b/frontend/src/lib/components/RichtlinienRuleCard.svelte
new file mode 100644
index 00000000..cbf9b163
--- /dev/null
+++ b/frontend/src/lib/components/RichtlinienRuleCard.svelte
@@ -0,0 +1,30 @@
+
+
+
+
+ {icon}
+
{title}
+
+
{body}
+
+ {#if beispielOutput !== undefined}
+
+
+ {beispielLabel}
+
+
+ → {beispielOutput}
+
+
+ {/if}
+
diff --git a/frontend/src/lib/components/RichtlinienRuleCard.svelte.spec.ts b/frontend/src/lib/components/RichtlinienRuleCard.svelte.spec.ts
new file mode 100644
index 00000000..55852c1b
--- /dev/null
+++ b/frontend/src/lib/components/RichtlinienRuleCard.svelte.spec.ts
@@ -0,0 +1,49 @@
+import { describe, it, expect, afterEach } from 'vitest';
+import { cleanup, render } from 'vitest-browser-svelte';
+import { page } from 'vitest/browser';
+import RichtlinienRuleCard from './RichtlinienRuleCard.svelte';
+
+afterEach(cleanup);
+
+const defaultProps = {
+ icon: '✍',
+ title: 'Unleserliche Wörter',
+ body: 'Schreiben Sie [unleserlich].',
+ beispielOutput: '[unleserlich]'
+};
+
+describe('RichtlinienRuleCard', () => {
+ it('renders an h3 with the title', async () => {
+ render(RichtlinienRuleCard, { props: defaultProps });
+ await expect
+ .element(page.getByRole('heading', { level: 3 }))
+ .toHaveTextContent('Unleserliche Wörter');
+ });
+
+ it('renders the body text', async () => {
+ render(RichtlinienRuleCard, { props: defaultProps });
+ await expect.element(page.getByText('Schreiben Sie [unleserlich].')).toBeInTheDocument();
+ });
+
+ it('renders icon in a span with aria-hidden="true"', async () => {
+ render(RichtlinienRuleCard, { props: defaultProps });
+ const iconSpan = document.querySelector('span[aria-hidden="true"]');
+ expect(iconSpan).not.toBeNull();
+ expect(iconSpan!.textContent).toContain('✍');
+ });
+
+ it('renders beispielOutput in monospace with → arrow', async () => {
+ render(RichtlinienRuleCard, { props: defaultProps });
+ const mono = document.querySelector('code, [class*="font-mono"]');
+ expect(mono).not.toBeNull();
+ expect(mono!.textContent).toContain('[unleserlich]');
+ await expect.element(page.getByText(/→/)).toBeInTheDocument();
+ });
+
+ it('does not render beispiel section when beispielOutput is absent', async () => {
+ render(RichtlinienRuleCard, {
+ props: { icon: '✍', title: 'Test', body: 'Body' }
+ });
+ expect(document.querySelector('code, [class*="font-mono"]')).toBeNull();
+ });
+});
diff --git a/frontend/src/routes/hilfe/transkription/+page.svelte b/frontend/src/routes/hilfe/transkription/+page.svelte
new file mode 100644
index 00000000..af42eb9c
--- /dev/null
+++ b/frontend/src/routes/hilfe/transkription/+page.svelte
@@ -0,0 +1,124 @@
+
+
+
+ {m.richtlinien_title()} — Familienarchiv
+
+
+
+
+
{m.richtlinien_title()}
+
+
+
{m.richtlinien_intro()}
+
+
+
+
+
+
+ {m.richtlinien_rules_label()}
+
+
+ {#each rules as rule (rule.title)}
+
+ {/each}
+
+
+
+
+ {m.richtlinien_klaerung_label()}
+
+
+ {m.richtlinien_klaerung_intro()}
+
+
+ {#each klaerungChips as chip (chip)}
+ {chip}
+ {/each}
+
+
+
+
+
{m.richtlinien_closing_title()}
+
{m.richtlinien_closing_body()}
+
+
+
+
diff --git a/frontend/src/routes/hilfe/transkription/+page.ts b/frontend/src/routes/hilfe/transkription/+page.ts
new file mode 100644
index 00000000..189f71e2
--- /dev/null
+++ b/frontend/src/routes/hilfe/transkription/+page.ts
@@ -0,0 +1 @@
+export const prerender = true;
diff --git a/frontend/src/routes/hilfe/transkription/page.svelte.spec.ts b/frontend/src/routes/hilfe/transkription/page.svelte.spec.ts
new file mode 100644
index 00000000..259b9f5c
--- /dev/null
+++ b/frontend/src/routes/hilfe/transkription/page.svelte.spec.ts
@@ -0,0 +1,72 @@
+import { describe, it, expect, afterEach } from 'vitest';
+import { cleanup, render } from 'vitest-browser-svelte';
+import { page } from 'vitest/browser';
+import Page from './+page.svelte';
+
+afterEach(cleanup);
+
+describe('Richtlinien page — structure', () => {
+ it('renders h1 with richtlinien title', async () => {
+ render(Page);
+ await expect
+ .element(page.getByRole('heading', { level: 1 }))
+ .toHaveTextContent('Transkriptions-Richtlinien');
+ });
+
+ it('renders intro paragraph', async () => {
+ render(Page);
+ await expect.element(page.getByText(/Damit alle Briefe einheitlich/)).toBeInTheDocument();
+ });
+
+ it('renders Wikipedia external link with security attributes and new-tab annotation', async () => {
+ render(Page);
+ const wikiLink = page.getByRole('link', { name: /Wikipedia/ });
+ await expect.element(wikiLink).toBeInTheDocument();
+ await expect.element(wikiLink).toHaveAttribute('target', '_blank');
+ await expect.element(wikiLink).toHaveAttribute('rel', 'noopener noreferrer');
+ await expect.element(wikiLink).toHaveAttribute('referrerpolicy', 'no-referrer');
+ // visible annotation (not sr-only)
+ const link = document.querySelector('a[href*="wikipedia"]') as HTMLAnchorElement;
+ expect(link.textContent).toContain('öffnet in neuem Tab');
+ });
+
+ it('renders Regeln h2 section', async () => {
+ render(Page);
+ await expect
+ .element(page.getByRole('heading', { level: 2, name: /Regeln für die Transkription/ }))
+ .toBeInTheDocument();
+ });
+
+ it('renders Noch in Klärung h2 section', async () => {
+ render(Page);
+ await expect
+ .element(page.getByRole('heading', { level: 2, name: /Noch in Klärung/ }))
+ .toBeInTheDocument();
+ });
+
+ it('renders closing invitation card', async () => {
+ render(Page);
+ await expect.element(page.getByText(/Fehlt eine Regel/)).toBeInTheDocument();
+ });
+});
+
+describe('Richtlinien page — rule cards', () => {
+ it('renders five rule card titles', async () => {
+ render(Page);
+ await expect.element(page.getByText('Nicht lesbare Wörter')).toBeInTheDocument();
+ await expect.element(page.getByText('Durchgestrichene Wörter')).toBeInTheDocument();
+ await expect.element(page.getByText(/Das lange s/)).toBeInTheDocument();
+ await expect.element(page.getByText('Unsichere Namen')).toBeInTheDocument();
+ await expect.element(page.getByText(/Dialekt/)).toBeInTheDocument();
+ });
+});
+
+describe('Richtlinien page — Noch in Klärung chips', () => {
+ it('renders four clarification chips', async () => {
+ render(Page);
+ await expect.element(page.getByText('Abkürzungen')).toBeInTheDocument();
+ await expect.element(page.getByText('Datumsformate')).toBeInTheDocument();
+ await expect.element(page.getByText(/Zeilenumbrüche/)).toBeInTheDocument();
+ await expect.element(page.getByText(/Groß-\/Kleinschreibung/)).toBeInTheDocument();
+ });
+});