diff --git a/src/app/api/og/static/route.tsx b/src/app/api/og/static/route.tsx
index 62eefd3..f296b70 100644
--- a/src/app/api/og/static/route.tsx
+++ b/src/app/api/og/static/route.tsx
@@ -69,6 +69,18 @@ export async function GET(request: NextRequest) {
tagline: 'Open source and ready to deploy',
showBranding: true,
},
+ extension: {
+ title: 'Browser Extension',
+ subtitle: 'Jump between Atmosphere clients in one click',
+ tagline: 'Inspect AT URIs, auto-redirect by lexicon, copy universal links.',
+ showBranding: true,
+ },
+ 'universal-links': {
+ title: 'Universal Links',
+ subtitle: 'One link, every Atmosphere client',
+ tagline: 'Share aturi.to/handle/collection/rkey — recipients pick the app.',
+ showBranding: true,
+ },
};
const content = pageContent[page] || pageContent.home;
diff --git a/src/app/extension/page.tsx b/src/app/extension/page.tsx
new file mode 100644
index 0000000..721c89a
--- /dev/null
+++ b/src/app/extension/page.tsx
@@ -0,0 +1,34 @@
+import type { Metadata } from 'next';
+import Header from '@/components/Header';
+import ExtensionLanding from '@/components/landing/ExtensionLanding';
+
+export const metadata: Metadata = {
+ title: 'Browser Extension · aturi.to',
+ description:
+ 'Jump between Atmosphere clients in one click, inspect AT URIs anywhere on the web, and auto-redirect to your preferred app per lexicon.',
+ openGraph: {
+ title: 'Browser Extension · aturi.to',
+ description:
+ 'Jump between Atmosphere clients in one click, inspect AT URIs anywhere on the web, and auto-redirect to your preferred app per lexicon.',
+ images: ['/api/og/static?page=extension'],
+ },
+ twitter: {
+ card: 'summary_large_image',
+ title: 'Browser Extension · aturi.to',
+ description:
+ 'Jump between Atmosphere clients in one click, inspect AT URIs anywhere on the web, and auto-redirect to your preferred app per lexicon.',
+ images: ['/api/og/static?page=extension'],
+ },
+};
+
+export default function ExtensionPage() {
+ return (
+
+
+
+
+ );
+}
diff --git a/src/app/globals.css b/src/app/globals.css
index 49a9255..32a9a84 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -1775,3 +1775,59 @@ html {
font-size: 1.625rem;
}
}
+
+/* ---------- Product landing pages (extension, universal links) ---------- */
+
+/* Hero block on landing pages stacks the copy + a hero visual side-by-side
+ on desktop, and stacks the visual above the copy on mobile. */
+.landing-hero {
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) !important;
+}
+
+.landing-hero > div:last-child {
+ display: flex;
+ justify-content: center;
+}
+
+@media (max-width: 820px) {
+ .landing-hero {
+ grid-template-columns: minmax(0, 1fr) !important;
+ }
+}
+
+/* Feature row: copy + visual two-column block, flipped order via
+ `data-flip`. Mirrors `.home-product-strip` but tuned for the
+ landing pages' narrower max width and tighter vertical rhythm. */
+.landing-feature {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
+ gap: 2.5rem;
+ align-items: center;
+}
+
+.landing-feature[data-flip] .landing-feature-visual {
+ order: 2;
+}
+
+.landing-feature-visual {
+ display: flex;
+ justify-content: center;
+}
+
+.landing-feature-body p {
+ margin: 0 0 0.875rem;
+}
+
+.landing-feature-body p:last-child {
+ margin-bottom: 0;
+}
+
+@media (max-width: 820px) {
+ .landing-feature {
+ grid-template-columns: minmax(0, 1fr);
+ gap: 1.5rem;
+ }
+ .landing-feature[data-flip] .landing-feature-visual {
+ order: 0;
+ }
+}
diff --git a/src/app/universal-links/page.tsx b/src/app/universal-links/page.tsx
new file mode 100644
index 0000000..6b60746
--- /dev/null
+++ b/src/app/universal-links/page.tsx
@@ -0,0 +1,34 @@
+import type { Metadata } from 'next';
+import Header from '@/components/Header';
+import UniversalLinksLanding from '@/components/landing/UniversalLinksLanding';
+
+export const metadata: Metadata = {
+ title: 'Universal Links · aturi.to',
+ description:
+ 'Share one aturi.to link and let your audience pick the Atmosphere client they want to open it in — 25+ supported clients, no lock-in, no sign-up.',
+ openGraph: {
+ title: 'Universal Links · aturi.to',
+ description:
+ 'Share one aturi.to link and let your audience pick the Atmosphere client they want to open it in — 25+ supported clients, no lock-in, no sign-up.',
+ images: ['/api/og/static?page=universal-links'],
+ },
+ twitter: {
+ card: 'summary_large_image',
+ title: 'Universal Links · aturi.to',
+ description:
+ 'Share one aturi.to link and let your audience pick the Atmosphere client they want to open it in — 25+ supported clients, no lock-in, no sign-up.',
+ images: ['/api/og/static?page=universal-links'],
+ },
+};
+
+export default function UniversalLinksPage() {
+ return (
+
+
+
+
+ );
+}
diff --git a/src/components/explore/ExploreLanding.tsx b/src/components/explore/ExploreLanding.tsx
index c08ba37..3dcb92a 100644
--- a/src/components/explore/ExploreLanding.tsx
+++ b/src/components/explore/ExploreLanding.tsx
@@ -1,10 +1,11 @@
'use client';
import Link from 'next/link';
-import { Compass, Download, Telescope } from 'lucide-react';
+import { Telescope } from 'lucide-react';
import AppearIn from './AppearIn';
import SearchBox from './SearchBox';
import JetstreamFeed from './JetstreamFeed';
+import CrossLinkCards from '@/components/landing/CrossLinkCards';
const SUGGESTIONS = ['aturi.to', 'bsky.app', 'dame.is', 'jay.bsky.team'];
@@ -90,77 +91,8 @@ export default function ExploreLanding() {
-
- }
- title="Universal links"
- body="Share aturi.to/handle/collection/rkey with anyone — they pick the Atmosphere app to open it in."
- href="/"
- />
- }
- title="Browser extension"
- body="Inspect AT URIs on any page and fast-travel between Atmosphere apps."
- href="/extension"
- />
-
+
);
}
-
-function CrossLinkCard({
- icon,
- title,
- body,
- href,
-}: {
- icon: React.ReactNode;
- title: string;
- body: string;
- href: string;
-}) {
- return (
- {
- e.currentTarget.style.borderColor = 'var(--text-accent)';
- e.currentTarget.style.background = 'var(--bg-tertiary)';
- }}
- onMouseLeave={(e) => {
- e.currentTarget.style.borderColor = 'var(--border-medium)';
- e.currentTarget.style.background = 'var(--bg-secondary)';
- }}
- >
- {icon}
-
{title}
-
- {body}
-
-
- );
-}
diff --git a/src/components/landing/AutoRedirectVisual.tsx b/src/components/landing/AutoRedirectVisual.tsx
new file mode 100644
index 0000000..3c98cb6
--- /dev/null
+++ b/src/components/landing/AutoRedirectVisual.tsx
@@ -0,0 +1,164 @@
+'use client';
+
+import { Check, ChevronRight } from 'lucide-react';
+import {
+ BlueskySVG,
+ DeerSVG,
+ LeafletSVG,
+ TangledSVG,
+ GrainSVG,
+} from '@/utils/waypointIcons';
+
+const ROWS: { collection: string; label: string; icon: React.ReactNode; client: string }[] = [
+ {
+ collection: 'app.bsky.feed.post',
+ label: 'Posts',
+ icon: ,
+ client: 'Deer',
+ },
+ {
+ collection: 'pub.leaflet.document',
+ label: 'Documents',
+ icon: ,
+ client: 'Leaflet',
+ },
+ {
+ collection: 'sh.tangled.repo',
+ label: 'Repositories',
+ icon: ,
+ client: 'Tangled',
+ },
+ {
+ collection: 'social.grain.gallery',
+ label: 'Photo galleries',
+ icon: ,
+ client: 'Grain',
+ },
+ {
+ collection: 'app.bsky.actor.profile',
+ label: 'Profiles',
+ icon: ,
+ client: 'Bluesky',
+ },
+];
+
+/**
+ * Static visual of the per-lexicon auto-redirect preferences screen.
+ * Communicates that the extension can be told "every time you land
+ * on X, send me to Y" — one of the features that doesn't fit in the
+ * popup mock on its own.
+ */
+export default function AutoRedirectVisual() {
+ return (
+
+
+
+ Auto-redirect
+
+
+ On
+
+
+
+ {ROWS.map((r) => (
+
+
+
+ {r.label}
+
+
+ {r.collection}
+
+
+
+
+
+ {r.icon}
+
+ {r.client}
+
+
+ ))}
+
+
+ );
+}
diff --git a/src/components/landing/ContextMenuVisual.tsx b/src/components/landing/ContextMenuVisual.tsx
new file mode 100644
index 0000000..6077f68
--- /dev/null
+++ b/src/components/landing/ContextMenuVisual.tsx
@@ -0,0 +1,122 @@
+'use client';
+
+import { Copy, ExternalLink, Search, Telescope } from 'lucide-react';
+import { Leaf } from 'lucide-react';
+
+/**
+ * Static visual of the right-click context menu the extension adds
+ * to AT URIs found on any web page. Shows that the extension's
+ * inspect/jump capabilities are available without ever opening the
+ * popup — they're folded into the browser's native chrome.
+ */
+export default function ContextMenuVisual() {
+ return (
+
+ {/* Snippet of page content showing a selected AT URI */}
+
+ );
+}
+
+function Divider() {
+ return (
+
+ );
+}
diff --git a/src/components/landing/CrossLinkCards.tsx b/src/components/landing/CrossLinkCards.tsx
new file mode 100644
index 0000000..3c4e530
--- /dev/null
+++ b/src/components/landing/CrossLinkCards.tsx
@@ -0,0 +1,111 @@
+'use client';
+
+import Link from 'next/link';
+import { Compass, Download, Telescope } from 'lucide-react';
+
+type ProductKey = 'universal-links' | 'extension' | 'explore';
+
+type Card = {
+ key: ProductKey;
+ href: string;
+ icon: React.ReactNode;
+ title: string;
+ body: string;
+};
+
+const CARDS: Card[] = [
+ {
+ key: 'universal-links',
+ href: '/universal-links',
+ icon: ,
+ title: 'Universal links',
+ body: 'Share aturi.to/handle/collection/rkey with anyone — they pick the Atmosphere app to open it in.',
+ },
+ {
+ key: 'extension',
+ href: '/extension',
+ icon: ,
+ title: 'Browser extension',
+ body: 'Inspect AT URIs on any page and fast-travel between Atmosphere apps.',
+ },
+ {
+ key: 'explore',
+ href: '/explore',
+ icon: ,
+ title: 'Atmosphere Explorer',
+ body: "Browse any account's PDS records, identity history, and backlinks across the Atmosphere.",
+ },
+];
+
+/**
+ * Bottom-of-page "explore the rest of aturi" cards. Pass `current` to
+ * hide the card for the page you're already on so we don't link a page
+ * to itself.
+ */
+export default function CrossLinkCards({ current }: { current?: ProductKey }) {
+ const cards = CARDS.filter((c) => c.key !== current);
+ return (
+
+ {cards.map((c) => (
+
+ ))}
+
+ );
+}
+
+function CrossLinkCard({
+ icon,
+ title,
+ body,
+ href,
+}: {
+ icon: React.ReactNode;
+ title: string;
+ body: string;
+ href: string;
+}) {
+ return (
+ {
+ e.currentTarget.style.borderColor = 'var(--text-accent)';
+ e.currentTarget.style.background = 'var(--bg-tertiary)';
+ }}
+ onMouseLeave={(e) => {
+ e.currentTarget.style.borderColor = 'var(--border-medium)';
+ e.currentTarget.style.background = 'var(--bg-secondary)';
+ }}
+ >
+ {icon}
+
{title}
+
+ {body}
+
+
+ );
+}
diff --git a/src/components/landing/ExtensionLanding.tsx b/src/components/landing/ExtensionLanding.tsx
new file mode 100644
index 0000000..a874881
--- /dev/null
+++ b/src/components/landing/ExtensionLanding.tsx
@@ -0,0 +1,283 @@
+'use client';
+
+import Link from 'next/link';
+import {
+ Compass,
+ Eye,
+ Link2,
+ MousePointerClick,
+ Repeat,
+ ShieldCheck,
+ Zap,
+} from 'lucide-react';
+import AppearIn from '@/components/explore/AppearIn';
+import BrowserChrome from '@/components/home/BrowserChrome';
+import ExtensionPopupVisual from '@/components/home/ExtensionPopupVisual';
+import DownloadButton, { BrowserFallbackList } from '@/components/home/DownloadButton';
+import { getWaypointCount } from '@/utils/waypoints';
+import CrossLinkCards from './CrossLinkCards';
+import InspectPanelVisual from './InspectPanelVisual';
+import AutoRedirectVisual from './AutoRedirectVisual';
+import ContextMenuVisual from './ContextMenuVisual';
+import FeatureSection from './FeatureSection';
+
+export default function ExtensionLanding() {
+ const waypointCount = getWaypointCount();
+ return (
+
+ {/* Hero: badge + headline + copy + CTA on the left, popup demo on the right */}
+
+
+
+ }>Browser extension
+
+ Jump between Atmosphere clients in one click
+
+
+ Land on a Bluesky post and want to read it in Deer? Click the leaf
+ in your toolbar — the popup detects the AT URI on the page and
+ offers every app that can render it. Pick a favorite once and let
+ auto-redirect handle the rest.
+
+
+
+ {waypointCount} curated Atmosphere clients
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ , label: 'One-click jump' }}
+ title="Every app that renders the record, one click away"
+ body={
+ <>
+
+ The popup picks up whatever AT URI is on the page and surfaces a
+ recommended client at the top, plus every other waypoint that
+ supports that lexicon — from Bluesky and Deer to Leaflet,
+ Tangled, Margin, Grain, and the rest of the catalog.
+
+
+ Each row links straight to the equivalent page on the other
+ client, so there’s no copy-pasting, no fiddling with URL
+ schemes, no re-resolving handles.
+
+ >
+ }
+ visual={
+
+
+
+ }
+ />
+
+
+
+ , label: 'Inspect mode' }}
+ title="See the AT URI under everything"
+ body={
+ <>
+
+ Switch to the Inspect tab to read the underlying record: the DID
+ behind the handle, the PDS hosting it, the lexicon collection,
+ and the count of inbound backlinks pointing at it.
+
+
+ Click any field to copy it, or jump straight into the
+ Atmosphere Explorer to read the raw JSON. The same data
+ Bluesky’s API serves, surfaced inline as you browse.
+
+ >
+ }
+ visual={}
+ />
+
+
+
+ , label: 'Auto-redirect' }}
+ title="Set it once. Every link opens where you want."
+ body={
+ <>
+
+ Pick a preferred client for each kind of record — posts in
+ Deer, documents in Leaflet, repositories in Tangled — and the
+ extension transparently sends you there whenever you click an
+ Atmosphere link from anywhere on the web.
+
+
+ Preferences are per-lexicon and per-device. Disable
+ auto-redirect for one collection without losing the others, or
+ turn the whole thing off and the extension goes back to a
+ popup-only tool.
+
+ >
+ }
+ visual={}
+ />
+
+
+
+ , label: 'Right-click any AT URI' }}
+ title="Even raw URIs on random pages become first-class links"
+ body={
+ <>
+
+ The extension registers a context menu on any text that looks
+ like an at://{' '}
+ URI. Right-click it and you can open it in your recommended
+ app, inspect it on aturi.to, or copy a clean universal link
+ pointing at the same record.
+
+
+ Useful in DMs, READMEs, JSON viewers, dev tools — anywhere an
+ AT URI shows up in plain text instead of a styled link.
+
+ >
+ }
+ visual={}
+ />
+
+
+
+
+
+ Local-first
+
+
+ No account required. No telemetry. No background network calls.
+
+
+ The extension only talks to your PDS when you actively trigger it —
+ click the popup, hit Inspect, or follow a context-menu jump.
+ Preferences stay in your browser’s local storage; nothing is
+ uploaded.{' '}
+
+ Read the privacy policy
+
+ .
+
+
+
+
+
+
+
+
+
+ );
+}
+
+function Badge({ icon, children }: { icon: React.ReactNode; children: React.ReactNode }) {
+ return (
+
+ {icon}
+ {children}
+
+ );
+}
diff --git a/src/components/landing/FeatureSection.tsx b/src/components/landing/FeatureSection.tsx
new file mode 100644
index 0000000..57ca5c6
--- /dev/null
+++ b/src/components/landing/FeatureSection.tsx
@@ -0,0 +1,70 @@
+'use client';
+
+import type { ReactNode } from 'react';
+
+type Props = {
+ badge: { icon: ReactNode; label: string };
+ title: string;
+ body: ReactNode;
+ visual: ReactNode;
+ /** When true, the visual sits on the left instead of the right. */
+ flip?: boolean;
+};
+
+/**
+ * Two-column feature row used on the product landing pages. Mirrors
+ * ProductStrip from the homepage but lives outside the
+ * `home-product-strip` flow so landing pages can stack many of them
+ * without forking the homepage styles.
+ */
+export default function FeatureSection({ badge, title, body, visual, flip }: Props) {
+ return (
+
+
{visual}
+
+
+ {badge.icon}
+ {badge.label}
+
+
+ {title}
+
+
+ {body}
+
+
+
+ );
+}
diff --git a/src/components/landing/InspectPanelVisual.tsx b/src/components/landing/InspectPanelVisual.tsx
new file mode 100644
index 0000000..21c6668
--- /dev/null
+++ b/src/components/landing/InspectPanelVisual.tsx
@@ -0,0 +1,159 @@
+'use client';
+
+import { Activity, Database, Fingerprint, Hash, Link2 } from 'lucide-react';
+
+/**
+ * Static mock of the extension's "Inspect" tab. Shows the breakdown
+ * of an AT URI on the current page — authority (DID), collection
+ * (lexicon), record key — alongside the resolved handle and PDS.
+ * Used on the /extension landing page to communicate what the
+ * extension surfaces beyond just app-switching.
+ */
+export default function InspectPanelVisual() {
+ return (
+
+ );
+}
diff --git a/src/components/landing/SharingScenariosVisual.tsx b/src/components/landing/SharingScenariosVisual.tsx
new file mode 100644
index 0000000..86ef10f
--- /dev/null
+++ b/src/components/landing/SharingScenariosVisual.tsx
@@ -0,0 +1,136 @@
+'use client';
+
+import { AtSign, MessageCircle, Quote } from 'lucide-react';
+
+/**
+ * Three side-by-side "where you'd drop an aturi.to link" mocks. Each
+ * one is a tiny stylized snippet of a different real-world surface
+ * (a DM, a profile bio, a website footer) with an aturi.to URL inside.
+ * Communicates that the link is portable in a way that single-app
+ * deep links aren't.
+ */
+export default function SharingScenariosVisual() {
+ return (
+
+ );
+}
+
+function Link({ children }: { children: React.ReactNode }) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/components/landing/UniversalLinksLanding.tsx b/src/components/landing/UniversalLinksLanding.tsx
new file mode 100644
index 0000000..e0603a3
--- /dev/null
+++ b/src/components/landing/UniversalLinksLanding.tsx
@@ -0,0 +1,357 @@
+'use client';
+
+import Link from 'next/link';
+import { ArrowRight, Compass, Layers, Link2, Share2, Sparkles, UserCog } from 'lucide-react';
+import AppearIn from '@/components/explore/AppearIn';
+import WaypointJumpVisual from '@/components/home/WaypointJumpVisual';
+import WaypointCarousel from '@/components/home/WaypointCarousel';
+import CrossLinkCards from './CrossLinkCards';
+import FeatureSection from './FeatureSection';
+import UrlAnatomyVisual from './UrlAnatomyVisual';
+import RecordTypesGrid from './RecordTypesGrid';
+import PickerPreviewVisual from './PickerPreviewVisual';
+import SharingScenariosVisual from './SharingScenariosVisual';
+
+const DEMO_HANDLE = 'aturi.to';
+// Aturi's own DID — hardcoded so the carousel can build did-aware URLs
+// without a runtime profile fetch.
+const DEMO_DID = 'did:plc:gq4fo3u6tqzzdkjlwzpb23tj';
+
+export default function UniversalLinksLanding() {
+ return (
+
+ {/* Hero */}
+
+
+
+ }>Universal links
+
+ One link, every Atmosphere client
+
+
+ Drop an{' '}
+
+ aturi.to
+ {' '}
+ link anywhere — a DM, a footer, a bio. Your visitors land on a
+ friendly preview of the record and pick the Atmosphere client
+ they want to open it in. No client lock-in. No sign-up. Every
+ record, profile, list, and feed resolves to the right destination
+ across 25+ curated clients.
+
+ Every aturi.to URL follows the same pattern: the host, a handle
+ or DID, the lexicon collection, and the record key. The same
+ shape works for every record type — there’s nothing to
+ memorize beyond what an AT URI already looks like.
+
+
+ Drop the collection and the rkey, and you get a profile link.
+ Drop just the rkey, and you get a collection index. The path
+ always degrades gracefully.
+
+ If a lexicon has a public record, aturi.to has a URL for it. The
+ same{' '}
+ handle/collection/rkey{' '}
+ pattern resolves whether the record lives in app.bsky.feed.post,
+ pub.leaflet.document, sh.tangled.repo, or something brand new the
+ ecosystem invented last week.
+
+
+
+
+
+
+
+ , label: 'The picker' }}
+ title="A friendly landing page for every record"
+ body={
+ <>
+
+ Recipients land on a clean preview with a recommended client
+ pinned at the top and every alternative listed below. They can
+ read what the record is, decide where to open it, and never get
+ stranded in an app they don’t use.
+
+
+ Each link is a real, indexable URL — embeds get OpenGraph
+ cards, link unfurlers get a real title and description, and
+ bookmarks survive client churn.
+
+ Sign in with your atproto handle and aturi.to remembers your
+ preferred client for each record type. The next time you
+ follow a universal link, the picker auto-resolves to your
+ favorite app — no extra clicks, on any device you sign in to.
+
+
+ Want to share a link that forces a specific client? Add a{' '}
+ ?via={' '}
+ query param. Want to give your recipient the picker back? Drop
+ it. The default-vs-override decision lives in the URL, not in
+ the app.
+
+ >
+ }
+ visual={
+
+
+ Signed in as @aturi.to
+
+
+
+
+
+ Synced to your repo · works on every device you sign in to.
+
+
+ }
+ />
+
+
+
+
+
+ }>Share anywhere
+
+ The link people can actually paste into anything
+
+
+ An aturi.to URL is short, readable, and copy-pasteable. It works
+ in Bluesky posts, group chats, blog footers, business cards, QR
+ codes, podcast show notes — anywhere a regular link belongs.
+
+ );
+}
diff --git a/src/components/landing/UrlAnatomyVisual.tsx b/src/components/landing/UrlAnatomyVisual.tsx
new file mode 100644
index 0000000..81f3f69
--- /dev/null
+++ b/src/components/landing/UrlAnatomyVisual.tsx
@@ -0,0 +1,139 @@
+'use client';
+
+import { Link2 } from 'lucide-react';
+
+type Part = {
+ text: string;
+ label: string;
+ hint: string;
+ /** Color token used for the label + underline. */
+ accent: string;
+};
+
+const PARTS: Part[] = [
+ {
+ text: 'aturi.to',
+ label: 'Domain',
+ hint: 'The universal link host. Aliases for atpota.to are also supported.',
+ accent: 'var(--text-tertiary)',
+ },
+ {
+ text: '/aturi.to',
+ label: 'Handle or DID',
+ hint: 'Resolved to a PDS via the standard atproto identity chain.',
+ accent: 'var(--text-accent)',
+ },
+ {
+ text: '/app.bsky.feed.post',
+ label: 'Collection',
+ hint: 'The lexicon NSID of the record being linked.',
+ accent: 'var(--accent-moss)',
+ },
+ {
+ text: '/3lq9c2fhz3k2v',
+ label: 'Record key',
+ hint: 'The TID assigned by the PDS when the record was written.',
+ accent: 'var(--text-primary)',
+ },
+];
+
+/**
+ * Annotated breakdown of an aturi.to URL. Used on the /universal-links
+ * landing page to teach the structure of the URL pattern at a glance —
+ * which segments are required, what each one means, and why the same
+ * pattern works for every lexicon.
+ */
+export default function UrlAnatomyVisual() {
+ return (
+
+ {/* The URL itself, with each segment underlined in its own color. */}
+